Code viewer for World: Part 2: Capture Baby Yoda!!
// Cloned by Laura Campbell on 5 Nov 2022 from World "Capture Baby Yoda!! " by Laura Campbell 
// Please leave this clone trail here.

// ===================================================================================================================
// ===================================================================================================================
// ==== Assignment 1 | Part 2  ========================================================================================
/*
        STUDENT NAME:       Laura Campbell
        STUDENT NUMBER:     22269176
        STUDENT EMAIL:      laura.campbell26@mail.dcu.ie
        MODULE NAME:        Foundations of AI (online)
        MODULE CODE:        CA686I
        ACIENTBRAIN USER:   lauracampbell26
        
        
        I have based the theme and sounds of my complex world on Star Wars. 
        The StormTrooper is the enemy that is trying to capture Grogue (Baby Yoda) in the spacemaze. 
        Silly but was fun to work on :) 
        
        I hopefully have applied the correct credits due to the images, 
        sounds and code created based on the many chops & changes made.




*/
// ===================================================================================================================
// ===================================================================================================================
// ===================================================================================================================

// number of wall movements 
const WALLMOVENO = 10;

// set setInterval function used to adjust speed of run in milliseconds
const WALLMOVETICK = 500; // 500;

// Cloned by Laura Campbell on 23rd 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?


/* The speed of a single run: Step every n milliseconds. Default 100. */
AB.clockTick = 100; // 100;

/* Length of run: Maximum length of run in steps. Default 1000. */
AB.maxSteps = 1000;

/* Take screenshot on this step. (All resources should have finished loading.) Default 50. */
AB.screenshotStep  = 50;


//============ global constants: ============//

// Switch between 3d and 2d view (both using Three.js)
 const show3d = true;
// ===================================================================================================================
//***======== Laura Campbell
        // credits for image textures:
        // https://pixabay.com/images/search/star%20wars/
        // https://www.google.com/imgres?imgurl=https%3A%2F%2Fwallpaperaccess.com%2Ffull%2F1717708.jpg&imgrefurl=https%3A%2F%2Fwallpaperaccess.com%2Fwhite-galaxy&tbnid=Oydk4I900cYAdM&vet=12ahUKEwjy5K754IX7AhVMEsAKHSypCxYQMygGegUIARDGAQ..i&docid=JlWKk41SXJx8CM&w=1904&h=1274&q=galaxy%20space%20wallpaper%20white&hl=en-GB&ved=2ahUKEwjy5K754IX7AhVMEsAKHSypCxYQMygGegUIARDGAQ
        // https://play.google.com/store/apps/details?id=space.galaxy.wallpapershd&hl=en_US&gl=US
        // https://www.google.com/search?q=alien+head&tbm=isch&ved=2ahUKEwic6Of_3oX7AhWHQ0EAHbQpATMQ2-cCegQIABAA&oq=alien+hea&gs_lcp=CgNpbWcQARgAMgQIABBDMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEOgcIABCxAxBDUO8DWN0IYPUTaABwAHgAgAFHiAGpApIBATWYAQCgAQGqAQtnd3Mtd2l6LWltZ8ABAQ&sclient=img&ei=H0VdY9zcFoeHhbIPtNOEmAM&bih=697&biw=1076&rlz=1C5GCEM_enIE992IE993#imgrc=KI6qHtem2DaMAM
        // https://www.google.com/search?q=astronaut+head+&tbm=isch&ved=2ahUKEwjutvTe34X7AhUMi1wKHVceCTwQ2-cCegQIABAA&oq=astronaut+head+&gs_lcp=CgNpbWcQAzIECAAQQzIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABDIFCAAQgAQyBQgAEIAEMgUIABCABFCxClixCmC1DmgAcAB4AIABQIgBdJIBATKYAQCgAQGqAQtnd3Mtd2l6LWltZ8ABAQ&sclient=img&ei=5kVdY-6rMYyW8gLXvKTgAw&bih=697&biw=1076&rlz=1C5GCEM_enIE992IE993&hl=en-GB#imgrc=tGY3UmSc2vI8WM


 const TEXTURE_WALL  	= '/uploads/lauracampbell26/wall_starwars.jpeg' ;
 const TEXTURE_MAZE 	= '/uploads/lauracampbell26/spacewall.jpeg' ;

// ===================================================================================================================
//***======== Laura Campbell : Adding in additional maze box textures & textured path ========***//

         //const TEXTURE_MAZE3 	= '/uploads/lauracampbell26/arid_rt.jpg' ;
         const TEXTURE_MAZE2 	= '/uploads/lauracampbell26/spacewall2.jpeg' ;
         const TEXTURE_PATH 	= '/uploads/lauracampbell26/red.jpeg' ;

//***========


 const TEXTURE_AGENT 	= '/uploads/lauracampbell26/grogu.jpeg';    //space.jpeg' ;
 const TEXTURE_ENEMY 	= '/uploads/lauracampbell26/troop.jpeg';    //alien.png' ;

// ===================================================================================================================
//***======== Laura Campbell
        // credits for sound:
        // https://www.soundboard.com/sb/starwars
         const MUSIC_BACK  = '/uploads/lauracampbell26/imperial.mp3' ;
         const SOUND_ALARM = '/uploads/lauracampbell26/chewie.mp3' ;

// number of squares along side of world
const gridsize = 25; // 25;

// density of maze - number of internal boxes | (bug) use trunc or can get a non-integer
const NOBOXES =  Math.trunc ( (gridsize * gridsize) / 15 );  // 15

// size of square in pixels
const squaresize = 100;

// length of one side in pixels
const MAXPOS = gridsize * squaresize;

// a number, not a string
const SKYCOLOR 	= 0xddffdd;

// distance from centre to start the camera at
const startRadiusConst	 	= MAXPOS * 1 ;

// maximum distance from camera we will render things
const maxRadiusConst 		= MAXPOS * 10  ;



//============ change ABWorld defaults: ============//

ABHandler.MAXCAMERAPOS 	= maxRadiusConst ;
// "ground" exists at altitude zero
ABHandler.GROUNDZERO		= true;



//============ 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


// ===================================================================================================================
//***======== Laura Campbell: Changed skybox imagery ========***//
        // credits:
        // spacemoon-skybox, credit: https://opengameart.org/content/spacemoon-skybox
     const SKYBOX_ARRAY = [
                    "/uploads/lauracampbell26/indigo_ft.jpg",
                    "/uploads/lauracampbell26/indigo_bk.jpg",
                    "/uploads/lauracampbell26/indigo_up.jpg",
                    "/uploads/lauracampbell26/indigo_dn.jpg",
                    "/uploads/lauracampbell26/indigo_rt.jpg",
                    "/uploads/lauracampbell26/indigo_lf.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 square
const GRID_BLANK 	= 0;
const GRID_WALL 	= 1;
const GRID_MAZE 	= 2;

// ===================================================================================================================
//***======== Laura Campbell: : Different Blocks to be view in maze ========***//
        const GRID_MAZE2 	= 3;
        const GRID_MAZE3 	= 4;

// ===================================================================================================================
//***======== Laura Campbell: Shape sizing for path and players ========***//

        const sphereRadius  = 45;
        const sphereHeight  = 10;
        const sphereWidth   = 10;
        const pathRadius    = 20;
        const pathHeight    = 5;
        const pathWidth     = 5;

//***========

// 3d or 2d box height
var BOXHEIGHT;
// can query GRID about whether squares are occupied, will in fact be initialised as a 2D array
var GRID = new Array(gridsize);

var theagent, theenemy;

var wall_texture, agent_texture, enemy_texture, maze_texture, maze_texture2, path_texture;

// Position of StormTrooper and Grogu on Grid
var ei, ej, ai, aj;

var badsteps;
var goodsteps;

// ===================================================================================================================
//***======== Laura Campbell: Open and closed set of space on grid used in algorithim
            var openSet = [];
            var closedSet = [];

            //Route being taken will be recorded in path
            var path = [];


//***========

// asynchronous file loads - call initScene() when all finished
function loadResources()
{
	var loader1 = new THREE.TextureLoader();
	var loader2 = new THREE.TextureLoader();
	var loader3 = new THREE.TextureLoader();
	var loader4 = new THREE.TextureLoader();

	var loader5 = new THREE.TextureLoader();
//	var loader6 = new THREE.TextureLoader();
	var loader6 = 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();
	});




	loader5.load ( TEXTURE_MAZE2, function ( thetexture )
	{
		thetexture.minFilter  = THREE.LinearFilter;
		maze_texture2 = thetexture;
		if ( asynchFinished() )	initScene();
	});


// ===================================================================================================================
//***======== Laura Campbell: Had addiotnal texture but decided to use a wireframe for one set of maze blocks


/*	loader6.load ( TEXTURE_MAZE3, function ( thetexture ){
	    thetexture.minFilter  = THREE.LinearFilter;
	    maze_texture3 = thetexture;
	    if ( asynchFinished() )	initScene();
	});
*/

		loader6.load ( TEXTURE_PATH, function ( thetexture ){
	    thetexture.minFilter  = THREE.LinearFilter;
	    path_texture = thetexture;
	    if ( asynchFinished() )	initScene();
	});

}


function asynchFinished()		 // all file loads returned
{

// ===================================================================================================================
//***======== Laura Campbell: Removed if statement as not needed

	return (  wall_texture && agent_texture && enemy_texture && maze_texture && maze_texture2 && path_texture);

 /*//***** previous *****
	if ( wall_texture && agent_texture && enemy_texture && maze_texture && maze_texture2 && maze_texture3 && path_texture )   return true;
	else return false;
*/
}



//--- grid system -------------------------------------------------------------------------------
// my numbering is 0 to gridsize-1


function occupied ( i, j )		// is this spot occupied
{
    // variable objects
 if ( ( ai == i ) && ( aj == j ) ) return true;
 if ( ( ei == i ) && ( ej == j ) ) return true;


// ===================================================================================================================
//***======== Laura Campbell:  Added ".obstacle" so variable objects and path know to avoid fixed objects
 if ( GRID[i][j].obstacle == GRID_WALL ) return true;
 if ( GRID[i][j].obstacle == GRID_MAZE ) return true;
 if ( GRID[i][j].obstacle  == GRID_MAZE2 ) return true;
 if ( GRID[i][j].obstacle  == GRID_MAZE3 ) return true;




 return false;
}



// 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;
}


// ===================================================================================================================
//***======== Laura Campbell:  Function for block movements
//
//Array to store mazeBlocks
var mazeBlocks = [];
      
if (WALLMOVENO > 0 && WALLMOVETICK > 0) {
    moves = setInterval(function() {
      
        for (var i = 0; i < WALLMOVENO; i++)
        {    
            var resetBlock
            var newBlock
            
// selects random maze block
            var mazeBlock = mazeBlocks[AB.randomIntAtoB(0, mazeBlocks.length - 1)];

            do
            {
                i = AB.randomIntAtoB(1, gridsize - 2);
                j = AB.randomIntAtoB(1, gridsize - 2);
            }
            while (occupied(i, j) && i === mazeBlock.i && j === mazeBlock.j);

            // remove block from maze from previous position
            mazeBlock.removeBlock();
            // reset the previous position on grid 
            mazeBlocks.splice(mazeBlocks.indexOf(mazeBlock), 1);
            resetBlock = GRID[mazeBlock.i][mazeBlock.j];
            resetBlock.obstacle = GRID_BLANK;
            // Add new block in 
            newBlock = GRID[i][j];
            newBlock.obstacle = GRID_MAZE;
            newBlock.i = i;
            newBlock.j = j;
            newBlock.show();
            mazeBlocks.push(newBlock);
            GRID[i][j] = newBlock;
        }
    }, WALLMOVETICK);
}



function initScene()
{


	 var i,j, shape,thecube, thesphere;

	// set up GRID as 2D array

	 for ( i = 0; i < gridsize ; i++ ) {
		GRID[i] = new Array(gridsize);
	 }


	// set up walls

	 for ( i = 0; i < gridsize ; i++ ) {
	  for ( j = 0; j < gridsize ; j++ ) {
		if ( ( i===0 ) || ( i===gridsize-1 ) || ( j===0 ) || ( j===gridsize-1 ) )
		{

//***======== Laura Campbell : Adds in grid wall from spot function ========***//

    			GRID[i][j] = new Spot(i, j, GRID_WALL, false);
    			GRID[i][j].show();

    	/*//***** previous *****
                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 } );
    			// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
    			thecube.position.copy ( translate(i,j) );
    			ABWorld.scene.add(thecube);
        */
    		} else {
    		    // Confirms blank spots on grid
    		    GRID[i][j] = new Spot(i, j, GRID_BLANK,  true);
    		    //GRID[i][j] = GRID_BLANK;
    		}
	  }
	 }
   // set up maze

//1. Maze Blocks
    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);


//***======== Laura Campbell : Adds in grid maze from spot function have applied 2 textured Maze Blocks ========***//

		GRID[i][j] = new Spot(i, j, GRID_MAZE,false);
		GRID[i][j].show();
		mazeBlocks.push(GRID[i][j]);

    /*//***** previous *****
		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) );
		ABWorld.scene.add(thecube);
    */

	}
//2. Maze Blocks
    for ( var d=1 ; d <= NOBOXES ; d++ )
	{
		i = AB.randomIntAtoB(1,gridsize-3);		// inner squares are 1 to gridsize-2
		j = AB.randomIntAtoB(1,gridsize-3);


		GRID[i][j] = new Spot(i, j, GRID_MAZE2,false);
		GRID[i][j].show();
		mazeBlocks.push(GRID[i][j]);

    /*//***** previous *****
		GRID[i][j] = GRID_MAZE2 ;
		shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
		thecube  = new THREE.Mesh( shape );
		thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture2 } );
		thecube.position.copy ( translate(i,j) );
		ABWorld.scene.add(thecube);
    */
	}
//3. Maze Blocks
    for ( var e=1 ; e <= NOBOXES ; e++ )
	{
		i = AB.randomIntAtoB(1,gridsize-4);		// inner squares are 1 to gridsize-2
		j = AB.randomIntAtoB(1,gridsize-4);

//***======== Laura Campbell : Adds in grid maze from spot function and for this one I have applied a wireframe box instead ========***//

		GRID[i][j] = new Spot(i, j, GRID_MAZE3,false);
		shape       = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
		frame       = new THREE.WireframeGeometry( shape );
		lines       = new THREE.LineBasicMaterial( { color: 'green' } );
		thecube     = new THREE.LineSegments( frame , lines);
		thecube.material.transpaent = true;
		thecube.material.opacity = 0.01;
		thecube.position.copy ( translate(i,j) );
		ABWorld.scene.add(thecube);

	/*//***** previous *****
		GRID[i][j] = GRID_MAZE3 ;
		shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );
		thecube  = new THREE.Mesh( shape );
		thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture3 } );
		thecube.position.copy ( translate(i,j) );
		ABWorld.scene.add(thecube);
	    }
	}
    */
	}


//***======== Laura Campbell : Applying addNeighbors function to grid ========***//

//**** Addition - add in neighbour function
for (i = 0; i < gridsize; i++)
{
  for (j = 0; j < gridsize; j++)
  {
      GRID[i][j].addNeighbors(GRID);
  }
}
	// set up enemy
	// start in random location

	 do
	 {
	  i = AB.randomIntAtoB(1,gridsize-2);
	  j = AB.randomIntAtoB(1,gridsize-2);
	 }
	 while ( occupied(i,j) );  	  // search for empty square

	 ei = i;
	 ej = j;

//***======== Laura Campbell : Set up StormTrooper and Grogue as spheres to quickly determine them visiually from obstacles and maze ========***//

	 shape    = new THREE.SphereGeometry (  sphereRadius, sphereHeight, sphereWidth );
	 theenemy = new THREE.Mesh( shape );
 	 theenemy.material =  new THREE.MeshBasicMaterial( { map: enemy_texture } );
	 ABWorld.scene.add(theenemy);
	 drawEnemy();

/*//***** previous *****
	 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 location

	 do
	 {
	  i = AB.randomIntAtoB(1,gridsize-2);
	  j = AB.randomIntAtoB(1,gridsize-2);
	 }
	 while ( occupied(i,j) );  	  // search for empty spot

	 ai = i;
	 aj = j;

	 shape    = new THREE.SphereGeometry (  sphereRadius, sphereHeight, sphereWidth );
	 theagent = new THREE.Mesh( shape );
	 theagent.material =  new THREE.MeshBasicMaterial( { map: agent_texture } );
	 ABWorld.scene.add(theagent);
	 drawAgent();
/*//***** previous *****
	 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
	 });

}



// ===================================================================================================================

//***==================== Laura Campbell : Taken from A Star Tutorial ====================***//
// ===================================================================================================================



// An object to describe a spot in the grid
function Spot(i, j, obstacle,isWall)
{
    this.mazeBlockId;
  // Location of spot
  this.i = i;
  this.j = j;

  // f, g, and h values for A* algorithm
  this.f = 0;
  this.g = 0;
  this.h = 0;

  // surrounding neighbours of spot
  this.neighbors = [];
  // Where did I come from?
  this.previous = undefined;

//***==================== Laura Campbell :
//***======== Will determine if fixed object is an obstacle - maze blocks and wall
  this.obstacle = obstacle;
    /* Whether or not this spot is a wall. */
    this.isWall = isWall;
//***==================== Laura Campbell :
//***======== Unique Id of a spot on grid
//credit: https://stackoverflow.com/questions/70223215/threejs-remove-object-mesh-from-scene-by-uuid
 this.uniqueId;

//***==================== Laura Campbell :
//***======== Determining what is an obstacle and showing them on the grid

    this.show = function() {
        shape   = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
        thecube = new THREE.Mesh(shape);

        if (this.obstacle == GRID_WALL) {
            thecube.material = new THREE.MeshBasicMaterial({ map: wall_texture });
        } else if (this.obstacle == GRID_MAZE) {
            thecube.material = new THREE.MeshBasicMaterial({ map: maze_texture });
           this.mazeBlockId = thecube.uuid;

        } else if (this.obstacle == GRID_MAZE2) {
            thecube.material = new THREE.MeshBasicMaterial({ map: maze_texture2 });
            this.mazeBlockId = thecube.uuid;
        } else if (this.obstacle == GRID_MAZE3) {
            //thecube.material = new THREE.MeshBasicMaterial({ map: maze_texture3 });
            this.mazeBlockId = thecube.uuid;
        }

        thecube.position.copy(translate(this.i, this.j));
        ABWorld.scene.add(thecube);
    };
        this.removeBlock = function() {
        var square = ABWorld.scene.getObjectByProperty("uuid", this.mazeBlockId);
        ABWorld.scene.remove(square);
        this.mazeBlockId = null;
    }

//***==================== Laura Campbell :
//***======== function to highlight path and remove spot when unoccupied
    this.highlight = function(addPath)
    {

       if (addPath)
        {
          shape  = new THREE.SphereGeometry (  pathRadius, pathHeight, pathWidth );
          theSphere = new THREE.Mesh( shape );
          theSphere.material = new THREE.MeshBasicMaterial({ map: path_texture });
          theSphere.position.copy(translate(this.i, this.j));
          ABWorld.scene.add(theSphere);
          this.uniqueId = theSphere.uuid;
      } else {
          var occupiedSpot = ABWorld.scene.getObjectByProperty("uuid", this.uniqueId);
          ABWorld.scene.remove(occupiedSpot);
          this.uniqueId = null;
        }

    };


  // Figure out who my neighbors are
  this.addNeighbors = function(grid)
  {

    var i = this.i;
    var j = this.j;

    if (i < gridsize - 1)   this.neighbors.push(grid[i + 1][j]);
    if (i > 0)          this.neighbors.push(grid[i - 1][j]);
    if (j < gridsize - 1)   this.neighbors.push(grid[i][j + 1]);
    if (j > 0)          this.neighbors.push(grid[i][j - 1]);

  };

}


// --- 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)
}



// ===================================================================================================================

//***==================== Laura Campbell :A Star Tutorial ====================***//
// ===================================================================================================================
// this must be always optimistic - real time will be this or longer



function heuristic ( a, b )
{
    //credit: // https://www.geeksforgeeks.org/a-search-algorithm/
     return ( Math.sqrt((ai-a)*(ai-a)+(aj-b)*(aj-b)));


 //***** previous *****
    // Adjusted as no diagnoal moves to be used
     //Math.abs(a.i - b.i) +  Math.abs(a.j - b.j) );
     // Taken from A Star Tutorial
     //dist(a.i - b.i) + abs(a.j - b.j) );

}



// ===================================================================================================================

//***==================== Laura Campbell :From A Star Tutorial ====================***//
// ===================================================================================================================

function heuristicTrooper()
// This method figures out the best path to take using heuristic A* algorithm.
{


//***==================== Laura Campbell : clear occupied path before restarting search
     for (const occupiedSpot of path) { occupiedSpot.highlight(false); }



//***==================== Laura Campbell : clearing sets & path for new iteration
  openSet.splice(0,openSet.length);
  closedSet.splice(0,closedSet.length);
  path.splice(0,path.length);

//***==================== Laura Campbell :  Run loop to find successful path to Grogu aka Baby Yoda!
    // Begin at StormTrooper Positin on grid
    var trooper = GRID[ei][ej];
    openSet.push(trooper);

    // Now to search all the spots within the grid for the Grogu
    while (openSet.length > 0)
    {

            // Best next option
            var winner = 0;

            for (var i = 0; i < openSet.length; i++)
            {
              if (openSet[i].f < openSet[winner].f)
              {
                winner = i;
              }
            }

//***==================== Laura Campbell :
//***==== end variable is where grogu is and current is where our StormTroop is
            var current = openSet[winner];
            var end = GRID[ai][aj];

            // Did I finish?
            if (current === end)
            {
//***==================== Laura Campbell :
//***==== from grogu to the storm trooper we return the next best move to take
                var bestMove = current;
                while (bestMove.previous !== trooper)
                {
//***==================== Laura Campbell :
//***==== highlights move and path to be taken
                    bestMove.highlight(true);
                    path.push(bestMove);
                    bestMove = bestMove.previous;
                }

              return bestMove;
            }

//***==================== Laura Campbell :
//***==== Best option moves from openSet to closedSet
            openSet.splice(openSet.indexOf(current), 1);
            closedSet.push(current);

            // Check all the neighbors
            var neighbors = current.neighbors;

            //--- start of for loop -----------
            for ( i = 0; i < neighbors.length; i++)
            {
              var neighbor = neighbors[i];

 //***==================== Laura Campbell :
//***====  Valid next spot that not neighbour and obstacle
              if (!closedSet.includes(neighbor)  && !neighbor.obstacle)
              {
                var tempG = current.g + heuristic(neighbor, current); // add up g values in cumulative way

                // Is this a better path than before?
                var newPath = false;
                if (openSet.includes(neighbor))
                {
                  if (tempG < neighbor.g)
                  {
                    neighbor.g = tempG;
                    newPath = true;
                  }
                }
                else
                {
                  neighbor.g = tempG;
                  newPath = true;
                  openSet.push(neighbor);

                }

                // Yes, it's a better path
                if (newPath)
                {
                  neighbor.h = heuristic ( neighbor, end );
                  neighbor.f = neighbor.g + neighbor.h;
                  neighbor.previous = current;
                }
              }
            }
    }

}





// --- take actions -----------------------------------

function moveLogicalEnemy()
{
// move towards agent
// put some randomness in so it won't get stuck with barriers

/*//***** previous *****
    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);

*/
 //***==================== Laura Campbell :
  //***==================== Applying A Star search to trooper so that best pah can be found
  //***==================== Checks that each spot on grid is not occupied and makes next move based in alogrithm results

var aStarPath = heuristicTrooper();
if (!occupied(aStarPath.i, aStarPath.j))
    {
        ei = aStarPath.i;  // ei = i;
        ej = aStarPath.j;  // ej = j;
    }

}


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--;
 else if ( a == ACTION_RIGHT ) 	i++;
 else if ( a == ACTION_UP ) 		j++;
 else if ( 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 ) return true; 		// not ready yet

   // if not one of our special keys, send it to default key handling:

	if ( ! ourKeys ( event ) ) return true;

	// 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(); return false;
}





// --- score: -----------------------------------


function badstep()			// is the enemy within one square of the agent
{
 //***==================== Laura Campbell :
  //***==================== removed if statement

 return ( ( Math.abs(ei - ai) < 2 ) && ( Math.abs(ej - aj) < 2 )) ;

 /*//***** previous *****
    if ( ( Math.abs(ei - ai) < 2 ) && ( Math.abs(ej - aj) < 2 ) ) return true;
    else return false;
*/
}


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 + " &nbsp; x = (" + x.toString() + ") &nbsp; a = (" + a + ") " );
}


function   updateStatusAfter()		// agent and enemy have moved, can calculate score
{
 // new state after both have moved

 var y 		= AB.world.getState();
 var score = ( goodsteps / AB.step ) * 100;

 AB.msg ( " &nbsp; y = (" + y.toString() + ") <br>" +
		" Bad steps: " + badsteps +
		" &nbsp; Good steps: " + goodsteps +
		" &nbsp; 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 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> 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

    const s = ( goodsteps / AB.maxSteps ) * 100;   // float like 93.4372778
    const x = Math.round (s * 100);                // 9344
    return ( 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 = new Audio ( SOUND_ALARM );
	alarm.play();							// play once, no loop
}