//MAKE WOLRD VERTICAL//GROUP SQUARES INTO PIECES// =============================================================================================// More complex starter World for WWM// 3d-effect Maze World (really a 2-D problem)// Mark Humphrys, 2016.//// This more complex World shows:// - Skybox// - Internal maze (randomly drawn each time)// - Enemy actively chases agent// - Music/audio// - 2D world (clone this and set show3d = false)// - User keyboard control (clone this and comment out Mind actions to see)// =============================================================================================// =============================================================================================// 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 score zero.//// Scoring on the server side is done by taking average of n runs.// Runs where you get trapped and score zero can seriously affect this score. // =============================================================================================// World must define these:const CLOCKTICK =500;// speed of run - move things every n millisecondsconst MAXSTEPS =1000;// length of a run before final score//---- global constants: .......const gridsize =20;// 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 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 =false;// 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 //--- 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)// 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(){// most of World can be private // regular "var" syntax means private variables: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 = new Array ( NOBOXES );var theagent2;var deadBlock;//blocks not under control of player// 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 // regular "function" syntax means private functions: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 ) return true; 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}// This does the file read the old way using loadTexture.// (todo) Change to asynchronous TextureLoader. A bit complex:// Make blank skybox. Start 6 asynch file loads to call 6 return functions.// Each return function checks if all 6 loaded yet. Once all 6 loaded, paint the skybox. /*
// --- alternative skyboxes: .....................................
// space skybox, credit:
// http://en.spaceengine.org/forum/21-514-1
// x,y,z labelled differently
var materialArray = [
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_z.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_z.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_y.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_y.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_x.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_x.jpg" ), side: THREE.BackSide } ) ),
];
// urban photographic skyboxes, credit:
// http://opengameart.org/content/urban-skyboxes
var materialArray = [
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posx.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negx.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posy.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negy.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posz.jpg" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negz.jpg" ), side: THREE.BackSide } ) ),
];
*/// --- 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 ('/uploads/starter/door.jpg',function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
paintWalls (new THREE.MeshBasicMaterial({ map: thetexture }));});/*
var loader2 = new THREE.TextureLoader();
loader2.load ( '/uploads/starter/latin.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
} );
*/var loader3 =new THREE.TextureLoader();
loader3.load ('/uploads/starter/pacman.jpg',function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
theagent2.material =new THREE.MeshBasicMaterial({ map: thetexture });});/*
var loader4 = new THREE.TextureLoader();
loader4.load ( '/uploads/starter/ghost.3.png', 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, 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;}}function drawAgent2(){//adding extra squaresvar x = translate ( ai * squaresize );var z = translate ( aj * squaresize );var y =0;
theagent2.position.x = x;
theagent2.position.y = y;
theagent2.position.z = z;
threeworld.scene.add(theagent2);
threeworld.follow.copy ( theagent2.position );}function initLogicalAgent(){var i =10;var j =2;
ai = i;
aj = j;}function initThreeAgent(){var shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theagent2 =new THREE.Mesh( shape );
theagent2.material.color.setHex( BLANKCOLOR );
drawAgent2();}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--;elseif( a == ACTION_RIGHT ) i++;elseif( a == ACTION_UP ) j++;//else if ( a == ACTION_DOWN ) j--;/*
if(j==20)
{ i =10;
j = 2;//atBottom(i,j)}
*/if(! occupied(i,j)){
ai = i;
aj = j;}}/*
function atBottom(a,b)
{
//var i = a;
//var j = b;
resetAgent();
//drawDeadBlock(i,j);
}
function drawDeadBlock(a,b)
{
}
*///function resetAgent()//{//}function keyHandler(e)// user control // Note that this.takeAction(a) is constantly running at same time, redrawing the screen.{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 );}// --- score: -...................................function 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(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: <b> "+ step +" </b> x = ("+ x.toString()+") a = ("+ a +") ";
$("#user_span3").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_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(){// (subtle bug) must reset variables like these inside newRun (in case do multiple runs)this.endCondition =false;
badsteps =0;
goodsteps =0;
step =0;// for all runs:
initGrid();
initLogicalWalls();// initLogicalMaze();
initLogicalAgent();// initLogicalEnemy();// for graphical runs only:if(true){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();
document.onkeydown = keyHandler;}};this.getState =function(){var x =[ ai, aj, ei, ej ];return( x );};this.takeAction =function( a ){
step++;if(true)
updateStatusBefore(a);// show status line before moves
moveLogicalAgent(a);// if ( ( step % 2 ) == 0 ) // slow the enemy down to every nth step// moveLogicalEnemy();if( badstep())
badsteps++;else
goodsteps++;if(true){
drawAgent2();// 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 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=red> <B> Run over. </B> </font> ");}};this.getScore =function(){return(( goodsteps / step )*100);};}//---- end of World class .........................................// --- music and sound effects ...............................// 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 spans
var x = "<audio id=theaudio src=/uploads/starter/Defense.Line.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();
}
function soundAlarm()
{
var x = "<audio src=/uploads/starter/air.horn.mp3 autoplay > </audio>";
$("#user_span2").html( x );
}
*/