// Cloned by Aoife Doherty on 4 Nov 2020 from World "Complex World" by Starter user // Please leave this clone trail here.//Testvar aoife =0// ==== Starter World =================================================================================================// This code is designed for use on the Ancient Brain site.// This code may be freely copied and edited by anyone on the Ancient Brain site.// To include a working run of this program on another site, see the "Embed code" links provided on Ancient Brain.// ====================================================================================================================// =============================================================================================// 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.// =============================================================================================// ===================================================================================================================// === Start of tweaker's box ======================================================================================== // ===================================================================================================================// The easiest things to modify are in this box.// You should be able to change things in this box without being a JavaScript programmer.// Go ahead and change some of these. What's the worst that could happen?
AB.clockTick =0.0000001;// 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 show3d =true;// Switch between 3d and 2d view (both using Three.js) const TEXTURE_WALL ='/uploads/starter/door.jpg';const TEXTURE_MAZE ='/uploads/aoifemariedoherty/Cavewallblack.jpg';const TEXTURE_AGENT ='/uploads/aoifemariedoherty/indiana.jpg';const TEXTURE_ENEMY ='/uploads/aoifemariedoherty/stonegrey.jpg';// credits://https://www.facebook.com/MattredBackground/download.jpg //https://colourlex.com/project/zinc-yellow/// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg// http://en.wikipedia.org/wiki/File:Inscription_displaying_apices_(from_the_shrine_of_the_Augustales_at_Herculaneum).jpg//https://www.economist.com/babbage/2014/06/23/the-colour-purple.html//add in mona lisaconst MUSIC_BACK ='/uploads/starter/Defense.Line.mp3';const SOUND_ALARM ='/uploads/starter/air.horn.mp3';// credits:// http://www.dl-sounds.com/royalty-free/defense-line/// http://soundbible.com/1542-Air-Horn.html const gridsize =50;// number of squares along side of world **AOIFE CHANGED THIS TO 50** const NOBOXES =Math.trunc ((gridsize * gridsize)/3);//**AOIFE CHANGED THIS TO GRID SIZE DIVIDED BY 3**// density of maze - number of internal boxes// (bug) use trunc or can get a non-integer const squaresize =100;// size of square in pixelsconst spheresize =55;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 atconst maxRadiusConst = MAXPOS *10;// maximum distance from camera we will render things //--- change ABWorld defaults: -------------------------------ABHandler.MAXCAMERAPOS = maxRadiusConst ;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 // https://threejs.org/docs/#api/en/loaders/CubeTextureLoader // mountain skybox, credit:// https://opengameart.org/content/cloudy-skyboxes //*AOIFE CHANGED THISconst SKYBOX_ARRAY =["/uploads/aoifemariedoherty/bluecloud_rt.jpg",//*AOIFE CHANGED ALL THESE; credit below"/uploads/aoifemariedoherty/bluecloud_lf.jpg","/uploads/aoifemariedoherty/bluecloud_up.jpg","/uploads/aoifemariedoherty/bluecloud_dn.jpg","/uploads/aoifemariedoherty/bluecloud_bk.jpg","/uploads/aoifemariedoherty/bluecloud_ft.jpg"];// space skybox, credit:// https://opengameart.org/content/cloudy-skyboxes //*AOIFE CHANGED THIS// ===================================================================================================================// === End of tweaker's box ==========================================================================================// ===================================================================================================================//--- 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;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 theagent, theenemy;var wall_texture, agent_texture, enemy_texture, maze_texture;// enemy and agent position on squaresvar ei, ej, ai, aj;var badsteps;var goodsteps;var shownPath =[];var path =[];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();});}function asynchFinished()// all file loads returned {if( wall_texture && agent_texture && enemy_texture && maze_texture)returntrue;elsereturnfalse;}//--- grid system -------------------------------------------------------------------------------// my numbering is 0 to gridsize-1function 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;}// 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 arrayfor( i =0; i < gridsize ; i++)
GRID[i]=newArray(gridsize);// set up wallsfor( 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 locationdo{
i = AB.randomIntAtoB(1,gridsize-2);
j = AB.randomIntAtoB(1,gridsize-2);}while( occupied(i,j));// search for empty square
ei = i;
ej = j;
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 locationdo{
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.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
theagent =new THREE.Mesh( shape );
theagent.material =new THREE.MeshBasicMaterial({ map: agent_texture });ABWorld.scene.add(theagent);
drawAgent();// finally skybox // setting up skybox is simple // just pass it array of 6 URLs and it does the asych load ABWorld.scene.background =new THREE.CubeTextureLoader().load ( SKYBOX_ARRAY,function(){ABWorld.render();
AB.removeLoading();
AB.runReady =true;// start the run loop});}// --- 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)}//--------------------------------------------MAIN PART OF CODE ADDED BY AOIFE----------------------------------------//-----Step 1: Function to remove from list by index//-----Reason: Removes from OpenSetfunction removeFromArray (array_name,element){for(var i = array_name.length -1; i >=0; i--)//loop through array backwardsif(array_name[i]== element)//if array index == element (to be deleted)
array_name.splice(i,1);//then splice the index (i.e. so idea is to splice from end so don't change indexes when delete)}//--------Step 2: Function to generate info for each spot on grid.//--------Reason: Keeps the data for each spot on grid (i.e. variables below, neighbours, x, y) for A*functionMakeSpot(i,j){this.i = i;//this is x point, so can drawthis.j = j;//this is y point, so can drawthis.f =0;//the f value for spot (of f = g + h)this.g =0;this.h =0;this.neighbors =[];//what should I add to the opensetthis.previous =undefined;//unecessary because by definition, undefined.this.wall = GRID[i][j]!== GRID_BLANK;//changed the wall bit from original scriptthis.addNeighbors =function(grid){var x =this.i;var y =this.j;if(x < gridsize -1)this.neighbors.push(grid[x +1][y])//got these from original A* scriptif(y < gridsize -1)this.neighbors.push(grid[x][y +1])if(x >0)this.neighbors.push(grid[x -1][y])if(y >0)this.neighbors.push(grid[x][y -1])//this part is for diagonals // if (x < gridsize - 1 && y > 0) this.neighbors.push(grid[x + 1][y - 1])// if (x < gridsize - 1 && y < gridsize - 1) this.neighbors.push(grid[x + 1][y + 1])// if (x > 0 && y > 0) this.neighbors.push(grid[x - 1][y - 1])// if (x > 0 && y < gridsize - 1) this.neighbors.push(grid[x - 1][y + 1])}}//-----Step 3: Functions for the optimistic heuristic//-----Reason: To calculate the heuristic (h(n) -> the distance from the current point to the goal).function heuristic(a,b){return(Math.abs(a.i - b.i)+Math.abs(a.j-b.j));//manhattan disease; no diagonal.}function euc_heuristic(a,b){return(Math.sqrt(Math.pow((a.i-b.i),2)+Math.pow((a.j-b.j),2)));//Euclidean distance; no diagonal.}function oct_heuristic (a,b){return(Math.max(Math.abs(a.i-b.i),Math.abs(a.j-b.j))+(Math.sqrt(2)-1)*Math.min(Math.abs(a.i-b.i),Math.abs(a.j-b.j)))}function chebyshev_heuristic(a,b){return(Math.max((Math.abs(a.i-b.i),Math.abs(a.j-b.j))))}//----Step 4: Function to draw the pathfunction highlightPath(pathName, colourName){//for each step up to the end (not including the end)for(var i =0; i < pathName.length -1; i++){//make the shape: https://threejs.org/docs/#api/en/geometries/BoxGeometry (squaresize set as a constant above)
shape =new THREE.BoxGeometry(squaresize,.1, squaresize)//to draw the shape so unaffected by light: https://threejs.org/docs/#api/en/materials/MeshBasicMaterial.color; could change with the parametersShownPath=new THREE.Mesh(shape)//set the colour https://threejs.org/docs/#api/en/materials/MeshBasicMaterialShownPath.material =new THREE.MeshBasicMaterial({color: colourName});//using translate function above; translate my (i,j) grid coordinates to three.js (x,y,z) coordinatesShownPath.position.copy(translate(pathName[i].i, pathName[i].j))//add the ShownPath to the sceneABWorld.scene.add(ShownPath)//add the ShownPath to the drawn path (defined above before function)
shownPath.push(ShownPath)}}//----Step 5: A star algorithm.//----Reason: generate A starfunction moveLogicalEnemy(){var grid =newArray(gridsize);//this is making a list, the number of columns in the gridvar openSet =[];//set of neighbors that we need to checkvar closedSet =[];//set of nodes that we don't need to check anymore.//need to remove past positions for(var i =0; i < shownPath.length; i++)ABWorld.scene.remove(shownPath[i]);//for each i up to gridsize, make a empty, at that pointfor(i =0; i < gridsize; i++)
grid[i]=newArray(gridsize);//for each i, for each j, do function MakeSpot for i and j; makes the variables that will be filled in in futurefor(var i =0; i < gridsize; i++)for(var j =0; j < gridsize; j++)
grid[i][j]=newMakeSpot(i, j);//for each i, for each j, make the neighboursfor(i =0; i < gridsize; i++)for(var j =0; j < gridsize; j++)
grid[i][j].addNeighbors(grid);//put the position of the enemy into openSet//make sure openSet length > 0for(openSet.push(grid[ei][ej]); openSet.length >0;){//index of the winner in openSetvar winner =0;//for i in length openSetfor(var i =0; i < openSet.length; i++)//if the f value of openSet[i] < winnerif(openSet[i].f < openSet[winner].f)//winner becomes i
winner = i;//current is the openSet[0] which is the winnervar current = openSet[winner];//setting the current to where we want to get to (e.g. if change to 5,9 the enemy just goes to 5,9 and then nothing happens)if(current === grid[ai][aj]){//make a listvarMoveOptionsForEnemy=[];//make a variable with our best optionvarBestOption= current;//agent's current position//pushed BestOption into MoveOptionsForEnemy because it's the agent's position//then we push in all the nodes making up the path to goalMoveOptionsForEnemy.push(BestOption);while(BestOption.previous){//move the previous to the listMoveOptionsForEnemy.push(BestOption.previous);//the previous becomes the BestOption (and then that's iterated through)BestOption=BestOption.previous;}//highlights the path between enemy and agent
highlightPath(MoveOptionsForEnemy,255);//to get the second last position in list (to be our next position)varNumberOfOptionsMinusTwo=MoveOptionsForEnemy.length -2;//variable to check if occupiedvar isOccupied = occupied(MoveOptionsForEnemy[NumberOfOptionsMinusTwo].i,MoveOptionsForEnemy[NumberOfOptionsMinusTwo].j
);//if not occupiedif(!isOccupied){//move enemy from last position to second last
ei =MoveOptionsForEnemy[NumberOfOptionsMinusTwo].i;
ej =MoveOptionsForEnemy[NumberOfOptionsMinusTwo].j;}//finishedbreak}//if it's not going to go in the direction of the agent
removeFromArray(openSet, current)//move to the closed list
closedSet.push(current);//all the potential neighbors to be checkedvar neighbors = current.neighbors;//for each neighbor in the set of neighborsfor(i =0; i < neighbors.length; i++){//make a variable called neighborvar neighbor = neighbors[i];//if any of the co-ordinate neighbours is the agentif(neighbor[i]=== grid[ai][aj]){//needs to know where it came from
neighbor.previous = current;//break loopbreak}//if the neighbor isn't in the closed set, and it's not a wallif(!closedSet.includes(neighbor)&&!neighbor.wall){//calculate the heuristic from that pointvar tempG = current.g + heuristic(neighbor, current);//variable called newPath, set to false.var newPath =false;//if it's already in openSet to be checked, if tempG is lower, make the g value the new one.if(openSet.includes(neighbor)){if(tempG < neighbor.g){
neighbor.g = tempG;
newPath =true;}}//set the g of that neighbor to be tempG, it's a new path to be checked, send it to be checked.else{
neighbor.g = tempG;
newPath =true;
openSet.push(neighbor);}//if it's a new path to be checked, calculate info.if(newPath){
neighbor.h = heuristic(neighbor, grid[ai][aj]);
neighbor.f = neighbor.g + neighbor.h;
neighbor.previous = current;//keeps track of where you've been}}}}}// --- take actions -----------------------------------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++;//changed so it fits print out for ej and ajelseif( a == ACTION_DOWN ) j--;//changed so it fits print out for ej and ajif(! occupied(i,j)){
ai = i;
aj = j;}}// --- key handling --------------------------------------------------------------------------------------// This is hard to see while the Mind is also moving the agent:// AB.mind.getAction() and AB.world.takeAction() are constantly running in a loop at the same time // have to turn off Mind actions to really see user key control // we will handle these keys: var OURKEYS =[37,38,39,40];function ourKeys ( event ){return( OURKEYS.includes ( event.keyCode ));}function keyHandler ( event ){if(! AB.runReady )returntrue;// not ready yet // if not one of our special keys, send it to default key handling:if(! ourKeys ( event ))returntrue;// else handle key and prevent default handling:if( event.keyCode ==37) moveLogicalAgent (ACTION_LEFT);if( event.keyCode ==38) moveLogicalAgent (ACTION_DOWN);if( event.keyCode ==39) moveLogicalAgent (ACTION_RIGHT);if( event.keyCode ==40) moveLogicalAgent (ACTION_UP);// when the World is embedded in an iframe in a page, we want arrow key events handled by World and not passed up to parent
event.stopPropagation(); event.preventDefault();returnfalse;}// --- 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 = AB.world.getState();
AB.msg (" Step: "+ AB.step +" x = ("+ x.toString()+") a = ("+ a +") ");}function updateStatusAfter()// agent and enemy have moved, can calculate score{// new state after both have movedvar y = AB.world.getState();var score =( goodsteps / AB.step )*100;
AB.msg (" y = ("+ y.toString()+") <br>"+" Bad steps: "+ badsteps +" Good steps: "+ goodsteps +" Score: "+ score.toFixed(2)+"% ",2);}
AB.world.newRun =function(){
AB.loadingScreen();
AB.runReady =false;
badsteps =0;
goodsteps =0;if( show3d ){
BOXHEIGHT = squaresize;ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR );}else{
BOXHEIGHT =1;ABWorld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR );}
loadResources();// aynch file loads // calls initScene() when it returns
document.onkeydown = keyHandler;};
AB.world.getState =function(){var x =[ ai, aj, ei, ej ];return( x );};
AB.world.takeAction =function( a ){
updateStatusBefore(a);// show status line before moves
moveLogicalAgent(a);if(( AB.step %2)==0)// slow the enemy down to every nth step //aoife changed this
moveLogicalEnemy();if( badstep()) badsteps++;else goodsteps++;
drawAgent();
drawEnemy();
updateStatusAfter();// show status line after moves if( agentBlocked())// if agent blocked in, run over {
AB.abortRun =true;
goodsteps =0;// you score zero as far as database is concerned
musicPause();
soundAlarm();}};
AB.world.endRun =function(){
musicPause();if( AB.abortRun ) AB.msg (" <br> <font color=red> <B> Indiana Jones trapped. Final score zero. </B> </font> ",3);else AB.msg (" <br> <font color=green> <B> Run over. </B> </font> ",3);};
AB.world.getScore =function(){// only called at end - do not use AB.step because it may have just incremented past AB.maxStepsvar s =( goodsteps / AB.maxSteps )*100;// float like 93.4372778 var x =Math.round (s *100);// 9344return( x /100);// 93.44};// --- music and sound effects ----------------------------------------var backmusic = AB.backgroundMusic ('/uploads/aoifemariedoherty/IJTheme.mp3');function musicPlay(){ backmusic.play();}function musicPause(){ backmusic.pause();}function soundAlarm(){var alarm =newAudio('/uploads/aoifemariedoherty/adiossapito1.mp3');
alarm.play();// play once, no loop }