Code viewer for World: Agent and Enemy into DCU c...
// Cloned by Prateek Bhargava on 25 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 is built as a part of Assignment 1 of CA686 course
// The Base code is taken from 'Complex world' hosted on Ancient Brian; and modifications were performed on top of it
// ====================================================================================================================

// ==== Modifications =================================================================================================
// Modifications performed by: Prateek Bhargava [student ID: 20210628]
// Modification details: Inclusion of A* algorithm into the behaviour of Enemy's movement
// Modification details: Change of look and feel of the world
// ====================================================================================================================

// ====================================================================================================
// Disclaimer:
// The objective of this excersice was to learn and understand Heuristic search and A* search algorithm
// Ancient Brain and its worlds were extensively used for this learning and inspiration
// ====================================================================================================

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


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/bhargap2/wall.PNG' ;
 const TEXTURE_MAZE   = '/uploads/bhargap2/brick.PNG' ;
 const TEXTURE_AGENT  = '/uploads/bhargap2/jokerdark.PNG' ;
 const TEXTURE_ENEMY  = '/uploads/bhargap2/batmanyellow.PNG' ;

/* Code by Prateek ---- Begin
   Explanation: Color images are used to differentiate and visualy identify the types of nodes in A* algorithm
   OPEN nodes (represented by orange) are the ones that have not been 'evaluated' yet, or for whom the calculation of f=g+h is not yet performed
   CLOSED (represented by green) are those for which evaluation is completed
   PATH (represented by blue) is the path decided by the A* algorithm 
*/
 const TEXTURE_TILE             = '/uploads/bhargap2/white.PNG' ;
 const TEXTURE_TILE_OPEN        = '/uploads/bhargap2/lightorange.PNG' ;
 const TEXTURE_TILE_CLOSED      = '/uploads/bhargap2/green.PNG' ;
 const TEXTURE_TILE_PATH        = '/uploads/bhargap2/blue.PNG' ; 

/* Code by Prateek ---- End */


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

const NOBOXES =  Math.trunc ( (gridsize * gridsize) / 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 

/* Code by Prateek ---- Begin
   Explanation: There are 6 images that form the 3D world in which the maze is set-up.
   In order for the setup to work, these images should be 1024*1024 pixels
*/

 const SKYBOX_ARRAY = [ 

  "/uploads/bhargap2/DCUxpos.png",
  "/uploads/bhargap2/DCUxneg.png",
  "/uploads/bhargap2/DCUzpos.png",    
  "/uploads/bhargap2/DCUzneg.png",
  "/uploads/bhargap2/DCUyneg.png",
  "/uploads/bhargap2/DCUypos.png"
                ];

/* Code by Prateek ---- End */



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

/* Code by Prateek ---- Begin
   Explanation: In order to identify and visually see the working of A* algorithm, the concept of 'Tiles' is used
*/
var GRID_TILES = new Array(gridsize); 
/* Code by Prateek ---- End */

var theagent, theenemy;
  
var wall_texture, agent_texture, enemy_texture, maze_texture, tile_texture, tile_open_texture, tile_closed_texture, tile_path_texture;   // Code by Prateek 


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

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();

  /* Code by Prateek ---- Begin
   Explanation: Loading the Tile concept
  */
  var loader5 = new THREE.TextureLoader();  
  var loader6 = new THREE.TextureLoader();  
  var loader7 = new THREE.TextureLoader();  
  var loader8 = new THREE.TextureLoader();  
  /* Code by Prateek ---- End */

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

  /* Code by Prateek ---- Begin
   Explanation: Loading the files for Tile concept
  */
   loader5.load ( TEXTURE_TILE, function ( thetexture )  
  {
          thetexture.minFilter  = THREE.LinearFilter;
          tile_texture = thetexture;
          if ( asynchFinished() ) initScene();             
  });
  
  loader6.load ( TEXTURE_TILE_OPEN, function ( thetexture )  
  {
          thetexture.minFilter  = THREE.LinearFilter;
          tile_open_texture = thetexture;
          if ( asynchFinished() ) initScene();             
  });
  
  loader7.load ( TEXTURE_TILE_CLOSED, function ( thetexture )  
  {
          thetexture.minFilter  = THREE.LinearFilter;
          tile_closed_texture = thetexture;
          if ( asynchFinished() ) initScene();             
  });
  
  loader8.load ( TEXTURE_TILE_PATH, function ( thetexture )  
  {
          thetexture.minFilter  = THREE.LinearFilter;
          tile_path_texture = thetexture;
          if ( asynchFinished() ) initScene();             
  });
  /* Code by Prateek ---- End */   
}


function asynchFinished()    // all file loads returned 
{
  if ( wall_texture && agent_texture && enemy_texture && maze_texture 
       && tile_texture && tile_open_texture && tile_closed_texture && tile_path_texture                               // Code by Prateek
    )   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;
   
  // set up GRID as 2D array
   
   for ( i = 0; i < gridsize ; i++ ) 
   {
    GRID[i] = new Array(gridsize);

    /* Code by Prateek ---- Begin
   Explanation: setting up the 2D GRID for the 'Tile' concept
   */
    GRID_TILES[i] = new Array(gridsize);
         }
        // set up tiles
         
    for ( i = 0; i < gridsize ; i++ ) 
      for ( j = 0; j < gridsize ; j++ ) 
      {
          var curPos = translate(i,j);
          GRID_TILES[i][j] = new Array(4);
              
              shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );                  
              thecube  = new THREE.Mesh( shape );
              thecube.material = new THREE.MeshBasicMaterial( { map: tile_texture } );
              thecube.position.copy ( curPos );
              GRID_TILES[i][j][0] = thecube;
              ABWorld.scene.add(GRID_TILES[i][j][0]);
              
              shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );                  
              thecube  = new THREE.Mesh( shape );
              thecube.material = new THREE.MeshBasicMaterial( { map: tile_open_texture } );
              thecube.position.copy ( curPos );
              GRID_TILES[i][j][1] = thecube;
              shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );                  
              thecube  = new THREE.Mesh( shape );
              thecube.material = new THREE.MeshBasicMaterial( { map: tile_closed_texture } );
              thecube.position.copy ( curPos );
              GRID_TILES[i][j][2] = thecube;
              shape    = new THREE.BoxGeometry ( squaresize, 1, squaresize );                  
              thecube  = new THREE.Mesh( shape );
              thecube.material = new THREE.MeshBasicMaterial( { map: tile_path_texture } );
              thecube.position.copy ( curPos );
              GRID_TILES[i][j][3] = thecube;
      }
    /* Code by Prateek ---- End */      


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

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




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

/* Code by Prateek ---- Begin
   Explanation: A* algorithm implementation, that defines the movement of the Enemy
*/

function moveLogicalEnemy()
{
    runAStar();
}

function Spot(i, j) 
{
    // Location
    this.i = i;
    this.j = j;
    
    // f, g, and h values for A*
    this.f = 0;
    this.g = 0;
    this.h = 0;
    
    // Neighbors
    this.neighbors = [];
    
    // Where did I come from?
    this.previous = undefined;
    
    this.wall = (GRID[i][j] !== GRID_BLANK);

    // 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]);
        if (i > 0 && j > 0)                         this.neighbors.push(grid[i - 1][j - 1]);
        if (i < gridsize - 1 && j > 0)              this.neighbors.push(grid[i + 1][j - 1]);
        if (i > 0 && j < gridsize - 1)              this.neighbors.push(grid[i - 1][j + 1]);
        if (i < gridsize - 1 && j < gridsize - 1)   this.neighbors.push(grid[i + 1][j + 1]);
    }
}

function heuristic( a, b ) {
    //  return ( abs(a.i-b.i)+ abs(a.j-b.j));         //failed experiment
    return ( Math.sqrt((a.i-b.i)^2 + (a.j-b.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);
}

function runAStar() {
    
    var grid = [];
    
    // Making a 2D array
    for (var i = 0; i < gridsize; i++) 
        grid[i] = new Array(gridsize);
    
    for (var i = 0; i < gridsize; i++) 
        for (var j = 0; j < gridsize; j++) 
            grid[i][j] = new Spot(i, j);
    
    // All the neighbors
    for (var i = 0; i < gridsize; i++) 
        for (var j = 0; j < gridsize; j++) 
            grid[i][j].addNeighbors(grid);

    // Open and closed set
    var openSet = [];
    var closedSet = [];
    /* Explanation by Prateek: Starting by storing the location of enemy into openSet */
    openSet.push(grid[ei][ej]);
    
    var agentSpot = undefined;
    while(openSet.length > 0)
    {
        // Best next option
        /* Explanation by Prateek: Through below FOR loop, we are trying to find:
        Out of all Nodes in OpenSet, which one has the lowest cost value (f), where f=g+h 
        */
        var winner = 0;
        
        for (var i = 0; i < openSet.length; i++) 
          if (openSet[i].f < openSet[winner].f) 
            winner = i;
            
        var current = openSet[winner];
        
        // Did I finish?
        if (current.i === ai && current.j === aj) 
        {
          agentSpot = current;
          break;
        }

        // Best option moves from openSet to closedSet
        removeFromArray(openSet, current);
        closedSet.push(current);
    
        // Check all the neighbors
        var neighbors = current.neighbors;
        
        //--- start of for loop -----------
        for (var i = 0; i < neighbors.length; i++) 
        {
          var neighbor = neighbors[i];
    
          /* Explanation by Prateek: Below IF condition checks whether the neighbor node 
          has already been evaluated (in closedSet) or if it is invalid (obstacle/wall)*/
          if (!closedSet.includes(neighbor) && !neighbor.wall) 
          {
            var tempG = current.g + heuristic(neighbor, current);
    
            // 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, grid[ai][aj]);
              neighbor.f = neighbor.g + neighbor.h;
              neighbor.previous = current;
            }
          }
        }
    }

    if (agentSpot) {
        /* Explanation by Prateek: Once the above loop breaks 
        and agentSpot (initialised as undefined) gets assigned a value,
        we color all the openSet nodes as orange and closedSet nodes as green.
        This is so that we can visually see and identify these nodes when the world is executed.
        */      
        for (p=0; p<openSet.length; p++) {
            ABWorld.scene.add(GRID_TILES[openSet[p].i][openSet[p].j][1]);
        }
        for (p=0; p<closedSet.length; p++) {
            ABWorld.scene.add(GRID_TILES[closedSet[p].i][closedSet[p].j][2]);
        }
        /* Explanation by Prateek: In below IF condition, we are checking whether the enemy is right next to agent.
        If not, then the condition satisfies and we proceed to color the PATH as blue.
        If yes, then there is no PATH.
        */
        if (agentSpot.previous!==grid[ei][ej]) 
        {
            /* Explanation by Prateek: Here agentSpot = openSet[winner]
              Below we are tracing back the path to the first move, in order to identify and color the PATH
            */
            var nextSpot = undefined;
            var temp = agentSpot;
            while (temp.previous) 
            {
                nextSpot = temp;
                temp = temp.previous;
                ABWorld.scene.add(GRID_TILES[nextSpot.i][nextSpot.j][3]);
            }
            ei = nextSpot.i;
            ej = nextSpot.j;
        }
        /* Explanation by Prateek: go to this ELSE block if enemy is right next to agent
        */
        else
        {
            /*
                "Trapping" works by Enemy standing beside Agent while Agent
                is in a hole. That is, Enemy cannot trap Agent by standing
                diagonally to it. Therefore, the final move of the agent needs
                to be right next to one of Agent's sides.
            */
            var i, j;
            
            /*
                "Intimidation" tactic -
                Enemy always moves next to Agent as this is how "trapping"
                works. This implementation mostly leads to low-score run-overs
                as Agent cannot move diagonally.
            */
            // if ( ei < ai ) i = ei+1;
            // if ( ei == ai ) i = ei; 
            // if ( ei > ai ) i = ei-1;
            
            // if ( ej < aj ) j = ej+1;
            // if ( ej == aj ) j = ej; 
            // if ( ej > aj ) j = ej-1;
            
            /*
                Leeway tactic -
                Without utilising *any* knowledge about the surroundings,
                with just some stochasticity, Enemy is occasionally letting
                Agent to move past. This allows the game to move to other
                parts of the grid and increases the chances of Agent entering
                a hole.
            */
            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
            {
                ei = i;
                ej = j;
            }
        }
    }
}


/* Code by Prateek ---- End */ 


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 ];  // This is the key codes of arrow keys

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
{
 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 )

/* Code by Prateek ---- Begin
   Explanation: Adding FOR loop to execute the A* algorithm at every step that the Enemy takes
*/    
    { // slow the enemy down to every nth step
    // clean the floor
     
     for ( i = 0; i < gridsize ; i++ ) 
      for ( j = 0; j < gridsize ; j++ ) 
        {
          /* Explanation by Prateek: Before me make agent's next move,
          we remove the orange/green/blue coloring of tiles
          and set the tiles to white
          */
            ABWorld.scene.remove(GRID_TILES[i][j][1]);
            ABWorld.scene.remove(GRID_TILES[i][j][2]);
            ABWorld.scene.remove(GRID_TILES[i][j][3]);
                ABWorld.scene.add(GRID_TILES[i][j][0]);
        }
    moveLogicalEnemy();
  }

/* Code by Prateek ---- End */ 


  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
    
    var s = ( goodsteps / AB.maxSteps ) * 100;   // float like 93.4372778 
    var x = Math.round (s * 100);                // 9344
    return ( x / 100 );                          // 93.44
};

// ==== End of code =================================================================================================