// Clicked Clone and edit on my already cloned world by mistake// Cloned by Darragh McG on 2 Nov 2023 from World "Practical 1: Darragh McGonigle - A* - Complex World" by Darragh McG // Please leave this clone trail here.// Cloned by Darragh McG on 31 Oct 2023 from World "Complex World" by Starter user // Please leave this clone trail here.// ==== 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.// ====================================================================================================================// =============================================================================================// 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 =false;// Switch between 3d and 2d view (both using Three.js) // Darragh - Changed Textures// ----------------------------------------------------------------------------const TEXTURE_WALL ='/uploads/darraghmcg/wall.png';const TEXTURE_MAZE ='/uploads/darraghmcg/wall.png';// Pacman image credit https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/Original_PacMan.svg/800px-Original_PacMan.svg.pngconst TEXTURE_AGENT ='/uploads/darraghmcg/packman1.png';// Enemy ghoat image credit https://i.pinimg.com/736x/05/05/ce/0505cea3c74abd202c39969568803da2.jpgconst TEXTURE_ENEMY ={0:'/uploads/darraghmcg/ghost1.png',1:'/uploads/darraghmcg/ghost2.png',2:'/uploads/darraghmcg/ghost3.png',3:'/uploads/darraghmcg/ghost4.png',4:'/uploads/darraghmcg/ghost5.png'};const TEXTURE_SEARCHPATH ='/uploads/darraghmcg/searchpathtext.png';// ----------------------------------------------------------------------------// credits:// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_iconsconst 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 =40;// 20; // number of squares along side of world const NOBOXES =Math.trunc ((gridsize * gridsize)/4);// 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 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 // Darragh - Changed Skybox// space skybox, credit:// https://opengameart.org/content/space-skyboxes-0// ----------------------------------------------------------------------------const SKYBOX_ARRAY =["/uploads/darraghmcg/bkg1_right.png","/uploads/darraghmcg/bkg1_left.png","/uploads/darraghmcg/bkg1_top.png","/uploads/darraghmcg/bkg1_bot.png","/uploads/darraghmcg/bkg1_front.png","/uploads/darraghmcg/bkg1_back.png"];// ----------------------------------------------------------------------------// Darragh - Tweaker Select Movement Pattern// 0 - AStar// 1 - BFS// 2 - Naive// 3 - AStar Longest Path// 4 - AStar Achilles// ----------------------------------------------------------------------------const MOVEMENT_PATTERN =0;// ----------------------------------------------------------------------------// ===================================================================================================================// === 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 height var GRID =newArray(gridsize);// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array // Darragh - Added board variable to store game state// ----------------------------------------------------------------------------
let BOARD =newArray(gridsize);// ----------------------------------------------------------------------------var theagent, theenemy;// Darragh - Added search path texture for search path blocks// ----------------------------------------------------------------------------var wall_texture, agent_texture, enemy_texture, maze_texture, searchpath_texture;// ----------------------------------------------------------------------------// enemy and agent position on squaresvar ei, ej, ai, aj;var badsteps;var goodsteps;// Darragh - Added varibles to store the starting points of the enemy and the agent for analysis
let enemy_start;
let agent_start;//Track if the enemy is using the achilles movemnet pattern if they performed recalculations this step//Darragh - Variables to store most recent eval time and count// ----------------------------------------------------------------------------
let enemyEvalTime =0;
let enemyEvalCount =0;
let achillesRecomp =false;// ----------------------------------------------------------------------------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();// Darragh - Added texture loader for searchpath block texture// ----------------------------------------------------------------------------var loader5 =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();});// Darragh - Added selector so that diffrent enemy texture is used for diffrent movement options
loader3.load ( TEXTURE_ENEMY[MOVEMENT_PATTERN],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();});// Darragh - Added load call for searchpath block texture// ----------------------------------------------------------------------------
loader5.load ( TEXTURE_SEARCHPATH,function( thetexture ){
thetexture.minFilter = THREE.LinearFilter;
searchpath_texture = thetexture;if( asynchFinished()) initScene();});// ----------------------------------------------------------------------------}function asynchFinished()// all file loads returned {if( wall_texture && agent_texture && enemy_texture && maze_texture && searchpath_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;}// Darragh - Added Logging Classes to enable download of stats// ----------------------------------------------------------------------------const logHeadings =["gridsize","no_boxes","max_step","enemy_start","agent_start","step","agent_pos","enemy_pos","bad_steps","good_steps","score","distance","enemy_eval_count","enemy_eval_time","achilles_recomp","heat_map"];classLogFile{
constructor(){this.lines =[logHeadings];}
addLine(logMesssage){const logline =[logMesssage.gridsize,
logMesssage.no_boxes,
logMesssage.max_step,
logMesssage.enemy_start,
logMesssage.agent_start,
logMesssage.step,
logMesssage.agent_pos,
logMesssage.enemy_pos,
logMesssage.bad_steps,
logMesssage.good_steps,
logMesssage.score,
logMesssage.distance,
logMesssage.enemy_eval_count,
logMesssage.enemy_eval_time,
logMesssage.achilles_recomp,
logMesssage.heat_map];this.lines.push(logline);}}const LOGFILE =newLogFile();//Darragh - Download array example used//https://medium.com/@idorenyinudoh10/how-to-export-data-from-javascript-to-a-csv-file-955bdfc394a9function downloadLogFile(){
tsvString = LOGFILE.lines.map(line => line.map(field => JSON.stringify(field)).join("\t")).join("\n");const blob =newBlob([tsvString],{ type:'text/tsv;charset=utf-8,'});const objUrl = URL.createObjectURL(blob);const link = document.createElement('a');
link.setAttribute('href', objUrl);
link.setAttribute('download',`${newDate().toLocaleString("ja-JP")}ResultsMode${MOVEMENT_PATTERN}.tsv`);
link.textContent ='Click to Download';
document. getElementById('user_span3').append(link);}classLogMessage{
constructor(step, agent_pos, enemy_pos, bad_steps, good_steps, score, distance){this.gridsize = gridsize;this.no_boxes = NOBOXES;this.max_step = AB.maxSteps;this.enemy_start = enemy_start;this.agent_start = agent_start;this.step = step;this.agent_pos = agent_pos;this.enemy_pos = enemy_pos;this.bad_steps = bad_steps;this.good_steps = good_steps;this.score = score;this.distance = distance;this.enemy_eval_count = enemyEvalCount,this.enemy_eval_time = enemyEvalTime,this.achilles_recomp = achillesRecomp,this.heat_map = structuredClone(heatmap.map);}}
let heatmap;classHeatMap{
constructor(){this.map =newArray(gridsize);for(let i=0; i < gridsize; i++){this.map[i]=[];for(let j=0; j < gridsize; j++){if(BOARD[i][j].wall)this.map[i].push(-1);elsethis.map[i].push(0);}}}
increase(position){this.map[position[1]][position[0]]=this.map[position[1]][position[0]]+1;}}// ----------------------------------------------------------------------------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();//Darragh - Store the initial starting points of Agent and Enemy// ----------------------------------------------------------------------------
enemy_start =[ei, ej];
agent_start =[ai, aj];// ----------------------------------------------------------------------------// 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});// Darragh - Create initial board and heatmap// ----------------------------------------------------------------------------
resetBoard();
heatmap =newHeatMap();// ----------------------------------------------------------------------------}// Darragh// -- Reset Game Board// Reinistialised a blacnk game board to clear previous BoardPositions// ----------------------------------------------------------------------------function resetBoard(){for(let i =0; i < GRID.length; i++){const row = GRID[i];const rowList =newArray(row.length);for(let j =0; j < row.length; j++){const isWall = row[j]>0;const position =newBoardPosition(i, j, isWall);
rowList[j]= position;}
BOARD[i]= rowList;}}// ----------------------------------------------------------------------------// --- 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)}// Darragh - Options for enemy movement patters// ----------------------------------------------------------------------------// Darragh - Naive Movement Pattern// This is the defualt movement but with no diagonals// It simply aims to reduce distance between itself and the target with no regard for obstaclesfunction moveEnemyNaive(){// move towards agent // put some randomness in so it won't get stuck with barriers const start =newDate().getTime();var i, j;if( ei < ai ) i = AB.randomIntAtoB(ei, ei+1);if( ei == ai ) i = ei;if( ei > ai ) i = AB.randomIntAtoB(ei-1, ei);if( ej < aj ) j = AB.randomIntAtoB(ej, ej+1);if( ej == aj ) j = ej;if( ej > aj ) j = AB.randomIntAtoB(ej-1, ej);// Count the 6 evalutaions above
enemyEvalCount +=6;// Darragh - // Remove Diagonal Moves by only allowing enemy to move in either the i of the j direction per game stepif(i !== ei &&! occupied(i,ej))// if no obstacle then move, else just miss a turn{
ei = i;}elseif(!occupied(ei, j)){
ej = j;}
enemyEvalTime =newDate().getTime()- start;}// - End of Naive Movement Pattern// Darragh - A* Movement Pattern// As described by the brief enemy will use A* to find the shortest path to the enemy// This will be recalculated each step// Work based on "World: A star" https://ancientbrain.com/world.php?world=2230793148 as shown in the lab session// Also based on Daniel Shiffman// Darragh - Adapted Shiffman's functions "Spot" and changed it to a class as personal preferenceclassBoardPosition{
constructor(i, j, wall){// Position coords i = x, j = ythis.i = i;this.j = j;// f, g, h values for A*this.f =0;this.g =0;this.h =0;//Neighboursthis.neighbours =[];//Previous Postion in paththis.previous =undefined;// Darragh - Changed wall check to be done prior to creationthis.wall = wall;}
addNeighbours(){if(this.i < GRID.length -1)this.neighbours.push(BOARD[this.i +1][this.j]);if(this.i >0)this.neighbours.push(BOARD[this.i -1][this.j]);if(GRID.length &&this.j < GRID[0].length -1)this.neighbours.push(BOARD[this.i][this.j +1]);if(this.j >0)this.neighbours.push(BOARD[this.i][this.j -1]);}}// Default AStar hueristic from "World: A star" with abs replaces with Math.abs as abs is a p5 functionfunction defaultAStarHeuristic ( a, b ){return(Math.abs(a.i - b.i)+Math.abs(a.j - b.j));}// Darragh - Hueristic that returns constant value// As shown in the lab exercise this should effectivly perform BFS instead of AStarfunction bfsHeuristic ( a, b ){return(0);}// Darragh - Hueristic that returns inverted value to allow for longest path searchfunction invertedAStarHeuristic (a, b){return-(Math.abs(a.i - b.i)+Math.abs(a.j - b.j));}// Default AStar gfn from "World: A star" with abs replaces with Math.abs as abs is a p5 functionfunction defaultGFN ( a, b ){return(Math.abs(a.i - b.i)+Math.abs(a.j - b.j));}// Darragh - GFN that returns inverted value to allow for longest path searchfunction invertedGFN ( a, b ){return-(Math.abs(a.i - b.i)+Math.abs(a.j - b.j));}// Darragh - Removes old search path blocks between runs to avoid clutterfunction cleanOldPath(){// Find all objects in the Three.js scene with the searchpath texture and remove themconst cubesToDelete =ABWorld.scene.children.filter((child)=> child.material && child.material.map.image.src.includes(TEXTURE_SEARCHPATH));for(let cube of cubesToDelete)ABWorld.scene.remove(cube);}// Darragh - Helper fucntion to return a list containing the path from enemy to agentfunction getPath(end){const path =[];//If no path existsif(!end.previous){
console.log("No Path Found")return path;}// Start from the position before agent
let current = end.previous;//While not back to enemywhile(current.previous){
path.unshift(current);
current = current.previous;}return path;}// Darragh - Helper function to draw a path of positions // In the "World: A star" example the draw function is internalied into the "Spot" function// However I chose to split it out as in that case it handles the drawing of the whole board where in this case the board is already drawn so I just need to draw a few positionsfunction drawPath(path){for(let square of path){
shape =new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube =new THREE.Mesh( shape );
thecube.material =new THREE.MeshBasicMaterial({ map: searchpath_texture });
thecube.position.copy ( translate(square.i,square.j));// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates ABWorld.scene.add(thecube);}}// Darragh - Adapted default "World: A star" AStar fucntion// Darragh - Added "heurictic" as a property so that diffrent heuristics can be passed infunction calculateAStar(heuristic, gfn, endPos=BOARD[ai][aj]){const openSet =[];const closedSet =[];// Start search at current enemy position
let startPos = BOARD[ei][ej];
openSet.push(startPos);while(openSet.length){//Track squares evalutated
enemyEvalCount++
let winner =0;for(let i =0; i < openSet.length; i++)if(openSet[i].f < openSet[winner].f)
winner = i;const current = openSet[winner];//If the endPosition is foundif(current === endPos){break;}// Darragh - Opt to use the "winner" index as opposed to "World: A star" which uses a seperate function to iterate the openSet and remove// This serves the same purpose, as "winner" is the index of current in the openSet as seen on the line above// Remove current from openSet and put it in closedSet
openSet.splice(winner,1);
closedSet.push(current);// Darragh - Added call to "addNeighbours" function only when needed// This should save on memory as opposed to the alternative approach from "World: A star" where neighbours are computed at setup time for every "Spot" even those that are never visited
current.addNeighbours();const neighbours = current.neighbours;// Darragh - Changed to a for of loop rather than standard i=0; i < lenght; i++for(let neighbour of neighbours){if(!closedSet.includes(neighbour)&&!neighbour.wall){var g = current.g + gfn(neighbour, current);// Is this a better path
let newPath =false;if(openSet.includes(neighbour)){if(g < neighbour.g){
neighbour.g = g;
newPath =true;}}else{
neighbour.g = g;
newPath =true;
openSet.push(neighbour);}if(newPath){
neighbour.h = heuristic ( neighbour, endPos );
neighbour.f = neighbour.g + neighbour.h;
neighbour.previous = current;}}}}return endPos;}// Darragh - Seperated AStar calulation into another function to allow resuse in other movement patterns with diffrent heuristicsfunction moveEnemyAStar(heuristic, gfn=defaultGFN){const start =newDate().getTime();const endPosition = calculateAStar(heuristic, gfn);//Perform Time analysis before drawing path to eliminate bias of long path draw times
enemyEvalTime =newDate().getTime()- start;const path = getPath(endPosition);// Don't move if next to agent// Program will work without this check but there will be console errorsif(path.length){
ei = path[0].i;
ej = path[0].j;}
drawPath(path.slice(1));}// Darragh - Inspired by the paradox of Achilles and the tortoise the enemy performs AStar and determines a path to the enemys potistion// It will not adjust its target until it reaches its previous target// It does still recompute its path every step as to avoid collisions with the agent//variable that holds current target location of the achillesAStar search
let achillesTarget =null;function moveEnemyAchillesAStar(){const start =newDate().getTime()// If enemy has reached its target reset targetif(achillesTarget){if(ei === achillesTarget[0]&& ej === achillesTarget[1]){
achillesTarget =null;}}//If enemy has no target observe agent positionif(!achillesTarget){
achillesRecomp =true
achillesTarget =[ai, aj];}// Perform a star towards targetconst endPosition = calculateAStar(defaultAStarHeuristic, defaultGFN, BOARD[achillesTarget[0]][achillesTarget[1]]);//Perform Time analysis before drawing path to eliminate bias of long path draw times
enemyEvalTime =newDate().getTime()- start;const path = getPath(endPosition);// getPath function doesn't include endPosition so add it back
path.push(endPosition);// if there is a space to move to moveif(path.length &&!occupied(path[0].i, path[0].j)){
ei = path[0].i;
ej = path[0].j;}
drawPath(path.slice(1));}// - End of Options for enemy movement patters// --- take actions -----------------------------------// Darragh - Enemy Movement Selector// variables holding analysis data about emeny eval time and numberfunction moveLogicalEnemy(){// Remove old search path
resetBoard();
cleanOldPath();//Reset eval time and count variables
enemyEvalTime =0
enemyEvalCount =0
achillesRecomp =false// 0 - AStar// 1 - BFS// 2 - Naive// 3 - AStar Longest Path// 4 - AStar Achillesswitch(MOVEMENT_PATTERN){case0:
moveEnemyAStar(defaultAStarHeuristic);break;case1:
moveEnemyAStar(bfsHeuristic);break;case2:
moveEnemyNaive();break;case3:
moveEnemyAStar(invertedAStarHeuristic, invertedGFN);break;case4:
moveEnemyAchillesAStar();break;}
heatmap.increase([ei,ej]);}// ----------------------------------------------------------------------------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;}}// --- 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;//Darragh Added distance readout// ----------------------------------------------------------------------------
let distance = defaultAStarHeuristic(BOARD[ai][aj], BOARD[ei][ej]);// ----------------------------------------------------------------------------
AB.msg (" y = ("+ y.toString()+") <br>"+" Bad steps: "+ badsteps +" Good steps: "+ goodsteps +" Score: "+ score.toFixed(2)+"% "+" Distance: "+ distance,2);//Darragh - Save status to log fileconst logMessage =newLogMessage(AB.step,[ai, aj],[ei, ej], badsteps, goodsteps, score, distance);
LOGFILE.addLine(logMessage);}
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 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(){
console.log(LOGFILE);
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.msg (' <br> <input id="clickMe" type="button" value="Request LogFile" onclick="downloadLogFile();" /> ',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 ( MUSIC_BACK );function musicPlay(){ backmusic.play();}function musicPause(){ backmusic.pause();}function soundAlarm(){var alarm =newAudio( SOUND_ALARM );
alarm.play();// play once, no loop }