Code viewer for World: SacryEnemy_DefaultAgent_By...
// Cloned by Rajesh Dande on 27 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        = 250;   /*<Rajesh Dande> Reduced it to 250 for faster completion of worst case runs.*/
//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/rajeshdande9/wall_pintrest.jpg' ; //concrete_wall_spooky_skull_twenty20.jpg spooky_door.jpg
 const TEXTURE_MAZE 	= '/uploads/rajeshdande9/Brick_white_wall_wikipedia_commons.jpg' ; //Red_brick_wall_Photos_Public_Domain.jpg
 const TEXTURE_AGENT 	= '/uploads/rajeshdande9/human_mind_vectorstock.jpg' ;
 const TEXTURE_ENEMY 	= '/uploads/rajeshdande9/AI_Scary_ghost_Pintrest.jpg' ;

// credits:
//wall and scary ghost from pintrest website, brickwalll from wikipediacommons, humanmind from wectorstock website
 
  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;						/* <Rajesh Dande> Modified the grid size as requested in assignment */	   

const NOBOXES =  Math.trunc ((gridsize * gridsize)/ 3 ); /* <Rajesh Dande> Modified the box density as requested in assignment */
// density of maze - number of internal boxes

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 

 const SKYBOX_ARRAY = [										 
                "/uploads/rajeshdande9/Created1.jpg", //starter/sky_pos_z.jpg
                "/uploads/rajeshdande9/test2.jpg",
                "/uploads/rajeshdande9/grass_texture241_Bgfons.jpg",
                "/uploads/rajeshdande9/grass_texture241_Bgfons.jpg",
                "/uploads/rajeshdande9/Created1.jpg",
                "/uploads/rajeshdande9/test2.jpg"
                ];
// pics credit: Used 2 pics from Bahubali movie song, edited them and used. Grass pic was taken from bgfons website.

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

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 theagent, theenemy, theenemypath;
  
var wall_texture, agent_texture, enemy_texture, maze_texture; 

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

var badsteps;
var goodsteps;

/*<Rajesh Dande> Add global variables required*/
var drawLine;
var invalidRun = false; 

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();
	
	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;		// <Rajesh Dande> Dont need seperate check for wall and maze since both are fixed objects. Speed matters.	 
 //if ( GRID[i][j] == GRID_MAZE ) return true;	
 
 if ( GRID[i][j].wallmaze) return true;		// <Rajesh Dande> fixed objects can be identified by wallmaze indicator assigned in initScene.	 
	 
 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 heuristic(l,m) //<Rajesh Dande> old unused heuristic related to just h calculation of neighbours - ignore this
{
     const a = new THREE.Vector3( l, 0, m);
     const b = new THREE.Vector3(ai,0,aj);
     return a.distanceTo(b);
}*/

/*<Rajesh Dande> Heuristic function to get the distnace between two spots in 3js*/
 function heuristic1(a,b) 
{
     const a1 = new THREE.Vector3( a.i, 0, a.j);
     const b1 = new THREE.Vector3(b.i,0,b.j);
     return a1.distanceTo(b1);
}

/* <Rajesh Dande> START - Create spot and removal from array functions similar to Daniel Shiffman's A* search coding train sample*/
// An object to describe a spot in the grid
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;
  
  //wall, maze indicator
  this.wallmaze = false;
  
  // Neighbors
  this.neighbors = [];

  // Where did I come from?
  this.previous = undefined;

  // 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]);
	//diagonals are also neighbours
    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]);
    
  };
  
    this.removelinks = function(grid) 
  {      
    var i = this.i;
    var j = this.j;    
    grid[i][j].previous = undefined;
  };
  
}

function removeFromArray(arr, elt) 
{
  for (var i = arr.length - 1; i >= 0; i--) 
    if (arr[i] == elt) 
      arr.splice(i, 1);
}
/* <Rajesh Dande> END - Create spot and removal from array functions similar to Daniel Shiffman's A* search coding train sample*/

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);	
		
  /*<Rajesh Dande> START - Setting the spots and neighbours similar to Daniel Shiffman's A* search coding train sample*/
     for (var i = 0; i < gridsize; i++) 
        for (var j = 0; j < gridsize; j++) 
        {
            GRID[i][j] = new Spot(i, j);
        }		
     for (var i = 0; i < gridsize; i++) 
        for (var j = 0; j < gridsize; j++) 
        {
            GRID[i][j].addNeighbors(GRID);
        }
  /*<Rajesh Dande> END - Setting the spots and neighbours similar to Daniel Shiffman's A* search coding train sample*/

	// 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].wallmaze = true;		 /*<Rajesh Dande> Set the wallmaze indicator to true for walls*/
			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].wallmaze = false; /*<Rajesh Dande> Set the wallmaze indicator to false for blank spaces*/
	}

   // 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].wallmaze = true ; /*<Rajesh Dande> Set the wallmaze indicator to true for 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();

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

/*<Rajesh Dande> create a functin to draw the path line in 3js*/
function drawPathToAgent(path)
{
    var x,y;
	var points = [];    
    for (l = path.length-1; l>0; l--) 
    {
        x = path[l-1].i;
        y = path[l-1].j;
        points.push(translate(x,y));      
		
	 /*<Rajesh Dande> Wanted to get the enemy shape through out the path instead of plain path line.
	 Succeeded in getting it, but could not remove previous move path shapes on time. will mention it as future scope in my document.*/
	 
	 /*shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );			 
	 theenemypath = new THREE.Mesh( shape );
 	 theenemypath.material =  new THREE.MeshBasicMaterial( { map: enemy_texture } );
	 
	 ABWorld.scene.add(theenemypath);// use ABWorld.scene.remove(theenemypath); for removal. Put removal in other function and call removal at the begining of move enemy. May need another prePath variable and assign path to prePath at the end of moveenemy etc..
	 
	 theenemypath.position.copy ( translate(x,y) ); 
	 ABWorld.lookat.copy ( theenemypath.position );*/		
    }
    var geometry = new THREE.Geometry().setFromPoints( points );
	var material = new THREE.LineBasicMaterial({color: 'red'});
    var line = new THREE.Line( geometry, material );    
    
  ABWorld.scene.add( line ); 
  
  return line;  
}
// --- take actions -----------------------------------

function moveLogicalEnemy()
{ 

/*<Rajesh Dande> START - path search logic similar to Daniel Shiffman's A* search coding train sample*/
var start = GRID[ei][ej]; //start at enemy location in this case.
var end   = GRID[ai][aj]; //end at agent location in this case.
var openSet = [];
var closedSet = [];
var path = [];
var maxSpots = (gridsize * gridsize);
var node;
var distance=0;

  if (drawLine) ABWorld.scene.remove( drawLine ); //Remove the path drawn in previous move.
    
  openSet.push(start);

  console.log('start search');
  console.log("Enemy and agents are at EI : " + ei + " EJ: " + ej + " AI : " + ai + " AJ: " + aj);

for (var w=0; w<maxSpots; w++)
  { 
	if (openSet.length > 0) 
	{
    // Best next option
		var winner = 0;
    
		for (var i = 0; i < openSet.length; i++) 
			if (openSet[i].f < openSet[winner].f) 
			winner = i;
		    var current = openSet[winner];
		    distance = heuristic1(current,end);
		// Did I finish?
		//if (current === end)
       if (distance == 1)
		{
		console.log("Success - found path till agent !!");
	    node = current;
        // noLoop();
        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];	  

      // Valid next spot?
      if (!closedSet.includes(neighbor) && (!neighbor.wallmaze)) // check if neighbour is not a maze or wall; and not present in closed set	
      {
        var tempG = current.g + heuristic1(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 = heuristic1(neighbor, end);
          neighbor.f = neighbor.g + neighbor.h;
          neighbor.previous = current;
        }
      }
    }
    //--- end of for loop -----------    
  }
  // --- end still searching -----------------------------
    
  else 
  {
    console.log('Fail - no path exists till Agent :( ');	
    invalidRun = true;
    AB.abortRun = true;
	//noLoop();
	//break; //abortrun will take care of stopping the run immediately. No seperate break statement required to exit the loop.   	
  }
  
}
  // Find the path by working backwards
  path.push(node);
  while (node.previous) 
  {
    path.push(node.previous);
    node = node.previous;
  }
  /*<Rajesh Dande> END - path search logic similar to Daniel Shiffman's A* search coding train sample*/
  
  drawLine = drawPathToAgent(path); /*<Rajesh Dande> draw the path*/

/*<Rajesh Dande> START - Decide the enemy's move */	 
  var goToSpot = (path.length)-2;
  var d=0, e=0;

	if (( ( Math.abs(ei - ai) ==0 ) && ( Math.abs(ej - aj) <=1 ) ) || ( ( Math.abs(ei - ai) <=1 ) && ( Math.abs(ej - aj) ==0 ) ) ){
		//If enemy is close to agent, stay there to reduce the agent's score.
		d=ei;
		e=ej;
	}
	else {
		//Go to next valid spot to get close to the enemy
		d = path[goToSpot].i;
		e = path[goToSpot].j;
	}
	ei = d;
	ej = e;
/*<Rajesh Dande> END - Decide the enemy's move */	 	

/*<Rajesh Dande> Clean up the previous links*/
  for (var i = 0; i < gridsize; i++) 
    for (var j = 0; j < gridsize; j++) 
    {
        GRID[i][j].removelinks(GRID);
    }
  
/*<Rajesh Dande> Old approach of just calculating just h of all 8 neighbours -- IGNORE THIS*/
 /*var i, j;

 var l,r,u,d,lu,ru,rd,ld,x,y,mh;
 
 if (occupied(ei+1,ej)) u=0;
 else u = heuristic(ei+1,ej);
 if (occupied(ei-1,ej)) d=0;
 else d = heuristic(ei-1,ej);
 
 if (occupied(ei,ej+1)) r=0;
 else r = heuristic(ei,ej+1);
 if (occupied(ei,ej-1)) l=0;
 else l = heuristic(ei,ej-1);
 
 if (occupied(ei+1,ej+1)) ru=0;
 else ru = heuristic(ei+1,ej+1);
 if (occupied(ei-1,ej+1)) lu=0;
 else lu = heuristic(ei-1,ej+1);
 
 if (occupied(ei+1,ej-1)) rd=0;
 else rd = heuristic(ei+1,ej-1);
 if (occupied(ei-1,ej-1)) ld=0;
 else ld = heuristic(ei-1,ej-1); 
 
 console.log("Enemy current positioon ei : " + ei + " ej: " + ej);
 
 console.log("Print neighbour heuristic values U: " + u + " L: " + l + " D: " + d + " LU: " + lu
 + " RU: " + ru + " RD: " + rd + " LD: " + ld+ " R: " + r);
 
 mh = [
 {pos: "spot1", val: u, x: ei+1 , y: ej},
 {pos: "spot2", val: d, x: ei-1 , y: ej},
 {pos: "spot3", val: r, x: ei, y: ej+1},
 {pos: "spot4", val: l, x: ei, y: ej-1},
 {pos: "spot5", val: ru, x: ei+1 , y: ej+1},
 {pos: "spot6", val: lu, x: ei-1 , y: ej+1},
 {pos: "spot7", val: rd, x: ei+1 , y: ej-1},
 {pos: "spot8", val: ld, x: ei-1 , y: ej-1}
 ];
  
 function findLeastHvalue() {
    var leastHvalueSoFar = 99999999;
    var result;
    
    for (var i = 0; i < mh.length; i++) {
        var a = mh[i].val;
        var s = mh[i].x;
        var t = mh[i].y;
        console.log("check value: " + a + "Check x pos: " + s + "Check y pos: " + t);
        if ( mh[i].val!==0 && mh[i].val < leastHvalueSoFar) {
            result = mh[i];
            leastHvalueSoFar = mh[i].val;
        }
    }
    console.log("Final LeastH for that spot: " + leastHvalueSoFar);
    return result;
}
var goToSpot = findLeastHvalue();
if ( ( Math.abs(ei - ai) < 2 ) && ( Math.abs(ej - aj) < 2 ) ){
	i=ei;
	j=ej;
}
else {
	i = goToSpot.x;
    j= goToSpot.y;
}
ei = i;
ej = j;*/
/*<Rajesh Dande> End of Old approach of just calculating h of all 8 neighbours -- IGNORE THIS*/

}

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

 if ( a == ACTION_LEFT ) 		i--;
 else if ( a == ACTION_RIGHT ) 	i++;
 else if ( a == ACTION_UP ) 	j++;
 else if ( a == ACTION_DOWN ) 	j--;

 if ( ! occupied(i,j) ) 
 {
  ai = i;
  aj = j;
 }
}

// --- key handling --------------------------------------------------------------------------------------
// This is hard to see while the Mind is also moving the agent:
// AB.mind.getAction() and AB.world.takeAction() are constantly running in a loop at the same time 
// have to turn off Mind actions to really see user key control 

// we will handle these keys: 

var OURKEYS = [ 37, 38, 39, 40 ];

function ourKeys ( event ) { return ( OURKEYS.includes ( event.keyCode ) ); }

function keyHandler ( event )		
{
	if ( ! AB.runReady ) return true; 		// not ready yet 

   // if not one of our special keys, send it to default key handling:	
	if ( ! ourKeys ( event ) ) return true;
	
	// else handle key and prevent default handling:	
	if ( event.keyCode == 37 )   moveLogicalAgent ( ACTION_LEFT 	);   
    if ( event.keyCode == 38 )   moveLogicalAgent ( ACTION_DOWN  	); 	 
    if ( event.keyCode == 39 )   moveLogicalAgent ( ACTION_RIGHT 	); 	 
    if ( event.keyCode == 40 )   moveLogicalAgent ( ACTION_UP		);   
	
	// when the World is embedded in an iframe in a page, we want arrow key events handled by World and not passed up to parent 
	event.stopPropagation(); event.preventDefault(); return false;
}

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

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

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

function updateStatusBefore(a)
// this is called before anyone has moved on this step, agent has just proposed an action
// update status to show old state and proposed move 
{
 var x 		= AB.world.getState();
 AB.msg ( " Step: " + AB.step + " &nbsp; x = (" + x.toString() + ") &nbsp; a = (" + a + ") " ); 
}

function   updateStatusAfter()		// agent and enemy have moved, can calculate score
{
 // new state after both have moved 
 var y 		= AB.world.getState();
 var score = ( goodsteps / AB.step ) * 100; 

 AB.msg ( " &nbsp; y = (" + y.toString() + ") <br>" +
		" Bad steps: " + badsteps + 
		" &nbsp; Good steps: " + goodsteps + 
		" &nbsp; Score: " + score.toFixed(2) + "% ", 2 ); 
}

AB.world.newRun = function() 
{
	AB.loadingScreen();
	AB.runReady = false;  

	badsteps = 0;	
	goodsteps = 0;

	if ( show3d )
	{
	 BOXHEIGHT = squaresize;
	 ABWorld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 	
	}	     
	else
	{
	 BOXHEIGHT = 1;
	 ABWorld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 		     
	}

	loadResources();		// aynch file loads		
							// calls initScene() when it returns 
	document.onkeydown = keyHandler;			 
};

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

AB.world.takeAction = function ( a )
{
  updateStatusBefore(a);			// show status line before moves 
  moveLogicalAgent(a);

  if ( ( AB.step % 2 ) === 0 )		// slow the enemy down to every nth step
    moveLogicalEnemy();

  if ( badstep() )  badsteps++;
  else   			goodsteps++;

   drawAgent();
   drawEnemy();
   updateStatusAfter();			// show status line after moves  

  if ( agentBlocked() )			// if agent blocked in, run over 
  {
	AB.abortRun = true;
	goodsteps = 0;			// you score zero as far as database is concerned 			 
	musicPause();
	soundAlarm();
  }
};

AB.world.endRun = function()
{
  musicPause(); 
  if ( AB.abortRun ) 
  {
	  /*<Rajesh Dande> Provide different message for two different aborts. Agent gets trapped compared to path does not exists between enemy and agent.
	    if we increse the maze density to number of squares/1.2 - we will get path does not exists situations easily.*/
	  
      if (invalidRun) AB.msg ( " <br> <font color=blue> <B> Enemy to Agent path does not exist. Please reload.</B> </font>   ", 3  );
      else 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. Max steps reached.</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 
}