Code viewer for World: Cat is the Enemy again. (AC)

// Cloned by AC on 5 Nov 2020 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) 


 const TEXTURE_WALL 	= '/uploads/starter/door.jpg' ;
 const TEXTURE_MAZE 	= '/uploads/ac2021/Tower.png' ;
 const TEXTURE_AGENT 	= '/uploads/ac2021/MJ_bird.png' ;
 const TEXTURE_ENEMY 	= '/uploads/ac2021/thiscatdoesnotexist.com.jpg' ;
 const TEXTURE_DIAGONAL = '/uploads/ac2021/diagonal.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 

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

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

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

const MAXPOS = gridsize * squaresize;		// length of one side in pixels 
	
const SKYCOLOR 	= 0xddffdd;				// a number, not a string 

 
const startRadiusConst	 	= MAXPOS * 0.8 ;		// distance from centre to start the camera at
const 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"
                
                ];
*/

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

// ===================================================================================================================
// === AC: A* related code ===========================================================================================
// ===================================================================================================================

var e_mode = 2;             // enemy stategy mode.  1 is generic, 2 is A*
var max_e_mode = 2;         // max # of enemy stategy implemented
var bStat = true;           // Statistic mode
var bVerbose = true;        // console.log verbose mode

/*  Class:          AStarNode 
    Description:    Nodes to contain A Star information on each square that was visited during A * Search 
    Methods:        1) constructor
                    2) registerNeighbors
                    */
class AStarNode {
    constructor(i, j){
      this.i = i;
      this.j = j;
      this.f = 0;
      this.g = 0;
      this.h = 0
      this.checkNeighbor = false;   //boolean value to see if registerNeighbors has been done on this AStarNode.
      this.neighbors = [];          //Stores all neighbors in an array for later retrieval by AStarPath
      this.father = null;           //AStarNode
    }
    
    
    /*  Description: Register all neighbor of this AStarNode. 
        Parameter:   blockedlist - can pass in an array of blocked list where the coordinate is represented "x,y" in string
                     if blockedlist is null, then refer to the GRID info.  If agent call this function, should make blockedlist a 
                     must to prevent it from using the GRID info.
    */
    
    registerNeighbors(blockedlist=null)  // this needs to explicitly call to prevent surveying the entire map on one call.
    {
        
      if (!this.checkNeighbor)
      {
        for (var k = -1; k <= 1; k++) 
        {
            for(var l = -1; l <= 1; l++)   
            {    
                
                if(blockedlist != null)
                {
                    if (!blockedlist.includes([this.i+k, this.j+l].toString()) && ((k != 0) || (l != 0)))
                    {
                        this.neighbors.push(new AStarNode(this.i+k, this.j+l));  
                    }
                }
                else
                {
                    if (!(GRID[this.i+k][this.j+l] == GRID_WALL ) && !(GRID[this.i+k][this.j+l]  == GRID_MAZE ) && ((k != 0) || (l != 0)))
                        this.neighbors.push(new AStarNode(this.i+k, this.j+l));   
            
                    
                }
            }
        }
        this.checkNeighbor = true;
      }
    }
    
} // AStarNode

/*  Class:          AStarPath 
    Description:    computes the AStar Path under different methods/strategies implemented.
    Stategies:      1 - Generic as given from the original code
                    2 - A* 
                    3 - A* where end is agent's possible next move
    Methods:        1) constructor                    
                    2) findPath
                    3) heuristic
                    4) producePath
                    5) printPath
                        */

class AStarPath
{
    
    constructor(start_i, start_j, end_i, end_j, strategy = 1, blockedlist = null){

        this.start = new AStarNode(start_i, start_j);       //Start node
        this.end = new AStarNode(end_i, end_j);             //End node
        this.openSet = [];                                  //Store unchecked nodes
        this.closedSet = [];                                //Store checked nodes
        this.checkedSet= {};                                //Store checked neighbor nodes and reuses them
        this.openSet.push(this.start);                      //Initialize result path 
        this.foundPath = false;                             //true if there is a path
        this.path = [];                                     //store result path
        this.h_mode = strategy;                             //strategy used       
        this.blockedlist = blockedlist;                     //list containing the squares that are blocked
    }
    

    /* A* Search Alogrithm to find path.
    */
    findPath()
    {
    
      var counter = 0;
      while (!this.foundPath && this.openSet.length > 0)
      {
          counter += 1;
        if (counter > 100) 
            break;
      
        if (this.openSet.length > 0)
        {
            //still remain nodes in the openSet
            
            //get the node with the smallest f
            var min_f = 0;
            var keystr ="";
            for (var k = 1; k < this.openSet.length; k++)
                if(this.openSet[k].f < this.openSet[min_f].f)
                    min_f = k;
            
                var current = this.openSet[min_f];
                //set keystr
                keystr = current.i + "-" + current.j;
                
                if (this.checkedSet[keystr] === undefined)  // this should happen on the first node only
                    this.checkedSet[keystr] = current;
                
            
            if ((current.i == this.end.i) && (current.j == this.end.j))
            {
                // path Found. 
                this.closedSet.push(current);
                this.end = current;
                this.foundPath = true;
                this.producePath();
                return true;
                
            }
            else
            {
                this.closedSet.push(current);
                //remove current from openSet
                this.openSet.splice(min_f, 1);
                
                // no path found
                // compare neighbors
                
                // get info on the neighbors
                if (!current.checkNeighbors)
                {
                    current.registerNeighbors(this.blockedlist);  
                }
                
                var neighbors = current.neighbors;
                
                // start at the direction where it points to the agent first. however, start at upper left for now.
                var neighbor;
                for(var n = 0; n< neighbors.length; n++)
                {
                    neighbor = neighbors[n];  //AStarNode
                    //set keystr
                    keystr = neighbor.i + "-" + neighbor.j;
                    
                    // if neighbor has been checked before, use the one checkedSet for comparison. 
                    if (this.checkedSet[keystr] === undefined)  // this should happen on the first node
                        this.checkedSet[keystr] = neighbor;
                    else
                    {
                        neighbor = this.checkedSet[keystr];
                    }


                    if (!this.closedSet.includes(neighbor))
                    {
                    
                        var bBetterPath = false;
                        if (this.openSet.includes(neighbor))
                        {
                            if (neighbor.g > current.g + 1)  // always 1 as it is one step to the next square including diagonals.
                            {
                                neighbor.g = current.g + 1;
                                bBetterPath = true;
                            }        
                        }
                    
                        else 
                        {
                            neighbor.g = current.g + 1;
                            bBetterPath = true;
                        }
                    
                        if (bBetterPath)
                        {
                        
                            neighbor.h = this.heuristic(neighbor.i, neighbor.j, this.end.i, this.end.j, this.h_mode);
                            neighbor.f = neighbor.g + neighbor.h
                            neighbor.father = current;
                            this.openSet.push(neighbor);
                        
                        }
                            
                    }
                    else
                    {
                        // console.log("found inside closedSet")
                    }
                }
                
            }
            
            
        }
        else
        {
            //path not found
            this.foundPath = false;
            return false;
        }
        
      }
    }
    
    /* Description:     heuristic function
       Parameters:      x1, y1  - starting coordinates
                        x2, y2  - ending coordinates
       Methods:         1 - basic distance calculation
                        2 - find the max distance of possible moves around x2,y2 and return the max distance.
                            this will be useful when the agent is next to walls and blocks as enemy will go away from walls.
        */
                        
    heuristic(x1, y1, x2, y2, method = 1)
    {
        var a, b, r;
        var max_h = 0;

        if (method == 2)
        {
            // return the max heuristic of the neighbors around x2,y2 for anticipation of its next move. 
            
            for ( var k = -1; k <= 1; k++)
            {
                for (var l = -1; l <= 1; l++)
                {
                    if (!occupied(x2+k, y2+l))
                    {
                        a = x1 - (x2 + k);
                        b = y1 - (y2 - l);
                    
                        r = Math.sqrt( a*a + b*b );
                        if(max_h < r)
                        {
                            max_h = r;
                        }
                    }
                }
            }
            return max_h;
        }
        else
        {
            // simple h() which is the straight line distance between two points
            a = x1 - x2;
            b = y1 - y2;

            return Math.sqrt( a*a + b*b );
        }
    }
    
    
    /* Description:     recreate Path from end */
    producePath()
    {
        var current = this.end;
        var counter = 0;
        do
        {
            
            this.path.push(current);
            current = current.father;
                
        }
        while (current != null);
        return true;
    }
    
    /* Description:     printPath to console.log */
    
    printPath(bHighlight = true)
    {
        if (this.path.length >0 )
        {
            if (bHighlight)  
            {
                highlightPath(this.path);
            }
            var tempStr = "";
            for (var i = 0; i < this.path.length; i++)
            {
                if (tempStr != "")
                {
                    tempStr = " -> " + tempStr;  
                }
                
                tempStr = this.path[i].i + "," + this.path[i].j +  tempStr; 
            }
            if(bVerbose) console.log("Path found: " + tempStr);
            return true;
        }
        else {
            if(bVerbose) console.log("No path available.");
            return false;
        }
    }
    
}//AStarPath


/* Unit Test of AStar*/
/* function testAStar(x=5, y=5)
{
    console.log("starting test");
    
    let a = new AStarNode(x,y);
    
    console.log("i :" + a.i);
    console.log("j:" + a.j);
    console.log("checkNeighbor :" + a.checkNeighbor);
    console.log("f:" + a.f);
    console.log("g:" + a.g);
    console.log("h:" + a.h);
    
    a.registerNeighbors();
    console.log("neighbors.count:" + a.neighbors.length);
    return false;
}
*/
/* Unit Test of AstarPath*/
/*
function testAStarPath(start_i=1, start_j=1, end_i=5, end_j=5)
{
    //let s = new AStarNode(start_i, start_j);
    //let e = new AStarNode(end_i, end_j);
    
    let p = new AStarPath(start_i, start_j, end_i, end_j);
    
    console.log(p.findPath());
    p.printPath();
    
    
    
}
*/

/* Description:  Function to change the enemy mode.  use "E" key to toggle modes between available enemy modes. */

function changeEnemyMode(mode=0)
{
    
    if ((mode == 0) || (mode < 0) || (mode > max_e_mode))
        e_mode = enemyManager.changeStrategy();
    else
        e_mode = enemyManager.changeStrategy( mode);
    return e_mode;
}


/*  Class:          Manager
    Description:    object to control the moves of enemy and the strategy its using. 
    Methods:        1) constructor
                    2) changeStrategy
                    3) getNextStep
                    4) takeNextStep
*/
class Manager {
  constructor(strategy) {
    this.strategy = strategy;
    this.lastPath = null;
    
  }
  changeStrategy(changeTo = -1)
  {
      if(changeTo = -1)
      {
          this.strategy = ((this.strategy) % max_e_mode) + 1 ;
      }
      else if (changeTo > 0 && changeTo <= max_e_mode)
      {
          this.strategy = changeTo;
      }
      else
      {
          console.log("No such strategy");
      }
      
      return this.strategy;
  }
  
  takeNextStep()
  {
      var coordinates = this.getNextStep();
      if (coordinates[0])
      {
          ei = coordinates[1];
          ej = coordinates[2];
      }
  }
  
  getNextStep()
  {
      if (this.strategy == 1)
      {
        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); 
             
        if ( ! occupied(i,j) )  	// if no obstacle then move, else just miss a turn
        {  
            return [true, i, j]
            console.log("moved to :" +  ei + "," + ej)
        }
        
      }
       else if ((this.strategy == 2) || (this.strategy == 3))
      {
        var p;
        if (this.strategy == 3)
        {
            p = new AStarPath(ei, ej, ai, aj, 2);
        }
        else
        {
            p = new AStarPath(ei, ej, ai, aj);
        }
        p.findPath();
        if (p.foundPath)
        {
            if(this.lastPath != null)
                highlightPath(this.lastPath, false);
            p.printPath();
            this.lastPath = p.path
            
            var first_Step = p.path[p.path.length-2];
            if (!occupied(first_Step.i, first_Step.j))
            {
                
                return [true, first_Step.i, first_Step.j];
            }
            
        }
        else
        {
            console.log("is there a teleport?");
        }
      }
      
      return [false, -1, -1];
  }
}// Manager

// =========================== ======================================================================
// === End of A* related codes = More codes below:  search for "AC:" ================================
// =========================== ======================================================================





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

var BOXHEIGHT;		// 3d or 2d box height 

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

var OPENPATH = new Array(gridsize);         // AC: added for showing the A* Path

var theagent, theenemy;
  
var wall_texture, agent_texture, enemy_texture, maze_texture;


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

// AC: initial enemy and agent position
var initial_e, initial_a;  

var badsteps;
var goodsteps;


	
function loadResources()		// asynchronous file loads - call initScene() when all finished 
{
	var loader1 = new THREE.TextureLoader();
	var loader2 = new THREE.TextureLoader();
	var loader3 = new THREE.TextureLoader();
	var loader4 = new THREE.TextureLoader();
	
	//var loader5 = new THREE.TEXTURELoader();
	
	
	
	loader1.load ( TEXTURE_WALL, function ( thetexture )  		
	{
		thetexture.minFilter  = THREE.LinearFilter;
		wall_texture = thetexture;
		if ( asynchFinished() )	initScene();		// if all file loads have returned 
	});
		
	loader2.load ( TEXTURE_AGENT, function ( thetexture )  	 
	{
		thetexture.minFilter  = THREE.LinearFilter;
		agent_texture = thetexture;
		if ( asynchFinished() )	initScene();		 
	});	
	
	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 
{
	if ( wall_texture && agent_texture && enemy_texture && maze_texture )   return true; 
	else return false;
}	
	
	
 

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

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

 if ( GRID[i][j] == GRID_WALL ) return true;		// fixed objects	 
 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 )			
{
	var v = new THREE.Vector3();
	
	v.y = 0;	
	v.x = ( i * squaresize ) - ( MAXPOS/2 );   		 
	v.z = ( j * squaresize ) - ( MAXPOS/2 );   	
	
	return v;
}



	
function initScene()		// all file loads have returned 
{
	 var i,j, shape, thecube, theplane;
	 
	// set up GRID as 2D array
	 
	 for ( i = 0; i < gridsize ; i++ ){
		GRID[i] = new Array(gridsize);
		OPENPATH[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 ) )
		{
			GRID[i][j] = GRID_WALL;		 
			shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );			 
			thecube  = new THREE.Mesh( shape );
			thecube.material = new THREE.MeshBasicMaterial( { map: wall_texture } );
			
			thecube.position.copy ( translate(i,j) ); 		  	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
			ABWorld.scene.add(thecube);
		}
		else 
		{
   			GRID[i][j] = GRID_BLANK;
   			
   			//AC: added a plane for drawing the AStarPath. highlightPath() will turn the referenced Plane.visible to true
   			
   			shape = new THREE.BoxGeometry( squaresize, 1, squaresize );
            theplane = new THREE.Mesh( shape);
            theplane.material = new THREE.MeshBasicMaterial();
            theplane.material.color = new THREE.Color("rgb(255,0,0)");
            theplane.position.copy (translate(i,j));
            theplane.visible = false;
            
      		OPENPATH[i][j]= theplane;
   			ABWorld.scene.add(OPENPATH[i][j]);
		}
		
   // set up maze 
   
    for ( var c=1 ; c <= NOBOXES ; c++ )
	{
		i = AB.randomIntAtoB(1,gridsize-2);		// inner squares are 1 to gridsize-2
		j = AB.randomIntAtoB(1,gridsize-2);
			
		GRID[i][j] = GRID_MAZE ;
		
    	shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );			 
		thecube  = new THREE.Mesh( shape );
		thecube.material = new THREE.MeshBasicMaterial( { map: maze_texture } );		  

        thecube.position.copy ( translate(i,j) );           	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates
        ABWorld.scene.add( thecube);
	}
	 	 
   
	// set up enemy 
	// start in random 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;
	 
	 initial_e = [ei,ej];         // AC: store initial enemy position      
	 
	 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 square 

	 ai = i;
	 aj = j;
 
     initial_a = [ai,aj];         // AC: store initial agent position 
 
	 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
	 });
 		
}
 
 
 


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


/* AC: Description: Turn Path on and off    
*/ 

function highlightPath(aStarPath, changeTo=true)
{
    for (var i = aStarPath.length - 2; i > 0; i--)
        OPENPATH[aStarPath[i].i][aStarPath[i].j].visible = changeTo;
}


// --- take actions -----------------------------------
var lasti;
var lastj;
let enemyManager = new Manager(e_mode);  // AC: the handle of enemy control.

function moveLogicalEnemy()
{ 
    enemyManager.takeNextStep()  // AC: instruct enemyManger to take next Step
}


function moveLogicalAgent( a )			// this is called by the infrastructure that gets action a from the Mind 
{ 
 var i = ai;
 var j = aj;		 

    console.log("agent turn")
      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, 65, 69, 82, 83, 86 ];   // AC: added 65, 69, 82, 83

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:
	
	console.log("key")
	
	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		);   
	
	// AC: additional key codes added

    if ( event.keyCode == 65)    changeAgentMode();     // "A" key for changing Agent strategy mode
    if ( event.keyCode == 69)    changeEnemyMode();     // "E" key for changing Enemy strategy mode
    if ( event.keyCode == 82)    resetScene();          // "R" key for reseting the scene and start over.
    if ( event.keyCode == 83)    bStat = !bStat;        // "S" toggles Statistic mode
    if ( event.keyCode == 86)    bVerbose = !bVerbose   // "V" toggles Console.log verbose  
	
	
	// 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
{
 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();
 var mode_str = "Agent Mode: " + a_mode + "&nbsp; Enemy Mode:" + e_mode + " <br>"; // AC: added the Stategy mode line

 AB.msg ( mode_str + " 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;
 
 //AC: added agent statistics for agent stategy mode 2 which records dead-end and blocked area founded during exploration
 
 var agent_str ="";
 if (a_mode == 2)
    agent_str = "<br> Dead End Found: " + deadEnd.length + "&nbsp; Blocked Area Found: " + blocked.length;
    
 AB.msg ( " &nbsp; y = (" + y.toString() + ") <br>" +
		" Bad steps: " + badsteps + 
		" &nbsp; Good steps: " + goodsteps + 
		" &nbsp; Score: " + score.toFixed(2) + "% " +agent_str, 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 
  {
	if (!bStat)                 // AC: in Statistic mode to keep keyhandler working.
	    AB.abortRun = true;
	else
	    AB.world.endRun();
	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
    
    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 
}


// AC: Description: Reset scene and score.  Press "R".  change Agent Mode or enemy Mode on random.
  
function resetScene()
{
    //relocate enemy and agent
    
    ai = initial_a[0];
    aj = initial_a[1];
    
    drawAgent();
    
    ei = initial_e[0];
    ej = initial_e[1];
    
    drawEnemy();
    
    if(AB.Step % 2 == 0)
    {
        //changeAgentMode();
    }
    {
        //changeEnemyMode();
    }
    
    goodsteps = 0;
    badsteps = 0;
    blocked = [];
    deadEnd = [];
    AB.steps = 0;
    AB.step = 0;
    
}