Code viewer for World: Drone Tag
/*
This is a simulation has been adapted from the original to encorporate 3d environment. 
Two drones have been added, one is the chaser and the other is being chased. 
They avoid blocks, randomly generated in the 3d environment. 
The drone skins were custom painted, as the original model had a tga file greater tham the 2MB file upload limit. 
In addition, the cube surrounding the play area has transparent blocks, with green "neon" bars. 
The second drone will go for the goal (in most cases->unpatched issue), while the first will catch it.
*/

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

//---- global constants: -------------------------------------------------------
const gridsize      = 30;       // 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) / 10 );

const SKYCOLOR      = 0x003366;       // 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 * 2 ;    // 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_BACKWARDS  = 2;
const ACTION_FORWARDS   = 3;
const ACTION_UP         = 4;     
const ACTION_DOWN       = 5;
const ACTION_STAYSTILL  = 6;

//contents of a grid square
const GRID_BLANK  = 0;
const GRID_WALL   = 1;
const GRID_MAZE   = 2;
const GRID_GOAL   = 3;

// --- 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() { 

var BOXHEIGHT;    // 3d or 2d box height 
var GRID    = new Array(gridsize);      // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array   
var WALLS   = new Array ( 4 * gridsize );   // need to keep handle to each wall block object so can find it later to paint it 
var MAZE    = new Array ( NOBOXES );
var GOAL, droneOne, droneTwo;     

var droneTwoRotation = 0;
var droneOneRotation = 0;      // with 3D models, current rotation away from default orientation

//drone positions on the grid
var ei, ej, ek, ai, aj, ak;

//End Goal
var gi = randomintAtoB(2,gridsize-3);  
var gj = randomintAtoB(2,gridsize-3);
var gk = randomintAtoB(2,gridsize-3);

var badsteps;
var goodsteps;
var step;

var self = this;            // needed for private fn to call public fn - see below  

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] = new Array(gridsize);
    
    for (var k = 0; k < gridsize ; k++) 
    {
        GRID[i][j][k] = GRID_BLANK ;
    }
  }
 }
}

function occupied ( i, j, k )    // is this square occupied
{

 if ( GRID[i][j][k] == GRID_WALL ) return true;    // fixed objects   
 if ( GRID[i][j][k] == GRID_MAZE ) return true;     
   
 return false;
}
/*
function reachedGoal(i, j, k) {
    if ( (i==gi) && (j==gj) && (k==gk) ) {
        return true;    
    }
    return false;
}
*/ 
function translate ( x ) 
{
 return ( x - ( MAXPOS/2 ) );
}

//Load required textures
function loadTextures()
{
  var manager = new THREE.LoadingManager();   
  var loader = new THREE.OBJLoader( manager );
      
    loader.load( "/uploads/nikolif2/drone.obj", droneOneInit );
    loader.load( "/uploads/nikolif2/drone.obj", droneTwoInit );

    var loader1 = new THREE.TextureLoader();
    loader1.load ( '/uploads/nikolif2/wall.png',    function ( thetexture ) {      
        thetexture.minFilter = THREE.LinearFilter;
        paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture, transparent: true } ) );
    }); 

    var loader2 = new THREE.TextureLoader();
    loader2.load ( '/uploads/nikolif2/block.jpg',    function ( thetexture ) {      
        thetexture.minFilter = THREE.LinearFilter;
        paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
    }); 
    
    var loader3 = new THREE.TextureLoader();
    loader3.load ( '/uploads/nikolif2/goal.gif',    function ( thetexture ) {      
        thetexture.minFilter = THREE.LinearFilter;
        paintGoal ( new THREE.MeshBasicMaterial( { map: thetexture, transparent: true } ) );
    }); 
    
}

//-------Create Drones--------------
function droneOneInit ( object ) 
{ 
  object.scale.multiplyScalar ( 100 );        
  object.traverse(paintDroneOne);
  droneOne = object;
  threeworld.scene.add( droneOne ); 
}

function droneTwoInit ( object )
{
  object.scale.multiplyScalar ( 100 );   
  object.traverse(paintDroneTwo);
  droneTwo = object;
  threeworld.scene.add( droneTwo ); 
}

//-------Paint Drones--------------
function paintDroneOne (child) 
{
  if ( child instanceof THREE.Mesh ) 
  {
       child.material.map = THREE.ImageUtils.loadTexture( "/uploads/nikolif2/drone1.png" );
  }
}

function paintDroneTwo (child) 
{
  if ( child instanceof THREE.Mesh ) 
  {
       child.material.map = THREE.ImageUtils.loadTexture( "/uploads/nikolif2/drone2.png" );
  }
}

//--------------Skybox----------------
function initSkybox() 
{

  var materialArray = [
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/nikolif2/y2.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/nikolif2/y1.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/nikolif2/z1.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/nikolif2/z2.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/nikolif2/x2.jpg" ), side: THREE.BackSide } ) ),
  ( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/nikolif2/x1.jpg" ), 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 );
}

//---Init Walls------
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++) 
   for (var k = 0; k < gridsize ; k++) 
       if ( ( i===0 ) || ( i==gridsize-1 ) || ( j===0 ) || ( j==gridsize-1 ) || ( k===0 ) || ( k==gridsize-1 ))
       {
          GRID[i][j][k] = 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++) 
    for (var k = 0; k < gridsize ; k++) 
       if ( GRID[i][j][k] == GRID_WALL )
       {
       var shape    = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );      
       var thecube  = new THREE.Mesh( shape );
       thecube.material.color.setHex( BLANKCOLOR  );        
           // translate my simple (i,j,k) block-numbering coordinates to three.js (x,y,z) coordinates 
           thecube.position.x = translate ( i * squaresize );       
           thecube.position.z = translate ( j * squaresize );     
           thecube.position.y = translate ( k * squaresize ); 
     
       threeworld.scene.add(thecube);
       WALLS[t] = thecube;        // save it for later
       t++; 
       }
}   


//--------Paint Walls-----------------
function paintWalls ( material )     
{
 for ( var i = 0; i < WALLS.length; i++ )
 { 
   if ( WALLS[i] )  WALLS[i].material = material;
 }
}

//-------Init the 3d maze-------------
function initLogicalMaze()     
{
 for ( var c=1 ; c <= NOBOXES ; c++ )
 {
    var i = randomintAtoB(2,gridsize-3);  // inner squares are 1 to gridsize-2
    var j = randomintAtoB(2,gridsize-3);
    var k = randomintAtoB(2,gridsize-3);
    GRID[i][j][k] = GRID_MAZE ;     
 }
}

function initThreeMaze()        
{
 var t = 0;
 for (var i = 0; i < gridsize ; i++) 
  for (var j = 0; j < gridsize ; j++) 
      for (var k = 0; k < gridsize ; k++) 
       if ( GRID[i][j][k] == GRID_MAZE )
       {
            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 );    
            thecube.position.z = translate ( j * squaresize );    
            thecube.position.y = translate ( k * squaresize );  
         
            threeworld.scene.add(thecube);
            MAZE[t] = thecube;    // save it for later
            t++; 
      }
}

//-----Paint the maze-------
function paintMaze ( material )    
{
 for ( var i = 0; i < MAZE.length; i++ )
 { 
   if ( MAZE[i] )  MAZE[i].material = material;
 }
}

//Create the goal
function goalInit()        
{
    GRID[gi][gj][gk] = GRID_GOAL ; 
    
    var shape    = new THREE.BoxGeometry( squaresize, squaresize, squaresize );      
    var thecube  = new THREE.Mesh( shape );
    
    thecube.material.color.setHex( BLANKCOLOR  );       
    
    thecube.position.x = translate ( gi * squaresize );    
    thecube.position.z = translate ( gj * squaresize );    
    thecube.position.y = translate ( gk * squaresize );  
         
    threeworld.scene.add(thecube);
    
    GOAL = thecube;
}
function paintGoal ( material )    
{
  GOAL.material = material;
}

//----------------------DRONE1-----------------------------
function drawDroneOne() 
{
  var x = translate ( ei * squaresize );    
  var y = translate ( ej * squaresize );    
  var z = translate ( ek * squaresize );    

 droneOne.position.x = x;
 droneOne.position.y = y;
 droneOne.position.z = z;
 
 threeworld.lookat.copy ( droneOne.position );    
 threeworld.lookat.y = ( squaresize * 1.5 );  // point camera higher up

}

function initDroneOne()
{
// start in random location:
 var i, j, k;
 do
 {
  i = randomintAtoB(1,gridsize-2);
  j = randomintAtoB(1,gridsize-2);
  k = randomintAtoB(1,gridsize-2);
 }
 while ( occupied(i,j,k) );     // search for empty square 

 ei = i;
 ej = j;
 ek = k;
}

//Pick an action to be executed 
function droneOneGetAction()
{
    var move = ACTION_STAYSTILL;
    
    if((ai < ei) && (!occupied(ei+1,ej,ek))) {
        move=ACTION_LEFT; 
    }
    if((ai > ei) && (!occupied(ei-1,ej,ek))) {
        move=ACTION_RIGHT; 
    }
    if((aj > ej) && (!occupied(ei,ej+1,ek))) {
        move=ACTION_FORWARDS; 
    }
    if((aj < ej) && (!occupied(ei,ej-1,ek))) {
        move=ACTION_BACKWARDS; 
    }
    if((ak > ek) && (!occupied(ei,ej,ek+1))) {
        move=ACTION_UP; 
    }
    if((ak < ek) && (!occupied(ei,ej,ek-1))) {
        move=ACTION_DOWN; 
    }
    return move;
}

//Move the first drone
function moveDroneOne()
{ 
 var a = droneOneGetAction();
 //console.log("i" + ei + " k" + ek);
 var i = ei;
 var j = ej;    
 var k = ek;  

      if ( a == ACTION_LEFT )       { i--; }//decrease y-axis value
 else if ( a == ACTION_RIGHT )      { i++; }//increase x-axis value
 else if ( a == ACTION_FORWARDS )   { j++; }//increase y-axis value
 else if ( a == ACTION_BACKWARDS )  { j--; }//decrease y-axis value
 else if ( a == ACTION_UP )         { k++; }//increase z-axis value
 else if ( a == ACTION_DOWN )       { k--; }//decrease z-axis value

 if ( ! occupied(i,j,k) ) 
 {
  if ( true  )
  {
        if ( a == ACTION_LEFT )     {  rotateDroneOneTowards ( 3 * (Math.PI / 2) ); }
   else if ( a == ACTION_RIGHT )    {  rotateDroneOneTowards ( 1 * (Math.PI / 2) ); }
   else if ( a == ACTION_FORWARDS ) {  rotateDroneOneTowards ( 0 * (Math.PI / 2) ); }
   else if ( a == ACTION_BACKWARDS ){  rotateDroneOneTowards ( 2 * (Math.PI / 2) ); }
   else if ( a == ACTION_UP )       {  rotateDroneOneTowards ( 4 * (Math.PI / 2) ); }
   else if ( a == ACTION_DOWN )     {  rotateDroneOneTowards ( 5 * (Math.PI / 2) ); }
  }
  ei = i;
  ej = j;
  ek = k;
 }
}

function rotateDroneOneTowards ( newRotation )   
{
 if ( droneOneRotation == newRotation ) return;
 // else 
 var x = ( droneOneRotation + newRotation ) / 2; 
 droneOne.rotation.set ( 0, x, 0 );
 droneOneRotation = x; 
}

function drawDroneTwo()  // given ai, aj, draw it 
{
    if ( droneTwo )
    {
      var x = translate ( ai * squaresize );    
      var y = translate ( aj * squaresize );    
      var z = translate ( ak * squaresize );    
    
     droneTwo.position.x = x;
     droneTwo.position.y = y;
     droneTwo.position.z = z;
     
     threeworld.follow.copy ( droneTwo.position );    // follow vector = agent position (for camera following agent)
     threeworld.follow.y = ( squaresize * 1.5 );     // put camera higher up
    }
}

function initDroneTwo()
{
// start in random location:
 var i, j, k;
 do
 {
  i = randomintAtoB(1,gridsize-2);
  j = randomintAtoB(1,gridsize-2);
  k = randomintAtoB(1,gridsize-2);
 }
 while ( occupied(i,j,k) );     // search for empty square 
 ai = i;
 aj = j;
 ak = k;
}

//Pick an action to be executed 
function droneTwoGetAction()
{
    var move; 
    
    if((ai > gi) && (!occupied(ai-1,aj,ak))) {
        move=ACTION_LEFT; 
    }
    if((ai < gi) && (!occupied(ai+1,aj,ak))) {
        move=ACTION_RIGHT; 
    }
    if((aj < gj) && (!occupied(ai,aj+1,ak))) {
        move=ACTION_FORWARDS; 
    }
    if((aj > gj) && (!occupied(ai,aj-1,ak))) {
        move=ACTION_BACKWARDS; 
    }
    if((ak < gk) && (!occupied(ai,aj,ak+1))) {
        move=ACTION_UP; 
    }
    if((ak > gk) && (!occupied(ai,aj,ak-1))) {
        move=ACTION_DOWN; 
    }
    console.log(move);
    return move;
}

function moveDroneTwo()      // this is called by the infrastructure that gets action a from the Mind 
{ 
 var a = droneTwoGetAction();
 var i = ai;
 var j = aj;   
 var k = ak; 
 
      if ( a == ACTION_LEFT )       { i--;    }//decrease x-axis value
 else if ( a == ACTION_RIGHT )      { i++;    }//increase x-axis value
 else if ( a == ACTION_FORWARDS )   { j++;    }//increase y-axis value
 else if ( a == ACTION_BACKWARDS )  { j--;    }//decrease y-axis value
 else if ( a == ACTION_UP )         { k++;    }//increase z-axis value
 else if ( a == ACTION_DOWN )       { k--;    }//decrease z-axis value

 if ( ! occupied(i,j,k) ) 
 {
  if ( true  )
  {
    /*  
    if(reachedGoal(i,j,k)) {
        this.endCondition == true; 
    }
    */
  // rotate by some amount of radians from the normal position 
  // in degrees: +0, +90, +180, +270

        if ( a == ACTION_LEFT )     {  rotateDroneTwoTowards ( 3 * (Math.PI / 2) ); }
   else if ( a == ACTION_RIGHT )    {  rotateDroneTwoTowards ( 1 * (Math.PI / 2) ); }
   else if ( a == ACTION_FORWARDS ) {  rotateDroneTwoTowards ( 0 * (Math.PI / 2) ); }
   else if ( a == ACTION_BACKWARDS ){  rotateDroneTwoTowards ( 2 * (Math.PI / 2) ); }
   else if ( a == ACTION_UP )       {  rotateDroneTwoTowards ( 4 * (Math.PI / 2) ); }
   else if ( a == ACTION_DOWN )     {  rotateDroneTwoTowards ( 5 * (Math.PI / 2) ); }
  }
  ai = i;
  aj = j;
  ak = k;
 }
}

function rotateDroneTwoTowards ( newRotation )   
{
 if ( droneTwoRotation == newRotation ) return;
 // else 
 var x = ( droneTwoRotation + newRotation ) / 2; 
 droneTwo.rotation.set ( 0, x, 0 );
 droneTwoRotation = x; 
}


// ------------------- score: -----------------------------------
function badstep()      // is the enemy within one square of the agent
{
 if ( ( Math.abs(ei - ai) < 1 ) && ( Math.abs(ej - aj) < 1 ) && ( Math.abs(ek - ak) < 1 ) ) return true;
 else return false;
}
 
function   updateStatus()    
{
 var score = self.getScore();
 var status =  "   Step: " + step + " out of " + MAXSTEPS + ". Score: " + score; 

 $("#user_span1").html( status );
}

// If set to true, run will end.
this.endCondition;       

this.newRun = function() 
{
  this.endCondition = false;
  badsteps = 0;   
  goodsteps = 0;
  step = 0;

  // define logical data structure for the World, even if no graphical representation:
  initGrid();
  initLogicalWalls(); 
  initLogicalMaze();
  initDroneTwo();
  initDroneOne();

  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 "Royalty Free Music from Bensound"
    var x = "<audio  id=theaudio  src=/uploads/nikolif2/music.mp3   autoplay loop> </audio>" ;
    $("#user_span2").html( x );
    
    initSkybox();
    initThreeWalls();
    initThreeMaze();
    loadTextures();     // will return sometime later, but can go ahead and render now  
    goalInit();
  }   
};



this.getState = function()
{
 var x = [ ai, aj, ak, ei, ej, ek ];
  return ( x );  
};

this.takeAction = function ()
{
  step++;
  if((step%2)==0){
    moveDroneOne();   
  }
  moveDroneTwo();

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

  if ( true  )
  {
   drawDroneTwo();
   drawDroneOne();
   updateStatus();
  }

};

this.endRun = function()
{
};

this.getScore = function()
{
 return goodsteps;
};

}