// Cloned by Okikiola Sanni on 3 Dec 2022 from World "Castle World" by Starter user
// Please leave this clone trail here.
// ==== Starter World =================================================================================================
// This code is designed for use on the Ancient Brain site.
// This code may be freely copied and edited by anyone on the Ancient Brain site.
// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.
// ====================================================================================================================
// Demo of 3D model
// OBJ plus MTL
// A "Simple World" like chase inside an invisible arena inside the castle
// Chase uses x,y,z rather than grid of squares
// ===================================================================================================================
// === Start of tweaker's box ========================================================================================
// ===================================================================================================================
// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?
AB.clockTick = 100;
// Speed of run: Step every n milliseconds.
AB.maxSteps = 500;
// Length of run: Maximum length of run in steps.
AB.screenshotStep = 50;
// Take screenshot on this step. (All resources should have finished loading.)
//---- global constants: -------------------------------------------------------
const OBJPATH = "/uploads/starter/"; // path of OBJ and MTL
const OBJNAME = "castle.obj";
const MTLNAME = "castle.new.mtl";
// castle credit
// https://www.script-tutorials.com/webgl-with-three-js-lesson-6/
// http://www.script-tutorials.com/demos/409/index2.html
// =================================================================================
// Bug: Original MTL file fails with new Three.js
// The original "castle.mtl" works with old Three.js but fails with new version.
// Solution: Edit the MTL file. See note here:
// https://ancientbrain.com/docs.three.php
// =================================================================================
const TEXTURE_AGENT = '/uploads/starter/pacman.jpg' ;
const TEXTURE_ENEMY = '/uploads/starter/ghost.3.png' ;
// credits:
// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons
// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_icons
const MUSIC_BACK = '/uploads/starter/Defense.Line.mp3' ;
const SKYCOLOR = 0xffffcc ; // a number, not a string
const LIGHTCOLOR = 0xffffff ;
const squaresize = 50; // size of cube length
// basic castle model size
// see debug line to compute model size below
// Xsize: 6158.792236328125 Ysize: 703.5784759521484 Zsize: 3791.05908203125
const MODELLENGTH = 6159;
const MODELWIDTH = 3791;
const SCALE_CASTLE = 0.5; // scale it by this
const SCALEDMODELLENGTH = MODELLENGTH * SCALE_CASTLE;
const SCALEDMODELWIDTH = MODELWIDTH * SCALE_CASTLE;
const startRadiusConst = SCALEDMODELLENGTH * 0.5 ;
const maxRadiusConst = SCALEDMODELLENGTH * 10 ;
// camera points at 0,0
// where to put castle?
// fit bottom LHS corner to here:
const CX = - ( SCALEDMODELLENGTH * 0.3 );
const CZ = ( SCALEDMODELWIDTH * 0.3 );
// how far can the agent and enemy wander inside the castle
// experiment to see where the inner box fits inside the model
const MIN_X = CX + ( SCALEDMODELLENGTH * 0.1 ) ;
const MAX_X = CX + ( SCALEDMODELLENGTH * 0.55 ) ;
const MIN_Z = CZ - ( SCALEDMODELWIDTH * 0.45 ) ;
const MAX_Z = CZ - ( SCALEDMODELWIDTH * 0.1 ) ;
// how much do they move each step
const MOVE = squaresize ;
// how close do they have to be to lose score
const BADCLOSE = squaresize * 5;
//--- change ABWorld defaults: -------------------------------
ABHandler.GROUNDZERO = true; // "ground" exists at altitude zero
ABWorld.drawCameraControls = false;
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
// we have a splash screen
// behind the splash screen, newRun is running, loading resources
// do not start the run loop until resources ready AND splash screen is dismissed
var resourcesLoaded = false;
var splashClicked = false;
//--- Mind can pick one of these actions -----------------
const ACTION_LEFT = 0;
const ACTION_RIGHT = 1;
const ACTION_UP = 2;
const ACTION_DOWN = 3;
const ACTION_STAYSTILL = 4;
var thecastle, theagent, theenemy;
var agent_texture, enemy_texture;
var badsteps;
var goodsteps;
function loadResources() // asynchronous file loads - call initScene when finished
{
// load castle model OBJ and materials MTL (which reference JPEGs)
var m = new THREE.MTLLoader();
m.setResourcePath ( OBJPATH );
m.setPath ( OBJPATH );
m.load ( MTLNAME, function ( materials )
{
materials.preload();
var o = new THREE.OBJLoader();
o.setMaterials ( materials );
o.setPath ( OBJPATH );
o.load ( OBJNAME, function ( object )
{
thecastle = object;
if ( asynchFinished() ) initScene();
});
});
// load cube textures
var loader1 = new THREE.TextureLoader();
var loader2 = new THREE.TextureLoader();
loader1.load ( TEXTURE_AGENT, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
agent_texture = thetexture;
if ( asynchFinished() ) initScene();
});
loader2.load ( TEXTURE_ENEMY, function ( thetexture )
{
thetexture.minFilter = THREE.LinearFilter;
enemy_texture = thetexture;
if ( asynchFinished() ) initScene();
});
}
// function asynchFinished()
// {
// if ( thecastle && agent_texture && enemy_texture ) return true;
// else return false;
// }
// function initScene() // file loads have returned
// {
// // add castle object to scene
// thecastle.scale.multiplyScalar ( SCALE_CASTLE );
// thecastle.position.y = - (squaresize * 0.5); // adjust so cubes (centred on 0) appear exactly above castle floor
// thecastle.position.x = CX;
// thecastle.position.z = CZ;
// ABWorld.scene.add ( thecastle );
// // calculate OBJ object size:
// // console.log ( "Xsize: " + ABWorld.objectXsize(thecastle) + " Ysize: " + ABWorld.objectYsize(thecastle) + " Zsize: " + ABWorld.objectZsize(thecastle) );
// // set up enemy
// shape = new THREE.BoxGeometry ( squaresize, squaresize, squaresize );
// theenemy = new THREE.Mesh( shape );
// theenemy.material = new THREE.MeshBasicMaterial( { map: enemy_texture } );
// ABWorld.scene.add(theenemy);
// // set position
// theenemy.position.y = 0;
// theenemy.position.x = AB.randomIntAtoB( MIN_X, MAX_X );
// theenemy.position.z = AB.randomIntAtoB( MIN_Z, MAX_Z );
// /*
// // examine where different points are and are they inside the castle:
// theenemy.position.x = 0;
// theenemy.position.z = 0;
// theenemy.position.x = MIN_X;
// theenemy.position.z = MIN_Z;
// theenemy.position.x = MAX_X;
// theenemy.position.z = MAX_Z;
// */
// // set up agent
// shape = new THREE.BoxGeometry ( squaresize, squaresize, squaresize );
// theagent = new THREE.Mesh( shape );
// theagent.material = new THREE.MeshBasicMaterial( { map: agent_texture } );
// ABWorld.scene.add(theagent);
// // put agent some distance from enemy
// do
// {
// x = AB.randomIntAtoB( MIN_X, MAX_X );
// z = AB.randomIntAtoB( MIN_Z, MAX_Z );
// }
// while ( AB.distance2D ( x, z, theenemy.position.x, theenemy.position,z ) < BADCLOSE );
// theagent.position.y = 0;
// theagent.position.x = x;
// theagent.position.z = z;
// // ready to start run loop?
// ABWorld.render();
// console.log ( "Resources loaded." );
// resourcesLoaded = true;
// if ( resourcesLoaded && splashClicked )
// AB.runReady = true; // start run loop
// }
// // --- check a proposed move is inside castle -----------------------------------
// function withinBounds ( x, z )
// {
// return ( ( x > MIN_X ) && ( x < MAX_X ) &&
// ( z > MIN_Z ) && ( z < MAX_Z ) );
// }
// // --- take actions -----------------------------------
// function moveLogicalEnemy()
// {
// // move towards agent
// // put some randomness in so it won't get stuck with barriers
// // proposed move
// var x = theenemy.position.x;
// var z = theenemy.position.z;
// if ( x < theagent.position.x ) x = x + AB.randomIntAtoB ( 0, MOVE ) ;
// else x = x - AB.randomIntAtoB ( 0, MOVE ) ;
// if ( z < theagent.position.z ) z = z + AB.randomIntAtoB ( 0, MOVE ) ;
// else z = z - AB.randomIntAtoB ( 0, MOVE ) ;
// if ( withinBounds ( x, z ) ) // if in bounds
// if ( AB.distance2D ( x, z, theagent.position.x, theagent.position.z ) > squaresize * 2 ) // limit to how close we go to agent
// {
// theenemy.position.x = x;
// theenemy.position.z = z;
// }
// // else just miss a turn
// }
// function moveLogicalAgent( a ) // this is called by the infrastructure that gets action a from the Mind
// {
// // proposed move
// var x = theagent.position.x;
// var z = theagent.position.z;
// if ( a == ACTION_LEFT ) x = x - MOVE ;
// else if ( a == ACTION_RIGHT ) x = x + MOVE ;
// else if ( a == ACTION_UP ) z = z + MOVE ;
// else if ( a == ACTION_DOWN ) z = z - MOVE ;
// if ( withinBounds ( x, z ) )
// if ( AB.distance2D ( x, z, theenemy.position.x, theenemy.position.z ) >= squaresize ) // limit to how close we go to enemy
// {
// theagent.position.x = x;
// theagent.position.z = z;
// }
// // else ignore action
// }
// // --- score: -----------------------------------
// function badstep()
// {
// if ( AB.distance2D ( theagent.position.x, theagent.position.z, theenemy.position.x, theenemy.position.z ) < BADCLOSE ) return true;
// else return false;
// }
// function updateStatus()
// {
// var score = AB.world.getScore();
// AB.msg ( "Step: " + AB.step + " out of " + AB.maxSteps + ". Score: " + score );
// }
// AB.world.newRun = function()
// {
// badsteps = 0;
// goodsteps = 0;
// ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );
// // newRun can run behind splash screen
// // do not start run loop until resources ready AND splash screen is dismissed
// AB.runReady = false;
// loadResources(); // aynch file loads
// // calls initScene() when it returns
// var thelight = new THREE.DirectionalLight ( LIGHTCOLOR, 3 );
// thelight.position.set ( startRadiusConst, startRadiusConst, startRadiusConst );
// ABWorld.scene.add(thelight);
// };
// AB.world.getState = function()
// {
// var x = [ theagent.position.x, theagent.position.z, theenemy.position.x, theenemy.position.z ];
// return ( x );
// };
// AB.world.takeAction = function ( a )
// {
// moveLogicalAgent(a);
// // if ( ( AB.step % 2 ) == 0 ) // slow the enemy down to every nth step
// moveLogicalEnemy();
// if ( badstep() ) badsteps++;
// else goodsteps++;
// updateStatus();
// };
// AB.world.getScore = function()
// {
// return goodsteps;
// };
// // --- Splash screen --------------------------------------------------------------
// // Splash screen is to get audio started on mobile/Chrome by user interaction.
// // This should works on all platforms - plays background music.
// // display standard splash screen (World title, World image, Start button)
// AB.newSplash();
// // when user clicks/touches button on splash screen, audio starts and run starts:
// AB.splashClick ( function ()
// {
// // audio linked to user interaction:
// AB.backgroundMusic ( MUSIC_BACK );
// AB.removeSplash(); // remove splash screen
// // ready to start run loop?
// splashClicked = true;
// if ( resourcesLoaded && splashClicked )
// AB.runReady = true; // start run loop
// });