Code viewer for World: Two Player Tron
//submission for ca318


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

const  SCREENSHOT_STEP = 5;



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

const gridsize = 40;						// number of squares along side of world	   

const NOBOXES =  0;
		// density of maze - number of internal boxes
		// (bug) use trunc or can get a non-integer 

const squaresize = 2;					// size of square in pixels
const MAXPOS = gridsize * squaresize;		// length of one side in pixels 
	
const SKYCOLOR 	= 0xddffdd;				// a number, not a string 
const BLANKCOLOR 	= SKYCOLOR ;			// make objects this color until texture arrives (from asynchronous file read)




const show3d = false;						// Switch between 3d and 2d view (both using Three.js) 
 
const startRadiusConst	 	= MAXPOS * 0.8 ;		// distance from centre to start the camera at
const skyboxConst			= MAXPOS * 3 ;		// where to put skybox 
const maxRadiusConst 		= MAXPOS * 10  ;		// 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;

// in initial view, (smaller-larger) on i axis is aligned with (left-right)
// in initial view, (smaller-larger) on j axis is aligned with (away from you - towards you)



// contents of a grid square

const GRID_BLANK 	= 0;
const GRID_WALL 	= 1;
const GRID_MAZE 	= 2;
 
 




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







//---- start of World class -------------------------------------------------------
 
function World() { 


// most of World can be private 
// regular "var" syntax means private variables:


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 handles to wall and maze objects so can find them later to paint them 
var MAZE 	= new Array ( NOBOXES );
var thewhite, theorange;
  

// orange and white position on squares, as well as their previous positions
var oi, oj, wi, wj, pwi, pwj, poi, poj;

var badsteps;
var goodsteps;
var  step;

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

 


// regular "function" syntax means private functions:


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
{
 if ( ( oi == i ) && ( oj == j ) ) return true;		// variable objects 
 if ( ( wi == i ) && ( wj == j ) ) return true;

 if ( GRID[i][j] == GRID_WALL ) return true;		// fixed objects	 
 if ( GRID[i][j] == GRID_MAZE ) return true;		 
	 
 return false;
}

 
// logically, coordinates are: y=0, x and z all positive (no negative)    
// logically my dimensions are all positive 0 to MAXPOS
// to centre everything on origin, subtract (MAXPOS/2) from all dimensions 

function translate ( x ) 
{
 return ( x - ( MAXPOS/2 ) );
}





//--- skybox ----------------------------------------------------------------------------------------------


function initSkybox() 
{

// x,y,z positive and negative faces have to be in certain order in the array 
 
// mountain skybox, credit:
// http://stemkoski.github.io/Three.js/Skybox.html

  var materialArray = [
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/smythc44/blackngreen.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/smythc44/blackngreen.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/smythc44/blackngreen.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/smythc44/floor.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/smythc44/blackngreen.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/smythc44/blackngreen.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 );						// We are inside a giant cube
}

function loadTextures()
{

 var loader1 = new THREE.TextureLoader();
 loader1.load ( '/uploads/starter/door.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintWalls ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
	} ); 

 var loader2 = new THREE.TextureLoader();
 loader2.load ( '/uploads/starter/latin.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintMaze ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 	} ); 

 var loader3 = new THREE.TextureLoader();
 loader3.load ( '/uploads/smythc44/tronwhite.jpg',	function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		thewhite.material =  new THREE.MeshBasicMaterial( { map: thetexture } );
	} ); 

 var loader4 = new THREE.TextureLoader();
 loader4.load ( '/uploads/smythc44/tronorange.png',	function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		theorange.material =  new THREE.MeshBasicMaterial( { map: thetexture } );
	} ); 

}


 


// --- add fixed objects ---------------------------------------- 
   
 
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, BOXHEIGHT, 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 paintWalls ( material )		 
{
 for ( var i = 0; i < WALLS.length; i++ )
 { 
   if ( WALLS[i] )  WALLS[i].material = material;
 }
}


//--- Add and draw beams following each player ----------------------
function initBeamsWhite(i, j)
{
    var shape    = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );			 
  	var thecube  = new THREE.Mesh( shape );
	thecube.material.color.setHex( 0xffffff  );			  

  	thecube.position.x = translate ( i * squaresize );   	
  	thecube.position.z = translate ( j * squaresize );   	
  	thecube.position.y =  0;	
 
 	threeworld.scene.add(thecube);
}
function initBeamsOrange(i, j)
{
    var shape    = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );			 
  	var thecube  = new THREE.Mesh( shape );
	thecube.material.color.setHex( 0xff6600  );			  

  	thecube.position.x = translate ( i * squaresize );   	
  	thecube.position.z = translate ( j * squaresize );   	
  	thecube.position.y =  0;	
 
 	threeworld.scene.add(thecube);
}

// --- orange functions -----------------------------------


function drawOrange()	// given oi, oj, draw it 
{
  var x = translate ( oi * squaresize );   	
  var z = translate ( oj * squaresize );   	
  var y =  0;	

 theorange.position.x = x;
 theorange.position.y = y;
 theorange.position.z = z;
 threeworld.scene.add(theorange);

 threeworld.lookat.copy ( theorange.position );		// if camera moving, look back at where the orange is  
}


function initLogicalOrange()
{
{
 oi = 4;
 oj = gridsize / 2;
 poi = oi - 1;
 poj = oj;
}
}


function initThreeOrange()
{
 var shape    = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );			 
 theorange = new THREE.Mesh( shape );
 theorange.material.color.setHex( BLANKCOLOR  );	
 drawOrange();		  
}


function moveLogicalOrange( a )
{ 
 var i = oi;
 var j = oj;		 

    if ( a == ACTION_LEFT ) 	
        i--;
    if ( a == ACTION_RIGHT )
        i++;
    if ( a == ACTION_UP ) 
        j++;
    if ( a == ACTION_DOWN )
        j--;
        if ( ! occupied(i,j) ) 
        {
            poi = oi;
            poj = oj;
            oi = i;
            oj = j;
    
            GRID[poi][poj] = GRID_MAZE;
            initBeamsOrange(poi, poj);
        }
}
//continues to move orange in the direction it was already moving
function keepMovingOrange()
{
    var a = ACTION_STAYSTILL;
    if(poi < oi) { a = ACTION_RIGHT; }
    if(poj < oj) { a = ACTION_UP; }
    if(oi < poi) { a = ACTION_LEFT; }
    if(oj < poj) { a = ACTION_DOWN; }
    moveLogicalOrange(a);
}



// --- white functions -----------------------------------


function drawWhite()	// given wi, wj, draw it 
{
  var x = translate ( wi * squaresize );   	
  var z = translate ( wj * squaresize );   	
  var y =  0;	

 thewhite.position.x = x;
 thewhite.position.y = y;
 thewhite.position.z = z;
 threeworld.scene.add(thewhite);

 threeworld.follow.copy ( thewhite.position );		// follow vector = white position (for camera following white)
}


function initLogicalWhite()
{
 wi = gridsize - 5;
 wj = gridsize / 2;
 pwi = wi+1;
 pwj = wj;
}

function initThreeWhite()
{
 var shape    = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );			 
 thewhite = new THREE.Mesh( shape );
 thewhite.material.color.setHex( BLANKCOLOR );	
 drawWhite(); 		  
}


function moveLogicalWhite( a )			// this is called by the infrastructure that gets action a from the Mind 
{ 
 var i = wi;
 var j = wj;		 
    if ( a == ACTION_LEFT ) 	
        i--;
    if ( a == ACTION_RIGHT )
        i++;
    if ( a == ACTION_UP ) 
        j++;
    if ( a == ACTION_DOWN )
        j--;
    if ( ! occupied(i,j) ) 
    {
        pwi = wi;
        pwj = wj;
        wi = i;
        wj = j;
    
        GRID[pwi][pwj] = GRID_MAZE;
        initBeamsWhite(pwi, pwj);
    }
}

//continues to move white in the direction it was already moving
function keepMovingWhite()
{
    var a = ACTION_STAYSTILL;
    if(pwi < wi) { a = ACTION_RIGHT; }
    if(pwj < wj) { a = ACTION_UP; }
    if(wi < pwi) { a = ACTION_LEFT; }
    if(wj < pwj) { a = ACTION_DOWN; }
    moveLogicalWhite(a);
}


function keyHandler(e)		
// user control 
// Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
{
    var actionWhite = ACTION_STAYSTILL;
    var actionOrange = ACTION_STAYSTILL;
    
    if (e.keyCode == 37) actionWhite = ACTION_LEFT 	;
    if (e.keyCode == 38) actionWhite = ACTION_DOWN  ;
    if (e.keyCode == 39) actionWhite = ACTION_RIGHT ;
    if (e.keyCode == 40) actionWhite = ACTION_UP	;
    
    moveLogicalWhite ( actionWhite );
    
    if (e.keyCode == 83) actionOrange = ACTION_UP	;
    if (e.keyCode == 65) actionOrange = ACTION_LEFT  ;
    if (e.keyCode == 87) actionOrange = ACTION_DOWN  ;
    if (e.keyCode == 68) actionOrange = ACTION_RIGHT ;
    
    moveLogicalOrange ( actionOrange );
}





// --- score: -----------------------------------


function badstep()			// is the orange within one square of the white
{
 if ( ( Math.abs(oi - wi) < 2 ) && ( Math.abs(oj - wj) < 2 ) ) return true;
 else return false;
}
//check if orange is about to crash
function crashOrange()
{
    var a = ACTION_STAYSTILL;
    if(poi < oi) { a = ACTION_RIGHT; }
    if(poj < oj) { a = ACTION_UP; }
    if(oi < poi) { a = ACTION_LEFT; }
    if(oj < poj) { a = ACTION_DOWN; }

    var i = oi;
    var j = oj;		 
    if ( a == ACTION_LEFT )         i--;
    if ( a == ACTION_RIGHT )        i++;
    if ( a == ACTION_UP )         j++;
    if ( a == ACTION_DOWN )        j--;

    if(occupied(i, j)) return true;
    else return false;
}
//check if white is about to crash
function crashWhite()
{
    var a = ACTION_STAYSTILL;
    if(pwi < wi) { a = ACTION_RIGHT; }
    if(pwj < wj) { a = ACTION_UP; }
    if(wi < pwi) { a = ACTION_LEFT; }
    if(wj < pwj) { a = ACTION_DOWN; }

    var i = wi;
    var j = wj;		 
    if ( a == ACTION_LEFT )         i--;
    if ( a == ACTION_RIGHT )        i++;
    if ( a == ACTION_UP )         j++;
    if ( a == ACTION_DOWN )        j--;

    if(occupied(i, j)) return true;
    else return false;
}

    this.endCondition;
    this.crashedOrange;
    this.crashedWhite;

//--- public functions / interface / API ----------------------------------------------------------

this.newRun = function() 
{

// (subtle bug) must reset variables like these inside newRun (in case do multiple runs)

  this.endCondition = false;
  this.crashedOrange = false;
  this.crashedWhite = false;
  badsteps = 0;	
  goodsteps = 0;
	step = 0;
var status = " Dont crash! <BR>"
 $("#user_span2").html(status);

var status = " --- Orange use wasd to play --- <BR>"; 
 $("#user_span4").html( status );
 
var status = " --- White use arrow keys to play --- <BR>"; 
 $("#user_span6").html( status );
 
 // for all runs:
 	initGrid();
	initLogicalWalls(); 
	initLogicalWhite();
	initLogicalOrange();

 // for graphical runs only:

  if ( true  )
  {
	if ( show3d )
	{
	 BOXHEIGHT = squaresize;
	 threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 	
	}	     
	else
	{
	 BOXHEIGHT = 1;
	 threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 		     
	}

	initSkybox();
 	initMusic();

	// Set up objects first:

	initThreeWalls(); 
	initThreeWhite();
	initThreeOrange();

	// Then paint them with textures - asynchronous load of textures from files. 
	// The texture file loads return at some unknown future time in some unknown order.
	// Because of the unknown order, it is probably best to make objects first and later paint them, rather than have the objects made when the file reads return.
	// It is safe to paint objects in random order, but might not be safe to create objects in random order. 

	loadTextures();	
   
	document.onkeydown =  keyHandler;
	
  }

};




this.getState = function()
{
 var x = [ wi, wj, oi, oj ];
  return ( x );  
}; 



this.takeAction = function ( a )
{
  step++;

  if ( true  )
   
    keepMovingOrange();              
    keepMovingWhite();

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

  if ( true  )
  {
   drawWhite();
   drawOrange();
  }
  //if orange crashed -> end game
  if(crashOrange())
  {
    soundAlarm();
    this.crashedOrange = true;
    this.endCondition = true;
    this.endRun();
  }
  //if white crashed -> end game
  if(crashWhite())
  {
    soundAlarm();
    this.crashedWhite = true;
    this.endCondition = true;
    this.endRun();
  }
};



this.endRun = function()
{
 if ( true  )
 {
  musicPause(); 
  
  if(this.crashedWhite)
    $("#user_span7").html( " &nbsp; <font color=red> <B> White crashed - Orange Wins </B> </font>   "  );
  else if(this.crashedOrange)
    $("#user_span7").html( " &nbsp; <font color=red> <B> Orange crashed - White wins. </B> </font>   "  );
  else
    $("#user_span7").html( " &nbsp; <font color=red> <B> It's a tie! </B> </font>   "  ); 
 }
 
};


this.getScore = function()
{
 return ( ( goodsteps / step ) * 100 );
};


}

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








// --- music and sound effects ----------------------------------------
// credits:
// don't sell this product
// No rights here :(



function initMusic()
{
	// put music element in one of the spans
  	var x = "<audio  id=theaudio  src=/uploads/smythc44/rockit.ogg  autoplay loop> </audio>" ;
  	$("#user_span1").html( x );
} 
 

function musicPlay()  
{
	// jQuery does not seem to parse pause() etc. so find the element the old way:
 	document.getElementById('theaudio').play();
}


function musicPause() 
{
 	document.getElementById('theaudio').pause();
}


function soundAlarm()
{
 	var x = "<audio    src=/uploads/smythc44/carcrash-soundeffect.mp3   autoplay  > </audio>";
  	$("#user_span2").html( x );
}