//Cloned Mark Humphrys Complex world. //Also took parts from Mark Humphrys Model world for my two characters. /* AIM: Try and find the most flags before the red enemy catches you. It gets more difficult as you go on. Use the arrow keys to move: Up, Down, Left, Right. Also sneaky jump forward with space bar but this is meant to be like a cheat code. Recommend user to adjust camera so they can view the grid depending on their preference. */ // World must define these: const CLOCKTICK = 100; // speed of run - move things every n milliseconds const MAXSTEPS = 1000; // length of a run before final score const SCREENSHOT_STEP = 50; //---- global constants: ------------------------------------------------------- const gridsize = 30; // number of squares along side of world const NOBOXES = Math.trunc ( (gridsize * gridsize) / 10 ); // 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 = 0xF0B27A ; // a number, not a string const BLANKCOLOR = SKYCOLOR ; // make objects this color until texture arrives (from asynchronous file read) const LIGHTCOLOR = 0xffffff ; const show3d = true; // Switch between 3d and 2d view (both using Three.js) const startRadiusConst = MAXPOS * 0.8 ; // distance from centre to start the camera at const skyboxConst = MAXPOS * 3 ; // where to put skybox const maxRadiusConst = MAXPOS * 10 ; // maximum distance from camera we will render things //--- 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; const ACTION_JUMPFORWARD = 5; //"cheat code" // 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) // contents of a grid square const GRID_BLANK = 0; const GRID_WALL = 1; const GRID_MAZE = 2; // --- some useful random functions ------------------------------------------- function randomfloatAtoB ( A, B ) { return ( A + ( Math.random() * (B-A) ) ); } function randomintAtoB ( A, B ) { return ( Math.round ( randomfloatAtoB ( A, B ) ) ); } function randomBoolean() { if ( Math.random() < 0.5 ) { return false; } else { return true; } } //---- start of World class ------------------------------------------------------- function World() { var BOXHEIGHT; // 3d or 2d box height var GRID = new Array(gridsize); // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array var WALLS = new Array ( 4 * gridsize ); // need to keep handles to wall and maze objects so can find them later to paint them var MAZE = new Array ( NOBOXES ); var theagent, theenemy; //People moving. User is the agent var flag; //Aim of the game is to get as many flags as possible before being caught. //position on squares var ei, ej, ai, aj; //enemy and agent var fi, fj; //flag var badsteps; var goodsteps; var step; var flags; //keeps track of how many flags you find. var self = this; // needed for private fn to call public fn - see below //---------------------- Grid ------------------------------------------------------------- function initGrid(){ for (var i = 0; i < gridsize ; i++) { GRID[i] = new Array(gridsize); // each element is an array for (var j = 0; j < gridsize ; j++) { GRID[i][j] = GRID_BLANK ; } } } 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 ( ( fi == i ) && ( fj == j ) ) return true; //make sure flag is not placed on piece of maze. if ( GRID[i][j] == GRID_WALL ) return true; // fixed objects if ( GRID[i][j] == GRID_MAZE ) return true; return false; } // 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 ( x ) { return ( x - ( MAXPOS/2 ) ); } // --- asynchronous load textures from file ---------------------------------------- /* credits: Characters: Mark Humphrys male02.obj flag: http://tf3dm.com/3d-model/usmc-flag-67417.html ALl other images: Google images. */ // First textures load when game starts up first. function loadTexturesFIRST() { var manager = new THREE.LoadingManager(); var loader = new THREE.OBJLoader( manager ); loader.load( "/uploads/starter/male02.obj", buildenemy ); loader.load( "/uploads/starter/male02.obj", buildagent); loader.load( "/uploads/meabhhoran1/usmcflag.obj", buildflag); var loader1 = new THREE.TextureLoader(); loader1.load ( '/uploads/meabhhoran1/rust.jpg', function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) ); } ); var loader2 = new THREE.TextureLoader(); loader2.load ( '/uploads/meabhoran1/grey.jpg', function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) ); } ); } //Second textures are for when the flag is found and reloads maze and new flag position function loadTexturesSECOND() { var manager = new THREE.LoadingManager(); var loader = new THREE.OBJLoader( manager ); loader.load( "/uploads/meabhhoran1/usmcflag.obj", buildflag); var loader1 = new THREE.TextureLoader(); loader1.load ( '/uploads/meabhhoran1/rust.jpg', function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) ); } ); var loader2 = new THREE.TextureLoader(); loader2.load ( '/uploads/meabhhoran1/grey.jpg', function ( thetexture ) { thetexture.minFilter = THREE.LinearFilter; paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) ); } ); } // --- build and paint ---------------------------------------- function buildenemy ( object ) { object.scale.multiplyScalar ( 2.5 ); // make 3d object n times bigger object.traverse( paintEnemy ); theenemy = object; threeworld.scene.add( theenemy ); } function paintEnemy ( child ) { if ( child instanceof THREE.Mesh ) { child.material.map = THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/redcol.jpg" ); } } function buildagent ( object ) { object.scale.multiplyScalar ( 2.5 ); // make 3d object n times bigger object.traverse( paintAgent ); theagent = object; threeworld.scene.add( theagent ); } function paintAgent ( child ) { if ( child instanceof THREE.Mesh ) { child.material.map = THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/armycol.jpg" ); } } function buildflag ( object ) { object.scale.multiplyScalar ( 5 ); // make 3d object n times bigger object.traverse( paintFlag ); flag = object; threeworld.scene.add( flag ); } function paintFlag ( child ) { if ( child instanceof THREE.Mesh ) { child.material.map = THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/flagirish.jpg" ); } } //--- skybox ---------------------------------------------------------------------------------------------- /* Image taken from Google images. Then I cropped it into 6 images. */ function initSkybox() { var materialArray = [ ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/2157a4.jpg" ), side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/2157a1.jpg" ), side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/2157a.jpg" ), side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/2157a3.jpg" ), side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/2157a2.jpg" ), side: THREE.BackSide } ) ), ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/meabhhoran1/2157a5.jpg" ), side: THREE.BackSide } ) ) ]; var skyGeometry = new THREE.CubeGeometry ( skyboxConst, skyboxConst, skyboxConst ); var skyMaterial = new THREE.MeshFaceMaterial ( materialArray ); var theskybox = new THREE.Mesh ( skyGeometry, skyMaterial ); threeworld.scene.add( theskybox ); // We are inside a giant cube } //------------------- Walls surrounding grid --------------------------------------------- function initLogicalWalls() // set up logical walls in data structure, whether doing graphical run or not { for (var i = 0; i < gridsize ; i++) for (var j = 0; j < gridsize ; j++) if ( ( i==0 ) || ( i==gridsize-1 ) || ( j==0 ) || ( j==gridsize-1 ) ){ GRID[i][j] = GRID_WALL ; } } function initThreeWalls() // graphical run only, set up blank boxes, painted later { var t = 0; for (var i = 0; i < gridsize ; i++) for (var j = 0; j < gridsize ; j++) if ( GRID[i][j] == GRID_WALL ) { var shape = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize ); var thecube = new THREE.Mesh( shape ); thecube.material.color.setHex( BLANKCOLOR ); thecube.position.x = translate ( i * squaresize ); // translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates thecube.position.z = translate ( j * squaresize ); thecube.position.y = 0; threeworld.scene.add(thecube); WALLS[t] = thecube; // save it for later t++; } } function paintWalls ( material ) { for ( var i = 0; i < WALLS.length; i++ ) { if ( WALLS[i] ) WALLS[i].material = material; } } //----------------- Maze within the walls of the grid, only appears after catching first flag ----------- function initLogicalMaze() { for ( var c=1 ; c <= 10 ; c++ ) { var i = randomintAtoB(1,gridsize-3); // inner squares are 1 to gridsize-2 var j = randomintAtoB(1,gridsize-2); GRID[i][j] = GRID_MAZE ; } } function initThreeMaze() { var t = 0; for (var i = 0; i < gridsize ; i++) for (var j = 0; j < gridsize ; j++) if ( GRID[i][j] == GRID_MAZE ) { var shape = new THREE.ConeGeometry( 50, 300, 10 ); var thecube = new THREE.Mesh( shape ); thecube.material.color.setHex( 0x9f9292 ); thecube.position.x = translate ( i * squaresize ); thecube.position.z = translate ( j * squaresize ); thecube.position.y = 0; threeworld.scene.add(thecube); MAZE[t] = thecube; // save it for later t++; } } function paintMaze ( material ) { for ( var i = 0; i < MAZE.length; i++ ) { if ( MAZE[i] ) MAZE[i].material = material; } } //------------------------- Flag functions ---------------- function drawFlag() // given fi, fj, draw it { if( flag ){ var x = translate ( fi * squaresize ); var z = translate ( fj * squaresize ); var y = ( -1 * squaresize ); flag.position.x = x; flag.position.y = y; flag.position.z = z; threeworld.lookat.copy ( flag.position ); // if camera moving, look back at where the enemy is threeworld.lookat.y = (squaresize * 1.5 ); } } function initLogicalFlag(){ // start in random location: var i, j; do { i = randomintAtoB(1,gridsize-2); j = randomintAtoB(1,gridsize-2); } while ( occupied(i,j) ); // search for empty square fi = i; fj = j; } // --- enemy functions ----------------------------------- function drawEnemy() // given ei, ej, draw it { if ( theenemy ) { var x = translate ( ei * squaresize ); var z = translate ( ej * squaresize ); var y = ( -1 * squaresize ) ; theenemy.position.x = x; theenemy.position.y = y; theenemy.position.z = z; threeworld.lookat.copy ( theenemy.position ); // if camera moving, look back at where the enemy is threeworld.lookat.y = ( squaresize * 1.5 ); } } function initLogicalEnemy() { // start in corner of grid. var i, j; do { i = 1; j = 1; } while ( occupied(i,j) ); // search for empty square ei = i; ej = j; } function moveLogicalEnemy() { // move towards agent // put some randomness in so it won't get stuck with barriers var i, j; if ( ei < ai ) i = randomintAtoB(ei, ei+1); if ( ei == ai ) i = ei; if ( ei > ai ) i = randomintAtoB(ei-1, ei); if ( ej < aj ) j = randomintAtoB(ei, ej+1); if ( ej == aj ) j = ej; if ( ej > aj ) j = randomintAtoB(ej-1, ei); if ( ! occupied(i,j) ) // if no obstacle then move, else just miss a turn { ei = i; ej = j; } } // --- agent functions ----------------------------------- function drawAgent() // given ai, aj, draw it { if( theagent ){ var x = translate ( ai * squaresize ); var z = translate ( aj * squaresize ); var y = ( -1 * squaresize ); theagent.position.x = x; theagent.position.y = y; theagent.position.z = z; threeworld.follow.copy ( theagent.position ); // follow vector = agent position (for camera following agent) threeworld.follow.y = ( squaresize * 1.5 ); } } function initLogicalAgent() { // start at opposite side of grid than enemy var i, j; do { i = 28; j = 28; } while ( occupied(i,j) ); // search for empty square ai = i; aj = 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--; else if ( a == ACTION_JUMPFORWARD) j = j-2; if ( ! occupied(i,j) ) { ai = i; aj = j; } } //---------------- User will control the agent ------------------- function keyHandler(e) // user control { if (e.keyCode == 37) moveLogicalAgent ( ACTION_LEFT ); if (e.keyCode == 38) moveLogicalAgent ( ACTION_DOWN ); if (e.keyCode == 39) moveLogicalAgent ( ACTION_RIGHT ); if (e.keyCode == 40) moveLogicalAgent ( ACTION_UP ); if (e.keyCode == 32) moveLogicalAgent ( ACTION_JUMPFORWARD); //cheat code to jump } // --- 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; } // agent is blocked on all sides, run over function agentBlocked() { return ( occupied (ai-1,aj) && occupied (ai+1,aj) && occupied ( ai,aj+1) && occupied ( ai,aj-1) ); } // if agent is caught by enemy. Run over. function agentCaught(){ return(ai == ei+1 && aj == ej || ai == ei && aj == ej+1 || ai == ei-1 && aj == ej || ai == ei && aj == ej-1 ); } /* When agent finds a flag. That flag is removed and created in a new random place. More of the maze is added in to make it more difficult for the user. */ function captureFlag(){ if(ai == fi && aj == fj || ai == fi+1 && aj == fj || ai == fi && aj == fj+1 || ai == fi-1 && aj == fj || ai == fi && aj == fj-1){ threeworld.scene.remove(flag); flags++; updateStatusAfter(); initLogicalFlag(); initLogicalMaze(); initThreeMaze(); loadTexturesSECOND(); } } //------------- Status ---------- function updateStatusBefore(a) // update status to show old state and proposed move { var x = self.getState(); var status = " Step: <b> " + step + " </b> x = (" + x.toString() + ") a = (" + a + ") "; var statusFlag = " Flags: <b> " + flags; $("#user_span3").html( status ); $("#user_span3").html( statusFlag ); } function updateStatusAfter() // agent and enemy have moved, can calculate score { // new state after both have moved var y = self.getState(); var status = " y = (" + y.toString() + ") <BR> "; $("#user_span4").html( status ); var score = self.getScore(); var status = " Bad steps: " + badsteps + " Good steps: " + goodsteps + " Score: " + score.toFixed(2) + "% "; $("#user_span5").html( status ); } //--- public functions / interface / API ---------------------------------------------------------- this.endCondition; // If set to true, run will end. this.newRun = function() { this.endCondition = false; badsteps = 0; goodsteps = 0; step = 0; flags = 0; // for all runs: initGrid(); initLogicalWalls(); initLogicalFlag(); initLogicalAgent(); initLogicalEnemy(); // for graphical runs only: if ( true ){ if ( show3d ){ BOXHEIGHT = squaresize; threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR ); var ambient = new THREE.AmbientLight(); threeworld.scene.add( ambient ); } else{ BOXHEIGHT = 1; threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR ); } initSkybox(); initMusic(); initThreeWalls(); initThreeMaze(); loadTexturesFIRST(); document.onkeydown = keyHandler; } }; this.getState = function(){ var x = [ ai, aj, ei, ej ]; return ( x ); }; this.nextStep = function() { var a = 4; step++; if ( true ) updateStatusBefore(a); // show status line before moves if ( ( step % 3 ) == 0 ) // slow the enemy down to every nth step moveLogicalEnemy(); if ( badstep() ) badsteps++; else goodsteps++; if ( true ){ drawAgent(); drawEnemy(); drawFlag(); updateStatusAfter(); // show status line after moves } if( captureFlag() ){ updateStatusAfter(); } if ( agentBlocked() ) // if agent blocked in, run over { this.endCondition = true; goodsteps = 0; // you score zero as far as database is concerned if ( true ){ musicPause(); soundAlarm(); } } if( agentCaught()){ this.endCondition = true; if ( true ){ musicPause(); soundAlarm(); } } }; this.endRun = function(){ if ( true ){ musicPause(); if ( this.endCondition ) $("#user_span6").html( " <font color=red> <B> Agent trapped. Final score zero. </B> </font> " ); else $("#user_span6").html( " <font color=green> <B> Run over. You captured this many flags: </B> </font> " + flags ); } }; this.getScore = function(){ return ( ( goodsteps / step ) * 100 + flags); }; } //---- end of World class ------------------------------------------------------- // --- music and sound effects ---------------------------------------- // credits: // http://www.dl-sounds.com/ //playing in background function initMusic() { // put music element in one of the spans var x = "<audio id=theaudio src=/uploads/meabhhoran1/unknownplanet.mp3 autoplay loop> </audio>" ; $("#user_span1").html( x ); } function musicPlay() { // jQuery does not seem to parse pause() etc. so find the element the old way: document.getElementById('theaudio').play(); } function musicPause() { document.getElementById('theaudio').pause(); } //When game over function soundAlarm() { var x = "<audio src=/uploads/meabhhoran1/siren_noise-kevangc-1337458893.mp3 autoplay > </audio>"; $("#user_span2").html( x ); }