// Cloned by Tristan Everitt on 19 Oct 2022 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 =true;// Switch between 3d and 2d view (both using Three.js)// Pool Water. CC0 1.0 Universal (CC0 1.0) Public Domain Dedication// Source: https://www.publicdomainpictures.net/en/view-image.php?image=126102&picture=pool-waterconst TEXTURE_WALL ='/uploads/tristan/water.jpg';// Der Kothaufen-Emoji (💩) (englisch: pile of poo)// Source: https://de.wikipedia.org/wiki/Kothaufen-Emoji// Licence: Apache License 2.0const TEXTURE_MAZE ='/uploads/tristan/poo.png';// Liz Truss official portrait is licensed under the United Kingdom Open Government Licence v3.0.// https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/// Source: https://commons.wikimedia.org/wiki/File:Liz_Truss_official_portrait.jpgconst TEXTURE_AGENT ='/uploads/tristan/truss.jpg';// Lettuce by Nick Youngson CC BY-SA 3.0 Alpha Stock Images// Source: https://www.picserver.org/photo/32776/Lettuce.html// Licence: Creative Commons 3 - CC BY-SA 3.0// Attribution: Alpha Stock Images - http://alphastockimages.com/// Original Author: Nick Youngson - link to - http://www.nyphotographic.com/const TEXTURE_ENEMY ='/uploads/tristan/lettuce.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// ROYALTY-FREE “BENNY HILL THEME” STYLE ALTERNATIVE// Source: http://toolofgod.com/my-music/royalty-free-benny-hill-theme-style-alternative/const MUSIC_BACK ='/uploads/tristan/Benny-Hill-Theme-style-Royalty-free.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 =30;// number of squares along side of worldconst NOBOXES =Math.trunc((gridsize * gridsize)/4);// 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.html//const 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"//];// Union Square Skybox - https://opengameart.org/content/urban-skyboxes// This is the work of Emil Persson, aka Humus. http://www.humus.name// These were previously under a custom licence, but he re-licensed them to CC-BY-3.0.const SKYBOX_ARRAY =["/uploads/tristan/union-square-posx.jpg","/uploads/tristan/union-square-negx.jpg","/uploads/tristan/union-square-posy.jpg","/uploads/tristan/union-square-negy.jpg","/uploads/tristan/union-square-posz.jpg","/uploads/tristan/union-square-negz.jpg"];// 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;
let BOXHEIGHT;// 3d or 2d box height
let GRID =newArray(gridsize);// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array
let theagent, theenemy;
let wall_texture, agent_texture, enemy_texture, maze_texture;// enemy and agent position on squares//et ei, ej, ai, aj;
let enemy =newNode();
let agent =newNode();
let badsteps;
let goodsteps;function loadResources()// asynchronous file loads - call initScene() when all finished{
let loader1 =new THREE.TextureLoader();
let loader2 =new THREE.TextureLoader();
let loader3 =new THREE.TextureLoader();
let 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{return!!(wall_texture && agent_texture && enemy_texture && maze_texture);}//--- grid system -------------------------------------------------------------------------------// my numbering is 0 to gridsize-1function occupied(i, j)// is this square occupied{const variableObjects = isEnemy(i, j)|| isAgent(i,j);return variableObjects || fixedObject(i, j);}function fixedObject(i, j){const outsideBoundary = i >= gridsize || j >= gridsize;return outsideBoundary || GRID[i][j]=== GRID_WALL || GRID[i][j]=== GRID_MAZE;}// 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){
let 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{
let 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(let 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
enemy.i = i;
enemy.j = 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
agent.i = i;
agent.j = 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(translateNode(enemy));// 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(translateNode(agent));// 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)}// --- take actions -----------------------------------function moveLogicalEnemy(){
useAStarSearch ? aStarEnemyMove(): legacyEnemyMove();}function legacyEnemyMove(){// move towards agent// put some randomness in so it won't get stuck with barriers
let i, j;if(enemy.i < agent.i) i = AB.randomIntAtoB(enemy.i, enemy.i +1);if(enemy.i === agent.i) i = enemy.i;if(enemy.i > agent.i) i = AB.randomIntAtoB(enemy.i -1, enemy.i);if(enemy.j < agent.j) j = AB.randomIntAtoB(enemy.j, enemy.j +1);if(enemy.j === agent.j) j = enemy.j;if(enemy.j > agent.j) j = AB.randomIntAtoB(enemy.j -1, enemy.j);if(!occupied(i, j))// if no obstacle then move, else just miss a turn{
enemy.i = i;
enemy.j = j;}}function moveLogicalAgent(a)// this is called by the infrastructure that gets action a from the Mind{
let i = agent.i;
let j = agent.j;if(a === ACTION_LEFT) i--;elseif(a === ACTION_RIGHT) i++;elseif(a === ACTION_UP) j++;elseif(a === ACTION_DOWN) j--;if(!sittingDuck &&!occupied(i, j)){
agent.i = i;
agent.j = 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:
let 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{return(Math.abs(enemy.i - agent.i)<2)&&(Math.abs(enemy.j - agent.j)<2);}function agentBlocked()// agent is blocked on all sides, run over{
let count =0;
let obstacles =[];if(occupied(agent.i -1, agent.j)){
count++;
obstacles.push(occupiedBy(agent.i -1, agent.j));}if(occupied(agent.i +1, agent.j)){
count++;
obstacles.push(occupiedBy(agent.i +1, agent.j));}if(occupied(agent.i, agent.j +1)){
count++;
obstacles.push(occupiedBy(agent.i, agent.j +1));}if(occupied(agent.i, agent.j -1)){
count++;
obstacles.push(occupiedBy(agent.i, agent.j -1));}const blocked = obstacles.length ===4;if(blocked){
let o ='';
obstacles.forEach(obstacle =>{
o +=' '+ obstacle
});
console.log('Agent blocked by: '+ o);}return blocked;}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{
let 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 moved
let y = AB.world.getState();
let 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(){
let x =[agent.i, agent.j, enemy.i, enemy.j];return(x);};
AB.world.takeAction =function(a){
updateStatusBefore(a);// show status line before moves
moveLogicalAgent(a);if((AB.step % enemyNthStep)===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.maxSteps
let s =(goodsteps / AB.maxSteps)*100;// float like 93.4372778
let x =Math.round(s *100);// 9344return(x /100);// 93.44};// --- music and sound effects ----------------------------------------
let backmusic = AB.backgroundMusic(MUSIC_BACK);function musicPlay(){
backmusic.play();}function musicPause(){
backmusic.pause();}function soundAlarm(){
let alarm =newAudio(SOUND_ALARM);
alarm.play();// play once, no loop}// ==================== [START] Tristan's parameters ====================// [7] Change the enemy to use A* to make a move.const useAStarSearch =true;const useHeuristic =true;// false: h(n) = 0const bestFirst =false;// true: f(n) = h(n); false: f(n) = g(n) + h(n)// ///////////////////////////////////////////// [6] Diagonal moves off: Change the World so the enemy cannot make diagonal moves. This makes obstacles much harder.const diagonal =false;// ///////////////////////////////////////////// [10] Prove it is A* by drawing the paths on screen.const drawAStarPath =true;// Draws the best path found by A*const drawAStarClosedSet =true;// Draw the points added to the closed setconst drawAStarOpenSet =true;// Draw the points added to the open set, excluding the best path.// ///////////////////////////////////////////const enemyNthStep =2;const sittingDuck =false;// No moving target used for debugging.// ==================== [END] Tristan's parameters ====================// ==================== [START] Tristan's functions ====================function findMinimumNode(set){// Best next option
let winner =0;for(let i =0; i <set.length; i++){
winner =set[i].f <set[winner].f ? i : winner;}returnset[winner];}function isFinalNode(source, target){return source.i === target.i && source.j === target.j;}function removeNode(set, n){set.splice(indexOfNode(set, n),1);}function aStarEnemyMove(){
clearPlanOfAttack();const planOfAttack = aStarMove(enemy, agent);if(planOfAttack.bestPath && planOfAttack.bestPath.length >0){const nextMove = planOfAttack.bestPath[0];
drawPlanOfAttack(planOfAttack);
console.log('[aStarEnemyMove] enemy=['+ enemy.i +', '+ enemy.j +'] agent=['+ agent.i +', '+ agent.j +'] nextMove: ['+ nextMove.i +', '+ nextMove.j +', h='+ nextMove.h +', g='+ nextMove.g +', f='+ nextMove.f +']');
enemy.i = nextMove.i;
enemy.j = nextMove.j;}else{
console.log('[aStarEnemyMove] enemy=['+ enemy.i +', '+ enemy.j +'] agent=['+ agent.i +', '+ agent.j +'] nextMove: none');}}function indexOfNode(collection, node){return collection.findIndex(e => e.i === node.i && e.j === node.j);}function containsNode(collection, node){return indexOfNode(collection, node)>=0;}function aStarMove(subject, destination){
let openSet =[];
let closedSet =[];
openSet.push(subject);
let current = createNode(subject.i, subject.j);while(openSet.length >0){
current = findMinimumNode(openSet);if(isFinalNode(current, destination)){
let c = current;
let path =[];// Build path from end to startwhile(c.previous){
path.push(c);
c = c.previous;}
current.bestPath = path.reverse();
current.openSet = openSet;
current.closedSet = closedSet;return current;}
removeNode(openSet, current);
closedSet.push(current);
let neighbours = examineValidNeighbours(current);
neighbours.forEach(neighbour =>{const validMove =!containsNode(closedSet, neighbour);if(validMove){const g =(bestFirst ?0: current.g);const h = heuristic(neighbour, current);const tempG = g + h;// Is this a better path than before?
let optimal =false;if(!containsNode(openSet, neighbour)){
optimal =true;
neighbour.h = heuristic(neighbour, destination);
openSet.push(neighbour);}elseif(tempG < neighbour.g){
optimal =true;}// Yes, it's a better pathif(optimal){
neighbour.g = tempG;
neighbour.h = heuristic(neighbour, destination);
neighbour.f = neighbour.g + neighbour.h;
neighbour.previous = current;}}});}
console.log('No path found');// Add empty defaults
current.bestPath =[];
current.openSet =[];
current.closedSet =[];return current;}function examineValidNeighbours(subject){
let i = subject.i;
let j = subject.j;
let neighbours =[];if(!fixedObject(i +1, j)){
neighbours.push(createNode(i +1, j));}if(!fixedObject(i -1, j)){
neighbours.push(createNode(i -1, j));}if(!fixedObject(i, j +1)){
neighbours.push(createNode(i, j +1));}if(!fixedObject(i, j -1)){
neighbours.push(createNode(i, j -1));}if(diagonal)// diagonals are also neighbours:{if(!fixedObject(i -1, j -1)){
neighbours.push(createNode(i -1, j -1));}if(!fixedObject(i +1, j -1)){
neighbours.push(createNode(i +1, j -1));}if(!fixedObject(i -1, j +1)){
neighbours.push(createNode(i -1, j +1));}if(!fixedObject(i +1, j +1)){
neighbours.push(createNode(i +1, j +1));}}return neighbours;}function heuristic(a, b){if(useHeuristic){return diagonal ? euclideanDistance(a, b):Math.abs(a.i - b.i)+Math.abs(a.j - b.j);}else{return0;}}function euclideanDistance(a, b){//http://en.wikipedia.org/wiki/Euclidean_distancereturnMath.sqrt(Math.pow((a.i - b.i),2)+Math.pow((a.j - b.j),2));}function createNode(i, j){const n =newNode();
n.i = i;
n.j = j;return n;}functionNode(){// Locationthis.i =null;this.j =null;this.previous =null;// f, g, and h values for A*this.f =0;this.g =0;this.h =0;}function drawPlanOfAttack(planOfAttack){if(drawAStarPath){const points =[];
planOfAttack.bestPath.forEach(node =>{// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
points.push(translateNode(node));});// A yellow diamondconst meshParams ={color:0xffff00};const radius =15;const widthSegments =32;const heightSegments =32;
showSpheres(points,meshParams, radius, widthSegments, heightSegments);}
drawOpenSet(planOfAttack);
drawClosedSet(planOfAttack);}function drawOpenSet(planOfAttack){if(drawAStarOpenSet){const points =[];
planOfAttack.openSet.forEach(node =>{if(!(isEnemy(node.i,node.j)|| isAgent(node.i,node.j))){// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
points.push(translateNode(node));}});// A blue sphereconst meshParams ={color:0x0b5394};const radius =10;const widthSegments =10;const heightSegments =2;
showSpheres(points,meshParams, radius, widthSegments, heightSegments);}}function drawClosedSet(planOfAttack){if(drawAStarClosedSet){const points =[];
planOfAttack.closedSet.forEach(node =>{if(!(isEnemy(node.i,node.j)|| isAgent(node.i,node.j))){// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
points.push(translateNode(node));}});// A red diamondconst meshParams ={color:0xe06666};const radius =10;const widthSegments =10;const heightSegments =2;
showSpheres(points,meshParams, radius, widthSegments, heightSegments);}}
let meshCleanup =[];function showSpheres(points, meshParameters, radius, widthSegments, heightSegments){// Remove first and last from list since they are the enemy/agent
points.shift();
points.pop();
points.forEach(point =>{const geometry =new THREE.SphereGeometry(radius, widthSegments, heightSegments);const material =new THREE.MeshBasicMaterial(meshParameters);const mesh =new THREE.Mesh(geometry, material);
meshCleanup.push(mesh);ABWorld.scene.add(mesh);
mesh.position.copy(point);});}function clearPlanOfAttack(){
meshCleanup.forEach(mesh =>{ABWorld.scene.remove(mesh);});
meshCleanup =[];}function occupiedBy(i, j){if(i >= gridsize || j >= gridsize){return'boundary';}elseif(GRID[i][j]=== GRID_WALL){return'wall';}elseif(GRID[i][j]=== GRID_MAZE){return'pillar';}elseif(enemy.i === i && enemy.j === j){return'enemy';}else{return'';}}function translateNode(n){return translate(n.i, n.j);}function isEnemy(i, j){return enemy.i === i && enemy.j === j;}function isAgent(i, j){return agent.i === i && agent.j === j;}// ==================== [END] Tristan's functions ====================