// ==============================================================// Simple starter World // 3d-effect World (really a 2-D problem)// Mark Humphrys, 2016. //// Hero = agent = pacman. // Enemy moves randomly.//// This simple World shows:// - Texture load from files (asynchronous file reads)// - Write status to <span> in run window//// It also shows functionality that is built-in to every World:// - Camera control buttons // - Pause/step run// =============================================================================================// =============================================================================================// Scoring:// Bad steps = steps where enemy is within one step of agent.// Good steps = steps where enemy is further away. // Score = good 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 =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_AGENT ='/uploads/starter/pacman.jpg';const TEXTURE_ENEMY ='/uploads/starter/ghost.3.png';const gridsize =5;// number of squares along side of world const squaresize =100;// size of square in pixelsconst MAXPOS = gridsize * squaresize;// length of one side in pixels const SKYCOLOR =0xffffcc;// a number, not a string const BLANKCOLOR = SKYCOLOR ;// make objects this color until texture arrives (from asynchronous file read)const startRadiusConst = MAXPOS *0.8;// distance from centre to start the camera atconst maxRadiusConst = MAXPOS *3;// 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)// --- 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 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 handle to each wall block object so can find it later to paint it 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 // 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]=false;}}}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]==true)returntrue;// fixed objectreturnfalse;}// 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));}// --- asynch 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// 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_AGENT,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
theagent.material =new THREE.MeshBasicMaterial({ map: thetexture });});var loader3 =new THREE.TextureLoader();
loader3.load ( TEXTURE_ENEMY,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
theenemy.material =new THREE.MeshBasicMaterial({ map: thetexture });});}// --- add fixed objects ---------------------------------------- // my numbering is 0 to gridsize-1function 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]=true;// set up data structure}}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]==true){var shape =new THREE.BoxGeometry( squaresize, squaresize, 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 )// paint blank boxes {for(var i =0; i < WALLS.length; i++){if( WALLS[i]) WALLS[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 at same place every time:
ei =Math.trunc ( gridsize /2);// this square will be free
ej =Math.trunc ( gridsize /2);// (bug) use Math.trunc or else you get a bad square number if gridsize is odd}function initThreeEnemy(){var shape =new THREE.BoxGeometry( squaresize, squaresize, squaresize );
theenemy =new THREE.Mesh( shape );
theenemy.material.color.setHex( BLANKCOLOR );
drawEnemy();}function moveLogicalEnemy(){// small random movevar i = randomintAtoB ( ei-1, ei+1);var j = randomintAtoB ( ej-1, ej+1);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 at same place every time:
ai =Math.trunc ( gridsize /2)+1;// this square will be free
aj =Math.trunc ( gridsize /2)+1;}function initThreeAgent(){var shape =new THREE.BoxGeometry( squaresize, squaresize, squaresize );
theagent =new THREE.Mesh( shape );
theagent.material.color.setHex( BLANKCOLOR );
drawAgent();}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++;elseif( 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))returntrue;elsereturnfalse;}function updateStatus(){var score = self.getScore();var status =" Step: "+ step +" out of "+ AB.maxSteps +". Score: "+ score;
$("#user_span1").html( status );}//--- public functions / interface / API ----------------------------------------------------------this.endCondition =false;// If set to true, run will end. this.newRun =function(){
badsteps =0;
goodsteps =0;
step =0;// define logical data structure for the World:
initGrid();
initLogicalWalls();
initLogicalAgent();
initLogicalEnemy();
threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );// Set up blank objects first:
initThreeWalls();
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();// will return sometime later, but can go ahead and render now };this.getState =function(){var x =[ ai, aj, ei, ej ];return( x );};this.takeAction =function( a ){
step++;
moveLogicalAgent(a);
moveLogicalEnemy();if( badstep())
badsteps++;else
goodsteps++;
drawAgent();
drawEnemy();
updateStatus();};this.endRun =function(){};this.getScore =function(){return goodsteps;};}//---- end of World class -------------------------------------------------------