Code viewer for World: Bomberman (clone by Kline)

// Cloned by Kline on 14 Nov 2022 from World "Bomberman" by Enhanced 
// Please leave this clone trail here.
 


// Cloned by Enhanced on 21 Jun 2018 from World "Cloned Bomberman" by Nathan Bonnard 
// Please leave this clone trail here.

// Cloned by Nathan Bonnard on 1 Jun 2018 from World "Bomberman" by Mark McAdam 
// Please leave this clone trail here.
/*
--------------------------------
New Features by Nathan Bonnard :
--------------------------------

The game was code with a unique object for the bomb of the player.
There was a bug when you wanted to put two bomb, the second one always killed the player.
Now you can't put bomb if there is already one.
It would have been too long to code the entire game so that you can drop many bombs.

----------------------------------
All credits to Simon Mark McAdam :
----------------------------------
*/

AB.drawRunControls = false;

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 noBomb = true;
    
    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()
    {
        //To void bugs, we can't throw bombs if there is alrdy one
        if(noBomb)
        {
            noBomb = false;
            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);
        noBomb = true;
        //-------------------------------------------
        //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 -------------------------------------------------------