// Cloned by test2 on 8 Jan 2018 from World "Touch World" by Starter user // Please leave this clone trail here.// Demo of a number of things:// - Capture your own Touch events (on mobile) so touch can control the agent rather than the camera // - Keyboard control of agent (on desktop) // - Button to play audio on mobile (mobile browsers block audio autoplay)// 2D World// No Mind. User controls agent.// Desktop:// Mouse moves camera// Keyboard arrows to move agent // Mobile (no keyboard):// Touch does not move camera// Touch moves agent // ============================================================================================= // To do my own mouse/touch: // Over-ride certain "threehandler" functions. See below. // =============================================================================================// Some features cloned from "Complex World" // - Skybox// - 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.// =============================================================================================// These 3 have default values, so this section is optional:
AB.clockTick =100;// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps =2000;// 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/starter/pacman.jpg';const TEXTURE_ENEMY ='/uploads/starter/ghost.3.png';const MUSIC_BACK ='/uploads/starter/Defense.Line.mp3';const MUSIC_ALARM ='/uploads/starter/air.horn.mp3';const gridsize =20;// number of squares along side of world const NOBOXES =Math.trunc ((gridsize * gridsize)/10);// density of maze - number of internal boxesconst squaresize =100;// size of square in pixelsconst MAXPOS = gridsize * squaresize;// length of one side in pixels const SKYCOLOR =0xddffdd;// a number, not a string const BLANKCOLOR = SKYCOLOR ;// make objects this color until texture arrives (from asynchronous file read)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 atconst skyboxConst = MAXPOS *3;// where to put skybox const maxRadiusConst = MAXPOS *10;// maximum distance from camera we will render things //--- change threeworld defaults: -------------------------------
threehandler.MAXCAMERAPOS = skyboxConst /2;// keep camera inside skybox
threehandler.GROUNDZERO =true;// "ground" exists at altitude zeroconst 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)// contents of a grid squareconst 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){returnfalse;}else{returntrue;}}//---- start of World class -------------------------------------------------------functionWorld(){var BOXHEIGHT;// 3d or 2d box height var GRID =newArray(gridsize);// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array var WALLS =newArray(4* gridsize );// need to keep handles to wall and maze objects so can find them later to paint them var MAZE =newArray( NOBOXES );var theagent, theenemy;// enemy and agent position on squaresvar ei, ej, ai, aj;var badsteps;var goodsteps;var step;var self =this;// needed for private fn to call public fn - see below function initGrid(){for(var i =0; i < gridsize ; i++){
GRID[i]=newArray(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 ))returntrue;// variable objects if(( ai == i )&&( aj == j ))returntrue;if( GRID[i][j]== GRID_WALL )returntrue;// fixed objects if( GRID[i][j]== GRID_MAZE )returntrue;returnfalse;}// 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));}//--- skybox ----------------------------------------------------------------------------------------------function initSkybox(){// 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.htmlvar materialArray =[(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("/uploads/starter/dawnmountain-xpos.png"), side: THREE.BackSide})),(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("/uploads/starter/dawnmountain-xneg.png"), side: THREE.BackSide})),(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("/uploads/starter/dawnmountain-ypos.png"), side: THREE.BackSide})),(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("/uploads/starter/dawnmountain-yneg.png"), side: THREE.BackSide})),(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("/uploads/starter/dawnmountain-zpos.png"), side: THREE.BackSide})),(new THREE.MeshBasicMaterial({ map: THREE.ImageUtils.loadTexture("/uploads/starter/dawnmountain-zneg.png"), 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}// --- asynchronous load textures from file ----------------------------------------// credits:// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg?uselang=en-gb// 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// loader return can call private functionfunction loadTextures(){var loader1 =new THREE.TextureLoader();
loader1.load ( TEXTURE_WALL,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
paintWalls (new THREE.MeshBasicMaterial({ map: thetexture }));});var loader2 =new THREE.TextureLoader();
loader2.load ( TEXTURE_MAZE,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
paintMaze (new THREE.MeshBasicMaterial({ map: thetexture }));});var loader3 =new THREE.TextureLoader();
loader3.load ( TEXTURE_AGENT,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
theagent.material =new THREE.MeshBasicMaterial({ map: thetexture });});var loader4 =new THREE.TextureLoader();
loader4.load ( TEXTURE_ENEMY,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
theenemy.material =new THREE.MeshBasicMaterial({ map: thetexture });});}// --- add fixed objects ---------------------------------------- function initLogicalWalls()// set up logical walls in data structure {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()// 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;}}function initLogicalMaze(){for(var c=1; c <= NOBOXES ; c++){var i = randomintAtoB(1,gridsize-2);// inner squares are 1 to gridsize-2var 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.BoxGeometry( squaresize, BOXHEIGHT, squaresize );var thecube =new THREE.Mesh( shape );
thecube.material.color.setHex( BLANKCOLOR );
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;}}// --- enemy functions -----------------------------------function drawEnemy()// given ei, ej, draw it {var x = translate ( ei * squaresize );var z = translate ( ej * squaresize );var y =0;
theenemy.position.x = x;
theenemy.position.y = y;
theenemy.position.z = z;
threeworld.scene.add(theenemy);
threeworld.lookat.copy ( theenemy.position );// if camera moving, look back at where the enemy is }function initLogicalEnemy(){// 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
ei = i;
ej = j;}function initThreeEnemy(){var shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theenemy =new THREE.Mesh( shape );
theenemy.material.color.setHex( BLANKCOLOR );
drawEnemy();}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(ej, ej+1);if( ej == aj ) j = ej;if( ej > aj ) j = randomintAtoB(ej-1, ej);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 {var x = translate ( ai * squaresize );var z = translate ( aj * squaresize );var y =0;
theagent.position.x = x;
theagent.position.y = y;
theagent.position.z = z;
threeworld.scene.add(theagent);
threeworld.follow.copy ( theagent.position );// follow vector = agent position (for camera following agent)}function initLogicalAgent(){// 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
ai = i;
aj = j;}function initThreeAgent(){var shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theagent =new THREE.Mesh( shape );
theagent.material.color.setHex( BLANKCOLOR );
drawAgent();}function moveLogicalAgent( a ){var i = ai;var j = aj;if( a == ACTION_LEFT ) i--;elseif( a == ACTION_RIGHT ) i++;elseif( a == ACTION_UP ) j++;elseif( a == ACTION_DOWN ) j--;if(! occupied(i,j)){
ai = i;
aj = j;}}// --- score and status: -----------------------------------// uses user_span4 to user_span6function badstep()// is the enemy within one square of the agent{if((Math.abs(ei - ai)<2)&&(Math.abs(ej - aj)<2))returntrue;elsereturnfalse;}function agentBlocked()// agent is blocked on all sides, run over{return( occupied (ai-1,aj)&&
occupied (ai+1,aj)&&
occupied ( ai,aj+1)&&
occupied ( ai,aj-1));}function updateStatusBefore(){var x = self.getState();var status =" Step: <b> "+ step +" </b> x = ("+ x.toString()+") ";
$("#user_span4").html( status );}function updateStatusAfter()// agent and enemy have moved, can calculate score{// new state after both have movedvar y = self.getState();var status =" y = ("+ y.toString()+") <BR> ";
$("#user_span5").html( status );var score = self.getScore();var status =" Bad steps: "+ badsteps +" Good steps: "+ goodsteps +" Score: "+ score.toFixed(2)+"% ";
$("#user_span6").html( status );}// --- keyboard and touch handling functions: ----------------------------------------function keyHandler ( e ){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 );};// touch drags to move the agent var startX, startY;var touchevents;// number of touch events in the current drag function initTouchDrag ( x, y )// x,y position on screen{
startX = x;
startY = y;
touchevents =0;};function touchDrag ( x, y )// compare with previous x,y position on screen to get direction of drag{if(( touchevents %10)==0)// slow it down to respond to every nth touch event - too many touch events{if( x > startX ) moveLogicalAgent ( ACTION_RIGHT );elseif( x < startX ) moveLogicalAgent ( ACTION_LEFT );if( y > startY ) moveLogicalAgent ( ACTION_UP );elseif( y < startY ) moveLogicalAgent ( ACTION_DOWN );}
touchevents++;
startX = x;
startY = y;};//--- public functions / interface / API ----------------------------------------------------------this.endCondition =false;// If set to true, run will end. this.newRun =function(){
badsteps =0;
goodsteps =0;
step =0;// for all runs:
initGrid();
initLogicalWalls();
initLogicalMaze();
initLogicalAgent();
initLogicalEnemy();if( show3d ){
BOXHEIGHT = squaresize;
threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );}else{
BOXHEIGHT =1;
threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR );}
initSkybox();
initMusic();// Set up objects first:
initThreeWalls();
initThreeMaze();
initThreeAgent();
initThreeEnemy();// Then paint them with textures - asynchronous load of textures from files. // The texture file loads return at some unknown future time in some unknown order.// Because of the unknown order, it is probably best to make objects first and later paint them, rather than have the objects made when the file reads return.// It is safe to paint objects in random order, but might not be safe to create objects in random order.
loadTextures();// --- redirect keyboard and touch event handling to my own functions: ----------------------------------------
document.onkeydown = keyHandler;// override threehandler default (which is camera control) to use my own functions:
threehandler.initTouchDrag = initTouchDrag;
threehandler.touchDrag = touchDrag
};this.getState =function(){var x =[ ai, aj, ei, ej ];return( x );};this.nextStep =function(){
step++;
updateStatusBefore();// show status line before moves if(( step %2)==0)// slow the enemy down to every nth step
moveLogicalEnemy();if( badstep())
badsteps++;else
goodsteps++;
drawAgent();
drawEnemy();
updateStatusAfter();// show status line after moves if( agentBlocked())// if agent blocked in, run over {this.endCondition =true;
goodsteps =0;// you score zero as far as database is concerned
musicPause();
soundAlarm();}};// uses user_span7this.endRun =function(){
musicPause();if(this.endCondition )
$("#user_span7").html(" <font color=red> <B> Agent trapped. Final score zero. </B> </font> ");else
$("#user_span7").html(" <font color=red> <B> Run over. </B> </font> ");};this.getScore =function(){return(( goodsteps / step )*100);};}//---- end of World class -------------------------------------------------------// --- music and sound effects ----------------------------------------// uses user_span1 to user_span3// credits:// http://www.dl-sounds.com/royalty-free/defense-line/// http://soundbible.com/1542-Air-Horn.html function initMusic(){// put music element in one of the spansvar x ="<audio id=theaudio src="+ MUSIC_BACK +" autoplay loop> </audio>";
$("#user_span1").html( x );// the above <audio autoplay> works on desktop // for mobile need user interaction to play: if(! AB.onDesktop()){// when clicked, button plays audio and then vanishes:
x =" <p> Start music: "+" <button onclick='playVanish();' style='font-size:larger;' > ♫ </button> </p> "+" <script> "+" function playVanish() "+" { "+" document.getElementById('theaudio').play(); "+" $('#user_span2').html( '' ); "+" } "+" </script> ";
$("#user_span2").html( x );}}// desktop JS can pause/play audio// mobile JS can pause music but cannot play alarm function musicPlay(){// jQuery way of doing this is awkward too, so do it the old way:
document.getElementById('theaudio').play();}function musicPause(){
document.getElementById('theaudio').pause();}function soundAlarm(){var x ="<audio src="+ MUSIC_ALARM +" autoplay > </audio>";
$("#user_span3").html( x );}