Code viewer for World: Bomberman
//Due to time constraints, parts of the code are very long as I didnt have time to identify any redundant code and remove it.
//I hope the comments and naming conventions make it as clear as possible, Thank you.


const CLOCKTICK = 150;        // speed of run - move things every n milliseconds
const MAXSTEPS = 1000;        // length of a run before final score

const  SCREENSHOT_STEP = 50;    


//---- global constants: -------------------------------------------------------

const gridsize = 15;          // number of squares along side of world     
const squaresize = 100;         // size of square in pixels
const MAXPOS = gridsize * squaresize;   // length of one side in pixels 
  
const NOBOXES = Math.trunc ( (gridsize * gridsize) / 15 );

const SKYCOLOR = 0xffffcc;        // a number, not a string 
const BLANKCOLOR = SKYCOLOR;      // make objects this color until texture arrives (from asynchronous file read)

const LIGHTCOLOR = 0xffffff;

const startRadiusConst = MAXPOS * 0.8;    // distance from centre to start the camera at
const skyboxConst = MAXPOS * 3;   // where to put skybox 
const maxRadiusConst = MAXPOS * 5;    // maximum distance from camera we will render things  

//--- 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;
const DROP_BOMB = 5;

// contents of a grid square

const GRID_BLANK = 0;
const GRID_WALL = 1;
const GRID_MAZE = 2;
const GRID_WEAK = 3; //Weak walls can be blown up with bombs
const GRID_BOMB = 4;
 
// --- some useful random functions  -------------------------------------------

function randomfloatAtoB ( A, B )      
{
    return ( A + ( Math.random() * (B-A) ) );
}
function randomintAtoB ( A, B )      
{
    return  ( Math.round ( randomfloatAtoB ( A, B ) ) );
}
function randomBoolean()       
{
    if ( Math.random() < 0.5 )
        return false;
    else
        return true;
}
function randomPick ( a, b )
{
    if ( randomBoolean() ) 
        return a;
    else
        return b;
}

//---- start of World class -------------------------------------------------------
 
function World() { 
    // most of World can be private 
    // regular "var" syntax means private variables:
    
    var GRID = new Array (gridsize);      // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array   
    var WALLS = new Array ( 4 * gridsize );   // need to keep handle to each wall block object so can find it later to paint it 
    var STRONGCUBES = new Array ( NOBOXES );
    var WEAKCUBES = new Array ( NOBOXES );
    var EXPLOSIONS = new Array ( NOBOXES );
    var theagent, theenemy, agentBomb, enemyBomb;     
    
    var agentRotation = 0;
    var enemyRotation = 0;      // with 3D models, current rotation away from default orientation
    
    // enemy and agent position on squares
    var ei , ej, ai, aj, agentbombi, agentbombj, enemybombi, enemybombj;
    var self = this;            // needed for private fn to call public fn - see below  
    var steps;
    var agentbombExploding = false;
    var enemybombExploding = false;
    var enemyBombPlaced = false;
    
    var agentmiddle; //Explosion in four diections
    var agentleftBlock;
    var agentrightBlock;
    var agentupBlock;
    var agentdownBlock;
    
    var enemymiddle; //Explosion in four diections
    var enemyleftBlock;
    var enemyrightBlock;
    var enemyupBlock;
    var enemydownBlock;
    
    function initGrid()
    {
        for (var i = 0; i < gridsize ; i++) 
        {
            GRID[i] = new Array(gridsize);    // each element is an array 
        
            for (var j = 0; j < gridsize ; j++) 
            {
                GRID[i][j] = GRID_BLANK ;
            }
        }
    }
    
    function occupied ( i, j )    // is this square occupied
    {
        // variable objects
        if ( ( ei == i ) && ( ej == j ) ) 
            return true;    // variable objects 
        if ( ( ai == i ) && ( aj == j ) ) 
            return true;
        if ( ( agentbombi == i ) && ( agentbombj == j ) ) 
            return true; 
        if ( ( enemybombi == i ) && ( enemybombj == j ) ) 
            return true;
        if ( GRID[i][j] == GRID_WEAK ) 
            return true; 
            
        // fixed objects
        if ( GRID[i][j] == GRID_WALL ) 
            return true;       
        if ( GRID[i][j] == GRID_MAZE ) 
            return true;     
          
        return false;
    }

    function translate ( x ) 
    {
     return ( x - ( MAXPOS/2 ) );
    }
    
    function loadTextures()
    {
       var manager = new THREE.LoadingManager();   
         var loader = new THREE.OBJLoader( manager );
        
         // load OBJ plus MTL (plus TGA files) 
          THREE.Loader.Handlers.add( /.tga$/i, new THREE.TGALoader() );
          var m = new THREE.MTLLoader();
          m.setTexturePath ( "/uploads/starter/" );
          m.setPath        ( "/uploads/starter/" );
          m.load( "Peter_Parker.mtl", function( materials ) {
         
            materials.preload();
            var o = new THREE.OBJLoader();
            o.setMaterials ( materials );
            o.setPath ( "/uploads/starter/" );
            o.load( "Peter_Parker.obj", function ( object ) {
              buildenemy ( object );
            } );
          } );
         
         // load OBJ plus MTL (plus TGA files) 
          m.load( "Peter_Parker.mtl", function( materials ) {
         
            materials.preload();
            var o = new THREE.OBJLoader();
            o.setMaterials ( materials );
            o.setPath ( "/uploads/starter/" );
            o.load( "Peter_Parker.obj", function ( object ) {
              buildagent ( object );
            } );
          } );
          
          //change path
            m.setPath ( "/uploads/mcadamm4/" );
            m.load( "bomb.mtl", function( materials ) {
         
            materials.preload();
            var o = new THREE.OBJLoader();
            o.setMaterials ( materials );
            o.setPath ( "/uploads/mcadamm4/" );
            o.load( "bomb.obj", function ( object ) {
              buildAgentBomb ( object );
            } );
          } );
          
          m.load( "bomb.mtl", function( materials ) {
          
          materials.preload();
            var o = new THREE.OBJLoader();
            o.setMaterials ( materials );
            o.setPath ( "/uploads/mcadamm4/" );
            o.load( "bomb.obj", function ( object ) {
              buildEnemyBomb ( object );
            } );
          } );
          
         var loader1 = new THREE.TextureLoader();
         loader1.load ( '/uploads/mcadamm4/novaexplosion.jpg',   function ( thetexture ) {      
            thetexture.minFilter = THREE.LinearFilter;
            paintExplosion ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
          } ); 
        
         var loader2 = new THREE.TextureLoader();
         loader2.load ( '/uploads/mcadamm4/blocks.jpg',   function ( thetexture ) {      
            thetexture.minFilter = THREE.LinearFilter;
            paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
          } ); 
        
         var loader3 = new THREE.TextureLoader();
         loader3.load ( '/uploads/mcadamm4/blocks.jpg',  function ( thetexture ) {      
            thetexture.minFilter = THREE.LinearFilter;
            paintStrongBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ));
          } ); 
        
         var loader4 = new THREE.TextureLoader();
         loader4.load ( '/uploads/mcadamm4/wood.jpg',  function ( thetexture ) {      
            thetexture.minFilter = THREE.LinearFilter;
            paintWeakBlocks ( new THREE.MeshBasicMaterial( { map: thetexture  } ));
          } ); 
    }

    function initSkybox() 
    {
        // urban photographic skyboxes, credit:
        // http://opengameart.org/content/urban-skyboxes
        var materialArray = [
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-xpos.png" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-xneg.png" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-ypos.png" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-yneg.png" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-zpos.png" ), side: THREE.BackSide } ) ),
     	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-zneg.png" ), side: THREE.BackSide } ) )
     	];
    
        var skyGeometry = new THREE.CubeGeometry ( skyboxConst, skyboxConst, skyboxConst ); 
        var skyMaterial = new THREE.MeshFaceMaterial ( materialArray );
        var theskybox = new THREE.Mesh ( skyGeometry, skyMaterial );
        threeworld.scene.add( theskybox ); // We are inside a giant cube
    }
    function initLogicalWalls()   // set up logical walls in data structure, whether doing graphical run or not 
    {
     for (var i = 0; i < gridsize ; i++) 
      for (var j = 0; j < gridsize ; j++) 
       if ( ( i==0 ) || ( i==gridsize-1 ) || ( j==0 ) || ( j==gridsize-1 ) )
       {
          GRID[i][j] = GRID_WALL;    
       }
    }
    function initThreeWalls()   // graphical run only, set up blank boxes, painted later  
    {
     var t = 0;
     for (var i = 0; i < gridsize ; i++) 
      for (var j = 0; j < gridsize ; j++) 
       if ( GRID[i][j] == GRID_WALL )
       {
       var shape    = new THREE.BoxGeometry( squaresize, squaresize, squaresize );       
       var thecube  = new THREE.Mesh( shape );
       thecube.material.color.setHex( BLANKCOLOR  );        
     
           thecube.position.x = translate ( i * squaresize );       // translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates 
           thecube.position.z = translate ( j * squaresize );     
           thecube.position.y =  0; 
     
       threeworld.scene.add(thecube);
       WALLS[t] = thecube;    // save it for later
       t++; 
       }
    }
    function initThreeMaze()        
    {
        var t1 = 0;
        var t2 = 0;
        for (var i = 0; i < gridsize ; i++) 
            for (var j = 0; j < gridsize ; j++) 
                if ( GRID[i][j] == GRID_MAZE )
                {
                    var strongShape = new THREE.BoxGeometry( squaresize, squaresize, squaresize );       
                    var strongCube = new THREE.Mesh( strongShape );
                    strongCube.material.color.setHex( BLANKCOLOR  );        
        
                    strongCube.position.x = translate ( i * squaresize );     
                    strongCube.position.z = translate ( j * squaresize );     
                    strongCube.position.y = 0; 
         
                    threeworld.scene.add(strongCube);
                    STRONGCUBES[t1] = strongCube;   // save it for later
                    t1++; 
               }
               else if ( GRID[i][j] == GRID_WEAK )
               {
                    var weakShape    = new THREE.BoxGeometry( squaresize, squaresize, squaresize );       
                    var weakCube  = new THREE.Mesh( weakShape );
                    weakCube.material.color.setHex( BLANKCOLOR  );        
        
                    weakCube.position.x = translate ( i * squaresize );     
                    weakCube.position.z = translate ( j * squaresize );     
                    weakCube.position.y =  0; 
                 
                    threeworld.scene.add(weakCube);
                    WEAKCUBES[t2] = weakCube;   // save it for later
                    t2++; 
                }
    }
    var strongArray = new Array(7); //This sets out the positions of the strong blocks on the grid
    strongArray[0] = [5,9];
    strongArray[1] = [2,4,5,6,8,9,10,12];
    strongArray[2] = [2,12];
    strongArray[3] = [2,3,5,6,8,9,11,12];
    strongArray[4] = [5,9]
    strongArray[5] = [2,3,5,7,9,11,12];
    strongArray[6] = [1,2,7,12,13];
    
    function initLogicalStrongBlocks() //Strong blocks cannot be blown up
    {
        var i = 0;
        for(var j=1; j<=7; j++) //Top half of grid
        {
            for(var n=0; n<strongArray[i].length; n++)
            {
                 GRID[strongArray[i][n]][j] = GRID_MAZE;
            }
            i++;
        }
        var k=0;
        for(var l=13; l>=8; l--) //Bottom half of grid
        {
            for(var m=0; m<strongArray[k].length; m++)
            {
                 GRID[strongArray[k][m]][l] = GRID_MAZE;
            }
            k++;
        }
    }
    
    var weakArray = new Array(7); //This sets out the positions of the weak blocks on the grid
    weakArray[0] = [8];
    weakArray[1] = [1,3,7,11,13];
    weakArray[2] = [5,9];
    weakArray[3] = [1,7];
    weakArray[4] = [2,3,11,12]
    weakArray[5] = [6,8];
    weakArray[6] = [4,10];
    
    function initLogicalWeakBlocks() //Weak blocks can be blown up
    {
        var i = 0;
        for(var j=1; j<=7; j++) //Top half of the grid
        {
            for(var n=0; n<weakArray[i].length; n++)
            {
                 GRID[weakArray[i][n]][j] = GRID_WEAK;
            }
            i++;
        }
        var k=0;
        for(var l=13; l>=8; l--) //Bottom half of the grid
        {
            for(var m=0; m<weakArray[k].length; m++)
            {
                 GRID[weakArray[k][m]][l] = GRID_WEAK;
            }
            k++;
        }
    }

//PAINT 
    function paintEnemy ( child ) 
    {
      if ( child instanceof THREE.Mesh ) 
      {
            child.material.map = THREE.ImageUtils.loadTexture( "/uploads/starter/ghost.3.png" );
      }
    }
    
    function paintAgent ( child ) 
    {
      if ( child instanceof THREE.Mesh ) 
      {
            child.material.map = THREE.ImageUtils.loadTexture( "/uploads/starter/pacman.jpg" );
      }
    }
    function paintWalls ( material )     
    {
        for ( var i = 0; i < WALLS.length; i++ )
        { 
            if ( WALLS[i] )  
                WALLS[i].material = material;
        }
    }
    function paintStrongBlocks ( material )          
    {
        for ( var i = 0; i < STRONGCUBES.length; i++ )
        { 
            if ( STRONGCUBES[i] )  
                STRONGCUBES[i].material = material;
        }
    }
    function paintWeakBlocks ( material )          
    {
        for ( var i = 0; i < WEAKCUBES.length; i++ )
        { 
            if ( WEAKCUBES[i] )  
                WEAKCUBES[i].material = material;
        }
    }
    function paintExplosion( material )
    {
               
        for(var i=0; i<=10; i++)
        {
            var shape = new THREE.SphereGeometry( 80, 80, 80 );       
            var sphere  = new THREE.Mesh( shape );
            sphere.material.color.setHex( BLANKCOLOR  ); 
            EXPLOSIONS[i] = sphere;
            EXPLOSIONS[i].material = material;
        }
    }
    
// --- ENEMY  -----------------------------------
    function initLogicalEnemy()
    {
        ei = 1;
        ej = 1;
    }
    function buildenemy ( object ) 
    { 
        object.scale.multiplyScalar ( 50 );        // make 3d object n times bigger 
        object.traverse( paintEnemy );
        theenemy = object;
          
        //Starting Position of enemy one
        theenemy.position.x = translate ( 1 * squaresize );
        theenemy.position.y = ( -50 );
        theenemy.position.z = translate ( 1 * squaresize );
          
        threeworld.scene.add( theenemy );
    }
    function drawEnemy()    // given ei, ej, draw it 
    {
        if ( theenemy )
        {
            var x = translate ( ei * squaresize );    
            var z = translate ( ej * squaresize );    
            var y =   -50;    
            
            theenemy.position.x = x;
            theenemy.position.y = y;
            theenemy.position.z = z;
             
            threeworld.lookat.copy ( theenemy.position );   // if camera moving, look back at where the enemy is  
            threeworld.lookat.y = ( squaresize * 1.5 );     // point camera higher up
        }
    }
    function igetAction()
    {
        if ( ei < ai )  
            return (   ACTION_RIGHT   ); 
        if ( ei > ai )  
            return (   ACTION_LEFT    ); 
      return ( ACTION_STAYSTILL );
    }
    function jgetAction()
    {
        if ( ej < aj )  
            return (   ACTION_UP    ); 
        if ( ej > aj )  
            return (   ACTION_DOWN  ); 
        return ( ACTION_STAYSTILL );
    }
    function enemyGetAction()
    {
        return randomPick ( igetAction(), jgetAction() );
    }
    function moveLogicalEnemy()
    { 
        if(!enemyBombPlaced) //Enemy moves randomly until he sets a bomb, then he must escape the bombs blast
            var a = enemyGetAction();
        var i = ei;
        var j = ej;  
        var bool = false;
        
        if( occupiedByAgentBomb((ei-1),ej) || //Is there an agent bomb nearby that he must escape
            occupiedByAgentBomb((ei+1),ej) || 
            occupiedByAgentBomb(ei,(ej-1)) || 
            occupiedByAgentBomb(ei,(ej+1)))
        {
            bool = true; //Make sure enemy dosnt move again after escaping
            switch (esacpeDirection()) 
            //Quite a few of the cases may be redundant if I have time I can cut the cases down 
            //by coupling AND's that have equivelant effects in the esacpeDirection() method 
            {
                case 0://Left
                    i-=2;
                    ei = i;
                    ej = j;
                    break;
                case 1://Right
                    i+=2;
                    ei = i;
                    ej = j;
                    break;
                case 2://Up
                    j-=2;
                    ei = i;
                    ej = j;
                    break;
                case 3://Down
                    j+=2;
                    ei = i;
                    ej = j;
                    break;
                case 4://Left & Up
                    i--;
                    j--;
                    ei = i;
                    ej = j;
                    break;
                case 5://Left & Down
                    i--;
                    j++;
                    ei = i;
                    ej = j;
                    break;
                case 6://Right & Up
                    i++;
                    j--;
                    ei = i;
                    ej = j;
                    break;
                case 7://Right & Down
                    i++;
                    j++;
                    ei = i;
                    ej = j;
                    break;
                case 8://Down & Left
                    j++;
                    i--;
                    ei = i;
                    ej = j;
                    break;
                case 9://Down & right
                    j++;
                    i++;
                    ei = i;
                    ej = j;
                    break;
                case 10://Up and left
                    j--;
                    i--;
                    ei = i;
                    ej = j;
                    break;
                case 11://Up and right
                    j--;
                    i++;
                    ei = i;
                    ej = j;
                    break;
                case 12:
                    break;
            }
        }
        
        if( occupiedByPlayer((ei-1),ej) || 
            occupiedByPlayer((ei+1),ej) || 
            occupiedByPlayer(ei,(ej-1)) || 
            occupiedByPlayer(ei,(ej+1)) ||
            occupiedByWeakBox((ei-1),ej) || 
            occupiedByWeakBox((ei+1),ej) || 
            occupiedByWeakBox(ei,(ej-1)) || 
            occupiedByWeakBox(ei,(ej+1)))
        {
            bool = true;
            switch (esacpeDirection()) 
            //Quite a few of the cases may be redundant if I have time I can cut the cases down 
            //by coupling AND's with equivelant effects in the esacpeDirection() method 
            {
                case 0://Left
                    i-=2;
                    enemyDrawBomb( ei, ej );
                    console.log(i,j);
                    ei = i;
                    ej = j;
                    break;
                case 1://Right
                    i+=2;
                    enemyDrawBomb( ei, ej );
                    console.log(i,j);
                    ei = i;
                    ej = j;
                    break;
                case 2://Up
                    j-=2;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 3://Down
                    j+=2;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 4://Left & Up
                    i--;
                    j--;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 5://Left & Down
                    i--;
                    j++;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 6://Right & Up
                    i++;
                    j--;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 7://Right & Down
                    i++;
                    j++;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 8://Down & Left
                    j++;
                    i--;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 9://Down & right
                    j++;
                    i++;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 10://Up and left
                    j--;
                    i--;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 11://Up and right
                    j--;
                    i++;
                    enemyDrawBomb( ei, ej );
                    ei = i;
                    ej = j;
                    break;
                case 12:
                    break;
            }
        }
        
        if ( a == ACTION_LEFT && !bool)   //If just moving randomly and not escaping his bomb
            i--;
        else if ( a == ACTION_RIGHT && !bool)   
            i++;
        else if ( a == ACTION_UP && !bool)    
            j++;
        else if ( a == ACTION_DOWN && !bool)  
            j--;

        if ( !occupied(i,j) && !bool ) 
        {
            if ( true  )
            {
              if ( a == ACTION_LEFT )   
                  rotateEnemyTowards ( 3 * (Math.PI / 2) );
              else if ( a == ACTION_RIGHT )
                  rotateEnemyTowards ( 1 * (Math.PI / 2) );
              else if ( a == ACTION_UP )
                  rotateEnemyTowards ( 0 * (Math.PI / 2) ); 
              else if ( a == ACTION_DOWN )
                  rotateEnemyTowards ( 2 * (Math.PI / 2) ); 
            }
        ei = i;
        ej = j;
        }
    }
    
    function esacpeDirection() //This method allows the enemy to escape the blasts of bombs if there is space
    {
        if (!occupied((ei-1),ej) && !occupied((ei-2),ej))
            return 0; //Left
        if(!occupied((ei+1),ej) && !occupied((ei+2),ej))
            return 1; //Right
        if(!occupied(ei,(ej-1)) && !occupied(ei,(ej-2)))
            return 2; //Up
        if(!occupied(ei,(ej+1)) && !occupied(ei,(ej+2)))
            return 3; //Down
        if(!occupied((ei-1),ej) && !occupied((ei-1),(ej-1)))
            return 4; //Left & Up
        if(!occupied((ei-1),ej) && !occupied((ei-1),(ej+1)))
            return 5;//Left & Down
        if(!occupied((ei+1),ej) && !occupied((ei+1),(ej-1)))
            return 6;//Right & Up
        if(!occupied((ei+1),ej) && !occupied((ei+1),(ej+1)))
            return 7;//Right & Down
        if(!occupied(ei,(ej+1)) && !occupied((ei-1),(ej+1)))
            return 8;//Down & Left
        if(!occupied(ei,(ej+1)) && !occupied((ei+1),(ej+1)))
            return 9;//Down & right
        if(!occupied(ei,(ej-1)) && !occupied((ei-1),(ej-1)))
            return 10;//Up and left
        if(!occupied(ei,(ej-1)) && !occupied((ei+1),(ej-1)))
            return 11;//Up and right
        else
            return 12;//No escape route
    }
    
    const INTERIMROT = 10;    // number of interim rotations drawn when model turns round
    
    // interim renders not working
    // temporary solution - rotate half-way - will continue the rotation later
    
    function rotateEnemyTowards ( newRotation )   
    {
        if ( enemyRotation == newRotation ) 
            return;
        var x = ( enemyRotation + newRotation ) / 2; 
        theenemy.rotation.set ( 0, x, 0 );
        enemyRotation = x;  
    }
    
    function buildAgentBomb( object )
    {
        object.scale.multiplyScalar ( 300 );        // make 3d object n times bigger 
        agentBomb = object;
    }
    function buildEnemyBomb( object )
    {
        object.scale.multiplyScalar ( 300 );        // make 3d object n times bigger 
        enemyBomb = object;
    }
    
    //DROP A BOMB
    function agentDrawBomb( )
    {
        agentbombi = ai; //Drop bomb at agents position
        agentbombj = aj;
        agentBomb.position.x = theagent.position.x;
        agentBomb.position.y = (theagent.position.y + 50); 
        agentBomb.position.z = theagent.position.z;
        
        threeworld.scene.add( agentBomb ); // Add a bomb
        setTimeout(agentBombExplode, 2000); //Delay the bombs explosion so agent can clear the area
        setTimeout(removeAgentBomb, 2000); //Delay the bombs removal to coincide with explosion
    }
    function enemyDrawBomb( enemyi, enemyj )
    {
        enemyBombPlaced = true;
        enemybombi = enemyi; //Drop bomb at enemy's position
        enemybombj = enemyj;
        enemyBomb.position.x = theenemy.position.x;
        enemyBomb.position.y = (theenemy.position.y + 50); 
        enemyBomb.position.z = theenemy.position.z;
        
        threeworld.scene.add( enemyBomb ); // Add a bomb
        setTimeout(enemyBombExplode, 2000); //Delay the bombs explosion enemy can clear the area
        setTimeout(removeEnemyBomb, 2000); //Delay the bombs removal to coincide with explosion
    }
    function removeAgentBomb()
    {
        threeworld.scene.remove( agentBomb );
    }
    function removeEnemyBomb()
    {
        threeworld.scene.remove( enemyBomb );
        enemyBombPlaced = false; 
    }
    
    function agentBombExplode()
    {
        agentmiddle = EXPLOSIONS[0]; //Show explosions and then remove them
        agentleftBlock = EXPLOSIONS[1];
        agentrightBlock = EXPLOSIONS[2];
        agentupBlock = EXPLOSIONS[3];
        agentdownBlock = EXPLOSIONS[4];
        //-------------------------------------------
        //The explosions form a cross so five need to be added
        var x = translate ( agentbombi * squaresize );     
        var z = translate ( agentbombj * squaresize );     
        var y = ( 0 ); 
        agentmiddle.position.x = x;
        agentmiddle.position.y = y;
        agentmiddle.position.z = z;
        threeworld.scene.add(agentmiddle);
        //-------------------------------------------
        var leftx = translate ( (agentbombi-1) * squaresize );     
        var leftz = translate ( agentbombj * squaresize );     
        var lefty = ( 0 ); 
        agentleftBlock.position.x = leftx;
        agentleftBlock.position.y = lefty;
        agentleftBlock.position.z = leftz;
        threeworld.scene.add(agentleftBlock);
        //-------------------------------------------
        var rightx = translate ( (agentbombi+1) * squaresize );    
        var rightz = translate ( agentbombj * squaresize );    
        var righty =   ( 0 ); 
        agentrightBlock.position.x = rightx;
        agentrightBlock.position.y = righty;
        agentrightBlock.position.z = rightz;
        threeworld.scene.add(agentrightBlock);
        //-------------------------------------------
        var upx = translate ( agentbombi * squaresize );     
        var upz = translate ( (agentbombj-1) * squaresize );     
        var upy =   ( 0 ); 
        agentupBlock.position.x = upx;
        agentupBlock.position.y = upy;
        agentupBlock.position.z = upz;
        threeworld.scene.add(agentupBlock);
        //-------------------------------------------
        var downx = translate ( agentbombi * squaresize );     
        var downz = translate ( (agentbombj+1) * squaresize );     
        var downy =   ( 0 ); 
        agentdownBlock.position.x = downx;
        agentdownBlock.position.y = downy;
        agentdownBlock.position.z = downz;
        threeworld.scene.add(agentdownBlock);
        setTimeout(removeAgentExplosion, 500);
        //-------------------------------------------
        //Remove any weak blocks that were hit in the blast
        for( var n=0; n<WEAKCUBES.length; n++ )
        {
            if( WEAKCUBES[n].position.x == leftx && WEAKCUBES[n].position.z == leftz)
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[(agentbombi-1)][agentbombj] = GRID_BLANK;
            }
            if( WEAKCUBES[n].position.x == rightx && WEAKCUBES[n].position.z == rightz)
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[(agentbombi+1)][agentbombj] = GRID_BLANK;
            }
            if( WEAKCUBES[n].position.x == upx && WEAKCUBES[n].position.z == upz)
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[agentbombi][(agentbombj-1)] = GRID_BLANK;
            }
            if( WEAKCUBES[n].position.x == downx && WEAKCUBES[n].position.z == downz )
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[agentbombi][(agentbombj+1)] = GRID_BLANK;
            }
            agentbombExploding = true;
        }
        //-------------------------------------------
    }
    function enemyBombExplode()
    {
        enemymiddle = EXPLOSIONS[5]; //Show explosions and then remove them
        enemyleftBlock = EXPLOSIONS[6];
        enemyrightBlock = EXPLOSIONS[7];
        enemyupBlock = EXPLOSIONS[8];
        enemydownBlock = EXPLOSIONS[9];
        //-------------------------------------------
        var x = translate ( enemybombi * squaresize );     
        var z = translate ( enemybombj * squaresize );     
        var y = ( 0 ); 
        enemymiddle.position.x = x;
        enemymiddle.position.y = y;
        enemymiddle.position.z = z;
        threeworld.scene.add(enemymiddle);
        //-------------------------------------------
        var leftx = translate ( (enemybombi-1) * squaresize );     
        var leftz = translate ( enemybombj * squaresize );     
        var lefty = ( 0 ); 
        enemyleftBlock.position.x = leftx;
        enemyleftBlock.position.y = lefty;
        enemyleftBlock.position.z = leftz;
        threeworld.scene.add(enemyleftBlock);
        //-------------------------------------------
        var rightx = translate ( (enemybombi+1) * squaresize );    
        var rightz = translate ( enemybombj * squaresize );    
        var righty =   ( 0 ); 
        enemyrightBlock.position.x = rightx;
        enemyrightBlock.position.y = righty;
        enemyrightBlock.position.z = rightz;
        threeworld.scene.add(enemyrightBlock);
        //-------------------------------------------
        var upx = translate ( enemybombi * squaresize );     
        var upz = translate ( (enemybombj-1) * squaresize );     
        var upy =   ( 0 ); 
        enemyupBlock.position.x = upx;
        enemyupBlock.position.y = upy;
        enemyupBlock.position.z = upz;
        threeworld.scene.add(enemyupBlock);
        //-------------------------------------------
        var downx = translate ( enemybombi * squaresize );     
        var downz = translate ( (enemybombj+1) * squaresize );     
        var downy =   ( 0 ); 
        enemydownBlock.position.x = downx;
        enemydownBlock.position.y = downy;
        enemydownBlock.position.z = downz;
        threeworld.scene.add(enemydownBlock);
        setTimeout(removeEnemyExplosion, 500);
        //-------------------------------------------
        //Remove any weak blocks that were hit in the blast
        for( var n=0; n<WEAKCUBES.length; n++ )
        {
            if( WEAKCUBES[n].position.x == leftx && WEAKCUBES[n].position.z == leftz)
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[(enemybombi-1)][enemybombj] = GRID_BLANK;
            }
            if( WEAKCUBES[n].position.x == rightx && WEAKCUBES[n].position.z == rightz)
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[(enemybombi+1)][enemybombj] = GRID_BLANK;
            }
            if( WEAKCUBES[n].position.x == upx && WEAKCUBES[n].position.z == upz)
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[enemybombi][(enemybombj-1)] = GRID_BLANK;
            }
            if( WEAKCUBES[n].position.x == downx && WEAKCUBES[n].position.z == downz )
            {
                threeworld.scene.remove(WEAKCUBES[n]);
                GRID[enemybombi][(enemybombj+1)] = GRID_BLANK;
            }
            enemybombExploding = true; //Stops the enemy moving back into his own bombs blast
        }
    }
    function removeAgentExplosion()
    {
        threeworld.scene.remove(agentmiddle);
        threeworld.scene.remove(agentleftBlock);
        threeworld.scene.remove(agentrightBlock);
        threeworld.scene.remove(agentupBlock);
        threeworld.scene.remove(agentdownBlock);
    }
    function removeEnemyExplosion()
    {
        threeworld.scene.remove(enemymiddle);
        threeworld.scene.remove(enemyleftBlock);
        threeworld.scene.remove(enemyrightBlock);
        threeworld.scene.remove(enemyupBlock);
        threeworld.scene.remove(enemydownBlock);
    }
    function agentBlownUp() //Did the Agent get blown up in the explosion
    {
        
        if( occupiedByPlayer(agentbombi,agentbombj) || 
            occupiedByPlayer((agentbombi-1),agentbombj) || 
            occupiedByPlayer((agentbombi+1),agentbombj) || 
            occupiedByPlayer(agentbombi,(agentbombj-1)) || 
            occupiedByPlayer(agentbombi,(agentbombj+1)))
            return true;
            
        agentbombi = 0; //Reset bomb position to outside map
        agentbombj = 0;
        return false;
    }
    
    function enemyBlownUp() //Did the Enemy get blown up in the explosion
    {
        if( occupiedByPlayer(enemybombi,enemybombj) || 
            occupiedByPlayer((enemybombi-1),enemybombj) || 
            occupiedByPlayer((enemybombi+1),enemybombj) || 
            occupiedByPlayer(enemybombi,(enemybombj-1)) || 
            occupiedByPlayer(enemybombi,(enemybombj+1)))
            return true;
            
        enemybombi = 14; //Reset bomb position to outside map
        enemybombj = 14;
        return false;
    }
    
    function occupiedByPlayer ( i, j )    // is this square occupied by a player
    {
        if ( ( ei == i ) && ( ej == j ) ) 
            return true;    // variable objects 
        if ( ( ai == i ) && ( aj == j ) ) 
            return true;
        return false;
    }
    function occupiedByWeakBox( i, j) 
    {
        if( (GRID[i][j] == GRID_WEAK) )
            return true;
        return false;
    }
    function occupiedByAgentBomb( i, j)
    {
        if( (agentbombi==i) && (agentbombj==j) )
            return true;
        return false;
    }

    // --- AGENT -----------------------------------
    
    function initLogicalAgent()
    {
        ai = 13;
        aj = 13;
    }
    function buildagent ( object ) 
    { 
        object.scale.multiplyScalar ( 50 );        // make 3d object n times bigger 
        object.traverse( paintAgent );
        theagent = object;
          
        //Starting Position of enemy one
        theagent.position.x = translate ( 13 * squaresize );
        theagent.position.y = ( -50 );
        theagent.position.z = translate ( 13 * squaresize );
        theagent.rotation.set ( 0, 3, 0 );
          
        threeworld.scene.add( theagent );
    }
    function drawAgent()  // given ai, aj, draw it 
    {
        if ( theagent )
        {
            var x = translate ( ai * squaresize );    
            var z = translate ( aj * squaresize );    
            var y = -50;    
            
            theagent.position.x = x;
            theagent.position.y = y;
            theagent.position.z = z;
             
            threeworld.follow.copy ( theagent.position );   // follow vector = agent position (for camera following agent)
            threeworld.follow.y = ( squaresize * 1.5 );     // put camera higher up
        }
    }
    
    function moveLogicalAgent( a )      //Agent is controlled by the user via keyboard
    { 
        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) ) 
        {
            if ( true  )
            {
                // if going to actually move, then turn body towards move
                // rotate by some amount of radians from the normal position 
                // in degrees: +0, +90, +180, +270
                
                if ( a == ACTION_LEFT )
                    rotateAgentTowards ( 3 * (Math.PI / 2) ); 
                else if ( a == ACTION_RIGHT )
                    rotateAgentTowards ( 1 * (Math.PI / 2) ); 
                else if ( a == ACTION_UP )
                    rotateAgentTowards ( 0 * (Math.PI / 2) ); 
                else if ( a == ACTION_DOWN )
                    rotateAgentTowards ( 2 * (Math.PI / 2) ); 
            }
            ai = i;
            aj = j;
        }
    }
    
    function rotateAgentTowards ( newRotation )   
    {
        if ( agentRotation == newRotation ) 
            return;
        var x = ( agentRotation + newRotation ) / 2; 
        theagent.rotation.set ( 0, x, 0 );
        agentRotation = x;  
    }
    
    function keyHandler(e)    
    {
        // user control 
        // Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
        
        if (e.keyCode == 32)  
            agentDrawBomb();
        if (e.keyCode == 37)  
            moveLogicalAgent ( ACTION_LEFT );
        if (e.keyCode == 38)  
            moveLogicalAgent ( ACTION_DOWN );
        if (e.keyCode == 39)  
            moveLogicalAgent ( ACTION_RIGHT );
        if (e.keyCode == 40)  
            moveLogicalAgent ( ACTION_UP );
    }
     
    function headerText()    
    {
        var status =  "[SPACEBAR] = Drop Bomb , [Arrow Keys] = Move Agent "; 
        $("#user_span1").html( status );
    }

    //--- public functions / interface / API ----------------------------------------------------------
    
    this.endCondition;      // If set to true, run will end. 
    
    this.newRun = function() 
    {
        steps = 0;
        this.endCondition = false;

        initGrid();
        initLogicalWalls(); 
        
        initLogicalStrongBlocks();
        initLogicalWeakBlocks();
        initLogicalAgent();
        initLogicalEnemy();
    
        if ( true  )
        {
            threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 
            
            // LIGHT
            var ambient = new THREE.AmbientLight();
            threeworld.scene.add( ambient );
            var thelight = new THREE.DirectionalLight ( LIGHTCOLOR, 3 );
            thelight.position.set ( startRadiusConst, startRadiusConst, startRadiusConst );
            threeworld.scene.add(thelight);
            
            // MUSIC
            var x = "<audio  id=theaudio  src=/uploads/mcadamm4/westworld.mp3   autoplay loop> </audio>" ;
            $("#user_span2").html( x );
            
            initSkybox();
            initThreeWalls();
            initThreeMaze();
            loadTextures();     // will return sometime later, but can go ahead and render now  
            
            document.onkeydown = keyHandler;
        }   
    };
    
    this.getState = function()
    {
        var x = [ ai, aj, ei, ej ];
        return ( x );  
    };
    
   
this.nextStep = function()		 
{
 var a = 4;

        steps++;
        headerText();
        if(steps%5==0)
            setTimeout(moveLogicalEnemy,4000);
            
        if ( true  )
        {
            drawAgent();
            drawEnemy();
            if(agentbombExploding) //Is there a bomb exploding
            {
                agentbombExploding = false; //Bomb has exploded so reset
                if(agentBlownUp()) // Has someone been blown up by the bomb
                {
                    this.endCondition = true;
                    var x = "<audio  id=theaudio  src=/uploads/mcadamm4/gameover.mp3   autoplay loop> </audio>" ;
                    $("#user_span2").html( x );
                }
            }
            if(enemybombExploding) //Is there a bomb exploding
            {
                enemybombExploding = false; //Bomb has exploded so reset
                if(enemyBlownUp()) // Has someone been blown up by the bomb
                {
                    this.endCondition = true;
                    var x = "<audio  id=theaudio  src=/uploads/mcadamm4/gameover.mp3   autoplay loop> </audio>" ;
                    $("#user_span2").html( x );
                }
            }
        }
    };

    this.endRun = function()
    {
        var status =  "GAME OVER!!"; 
        $("#user_span1").html( status );
    };
}

//---- end of World class -------------------------------------------------------