// Cloned by Thomas Mc Cann on 3 Nov 2019 from World "Complex World" by Starter user// Please leave this clone trail here.// ==== Starter World ===============================================================================================// (c) Ancient Brain Ltd. All rights reserved.// This code is only for use on the Ancient Brain site.// This code may be freely copied and edited by anyone on the Ancient Brain site.// This code may not be copied, re-published or used on any other website.// To include a run of this code on another website, see the "Embed code" links provided on the Ancient Brain site.// ==================================================================================================================// =============================================================================================// More complex starter World// 3d-effect Maze World (really a 2-D problem)// Movement is on a semi-visible grid of squares//// 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.// =============================================================================================// ===================================================================================================================// === 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 =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 show3d =true;// Switch between 3d and 2d view (both using Three.js)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/midnightsky/brain1.jpg";// credits:// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg// 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// const 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.htmlconst gridsize =20;// number of squares along side of worldconst NOBOXES =Math.trunc((gridsize * gridsize)/10);// density of maze - number of internal boxes// (bug) use trunc or can get a non-integerconst squaresize =100;// size of square in pixelsconst MAXPOS = gridsize * squaresize;// length of one side in pixelsconst SKYCOLOR =0xddffdd;// a number, not a stringconst 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:// http://stemkoski.github.io/Three.js/Skybox.htmlconst SKYBOX_ARRAY =["/uploads/starter/dawnmountain-xpos.png","/uploads/starter/dawnmountain-xneg.png","/uploads/starter/dawnmountain-ypos.png","/uploads/starter/dawnmountain-yneg.png","/uploads/starter/dawnmountain-zpos.png","/uploads/starter/dawnmountain-zneg.png"];// space skybox, credit:// http://en.spaceengine.org/forum/21-514-1// x,y,z labelled differently/*
const SKYBOX_ARRAY = [
"/uploads/starter/sky_pos_z.jpg",
"/uploads/starter/sky_neg_z.jpg",
"/uploads/starter/sky_pos_y.jpg",
"/uploads/starter/sky_neg_y.jpg",
"/uploads/starter/sky_pos_x.jpg",
"/uploads/starter/sky_neg_x.jpg"
];
*/// urban photographic skyboxes, credit:// http://opengameart.org/content/urban-skyboxes/*
const SKYBOX_ARRAY = [
"/uploads/starter/posx.jpg",
"/uploads/starter/negx.jpg",
"/uploads/starter/posy.jpg",
"/uploads/starter/negy.jpg",
"/uploads/starter/posz.jpg",
"/uploads/starter/negz.jpg"
];
*/// ===================================================================================================================// === End of tweaker's box ==========================================================================================// ===================================================================================================================// You will need to be some sort of JavaScript programmer to change things below the 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 heightvar GRID =newArray(gridsize);// can query GRID about whether squares are occupied, will in fact be initialised as a 2D arrayvar 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;function loadResources(){// asynchronous file loads - call initScene() when all finishedvar 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 returnedif(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 occupiedif(ei == i && ej == j)returntrue;// variable objectsif(ai == i && aj == j)returntrue;if(GRID[i][j]== GRID_WALL)returntrue;// fixed objectsif(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 dimensionsfunction 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 returnedvar 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) coordinatesABWorld.scene.add(thecube);}else GRID[i][j]= GRID_BLANK;// set up mazefor(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) coordinatesABWorld.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 loadABWorld.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) coordinatesABWorld.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) coordinatesABWorld.follow.copy(theagent.position);// follow vector = agent position (for camera following agent)}//*********************************************************************************************************************************//*********************************************************************************************************************************//// THOMAS MC CANN - CA686FL Foundations of Artificial Intelligence - Heuristic Search: Assignment 1////*********************************************************************************************************************************//*********************************************************************************************************************************// Enemy Maze Mind 2D Array// Required to evaluate each maze position and assign f(n), g(n) and h(n) values.// to assign f(n), g(n) and h(n) values to each Maze position, to add neighbouring squaresvar enemysMazeMap =newArray(gridsize);// MazePosion object constructor that take in i,j vales that correspond to the complex world GRID values/positionsfunctionMazePosition(i, j){// i and j hold the MazePosition locationthis.i = i;this.j = j;// Initialise each MazePosition's f(n),g(n) and h(n)// as part of the A* Alogortimthis.f =0;// f(n) = g(n) + h(n)this.g =0;// g(n) - distance from startthis.h =0;// h(n) - reseult of the heuristic evaluation function that calculates the curents positon from the goal ( target / good guy).// A MazePosition object keep a record of the MazePosition postion object before it// in the path which A* calcultes.this.cameFrom =null;// Array to hold each MazePosition objects neighboursthis.neighbours =[];// If the MazePosition is discovered to be a wall or part of the maze then 'this.wall' will be set to true as discovered during game timethis.wall =false;}// Create empty enemysMazeMap array gridsize X gridsizefor(let i =0; i < enemysMazeMap.length; i++){
enemysMazeMap[i]=newArray(gridsize);}// end of for loop// Initialise each MazePosition in the enemysMazeMap 2D arrayfor(let i =0; i < gridsize; i++){for(let j =0; j < gridsize; j++){
enemysMazeMap[i][j]=newMazePosition(i, j);// Adding the maze perimeter/boundary walls to limit search area// (I hope this is not cheating as neither good guy or bad guy use this area)// NOTE: Decided against this and GRID may not be initialised. However I migth add this later.// if(GRID[i][j] == GRID_WALL){// enemysMazeMap[i][j].wall = true;// }}}// Function to add each MazePosition's neighboursMazePosition.prototype.addNeigbours =function(gridsize){var i =this.i;var j =this.j;// The order in which neighbours are added will// affect the A star's alogrithm (in this example)// as nieghbours are evaluated by the default order in which// they were added to each spots/node neighbour array.// The enemy will only be able to move to positions// verticall and horizontally as the good guy can only move UP, DOWN, LEFT, RIGHT// Only vertical and horizontal neighbours will be added// This could be modified to factor in walls and only add neigbours to// Maze positions within the boundaries walls of the maze.// However in the interest of fairness in this exercise we will// let the enemy discover the walls during the Path finding (A*)// and add them to the enemysMazeMap as walls as discoverd at runtime.if(i < gridsize -1){this.neighbours.push(enemysMazeMap[i +1][j]);}if(i >0){this.neighbours.push(enemysMazeMap[i -1][j]);}if(j < gridsize -1){this.neighbours.push(enemysMazeMap[i][j +1]);}if(j >0){this.neighbours.push(enemysMazeMap[i][j -1]);}}// Used to partially reset each MazePsotion before the end of moveLogicalEnemy()function resetEnemyMazeMapPositionValues(){for(let i =0; i < gridsize; i++){for(let j =0; j < gridsize; j++){
enemysMazeMap[i][j].cameFrom =null;
enemysMazeMap[i][j].neighbours =[];
enemysMazeMap[i][j].f =0;// f(n) = g(n) + h(n)
enemysMazeMap[i][j].g =0;// g(n) - distance from start
enemysMazeMap[i][j].h =0;// h(n)// NOTE: We will not reset the boolean wall variable// NOTE: i an j will also remain as is.}}// end outter for loop}// Add each MazePositions neighbours from the enemysMazeMapfor(let i =0; i < gridsize; i++){for(let j =0; j < gridsize; j++){
enemysMazeMap[i][j].addNeigbours(enemysMazeMap);}}// end of for loop// A* heuristic evalution function to calculate the distance between the enemy and target// This function can be used to calculate both the euclidean and manhattan distance// between x,y positions in a grid system.function heuristic(enemyPosition, targetPosition, euclidean =false){// The raw Euclidean distance as a heurstic// Best when we can move both vertically and diagonally// It is the straight line distance between pointsif(euclidean){return AB.distance2D(
enemyPosition.i,
enemyPosition.j,
targetPosition.i,
targetPosition.j
);}// The Manhattan (Taxi cab) distance as a heurstic// Best when we can only move vertically and horizontallyelse{return(Math.abs(enemyPosition.i - targetPosition.i)+Math.abs(enemyPosition.j - targetPosition.j));}}// Utility function to remove a element from an array// Start at the back of the array// If we reomve an itme at th front hten all// elements shift forward (along with their indexes).functionRemoveFromArray(arr, ele){
arr.splice(arr.indexOf(ele),1);}// Objects to hold the enemy start and target positions which will be// updated during each call of moveLogicalEnemy() by the worldvar enemyStartPos =null;var targetPos =null;// --- take actions -----------------------------------//*********************************************************************************************************************************//*********************************************************************************************************************************//// THOMAS MC CANN - CA686FL Foundations of Artificial Intelligence - Heuristic Search: Assignment 1////*********************************************************************************************************************************//*********************************************************************************************************************************function moveLogicalEnemy(){
let path =[];
console.log("CONTENT OF ENENMY MAZE");
console.log(enemysMazeMap)// Start of A* Algorithm// Arrays to hold open and closed sets od postions in the maze during A* Algorithmvar openSet =[];var closedSet =[];// Get the current positions of the enemy and the good guy
enemyStartPos = enemysMazeMap[ei][ej];
targetPos = enemysMazeMap[ai][aj];
console.log("enemyStartPos is a type of: ");
console.log(typeof enemyStartPos);
console.log(Object.keys(enemyStartPos));
console.log(enemyStartPos);
console.log("Enemey in GRID: i = "+ ei +" j = "+ ej);// All good
console.log(targetPos);
console.log("Target in GRID: i = "+ ai +" j = "+ aj);// All good// Push the maze postion of the enemy to the openSet
openSet.push(enemyStartPos);
console.log("Content of openSet before WHILE loop: ");
console.log(openSet);// For Debug
console.log("OPENSET LENGHT : "+ openSet.length);// I ACCIDENTLY DELETED SOME CODE WITHOUT REALISING AND THEN HAD TO TRY REPLICATE IT ONCE I DISCOVERED WHAT HAD HAPPENED. // I HAVE SOMEHOW MADE THINGS WORSE :-(// SOMETHING BAD IS NOW HAPPENING IN THE MAIN WHILE LOOP!!! IT KEPT CRASHING ANCIENT BRAIN.// TRYING TO DEBUG IN ANCIENT BRAIN BUT HAVING DIFFICULTIES// While OpenSet has members continuewhile(openSet.length >0){
console.log("OPENSET INSIDE START OF WHILE LOOP");// For Debug
console.log(openSet);// For Debug// Evaluate the node with the lowest fvar indexOfLowestFInOpenSet =0;for(let i =0; i < openSet.length; i++){// If the f at index i is less than the other fif(openSet[i].f < openSet[indexOfLowestFInOpenSet].f){// The index of the array member with// the lowest f value (Note: f is g + h )
indexOfLowestFInOpenSet = i;}}
console.log("INSIDE WHILE LOOP INDEX OF OPENSET WITH LOWEST f VALUE"+ indexOfLowestFInOpenSet);// Get the MazePostion in the openSet with current lowest f(n)var current = openSet[indexOfLowestFInOpenSet];
console.log("OPENSET AFTER: ");// For Debug
console.log(openSet);// For Debug// Something bad happens after this.
console.log("CURRENT NODE IS SET TO:");
console.log(current);
console.log("TARGET NODE IS SET TO:");
console.log(targetPos);break;if(current === targetPos){
console.log("FOUND OPTIMAL PATH TO THE TARGET");// Clear and create a new path
path =[];var temp = current;while(temp !==null){
path.push(temp);
temp = temp.cameFrom;break;}
console.log("Showing optimal path found to target:")
console.log(path);}else{// Otherwsie remove the spot/node/element from the openSet// and push it on to the closed setRemoveFromArray(openSet, current);
console.log("AFTER ITEM REMOVED FROM OPEN SET");// SEEMS EMPTY
closedSet.push(current);// For Debug
console.log("OPENSET AFTER ITEM REMOVED FROM OPEN SET");// SEEMS EMPTY
console.log(openSet);// For Debug
console.log("CLOSED SET NOW HAS");//
console.log(closedSet);// For Debug// Get the current spots elementsvar neighbours = current.neighbours;
console.log("NEIGHBOURS OF CURRENT:");
console.log(neighbours);// Evaluate neighboursfor(let i =0; i < neighbours.length; i++){var neighbour = neighbours[i];
console.log("CURENT NEIGHBOUR BEING EXAMINED");
console.log(neighbour);if(
GRID [neighbour.i][neighbour.j]== GRID_WALL ||
GRID [neighbour.i][neighbour.j]== GRID_MAZE
){// If our inspection of the path show it is a wall or part of the maze then we do not want to// include it in our path.
neighbour.wall =true;
console.log("Found a wall");}// If the neighbour is not part of the closed set or is has been registered as a wall in thenif(!closedSet.includes(neighbour)&&!neighbour.wall){// tentative g(n) value if g(n) does not already// have a lower g(n) value. (Or no g(n) value at all.)// var tempG = current.g + heuristic(neighbour, current); <- Seems to increase h(n) way too much// Under certain conditions this seems more effective/faster// Each move is only one cell away from the last cell so added// 1 to the distance form start is adequate and will keep h(n) lower.// May not work as well under some other game parametersvar tempG = current.g +1;
console.log("TEMPG");
console.log(tempG);var newPath =false;if(openSet.includes(neighbour)){if(tempG < neighbour.g){
neighbour.g = tempG;// This g is lower then we have a new path!
newPath =true;}}else{// If this MazePosition is not already on the openSet push it on to the openSet// Use the tentative tempG as its new g value
neighbour.g = tempG;
openSet.push(neighbour);// We have a new curent more optimal path sp far
newPath =true;}
console.log("NEWPATH"+ newPath);// Only recalculate the h(n) and f(n) for this neighbour if it was a new pathif(newPath){// Calculate h
neighbour.h = heuristic(neighbour, targetPos);// f(n) = g(n) + h(n)
neighbour.f = neighbour.g + neighbour.h;// Keep track of where we came from
neighbour.cameFrom = current;
console.log(current);}}// end -- if(openSet.includes(neighbour))}// end for loop of checking neighbours}}// end while loop// Things to do before we exit the moveLogicalEnemy function.// console.log("Showing optimal path found to target:");//console.log(path);// Move the enemy on step along the best path.// ei = path[0].i;// ej = path[0].j;// console.log("Enemy currently at i: " + ei);// console.log("Enemy currently at j: " + ej);// console.log(path);// console.log("Next move for enemy is to j: " + path[0]);// if (!occupied(path[0].i,path[0].j) )// {// ei = i;// ej = j;// }// resetEnemyMazeMapPositionValues();}// end of moveLogicalEnemyfunction moveLogicalAgent(a){// this is called by the infrastructure that gets action a from the Mindvar 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;}}// --- 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 agentif(Math.abs(ei - ai)<2&&Math.abs(ej - aj)<2)returntrue;elsereturnfalse;}function agentBlocked(){// agent is blocked on all sides, run overreturn(
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 movevar 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
moveLogicalEnemy();if(badstep()) badsteps++;else goodsteps++;
drawAgent();
drawEnemy();
updateStatusAfter();// show status line after movesif(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> Agent 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.4372778var x =Math.round(s *100);// 9344return x /100;// 93.44};// --- music and sound effects ----------------------------------------var backmusic = AB.backgroundMusic(MUSIC_BACK);function musicPlay(){
backmusic.play();}function musicPause(){
backmusic.pause();}function soundAlarm(){var alarm =newAudio(SOUND_ALARM);
alarm.play();// play once, no loop}