Code viewer for World: Complex World (clone by Us...
// Cloned by User Number on 6 Sep 2023 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/starter/latin.jpg' ;
 const TEXTURE_AGENT 	= '/uploads/starter/pacman.jpg' ;
 const TEXTURE_ENEMY 	= '/uploads/starter/ghost.3.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 = 20;						// number of squares along side of world	   

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


function drawMinimap() {
    var ctx = minimapCanvas.getContext('2d');

    var cellWidth = minimapCanvas.width / gridsize;
    var cellHeight = minimapCanvas.height / gridsize;

    // Clear canvas
    ctx.clearRect(0, 0, minimapCanvas.width, minimapCanvas.height);

    // Draw grid
    for (var i = 0; i < gridsize; i++) {
        for (var j = 0; j < gridsize; j++) {
            if(solution.path.includes(problem.maze[i][j])){
                ctx.fillStyle = 'orange';  // Wall color
            }
            else if (problem.maze[i][j].iswall) {
                ctx.fillStyle = 'gray';  // Wall color
            } else if (i == problem.agent.i && j == problem.agent.j) {
                ctx.fillStyle = 'blue';  // Agent color
            } else if (i == problem.enemy.i && j == problem.enemy.j) {
                ctx.fillStyle = 'red';   // Enemy color
            } else {
                ctx.fillStyle = 'white'; // Blank color
            }
            ctx.fillRect(i * cellWidth, j * cellHeight, cellWidth, cellHeight);
        }
    }
}

function musicPlay()   { backmusic.play();  }
function musicPause()  { backmusic.pause(); }

											 
function soundAlarm()
{
	var alarm = new Audio ( SOUND_ALARM );
	alarm.play();							// play once, no loop 
}

// A Class to describe a node in the grid
class problemNode {
    constructor (i, j){
        // Location
        this.i = i;
        this.j = j;

        // Neighbors
        this.neighbors = [];
        // Wall / agent / enemy
        this.iswall = false;

        // 3d representation attributes - to be completed
        this.shape = null;
        this.mesh = null;

        this.previous = null;
        
    }
   
    // Figure out who my neighbors are
    addNeighbors(grid){
        let i = this.i;
        let 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 (diagonal){
        // 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]);
        }
    }

    makeWall(){
        this.iswall = true;

        this.shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );			 
        this.mesh  = new THREE.Mesh( this.shape );
        this.mesh.material = new THREE.MeshBasicMaterial( { map: wall_texture } );
        
        this.mesh.position.copy (translate(this.i,this.j)); 		  	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
        ABWorld.scene.add(this.mesh);
    }

    removeWall(){
        this.iswall = false;

        // Remove the wall from the scene
        if (this.mesh) {
            ABWorld.scene.remove(this.mesh);
            
            // Clean up to free memory
            this.mesh.geometry.dispose();
            this.mesh.material.dispose();
            this.mesh = null;
            this.shape = null;
        }
    }
}

class Enemy{
    constructor(i, j){
        this.i = i;
        this.j = j;
        this.shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );			 
        this.mesh = new THREE.Mesh( this.shape );
        this.mesh.material =  new THREE.MeshBasicMaterial( {map: enemy_texture} );
        ABWorld.scene.add(this.mesh);
    }
}

class Agent{
    constructor(i, j){
        this.i = i;
        this.j = j;
        this.shape    = new THREE.BoxGeometry ( squaresize, BOXHEIGHT, squaresize );			 
        this.mesh = new THREE.Mesh( this.shape );
        this.mesh.material =  new THREE.MeshBasicMaterial( {map: agent_texture} );
        ABWorld.scene.add(this.mesh);
    }
}

// consider redoing this class so that it uses a "has-a" relationship rather than "is-a" inheritance
class solutionNode extends problemNode{
    constructor(problemNode){
        super(problemNode.i, problemNode.j);
        // Inherits all of given problemNode's attributes
        this.i = problemNode.i;
        this.j = problemNode.j; 
        this.neighbors = problemNode.neighbors;
        this.iswall = problemNode.iswall;

        // f, g, and h values for A*
        this.f = 0;  // this is the evaluation function for A* which is f(n) = g(n) + h(n)
        this.g = 0;  // g(n) is the path cost from the initial state to node n
        this.h = 0;  // h(n) is the estimated cost of the shortest path from n to a goal state

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


class Problem {
    constructor(){
        // Start and end
        this.maze       = this.create_maze();
        this.enemy      = this.createEnemy();
        this.agent      = this.createAgent();
    }

    create_maze(){
        var maze = new Array(gridsize);  //gridsize is a global variable
        
        for ( let i = 0; i < gridsize ; i++ ) 
		    maze[i] = new Array(gridsize);	
    
        // Add an empty problemNode for every element of the array
        for (let i = 0; i < gridsize; i++) 
            for (let j = 0; j < gridsize; j++) 
                maze[i][j] = new problemNode(i, j);
        
        // Add neighbors to each node
        for (let i = 0; i < gridsize; i++) 
            for (let j = 0; j < gridsize; j++) 
                maze[i][j].addNeighbors(maze);
    
        // set up walls - this sets up the outer wall of the maze. 
        for (let i = 0; i < gridsize; i++){ 
            for ( let j = 0; j < gridsize; j++){
              if ((i==0) || (i==gridsize-1) || (j==0) || (j==gridsize-1)){
                  maze[i][j].makeWall();		
              }
            }
        }
          
        // set up maze - This adds the inner maze walls.   
        for ( var c=1 ; c <= NOBOXES ; c++ ){
            let i = AB.randomIntAtoB(1,gridsize-2);		// inner squares are 1 to gridsize-2
            let j = AB.randomIntAtoB(1,gridsize-2);
            
            if (!maze[i][j].iswall){
                maze[i][j].makeWall();
            }   
        }
        return maze;
    }

    createEnemy(){
        // start in random location
        let i = AB.randomIntAtoB(1,gridsize-2);
        let j = AB.randomIntAtoB(1,gridsize-2);
        
        do{
            i = AB.randomIntAtoB(1,gridsize-2);
            j = AB.randomIntAtoB(1,gridsize-2);
        } while (this.maze[i][j].iswall);  	  // search for empty square 

        let enemy = new Enemy(i,j);
        return enemy;
    }

    createAgent(){
        // start in random location
        let i = AB.randomIntAtoB(1,gridsize-2);
        let j = AB.randomIntAtoB(1,gridsize-2);
        
        do
        {
            i = AB.randomIntAtoB(1,gridsize-2);
            j = AB.randomIntAtoB(1,gridsize-2);
        }
        while (this.maze[i][j].iswall || (this.enemy.i == i && this.enemy.j == j));  	  // search for empty square 

        let agent = new Agent(i,j);

        return agent;
    }

    isOccupied(i, j){
        //check for wall, enemy and agent
        let result = false;

        if (this.maze[i][j].iswall) result = true;
        if (this.enemy.i == i && this.enemy.j == j) result = true;
        if (this.agent.i == i && this.agent.j == j) result = true;

        return result;
    }
}

class Solution{
    constructor(prblm){
        this.prblm = prblm;
        this.initial = prblm.maze[this.prblm.enemy.i][this.prblm.enemy.j];
        this.goal = prblm.maze[this.prblm.agent.i][this.prblm.agent.j];
        this.frontier = [];
        this.reached = [];
        this.path = [];
        this.pathend = null;
        this.pathfound = false;
        this.nextstep = null;

        //this.clean_previous(this.prblem);
        this.pathfound = this.solve();
        this.initial.previous = null;
        if (this.pathfound){
            this.path = this.backtrack();
            this.nextstep = this.path[this.path[0]];
            
            console.log("path.length = " + this.path.length)
        }
        else{
            console.log("No path found.");
        }


    }

    solve(){
        let node = this.initial;
        if (this.isGoal(node)){return true;}
        this.frontier.push(node);
        this.reached.push(node);

        do{
            node = this.frontier.pop();
            for (let i = 0; i < node.neighbors.length; i++){
                let s = node.neighbors[i];
                if (this.isGoal(s)){return true;}
                if (!this.reached.includes(s)){
                    this.reached.push(s);
                    this.frontier.push(s);
                    s.previous = node;
                }
            }
        }while(this.frontier.length > 0);
        return false;
    }

    isGoal(node){    
        for (let n = 0; n < node.neighbors.length; n++){
            if (node.neighbors[n].i == this.goal.i && node.neighbors[n].j == this.goal.j){
                this.pathend = node;
                return true;
            }
        }
        return false;
    }

    backtrack(){
        // Find the path by working backwards
        let temp_path = [];
        let temp = this.pathend;
        temp_path.push(temp);
        while (temp.previous){
            temp_path.push(temp.previous);
            temp = temp.previous;
        }
        return temp_path;
    }

    clean_previous(){
        for (let i = 0; i < gridsize; i++){
            for (let j = 0; j < gridsize; j++){
                this.prblm.maze[i][j].previous = null;
            }
        }
    }
}

// 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 BOXHEIGHT;		// 3d or 2d box height 

var problem = undefined;

var solution = undefined;
  
var wall_texture, agent_texture, enemy_texture, maze_texture; 

var diagonal = false;

var badsteps;
var goodsteps;

var minimapCanvas = document.createElement("canvas");
minimapCanvas.width = 200;  // Assuming a 200x200 pixel minimap for simplicity
minimapCanvas.height = 200;
minimapCanvas.style.position = "absolute";
minimapCanvas.style.bottom = "10px";
minimapCanvas.style.left = "10px";
minimapCanvas.style.zIndex = "1";  // Make sure the minimap is above the three.js canvas
document.body.appendChild(minimapCanvas);

	
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
 
// 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
	problem = new Problem();

	solution = new Solution(problem);
   
	drawEnemy();
	drawAgent();
	drawMinimap();
	musicPause();


	// 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 problem.enemy.i, problem.enemy.i, draw it 
{
	problem.enemy.mesh.position.copy ( translate(problem.enemy.i,problem.enemy.j) ); 		  	// translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 

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


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

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


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

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

	var i, j;
	if ( problem.enemy.i < problem.agent.i ) i = AB.randomIntAtoB(problem.enemy.i, problem.enemy.i+1); 
	if ( problem.enemy.i == problem.agent.i ) i = problem.enemy.i; 
	if ( problem.enemy.i > problem.agent.i ) i = AB.randomIntAtoB(problem.enemy.i-1, problem.enemy.i); 

	if ( problem.enemy.j < problem.agent.j ) j = AB.randomIntAtoB(problem.enemy.j, problem.enemy.j+1); 
	if ( problem.enemy.j == problem.agent.j ) j = problem.enemy.j; 
	if ( problem.enemy.j > problem.agent.j ) j = AB.randomIntAtoB(problem.enemy.j-1, problem.enemy.j); 
	
	if ( ! problem.isOccupied(i,j) )  	// if no obstacle then move, else just miss a turn
	{
		problem.enemy.i = i;
		problem.enemy.j = j;
	}
}

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

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

	if (!problem.isOccupied(i,j)){
		problem.agent.i = i;
		problem.agent.j = 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(problem.enemy.i - problem.agent.i) < 2 ) && ( Math.abs(problem.enemy.j - problem.agent.j) < 2 ) ) return true;
 else return false;
}

function agentBlocked()			// agent is blocked on all sides, run over
{
 return ( problem.isOccupied (problem.agent.i-1,problem.agent.j) && 
		problem.isOccupied (problem.agent.i+1,problem.agent.j) &&
		problem.isOccupied (problem.agent.i,problem.agent.j+1) &&
		problem.isOccupied (problem.agent.i,problem.agent.j-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 = [ problem.agent.i, problem.agent.j, problem.enemy.i, problem.enemy.j ];
  return ( x );  
};

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

	solution = new Solution(problem);

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


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


// --- music and sound effects ----------------------------------------

var backmusic = AB.backgroundMusic ( MUSIC_BACK );