Code viewer for World: Planet chase - Gelmis Bartulis
// Cloned by Gelmis Bartulis on 5 Jul 2021 from World "Complex World" by Starter user 
// Please leave this clone trail here.

 
// Name:        Gelmis Bartulis
// Student no.: 20213041




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

// Speed of run: Step every n milliseconds. Default 100.
	
AB.maxSteps        = 500;    

// 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/deadlock/closed.png' ;
const TEXTURE_MAZE 	    = '/uploads/deadlock/bkg1_left.png' ;
const TEXTURE_AGENT 	= '/uploads/deadlock/maze_wall.png' ;
const TEXTURE_ENEMY 	= '/uploads/deadlock/sunt.png' ;

// 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.html 


// Number of squares along side of world
const gridsize = 50; 	   
 
// density of maze - number of internal boxes
const NOBOXES =  Math.trunc ( (gridsize * gridsize) / 3 );// (bug) use trunc or can get a non-integer 
		
// 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 * 0.8 ;		

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

//--- 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"
//                 ];


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

// Custom Skybox, but too much red and too much detail in the background 

//  const SKYBOX_ARRAY = [										 
//                 "/uploads/deadlock/bkg1_right1.png",
//                 "/uploads/deadlock/bkg1_left2.png",
//                 "/uploads/deadlock/bkg1_top3.png",
//                 "/uploads/deadlock/bkg1_bottom4.png",
//                 "/uploads/deadlock/bkg1_front5.png",
//                 "/uploads/deadlock/bkg1_back6.png"
//                 ];




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

// enemy and agent position on squares
var ei, ej, ai, aj;

// Steps to calculate the score 
var badsteps;
var goodsteps;

// Location of the enemy square 
var enemySpot;

// Location of the agent square 
var agentSpot;

// Array for neighbors of the squares for A* 
var neighbors       = [];

// Keeping record of what neighbor shave been added to the set
var openSet         = [];

// And which neighbors and squares we have visited 
var closedSet       = [];

// Variables (agent & enemy overlapping grid - need new one)
var newGRID         = new Array(gridsize);

// The path that is the best option based on the f(n) score 
var openPath        = [];

// The start of thee A* algorithm
var start;

// The end of the algorithm 
var end;

// Whether the char can travel diagonally or not
const diagonal      =  true ;

// size of object  
const objectsize    = 50;                   

// Overall shape to be used 
var shape           = new THREE.SphereGeometry( objectsize, objectsize, objectsize );

// The material used
var material        = new THREE.MeshBasicMaterial ();

// To which object that would be assigned to
var theobject       = new THREE.Mesh ( shape, material );

var pathFailed = false;


/*********************** Resources *************************/

// asynchronous file loads - call initScene() when all finished 
function loadResources() {
    
    // Loading all used textures within the project 
	var loader1 = new THREE.TextureLoader();
	var loader2 = new THREE.TextureLoader();
	var loader3 = new THREE.TextureLoader();
	var loader4 = new THREE.TextureLoader();
	
	loader1.load ( TEXTURE_WALL, function ( thetexture ) {
		thetexture.minFilter  = THREE.LinearFilter;
		wall_texture = thetexture;
		if ( asynchFinished() )	initScene();	 
	});
		
	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();		 
	});
	
}

// File loading has finished 
function asynchFinished() {
	if ( wall_texture && agent_texture && enemy_texture && maze_texture )   return true; 
	else return false;
}	
	
	
// Initialise scene and make sure all variables have been loaded
// This code or section has been modified by Gelmis S. Bartulis 
function initScene() {
    
	 var i, j, shape, thecube;
	 
    // Set up 2 GRIDs 
    for ( i = 0; i < gridsize ; i++ ){
        GRID[i]     = new Array(gridsize); // Used for defining walls and maze 
        newGRID[i]  = new Array(gridsize); // Used for A* and dynamic variables
    }

    // Construct a small grid as floor imitation
    var gridHelper = new THREE.GridHelper( MAXPOS - squaresize - 2, squaresize, 0x800000, 0x800000 );
    
    // Position the grid directly to the center of the the GRID
    gridHelper.position.copy ( translateBox((gridsize / 2) , (gridsize / 2) )); 		 
    
    // Adding "floor" to the scene 
    ABWorld.scene.add(gridHelper);	
    
    
    // Populating the new grid with Spots
    for ( i = 0; i < gridsize ; i++ ) {
        for ( j = 0; j < gridsize ; j++ ) {
            
            // Adding values for A* f(n)
            newGRID[i][j]       = new Spot(i, j);
            
            // Noting down the walls
            newGRID[i][j].wall  = true;
        }
    }

    /*  
        Note:
        Wanted to use IcosahedronGeometry - need more time to go through PolyhedronGeometry
    */
    
    /*_________________________________________________*/
	// Set up walls within the other GRID 
    for ( i = 0; i < gridsize ; i++ ) 
        for ( j = 0; j < gridsize ; j++ ) 
        	if ( ( i===0 ) || ( i==gridsize-1 ) || ( j===0 ) || ( j==gridsize-1 ) ) {
        		
        		// Setting up the grid walls
        		GRID[i][j] = GRID_WALL;		 
        		   
        		// The walls will look like cones
        		shape    = new THREE.ConeGeometry( 50, BOXHEIGHT, 100 );		 
        		
        		// Generic variable being overpopulated
        		thecube  = new THREE.Mesh( shape );
        		
        		// Setting the material
        		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 { GRID[i][j] = GRID_BLANK;}
        	
        	
    /*_________________________________________________*/
    // Set up maze 
    for ( var c=1 ; c <= NOBOXES ; c++ ) {
        
        // inner squares are 1 to gridsize-2
		i = AB.randomIntAtoB(1, gridsize - 2);		
		j = AB.randomIntAtoB(1, gridsize - 2);
		
		// Setting up the maze objects
		GRID[i][j] = GRID_MAZE ;
		
		// They will be small spheres
		shape    = new THREE.SphereGeometry ( 50, BOXHEIGHT, 50 );			 
		thecube  = new THREE.Mesh( shape );
		thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture } );		  

        // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
		thecube.position.copy ( translate(i,j) ); 		  	
		ABWorld.scene.add(thecube);		
	}
	 	 
	 	
    /*_________________________________________________*/
    // Set up ENEMY - start in random location
    do { // inner squares are 1 to gridsize-2
		i = AB.randomIntAtoB(1, gridsize - 2);		
		j = AB.randomIntAtoB(1, gridsize - 2);
    }
    
    // While the square is not occupied
    while ( occupied(i,j) );  	  
    
    // Set the un-occupied square as it's location
    ei = i;
    ej = j;
    
    // Setup where the enemy is on as a Spot on the newGrid
    enemySpot = new Spot(ei, ej);
    
    // Add the neighbors that belong to the neighbor
    enemySpot.addNeighbors(gridsize);

    // The enemy will be a sphere - planet
    shape    = new THREE.SphereGeometry( 50, BOXHEIGHT, 50 );			 
    theenemy = new THREE.Mesh( shape );
    theenemy.material =  new THREE.MeshBasicMaterial( { map: enemy_texture } );
    ABWorld.scene.add(theenemy);
    
    // Drawing the enemy in the scene 
    drawEnemy();		  


    /*_________________________________________________*/
    // Setting up AGENT - start in random location
    do { // inner squares are 1 to gridsize-2
		i = AB.randomIntAtoB(1, gridsize - 2);		
		j = AB.randomIntAtoB(1, gridsize - 2);
    }
    
    // While the square is not occupied
    while ( occupied(i,j) );  	  
    
    // Set the un-occupied square as it's location
    ai = i;
    aj = j;
    
    // Setup where the agent is based
    agentSpot = new Spot(ei, ej);
    
    // Add agents neighbors
    agentSpot.addNeighbors(gridsize);

    // The agent will be a sphere
    shape    = new THREE.SphereGeometry(50, BOXHEIGHT, 50);			 
    theagent = new THREE.Mesh( shape );
    theagent.material =  new THREE.MeshBasicMaterial( { map: agent_texture } );
    ABWorld.scene.add(theagent);
    
    // Drawing the agent in the spot 
    drawAgent(); 
    
    
    /*_________________________________________________*/
    // After the enemy and agent have been added their location to the grid, 
    // The grid is filles with neighbor
    for (var i = 0; i < gridsize - 2; i++)
        for (var j = 0; j < gridsize - 2; j++)
            newGRID[i][j].addNeighbors(gridsize);

    // The start for A* within the grid
    start   = translate(newGRID[ei][ej]); 
    
    // The end of the grid as the end goal
    end     = translate(newGRID[ai][aj]); 
    
    // Initially openSet starts with the first grid square 
    openSet.push(start);

    // 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() { 
    
        // Render AB world logic
        ABWorld.render(); 
        
        // Stop loading 
        AB.removeLoading();
        
        // Start the run loop
        AB.runReady = true; 		
	 });
}
 

// --- draw moving objects -----------------------------------

// given ei, ej, draw it
function drawEnemy() {
    
    // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
	theenemy.position.copy ( translate(ei,ej) ); 		  	 

    // if camera moving, look back at where the enemy is
	ABWorld.lookat.copy ( theenemy.position );	 		  
}

// given ai, aj, draw it 
function drawAgent() {
    
    // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
	theagent.position.copy ( translate(ai,aj) ); 		  	

    // follow vector = agent position (for camera following agent)
	ABWorld.follow.copy ( theagent.position );			
}


// Checking whether the square is occupied or not by the maze / wall / agent / enemy 
function occupied ( i, j ) {
    
    // Enemy - variable object
    if ( ( ei == i ) && ( ej == j ) ) return true;		
    
    // Agent - variable object
    if ( ( ai == i ) && ( aj == j ) ) return true;
    
    // Wall - fixed object
    if ( GRID[i][j] == GRID_WALL ) return true;
    
    // Maze - fixed object
    if ( GRID[i][j] == GRID_MAZE ) 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 ) {
    
    // Empty vector
	var v = new THREE.Vector3();
	
	// Nulifying the y position
	v.y = 0;	
	
	// Setting the x as i postion
	v.x = ( i * squaresize ) - ( MAXPOS / 2 );   		 
	
	// Setting the z as j postion
	v.z = ( j * squaresize ) - ( MAXPOS / 2 );   	
	
	// Returning filled vector
	return v;
}

// This code or section has been modified by Gelmis S. Bartulis 
// A copy of previous method 
function translateBox ( i, j ) {
    
    // Empty vector
	var v = new THREE.Vector3();
	
	// Nulifying the y position
	v.y = -50;	
	
	// Setting the x as i postion
	v.x = ( i * squaresize ) - ( MAXPOS / 2 );   		 
	
	// Setting the z as j postion
	v.z = ( j * squaresize ) - ( MAXPOS / 2 );   	
	
	// Returning filled vector
	return v;
}



/*___________________ HEURISTICS _____________________*/

// This code or section has been modified by Gelmis S. Bartulis 
// Basic heuristic - calculating the distances 
// between two vectors using THREE.js library
function basicDistance(a, b)  {

    // Create a vector with given coordinates 
    const v1 = new THREE.Vector2( a.i, a.j );
    
    // Create a vector with given coordinates 
    const v2 = new THREE.Vector2( b.i, b.j );
    
    // Create a distance vector between the above coordinates  
    const d = v1.distanceTo( v2 );

    // Return the distance
    return ( d );
}

// This code or section has been modified by Gelmis S. Bartulis 
// Chebyshev distance = MAXIMUM (|pi - qi|) where i is 1 to N
function chebyshevDistance(a, b) {
    return ( Math.max(a, b) );
}

// This code or section has been modified by Gelmis S. Bartulis 
// Manhattan distance - the distance between two points is the sum of the absolute differences of their Cartesian coordinates
// D * (dx + dy)
// 4 directions - diagonal = false ; 
function manhattanDistance(a, b){
    return ( Math.abs(a.i - a.j) + Math.abs(b.i - b.j) );
}

// This code or section has been modified by Gelmis S. Bartulis 
// Diagonal heuristic using Chebyshev distance
// D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
// 8 directions - diagonal = true ; 
function diagonalDistance(a, b) {

    dx = Math.abs(b.i - a.i);
    dy = Math.abs(b.j - a.j);
    
    var d = 1 * (dx + dy) + (1 - 2 * 1) * Math.min(dx, dy)
    
    return d;
}

// This code or section has been modified by Gelmis S. Bartulis 
// Euclidean distance - distance between two points in Euclidean space is the length of a line segment between the two points.
// D * sqrt(dx * dx + dy * dy)
// Any direction - diagonal = true ; 
function euclideanDistance(x, y){
    return ( Math.sqrt( Math.pow((x.i - x.j), 2) + Math.pow((y.i - y.j), 2)) ) ;
}



// Function to delete element from the array
function removeFromArray(arr, elt)  {

    // Could use indexOf here instead to be more efficient
    for (var i = arr.length - 1; i >= 0; i--) 
        if (arr[i] == elt) 
            arr.splice(i, 1);
}

// This code or section has been added or modified by Gelmis S. Bartulis 
// An object to describe a square in the grid
function Spot(i, j)  {
    
    // Location of the object 
    this.i = i;
    this.j = j;

    // f, g, and h values for A*
    this.f = 0; // f(n) is the total cost of the path for A*
    this.g = 0; // g(n) is the cost of the path from the start node to n
    this.h = 0; // h(n) is a heuristic function that estimates the cost of the cheapest path from n to the goal

    // List of neighbors
    this.neighbors = [];
    
    // // List of walls
    this.wall = false;
    
    // Where did I come from?
    this.previous = undefined;
    

    // Figure out who my neighbors are
    this.addNeighbors = function(gridsize) {
        
        // Taking the global position of i and j 
        var i = this.i;
        var j = this.j;
        
        // Adding neighbors to the grid according to the location 
        if (i < gridsize - 1)   this.neighbors.push(newGRID[i + 1][j]);
        if (i > 0)              this.neighbors.push(newGRID[i - 1][j]);
        if (j < gridsize - 1)   this.neighbors.push(newGRID[i][j + 1]);
        if (j > 0)              this.neighbors.push(newGRID[i][j - 1]);
        
        if(diagonal){
            // Diagonals are also neighbours:
            if (i > 0 && j > 0)                         this.neighbors.push(newGRID[i - 1][j - 1]);
            if (i < gridsize - 1 && j > 0)              this.neighbors.push(newGRID[i + 1][j - 1]);
            if (i > 0 && j < gridsize - 1)              this.neighbors.push(newGRID[i - 1][j + 1]);
            if (i < gridsize - 1 && j < gridsize - 1)   this.neighbors.push(newGRID[i + 1][j + 1]);
        }
    };
}




/* 
    Note:
    There are several different distance calculators defined:
        - Manhattan distance;
        - Euclidean distance;
        - Basic distance calc;
        - Diagonal distance 
*/

// This code or section has been added or modified by Gelmis S. Bartulis 
// findPath function allows for A* algorithm to search for a new path
// this function is called every time enemy makes a move
function findPath() {
    
    // Defining what will go into the openSet 
    openSet     = [];
    
    // Defining what will go into the closedSet
    closedSet   = [];
    
    // Setting the objects location within an object  
    enemySpot   = new Spot(ei, ej);
    
    // Adding enemy's neighbors
    enemySpot.addNeighbors(gridsize);
    
    // Pushing the enemy location as the next position in the queue
    openSet.push(enemySpot);

    // Beginning to search
    while (openSet.length > 0) {

        // Best next option
        var winner = 0;
        
        // Checking the cost of f(n) to see if the 
        // cost is better than the previous one
        for (var i = 0; i < openSet.length; i++) 
            if (openSet[i].f < openSet[winner].f) 
                winner = i;

        // Setting the current space
        var current = openSet[winner];

        // Checking the agent's neighbors (which will be the location to go to)
        if (agentSpot.neighbors.includes(current)) {
            
            // Where the path will be noted 
            openPath = [];
            
            // Current variable is the current neighbor
            var temp = current;
            
            // Pushing the current neighbor to the path
            openPath.push(temp);
            
            // While that neighbor has previous 
            while (temp.previous) {
                
                // Push it to the current path list
                openPath.push(temp.previous);
                
                // Make the current one previous
                temp = temp.previous;
            }
            
            // Finally, return the full path with curent neighbors
            return openPath;
        }
        
        // Best option moves from openSet to closedSet
        removeFromArray(openSet, current);
        
        // Removing that current position frm the list
        closedSet.push(current);
        
        // Check all the neighbors
        var neighbors = current.neighbors;

        // For the ammount of neighbors check the possible cost of the path
        for (var i = 0; i < neighbors.length; i++) {
            
            // Each iteration is a single neighbor
            var neighbor = neighbors[i];
    
            // Checking whether the next spot is valid
            if (!occupied(neighbor.i, neighbor.j) && !closedSet.includes(neighbor)) {
                
                // Getting a temporary G score for the current neighbor
                var tempG = current.g + diagonalDistance(neighbor, current);   // <------------------- change to either manhattanDistance | euclideanDistance | basicDistance | diagonalDistance
                
                // Is this a better path than before?
                var newPath = false;
                
                // If that square has been visited, check the score of G 
                if (openSet.includes(neighbor)) {
                    
                    // If previous G is worse than the current G score
                    if (tempG < neighbor.g) {
                        
                        // Change to current 
                        neighbor.g = tempG;
                        
                        // Better new path has been found
                        newPath = true;
                    }
                } else {
                    
                    // Change to current G score
                    neighbor.g = tempG;
                    
                    // New path found
                    newPath = true;
                    
                    // Push the neighbor to the openSet to be visited
                    openSet.push(neighbor);
                }
        
                // Yes, it's a better path
                if (newPath) {
                    // Heustiric is calculated between the vectors
                    neighbor.h = diagonalDistance(neighbor, agentSpot);              // <------------------- change to either manhattanDistance | euclideanDistance | basicDistance | diagonalDistance
                    
                    // Cost of that path is calculated
                    neighbor.f = neighbor.g + neighbor.h;
                    
                    // Current neighbors becomes the previous
                    neighbor.previous = current;
                }// End of if
            }// End of if
        }// End of for loop
    }// End of while
    
    // Removing everything from the path 
    openPath = [];
    
    // Checking the current neighbor
    var currentTemp = current;
    
    // Adding the current to the path list
    openPath.push(currentTemp);
    
    // While previous neighbors exists 
    while (currentTemp.previous) {
        
        // Add it to the new list
        openPath.push(currentTemp.previous);
        
        // And the previous becomes the current 
        currentTemp = currentTemp.previous;
    }
}


// --- take actions -----------------------------------
// Moving the enemy 
function moveLogicalEnemy() { 
    
    // Initiate the A* algorithm for the enemy 
    var aStar = findPath();
    
    if(aStar){
        // Showing the distance to be travelled
        showDistance(aStar);
        
        console.log(aStar);
    
        // Setting the enemy position from A*
        for(var i = 0; i < aStar.length - 1; i++){
            ei = aStar[i].i;
            ej = aStar[i].j;
        }
    } else { //path lost
        console.log("__________________ Path has been lost ____________________");
    }
}


// This is called by the infrastructure that gets action a from the Mind 
function moveLogicalAgent( a )			
{ 
    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 no obstacle then move, else just miss a turn
    if ( !occupied(i, j)  ) {
        ai = i;
        aj = j;
        
        // This code or section has been added or modified by Gelmis S. Bartulis 
        // Adding the object of agent location to the object
        agentSpot = new Spot(ai, aj);
        
        // Adding agent's neighbors
        agentSpot.addNeighbors(gridsize);
    } 
}

/* Note:
    Further improvement: use sphere objects to show the line between the two vectors
*/

// This code or section has been added or modified by Gelmis S. Bartulis 
// Showing the distance using a basic line
function showDistance(distance){
    
    // The points that are between the agent and enemy 
    var points = [];
    
    // Global within within the function because are required at different parts 
    var geometry;
    
    // The object that will be drawn 
    var line;
    
    // The material used to draw the shape
    var material;

    // If the distance exists between the two points
    if(distance){
        
        // Put all the points that are given within the distance into a new array 
        for(let i = 0; i < distance.length-1; i++)
            points.push(translateBox(distance[i].i, distance[i].j));
        
        // Structure the geometry from the given points
        geometry = new THREE.BufferGeometry().setFromPoints( points );
        
        // Use the line material
        material = new THREE.LineBasicMaterial( { color: 0xffffff });
        
        // Create the final object to add to the scene
        line = new THREE.Line( geometry, material );
    
        // Add the object to the scene 
        ABWorld.scene.add(line);
       
        // Set timeout so that the line would disappear afterwards
        setTimeout( () => {
            // Get rid of the previous vars to allow new ones
			geometry.dispose();
			material.dispose();
			ABWorld.scene.remove( line );
		}, AB.clockTick);
    }
}




// --- 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: -----------------------------------
// is the enemy within one square of the agent
function badstep() {
    if ( ( Math.abs(ei - ai) < 2 ) && ( Math.abs(ej - aj) < 2 ) ) return true;
    else return false;
}

// agent is blocked on all sides, run over
function agentBlocked() {
    return ( 	occupied (ai-1,aj) 		    && 
            	occupied (ai+1,aj)		    &&
        	    occupied (  ai,aj+1)		&&
        	    occupied (  ai,aj-1) 	    );		
} 

// 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 
function updateStatusBefore(a) {
    var x 		= AB.world.getState();
    AB.msg ( " Step: " + AB.step + " &nbsp; x = (" + x.toString() + ") &nbsp; a = (" + a + ") " ); 
}


// agent and enemy have moved, can calculate score
// new state after both have moved
function   updateStatusAfter() {
 
    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  ); 		     
	}
	
	// aynch file loads		
	// calls initScene() when it returns
	loadResources();		

	document.onkeydown = keyHandler;	
};



AB.world.getState = function() {
    var x = [ ai, aj, ei, ej ];
    return ( x );  
};



AB.world.takeAction = function ( a ) {
  
    // show status line before moves 
    updateStatusBefore(a);			
    
    moveLogicalAgent(a);
    
    // slow the enemy down to every nth step
    if ( ( AB.step % 2 ) === 0 )		
        moveLogicalEnemy();

    
    if ( badstep() )    badsteps++;
    else   			    goodsteps++;
    
    drawAgent();
    drawEnemy();
    
    // show status line after moves	
    updateStatusAfter();		  
    
    // if agent blocked in, run over
    if ( agentBlocked() ) {
        AB.abortRun = true;
        
        // you score zero as far as database is concerned 	
        goodsteps = 0;					 
        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 if ( pathFailed )  AB.msg ( " <br> <font color=purple> <B> Path lost - Game over</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
    var s = ( goodsteps / AB.maxSteps ) * 100;   // float like 93.4372778 
    var 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 
}