See raw JS.
// Cloned by Ethan Harkin on 5 Nov 2018 from World "CA318 2018 World" by test // Please leave this clone trail here. // - Internal maze (randomly drawn each time) // - Enemy actively chases agent // ====================================================================================================== // Scoring: // Bad steps = steps where enemy is within one step of agent. // Good steps = steps where enemy is further away. // Score = good steps as percentage of all steps. // // There are situations where agent is trapped and cannot move. // If this happens, you get BLOCKPUNISH bad steps and the problem is reset - enemy is moved to a random square // ====================================================================================================== const BLOCKPUNISH = 50; // user_span1 - Status // user_span2 - Status // user_span3 - End message //--- 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; // in initial view, (smaller-larger) on i axis is aligned with (left-right) // in initial view, (smaller-larger) on j axis is aligned with (away from you - towards you) // =================================================================================================================== // === Start of tweaker's box ======================================================================================== // =================================================================================================================== // The easiest things to modify are in this box. AB.clockTick = 10; // Speed of run: Step every n milliseconds. Default 100. AB.maxSteps = 1000; // Length of run: Maximum length of run in steps. Default 1000. AB.screenshotStep = 50; // Take screenshot on this step. (All resources should have finished loading.) Default 50. //---- global constants: ------------------------------------------------------- const TEXTURE_WALL = '/uploads/starter/door.jpg' ; const TEXTURE_MAZE = '/uploads/starter/latin.jpg' ; const TEXTURE_AGENT = '/uploads/harkine4/snitch.jpg' ; const TEXTURE_ENEMY = '/uploads/harkine4/Harry_Potter_-_Quidditch_HBP_promo_1.jpg' ; // credits: // http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg // https://commons.wikimedia.org/wiki/Category:Pac-Man_icons // https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_icons // http://en.wikipedia.org/wiki/File:Inscription_displaying_apices_(from_the_shrine_of_the_Augustales_at_Herculaneum).jpg const SOUND_ALARM = '/uploads/starter/air.horn.mp3' ; // http://soundbible.com/1542-Air-Horn.html const gridsize = 20; // number of squares along side of world const BOXDENSITY = 0.1; const NOBOXES = Math.trunc ( (gridsize * gridsize) * BOXDENSITY ); // density of maze - number of internal boxes // (bug) use trunc or can get a non-integer const squaresize = 100; // size of square in pixels const MAXPOS = gridsize * squaresize; // length of one side in pixels const SKYCOLOR = 0xddffdd; // a number, not a string const startRadiusConst = MAXPOS * 0.8 ; // distance from centre to start the camera at const skyboxConst = MAXPOS * 2 ; // where to put skybox const maxRadiusConst = MAXPOS * 10 ; // maximum distance from camera we will render things //--- change ABWorld defaults: ------------------------------- ABHandler.MAXCAMERAPOS = skyboxConst * 0.6 ; // keep camera inside skybox ABHandler.GROUNDZERO = true; // "ground" exists at altitude zero //--- skybox: ------------------------------- // skybox is a collection of 6 files // x,y,z positive and negative faces have to be in certain order in the array // mountain skybox, credit: // http://stemkoski.github.io/Three.js/Skybox.html /* const SKYBOX_ARRAY = [ "/uploads/starter/dawnmountain-xpos.png", "/uploads/starter/dawnmountain-xneg.png", "/uploads/starter/dawnmountain-ypos.png", "/uploads/starter/dawnmountain-yneg.png", "/uploads/starter/dawnmountain-zpos.png", "/uploads/starter/dawnmountain-zneg.png" ]; */ const SKYBOX_ARRAY = [ "/uploads/harkine4/Quidditchpitch.jpg", "/uploads/harkine4/Quidditchpitch.jpg", "/uploads/harkine4/sky.jpg", "/uploads/harkine4/g.jpg", "/uploads/harkine4/Quidditchpitch.jpg", "/uploads/harkine4/Quidditchpitch.jpg" ]; // =================================================================================================================== // === End of tweaker's box ========================================================================================== // =================================================================================================================== // contents of a grid square const GRID_BLANK = 0; const GRID_WALL = 1; const GRID_MAZE = 2; //---- start of World class ------------------------------------------------------- function World() { var BOXHEIGHT = squaresize; var GRID = new Array(gridsize); // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array var theagent, theenemy; var wall_texture, agent_texture, enemy_texture, maze_texture; var skybox_texture_array = new Array ( 6 ); // a cube // enemy and agent position on squares var ei, ej, ai, aj; var badsteps; var goodsteps; var stuckfor = 0; // how long the enemy has been stuck - if stuck for a while start making random moves var self = this; // needed for private fn to call public fn function loadResources() // asynchronous file loads - call initScene() when all finished { var loader1 = new THREE.TextureLoader(); var loader2 = new THREE.TextureLoader(); var loader3 = new THREE.TextureLoader(); var loader4 = new THREE.TextureLoader(); loader1.load ( TEXTURE_WALL, function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; wall_texture = thetexture; if ( asynchFinished() ) initScene(); // if all file loads have returned }); loader2.load ( TEXTURE_AGENT, function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; agent_texture = thetexture; if ( asynchFinished() ) initScene(); }); loader3.load ( TEXTURE_ENEMY, function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; enemy_texture = thetexture; if ( asynchFinished() ) initScene(); }); loader4.load ( TEXTURE_MAZE, function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; maze_texture = thetexture; if ( asynchFinished() ) initScene(); }); for ( var i = 0; i < 6; i++ ) skyboxFileLoad ( i ); // launch 6 more asynchronous file loads for skybox } function skyboxFileLoad ( n ) // asynchronous file load of skybox texture n { var loader = new THREE.TextureLoader(); loader.load ( SKYBOX_ARRAY[n], function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; skybox_texture_array[n] = thetexture; if ( asynchFinished() ) initScene(); }); } function asynchFinished() // all file loads returned { for ( var i = 0; i < 6; i++ ) if ( ! skybox_texture_array[i] ) return false; // else if ( wall_texture && agent_texture && enemy_texture && maze_texture ) return true; else return false; } //--- grid system ------------------------------------------------------------------------------- // my numbering is 0 to gridsize-1 function occupied ( i, j ) // is this square occupied { if ( ( ei == i ) && ( ej == j ) ) return true; // variable objects if ( ( ai == i ) && ( aj == j ) ) return true; if ( GRID[i][j] == GRID_WALL ) return true; // fixed objects if ( GRID[i][j] == GRID_MAZE ) return true; return false; } // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates // logically, coordinates are: y=0, x and z all positive (no negative) // logically my dimensions are all positive 0 to MAXPOS // to centre everything on origin, subtract (MAXPOS/2) from all dimensions function translate ( i, j ) { var v = new THREE.Vector3(); v.y = 0 ; v.x = ( i * squaresize ) - ( MAXPOS/2 ); v.z = ( j * squaresize ) - ( MAXPOS/2 ); return v; } function initScene() // all file loads have returned { var i,j, shape, thecube; // set up GRID as 2D array for ( i = 0; i < gridsize ; i++ ) GRID[i] = new Array(gridsize); // set up walls for ( i = 0; i < gridsize ; i++ ) for ( j = 0; j < gridsize ; j++ ) if ( ( i==0 ) || ( i==gridsize-1 ) || ( j==0 ) || ( j==gridsize-1 ) ) { GRID[i][j] = GRID_WALL; shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize ); thecube = new THREE.Mesh( shape ); thecube.material = new THREE.MeshBasicMaterial( { map: wall_texture } ); thecube.position.copy ( translate(i,j) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube); } else GRID[i][j] = GRID_BLANK; // set up maze for ( var c=1 ; c <= NOBOXES ; c++ ) { i = AB.randomIntAtoB(1,gridsize-2); // inner squares are 1 to gridsize-2 j = AB.randomIntAtoB(1,gridsize-2); GRID[i][j] = GRID_MAZE ; shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize ); thecube = new THREE.Mesh( shape ); thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture } ); thecube.position.copy ( translate(i,j) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube); } // set up enemy // start in random location resetEnemyPos(); shape = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize ); theenemy = new THREE.Mesh( shape ); theenemy.material = new THREE.MeshBasicMaterial( { map: enemy_texture } ); ABWorld.scene.add(theenemy); drawEnemy(); // set up agent // start in random location do { i = AB.randomIntAtoB(1,gridsize-2); j = AB.randomIntAtoB(1,gridsize-2); } while ( occupied(i,j) ); // search for empty square ai = i; aj = j; shape = new THREE.SphereGeometry ( squaresize / 2 ); theagent = new THREE.Mesh( shape ); theagent.material = new THREE.MeshBasicMaterial( { map: agent_texture } ); ABWorld.scene.add(theagent); drawAgent(); // set up skybox var skybox_material_array = [ ( new THREE.MeshBasicMaterial ( { map: skybox_texture_array[0], side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: skybox_texture_array[1], side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: skybox_texture_array[2], side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: skybox_texture_array[3], side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: skybox_texture_array[4], side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: skybox_texture_array[5], side: THREE.BackSide } ) ) ]; var skyGeometry = new THREE.CubeGeometry ( skyboxConst, skyboxConst, skyboxConst ); var skyMaterial = new THREE.MeshFaceMaterial ( skybox_material_array ); var theskybox = new THREE.Mesh ( skyGeometry, skyMaterial ); // theskybox.position.y = skyboxConst * 0.4 ; ABWorld.scene.add( theskybox ); // We are inside a giant cube // can start the run loop ABWorld.render(); AB.removeLoading(); ABRun.runReady = true; } function resetEnemyPos() { var i,j; do { i = AB.randomIntAtoB(1,gridsize-2); j = AB.randomIntAtoB(1,gridsize-2); } while ( occupied(i,j) ); // search for empty square ei = i; ej = j; } // --- draw moving objects ----------------------------------- function drawEnemy() // given ei, ej, draw it { theenemy.position.copy ( translate(ei,ej) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.lookat.copy ( theenemy.position ); // if camera moving, look back at where the enemy is } function drawAgent() // given ai, aj, draw it { theagent.position.copy ( translate(ai,aj) ); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.follow.copy ( theagent.position ); // follow vector = agent position (for camera following agent) } // --- take actions ----------------------------------- function getEnemyAction() { // move towards agent // after n failed moves, start making random moves if ( stuckfor > 2 ) return ( AB.randomIntAtoB (0,3) ); if ( AB.randomBoolean() ) { if ( ej < aj ) return ( ACTION_UP ); if ( ej > aj ) return ( ACTION_DOWN ); } else { if ( ei < ai ) return ( ACTION_RIGHT ); if ( ei > ai ) return ( ACTION_LEFT ); } return ( AB.randomIntAtoB (0,3) ); } function moveLogicalEnemy( a ) { var i = ei; var j = ej; if ( a == ACTION_LEFT ) i--; else if ( a == ACTION_RIGHT ) i++; else if ( a == ACTION_UP ) j++; else if ( a == ACTION_DOWN ) j--; if ( occupied(i,j) ) stuckfor++; else { stuckfor = 0; ei = i; ej = j; } } function moveLogicalAgent( a ) // this is called by the infrastructure that gets action a from the Mind { var i = ai; var j = aj; if ( a == ACTION_LEFT ) i--; else if ( a == ACTION_RIGHT ) i++; else if ( a == ACTION_UP ) j++; else if ( a == ACTION_DOWN ) j--; if ( ! occupied(i,j) ) { ai = i; aj = j; } } // --- score: ----------------------------------- function badstep() // is the enemy within one square of the agent { if ( ( Math.abs(ei - ai) < 2 ) && ( Math.abs(ej - aj) < 2 ) ) return true; else return false; } function agentBlocked() // agent is blocked on all sides { return ( occupied (ai-1,aj) && occupied (ai+1,aj) && occupied ( ai,aj+1) && occupied ( ai,aj-1) ); } function updateStatusBefore(a) // this is called before anyone has moved on this step, agent has just proposed an action // update status to show old state and proposed move { var x = self.getState(); var status = " Step: " + ABRun.step + " x = (" + x.toString() + ") " + " a = (" + a + ") "; $("#user_span1").html( status ); } function updateStatusAfter() // agent and enemy have moved, can calculate score { // new state after both have moved var y = self.getState(); var score = ( goodsteps / ABRun.step ) * 100; var status = " y = (" + y.toString() + ") " + " <BR> Bad steps: " + badsteps + " Good steps: " + goodsteps + " Score: " + score.toFixed(2) + "% "; $("#user_span2").html( status ); } //--- public functions / interface / API ---------------------------------------------------------- this.newRun = function() { AB.loadingScreen(); ABRun.runReady = false; badsteps = 0; goodsteps = 0; ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR ); loadResources(); // aynch file loads // calls initScene() when it returns }; this.getState = function() { var x = [ ai, aj, ei, ej ]; return ( x ); }; this.takeAction = function ( a ) { updateStatusBefore(a); // show status line before moves moveLogicalAgent(a); if ( ( ABRun.step % 2 ) == 0 ) // slow the enemy down to every nth step { var e = getEnemyAction(); moveLogicalEnemy(e); } if ( badstep() ) badsteps++; else goodsteps++; drawAgent(); drawEnemy(); updateStatusAfter(); // show status line after moves if ( agentBlocked() ) // if agent blocked in, you get BLOCKPUNISH bad steps and the problem is reset { badsteps = badsteps + BLOCKPUNISH; ABRun.step = ABRun.step + BLOCKPUNISH; $("#user_span3").html( " <br> <font color=red> <B> Agent blocked. " + BLOCKPUNISH + " extra bad steps and reset enemy. </B> </font> " ); soundAlarm(); // enemy is moved to a random square resetEnemyPos(); } }; this.endRun = function() { $("#user_span3").html( " <br> <font color=green> <B> Run over. </B> </font> " ); }; this.getScore = function() { // only called at end - do not use ABRun.step because it may have just incremented past AB.maxSteps var s = ( goodsteps / AB.maxSteps ) * 100; // float like 93.4372778 var x = Math.round (s * 100); // 9344 return ( x / 100 ); // 93.44 }; } //---- end of World class ------------------------------------------------------- function soundAlarm() { var alarm = new Audio ( SOUND_ALARM ); alarm.play(); // play once, no loop }