Code viewer for World: Cold Pursuit 1

// Name : Pradnyesh Bhalange 
// Student Number :  21262382

// Cloned by Pradnyesh Bhalange on 28 Oct 2021 from World "Complex World" by Starter user 


// ==== 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/pradzb/outer_walls.jpg' ;
 const TEXTURE_MAZE   = '/uploads/pradzb/mid_walls.jpg' ;
 const TEXTURE_AGENT  = '/uploads/pradzb/thief.png';
 const TEXTURE_ENEMY  = '/uploads/pradzb/enemy.png' ;
 const TEXTURE_PATH   = '/uploads/pradzb/arrows.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/pradzb/MarioDeath-QuickSounds.com.mp3' ;

// credits:
// http://www.dl-sounds.com/royalty-free/defense-line/
// http://soundbible.com/1542-Air-Horn.html 

  
  
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 

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


//  const SKYBOX_ARRAY = [                    
//                         "/uploads/pradzb/corona_bk.png",
//                         "/uploads/pradzb/corona_ft.png",
//                         "/uploads/pradzb/corona_up.png",
//                         "/uploads/pradzb/corona_dn.png",
//                         "/uploads/pradzb/corona_lf.png",
//                         "/uploads/pradzb/corona_rt.png"
//                      ];
     


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

// #############################################################################################################################

//--- 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 enemy;
// var agent;
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;

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

// enemy and agent position on squares
var ei, ej, ai, aj;
var badsteps;
var goodsteps;
var openSet = [];
var closedSet = [];
var showPath = [];
var path = [];
var xi = [];
var xj = [];
var theenemypath;
// var diagonal = true;
var start;
var end;


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();		 
	});
	
	loader5.load ( TEXTURE_PATH, function ( thetexture )  
	{
		thetexture.minFilter  = THREE.LinearFilter;
		path_texture = thetexture;
		if ( asynchFinished() )	initScene();		 
	});
	
}


function asynchFinished()		 // all file loads returned 
{
	if ( wall_texture && agent_texture && enemy_texture && maze_texture && path_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].wall == 1) // fixed objects
    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;
}

//Math.abs() is used here to 
function heuristic(a, b) {
    return Math.abs(a.i - b.i) + Math.abs(a.j - b.j);                       // this will let enemy go across and down
  }


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 Spot(i, j) 
{
   this.i = i;
   this.j = j;
   this.f = 0;
   this.g = 0;
   this.h = 0;
   this.neighbors = [];
   this.previous = void 0;
    this.occupied = 0;
   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 || i === gridsize - 1 || j === 0 || j === gridsize - 1)
  {
     this.wall = true;
     shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
     (thecube = new THREE.Mesh(shape)).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 {
     this.wall = false;
  }
   
  for (var e = 0; e <= NOBOXES; e++) 
  {
     if (xi[e] === i && xj[e] === j) 
     {
      this.wall = true;		 
     }
  }
  

}

  
function initScene() {
   var i,j, shape, thecube;
   
   for (i =0; i < gridsize; i++)
   {
     GRID[i] = new Array(gridsize);
   }
   
   for (var a = 0, r =1; r <= NOBOXES; r++) 
   {
     i = AB.randomIntAtoB(1, gridsize - 2);
     j = AB.randomIntAtoB(1, gridsize - 2);
     xi[a] = i;
     xj[a] = j;
     shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
     (thecube = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({map : maze_texture });
     thecube.position.copy(translate(i, j));
     ABWorld.scene.add(thecube);
     a++;
   }
   
   for (i =0; i < gridsize; i++) {
     
     for (j = 0; j < gridsize; j++) {
       GRID[i][j] = new Spot(i, j);
     }
   }
   
   for (i = 0; i < gridsize; i++) {
     
     for (j = 0; j < gridsize; j++) {
       GRID[i][j].addNeighbors(GRID);
     }
   }
   
   do {
     i = AB.randomIntAtoB(1, gridsize - 2);
     j = AB.randomIntAtoB(1, gridsize - 2);
   } 
   while (occupied(i, j));
   ei = i;
   ej = j;
   shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
   (theenemy = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial({map : enemy_texture });
   ABWorld.scene.add(theenemy);
   drawEnemy();
   
   do 
   {
     i = AB.randomIntAtoB(1, gridsize - 2);
     j = AB.randomIntAtoB(1, gridsize - 2);
   } 
   while (occupied(i, j));
   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();
   
  (ABWorld.scene.background = new THREE.CubeTextureLoader().load(SKYBOX_ARRAY,function () {
        ABWorld.render(),
        AB.removeLoading(),
        (AB.runReady = !0);
      }
    ));
  }


// this is used to draw path using BOXGEOMETRY fro menemy to agent
function drawpathtoAgent() {
 
 for (var p = 0; p< path.length - 2; p++)
 {
   if (path.length > 2) 
   {
     var line = path[p];
     shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
     (theenemypath = new THREE.Mesh(shape)).material = new THREE.MeshBasicMaterial( {map : path_texture} );
     showPath[p] = theenemypath.uuid;
     theenemypath.position.copy(translate(line.i, line.j));
     ABWorld.scene.add(theenemypath);
   }
 }
}


function removedrawPath()
{
    for (var e = 0; e< showPath.length; e++)
    {
        var t = showPath[e];
        const a = ABWorld.scene.getObjectByProperty("uuid", t);
        ABWorld.scene.remove(a);
    }
    console.log("Path to agent removed");
}



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

function AStarsearch()
// the search goes on over many timesteps 
// each timestep, check one more square and draw current partial solution 
{
    openSet = [];
    closedSet = [];
    path = [];
    start = GRID[ei][ej];
    end = GRID[ai][aj];
    for(openSet.push(start); openSet.length > 0;)
  {
    var winner = 0;
    for (var i = 0; i < openSet.length; i++)
    {
     if (openSet[i].f < openSet[winner].f) 
     {
       winner = i;
     }
   }
    var current = openSet[winner];
    if (current === end)
    {
      console.log("success - found path");
      break;
    }
    removeFromArray(openSet, current); 
    closedSet.push(current);
    var r = current.neighbors;
    for (t = 0; t < r.length; t++) {
      var neighbor = r[t];
      if (!closedSet.includes(neighbor) && !neighbor.wall) {
        var tempG = current.g + heuristic(neighbor, current);
        var newPath = false;
        if (openSet.includes(neighbor)) 
        {
          if (tempG < neighbor.g) 
          {
            neighbor.g = tempG;
            newPath = true;
          }
        }
        else 
        {
          neighbor.g = tempG;
          newPath = true;
          openSet.push(neighbor);
        }

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

  var temp = current;
  path = [];
  for (path.push(temp);(temp.previous);)
  {
      path.push(temp.previous),
      temp = temp.previous;
  }
 }
  for (t = 0; t < gridsize; t++){
      for (var u = 0; u < gridsize; u++)
      {
     if (0 !== GRID[t][u].f) 
     {
       GRID[t][u].f = 0;
       GRID[t][u].g = 0;
       GRID[t][u].h = 0;
       GRID[t][u].previous = void 0;
     }
  }
    
  }
        
        
}

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

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

//  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
//  {
//   ei = i;
//   ej = j;
//  }
// }

function moveLogicalEnemy() {
  AStarsearch(), 
  drawpathtoAgent();
    sleep(30).then(() => {
        removedrawPath();
      
  var e = path.length,
    t = path[e - 2];

    if (t !== undefined) 
    {
       if (!occupied(t.i, t.j)) {
         ei = t.i;
         ej = t.j;
       }
      
  }});
}

function sleep(e) {
  return new Promise((t) => setTimeout(t, e));
}

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
{
    return Math.abs(ei - ai) < 2 && Math.abs(ej - aj) < 2;

}


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 
    console.log("start", start);
    console.log("end", end);
  moveLogicalAgent(a);
 
  if ( ( AB.step % 2 ) === 0 )		// slow the enemy down to every nth step
    // moveEnemyNew();
    moveLogicalEnemy();

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

  
  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 
    return Math.round (s * 100) / 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 
}