Code viewer for World: Minesweeper

//This world and its associated minds are my submission for the CA318 continuous assessment project.

//The world is the game Minesweeper - hopefully you have some familiarity with the basic rules.

// Minds:
// "No AI" provides no instructions from the mind, allowing the user to directly control the pointer
// "Stupid AI" moves and reveals tiles entirely randomly, and provides context for the more intelligent mind
// "Smarter AI" provides small improvements that can make a big dent on average score. 
// Firstly, the revealing of spaces is more spread out, which is better in Minesweeper -
// the stupid AI tends towards concentrated clusters of revealed tiles, which often means tripping a mine quicker.
// the main difference is that the smart AI takes advantage of ACTION_SCAN, which quickly deals with 0 blocks to break open large swathes of board.
// Hope you enjoy playing around with it!








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





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


//Welcome message! I wish I could have formatted this better, but however this system saves converts n (newline) into just an n character.
confirm("This game is based on the Windows classic Minesweeper. For the rules, check out the Wikipedia page. CONTROLS: UP/DOWN/LEFT/RIGHT: Move pointer. SPACEBAR: Uncover tile. S: Uncover large spaces quickly. Use this whenever you come across a blank space, basically. I hope you enjoy!")

//I didn't use this, but I think it might turn up elsewhere, so I've left it in to avoid stupid errors.
const NOBOXES =  Math.trunc ( (gridsize * gridsize) / 10 );
 
 
// Getting to choose the size of your board was always an important part fo the game for me
// so I wanted to make it nice and dynamic for the user without having to fiddle with code
//It's worth noting that the game crashes if you try to exceed 365 gridsize. I figured that was a reasonable size and didn't try to fix it.
var gridsize = Number(prompt("How big should the grid be?", "9")) + 2;
var numboxes = prompt("How many mines?", "10" );



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




const show3d = true;						// 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;
const ACTION_REVEAL         = 5; // uncovers a tile
const ACTION_FLAG           = 6; // places a flag on a space to help you remember it has a mine on it. Unused, as I couldn't get it working
const ACTION_SCAN           = 7; // handles 0 blocks quickly.

// 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_ZERO_BLOCKS = 0;
const GRID_ONE_BLOCKS = 1;
const GRID_TWO_BLOCKS = 2;
const GRID_THREE_BLOCKS = 3;
const GRID_FOUR_BLOCKS = 4;
const GRID_FIVE_BLOCKS = 5;
const GRID_SIX_BLOCKS = 6;
const GRID_SEVEN_BLOCKS = 7;
const GRID_EIGHT_BLOCKS = 8;
const GRID_NINE_BLOCKS = 9;


const GRID_BLANK 	= 10;
const GRID_WALL 	= 11;
const GRID_MINES 	= 12;

 
 




 
// --- 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 COVER = new Array(gridsize);            // this is going to be a 2D array too. It's for the blocks that go on top of the numbers and mines

var WALLS 	= new Array ( 4 * gridsize );		// need to keep handles to wall and maze objects so can find them later to paint them 
var MINES 	= new Array (numboxes);
var explosion = false;                          //bool to check whether you've revealed a mine - triggers endgame.


//keep tabs on all the special blocks
var ZERO_BLOCKS = new Array(gridsize*gridsize);  
var ONE_BLOCKS = new Array(gridsize*gridsize);
var TWO_BLOCKS = new Array(gridsize*gridsize);
var THREE_BLOCKS = new Array(gridsize*gridsize);
var FOUR_BLOCKS = new Array(gridsize*gridsize);
var FIVE_BLOCKS = new Array(gridsize*gridsize);
var SIX_BLOCKS = new Array(gridsize*gridsize);
var SEVEN_BLOCKS = new Array(gridsize*gridsize);
var EIGHT_BLOCKS = new Array(gridsize*gridsize);

var theagent;
  

// agent position on squares
var ai, aj;


var score = 0;  // this keeps track of how many tiles you've uncovered without revealing a mine.
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 )		// this has basically become "are you a wall?", but I didn't change the name because I didn't want to go searching through the code for all the times I'd have to change it
{
 if ( ( ai == i ) && ( aj == j ) ) return true;

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

 return false;
}

function isWall ( i, j )		// is this square a wall?
{
 if ( GRID[i][j] == GRID_WALL ) return true;		// fixed objects	 
 return false;
}

function isMine ( i, j )		// is this square a mine?
{
 if ( GRID[i][j] == GRID_MINES ) return true;		// fixed objects	 
 return false;
}
 
 
function mineSurround( i, j) // checks how many mines are in a space's surroundings.
{
    var nummines = 0;
    
    //top row
    if(isMine(i+1, j+1))
        nummines = nummines + 1;
    if(isMine(i+1, j))
        nummines = nummines + 1;
    if(isMine(i+1, j-1))
        nummines = nummines + 1;
        
    //middle row
    if(isMine(i, j+1))
        nummines = nummines + 1;
    if(isMine(i, j-1))
        nummines = nummines + 1;
        
    //bottom row
    if(isMine(i-1, j+1))
        nummines = nummines + 1;
    if(isMine(i-1, j))
        nummines = nummines + 1;
    if(isMine(i-1, j-1))
        nummines = nummines + 1;
        
    return nummines;
}
 
 
 //remove a cover block to reveal what's underneath.
 function revealCover(i, j)
{
    if(COVER[i][j].position.y == (0-(squaresize)) && GRID[i][j] !== 11)
    {
        COVER[i][j].position.y = (0-(3*squaresize));
        if(GRID[i][j] == GRID_MINES)
        {
            revealBombs()
            explosion = true;
        }
        else
            score++;
    }
}

//just something for fail-state flair
function revealBombs()
{
    for(var i = 0; i < gridsize; i++)
        for(var j = 0; j < gridsize; j++)
        {
            if(GRID[i][j] == GRID_MINES)
                COVER[i][j].position.y = (0-(3*squaresize));
        }

}


// couldn't get this to work, in the end.
// I've just commented it out instead of removing the code completely
function addFlag(i, j, material)  
{/*
    var thecube = COVER[i][j]
    thecube.material = material;*/
}
 
//handles 0 blocks
function scanBoard()
{
    for(var count = 0; count < 10; count++)
    {
        for(var i = 0; i < gridsize; i++)
            for(var j = 0; j < gridsize; j++)
            {
                if(GRID[i][j] == 0 && COVER[i][j].position.y == (0-3*squaresize))
                    revealSurround(i, j);
            }
    }
}

//reveals all tiles surrounding a given space. 
function revealSurround(i, j)
{
    revealCover(i+1, j+1);
    revealCover(i+1, j);
    revealCover(i+1, j-1);
        
    //middle row
    revealCover(i, j+1);
    revealCover(i, j-1);
        
    //bottom row
    revealCover(i-1, j+1);
    revealCover(i-1, j);
    revealCover(i-1, j-1);
        

}
 

 
// 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/starter/posx.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negx.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posy.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negy.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posz.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negz.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
}
*/

// This does the file read the old way using loadTexture.
// (todo) Change to asynchronous TextureLoader. A bit complex:
// Make blank skybox. Start 6 asynch file loads to call 6 return functions.
// Each return function checks if all 6 loaded yet. Once all 6 loaded, paint the skybox.		 


  



// --- alternative skyboxes: ------------------------------

// space skybox, credit:
// http://en.spaceengine.org/forum/21-514-1
// x,y,z labelled differently


// I liked this skybox best!


   var materialArray = [
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_z.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_z.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_y.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_y.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_pos_x.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/sky_neg_x.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
}


/*
// urban photographic skyboxes, credit:
// http://opengameart.org/content/urban-skyboxes

   var materialArray = [
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posx.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negx.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posy.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negy.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/posz.jpg" ), side: THREE.BackSide } ) ),
 	( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/negz.jpg" ), side: THREE.BackSide } ) ),
 	];

*/






// --- asynchronous load textures from file ----------------------------------------
// credits:
// http://commons.wikimedia.org/wiki/File:Old_door_handles.jpg?uselang=en-gb
// https://commons.wikimedia.org/wiki/Category:Pac-Man_icons
// https://commons.wikimedia.org/wiki/Category:Skull_and_crossbone_icons
// http://en.wikipedia.org/wiki/File:Inscription_displaying_apices_(from_the_shrine_of_the_Augustales_at_Herculaneum).jpg
  
// loader return can call private function

// Lots of new textures here. Everything is either made by me or found through google Image Search, marked for re-use with modification.
// It's probably not an issue, but better safe than sorry.
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/rimple/explosion-153710_960_720.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintMines ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 	} ); 

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

 var loader5 = new THREE.TextureLoader();
 loader5.load ( '/uploads/rimple/grey2.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintZeroBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })
 
 var loader6 = new THREE.TextureLoader();
 loader6.load ( '/uploads/rimple/grey_1block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintOneBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })
 
  var loader7 = new THREE.TextureLoader();
 loader7.load ( '/uploads/rimple/grey_2block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintTwoBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })


 var loader8 = new THREE.TextureLoader();
 loader8.load ( '/uploads/rimple/grey_3block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintThreeBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })


 var loader9 = new THREE.TextureLoader();
 loader9.load ( '/uploads/rimple/grey_4block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintFourBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })


 var loader10 = new THREE.TextureLoader();
 loader10.load ( '/uploads/rimple/grey_5block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintFiveBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })


 var loader11 = new THREE.TextureLoader();
 loader11.load ( '/uploads/rimple/grey_6block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintSixBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })

 var loader12 = new THREE.TextureLoader();
 loader12.load ( '/uploads/rimple/grey_7block-copy.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintSevenBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })

 var loader13 = new THREE.TextureLoader();
 loader13.load ( '/uploads/rimple/grey_8block.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintEightBlocks ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })

 var loader14 = new THREE.TextureLoader();
 loader14.load ( '/uploads/rimple/blue-pattern.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        paintCover ( new THREE.MeshBasicMaterial( { map: thetexture } ) );
 })

 var loader14 = new THREE.TextureLoader();
 loader14.load ( '/uploads/rimple/red_flag.jpg', function (thetexture) {
        thetexture.minFilter = THREE.LinearFilter;
        addFlag ( ai, aj, 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;
 }
}





function initLogicalMines()		 // set up the locations of the mines. Based on the maze creation method, with slight modifications
{
 for ( var c=1 ; c <= numboxes ; c++ )
 {
  	var i = randomintAtoB(1,gridsize-2);	// inner squares are 1 to gridsize-2
  	var j = randomintAtoB(1,gridsize-2);
  	
  	while(isMine(i, j))
  	{
  	    var i = randomintAtoB(1,gridsize-2);	// inner squares are 1 to gridsize-2
  	    var j = randomintAtoB(1,gridsize-2);
  	}
    	GRID[i][j] = GRID_MINES ;		 
 }
}


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


function paintMines ( material )		 
{
 for ( var i = 0; i < MINES.length; i++ )
 { 
   if ( MINES[i] )  MINES[i].material = material;
 }
}




// Adds in all the numbered blocks! I thought this would be much more complicated than it turned out to be
function initLogicalNumberBlocks()		 
{
    for(var i = 0; i < gridsize; i++)
    {
        for(var j = 0; j < gridsize; j++)
        {
            if(GRID[i][j] == 10) 
                GRID[i][j] = mineSurround(i,j);
        }
    }
}
//----------------------------------------------------------------------------------------------------------------
//initialise all the numbered blocks to fill the grid, plus the cover blocks
//----------------------------------------------------------------------------------------------------------------

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

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

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

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

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

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

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

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

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

function initThreeCover()
{
    var t = 0;
    
    for(var i = 0; i < gridsize; i++)
    {
        COVER[i] = new Array(gridsize);		// each element is an array 
        for(var j = 0; j < gridsize; j++)
        {
            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 );   	
  	        thecube.position.z = translate ( j * squaresize );   	
  	        thecube.position.y = 0-(squaresize);	
 
 	        threeworld.scene.add(thecube);
 	        COVER[i][j] = thecube;		// save it for later
	        t++; 
        }
    }
}

//----------------------------------------------------------------------------------------------------------------
//  Paint all of the numbered and cover blocks
//----------------------------------------------------------------------------------------------------------------

function paintZeroBlocks ( material )		 
{
 for ( var i = 0; i < ZERO_BLOCKS.length; i++ )
 { 
   if ( ZERO_BLOCKS[i] )  ZERO_BLOCKS[i].material = material;
 }
}
function paintOneBlocks ( material )		 
{
 for ( var i = 0; i < ONE_BLOCKS.length; i++ )
 { 
   if ( ONE_BLOCKS[i] )  ONE_BLOCKS[i].material = material;
 }
}
function paintTwoBlocks ( material )		 
{
 for ( var i = 0; i < TWO_BLOCKS.length; i++ )
 { 
   if ( TWO_BLOCKS[i] )  TWO_BLOCKS[i].material = material;
 }
}
function paintThreeBlocks ( material )		 
{
 for ( var i = 0; i < THREE_BLOCKS.length; i++ )
 { 
   if ( THREE_BLOCKS[i] )  THREE_BLOCKS[i].material = material;
 }
}
function paintFourBlocks ( material )		 
{
 for ( var i = 0; i < FOUR_BLOCKS.length; i++ )
 { 
   if ( FOUR_BLOCKS[i] )  FOUR_BLOCKS[i].material = material;
 }
}
function paintFiveBlocks ( material )		 
{
 for ( var i = 0; i < FIVE_BLOCKS.length; i++ )
 { 
   if ( FIVE_BLOCKS[i] )  FIVE_BLOCKS[i].material = material;
 }
}
function paintSixBlocks ( material )		 
{
 for ( var i = 0; i < SIX_BLOCKS.length; i++ )
 { 
   if ( SIX_BLOCKS[i] )  SIX_BLOCKS[i].material = material;
 }
}
function paintSevenBlocks ( material )		 
{
 for ( var i = 0; i < SEVEN_BLOCKS.length; i++ )
 { 
   if ( SEVEN_BLOCKS[i] )  SEVEN_BLOCKS[i].material = material;
 }
}
function paintEightBlocks ( material )		 
{
 for ( var i = 0; i < EIGHT_BLOCKS.length; i++ )
 { 
   if ( EIGHT_BLOCKS[i] )  EIGHT_BLOCKS[i].material = material;
 }
}

function paintCover ( material )		 
{
 for ( var i = 0; i < COVER.length; i++ )
 { 
     for(var j = 0; j < COVER.length; j++)
     {
        if ( COVER[i][j] )  COVER[i][j].material = material;
     }
 }
}



//--------------------------------------------------------------------------------------------------------


// --- enemy removed, not necessary -----------------------------------




// --- agent functions -----------------------------------


function drawAgent()	// given ai, aj, draw it 
{
  var x = translate ( ai * squaresize );   	
  var z = translate ( aj * squaresize );   	
  var y = 0;	

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

 threeworld.follow.copy ( theagent.position );		// follow vector = agent position (for camera following agent)
}


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

 ai = i;
 aj = j;
}

function initThreeAgent()
{
 var shape    = new THREE.SphereGeometry(12, 200, 200, 0, Math.PI * 2, 0, Math.PI * 2);	//making it a small, brightly coloured circle allows the player to see the revealed tile immediately.		 
 theagent = new THREE.Mesh( shape );
 theagent.material.color.setHex( BLANKCOLOR );	
 drawAgent(); 		  
}


function moveLogicalAgent( a )			// this is called by the infrastructure that gets action a from the Mind 
{ 
 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--;
 else if ( a == ACTION_REVEAL)  revealCover(i, j); //removes a cover block
 else if ( a == ACTION_FLAG)    addFlag(i, j); // places a flag. unused.
 else if ( a == ACTION_SCAN)    scanBoard(); // deals with 0 blocks.
 if ( ! isWall(i,j) ) 
 {
  ai = i;
  aj = j;
 }
}



function keyHandler(e)		
// user control 
// Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
{
    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	);
    if (e.keyCode == 32)  moveLogicalAgent ( ACTION_REVEAL	); // spacebar
    if (e.keyCode == 70)  moveLogicalAgent ( ACTION_FLAG	); // F key (useless in final)
    if (e.keyCode == 83)  moveLogicalAgent ( ACTION_SCAN	); // S key
   
}





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

function agentBlocked()			// agent is blocked on all sides, run over
{
 return ( 	occupied (ai-1,aj) 		&& 
		occupied (ai+1,aj)		&&
		occupied (  ai,aj+1)		&&
		occupied (  ai,aj-1) 	);		
} 


function updateStatusBefore(a)
// this is called before anyone has moved on this step, agent has just proposed an action
// update status to show old state and proposed move 
{
 var x = self.getState();
 var status = " Step: <b> " + step + " </b> &nbsp; x = (" + x.toString() + ") &nbsp; a = (" + a + ") "; 

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


function   updateStatusAfter()		// agent and enemy have moved, can calculate score
{
 // new state after both have moved
 var y = self.getState();
 var status = " &nbsp; y = (" + y.toString() + ") <BR> "; 
 $("#user_span4").html( status );

 var score = self.getScore();

 var status = "   Safe tiles revealed: " + score; 

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


function getLogicalGrid()
{
    var gridstring = "";
    
    for(var i = 0; i < gridsize; i++)
    {
        for(var j = 0; j < gridsize; j++)
        {
            gridstring = gridstring + "[" + GRID[i][j]+ "]";
        }
        gridstring = gridstring + "n";
    }
    return gridstring
}



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


	this.endCondition;			// If set to true, run will end. 
    var firstClear;             // Avoids spamming the user with "You win!" messages"


this.newRun = function() 
{

// (subtle bug) must reset variables like these inside newRun (in case do multiple runs)
    firstClear = true;
  this.endCondition = false;
	step = 0;


 // for all runs:

 	initGrid();
	initLogicalWalls(); 
	initLogicalMines();
	initLogicalNumberBlocks();
	initLogicalAgent();

    //confirm("Configuration: n" + getLogicalGrid());   <---good for debugging, but looks like garbage. Again, the newline just changes to an n
    
 // for graphical runs only:


// all the same, with the addition of extra blocks to the init statements
  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(); 
	initThreeMines();
	
	initThreeZeroBlocks();
	initThreeOneBlocks();
	initThreeTwoBlocks();
	initThreeThreeBlocks();
	initThreeFourBlocks();
	initThreeFiveBlocks();
	initThreeSixBlocks();
	initThreeSevenBlocks();
	initThreeEightBlocks();
	initThreeCover();
	
	initThreeAgent();

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

};



//removed enemy details, since the enemy doesn't exist any more
this.getState = function()
{
 var x = [ ai, aj];
  return ( x );  
};



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

  if ( true  )
   updateStatusBefore(a);			// show status line before moves 

  moveLogicalAgent(a);

  if ( true  )
  {
   drawAgent();
   updateStatusAfter();			// show status line after moves  
  }


  if ( explosion )			// if you've revealed a mine, game over.
  {
	this.endCondition = true;
  	if ( true  )
  	{
	 musicPause();
	 soundAlarm();
	}
  }
    // Tried to make this a hard game-ender, but couldn't make it work. 
    // In the end, I think I like the little pop-up message better.
    if(score == safetiles && firstClear) 
    {
        confirm("Congratulations, you've cleared the board! Restart to play again.");
        firstClear = false;
    }

};



this.endRun = function()
{
 if ( true  )
 {
  musicPause(); 
  if ( this.endCondition )
    $("#user_span6").html( " &nbsp; <font color=red> <B> You have died on the fields of a senseless war. Your loved ones will be notified if they can identify the body. </B> </font>   "  ); //A bit of flavour never hurt
  else
    $("#user_span6").html( " &nbsp; <font color=green> <B> Run over. </B> </font>   "  );
 }
};


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


}

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








// --- music and sound effects ----------------------------------------
// credits:
// http://www.dl-sounds.com/royalty-free/defense-line/
// http://soundbible.com/1542-Air-Horn.html 

//I basically took out all the music, as it was driving me nuts.

function initMusic()
{/*
	// put music element in one of the spans
  	var x = "<audio  id=theaudio  src=/uploads/starter/Defense.Line.mp3   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();
*/}

//alarm changed to an explosion to be thematically fitting.
function soundAlarm()
{
 	var x = "<audio    src=/uploads/rimple/explosion.mp3   autoplay  > </audio>";  //source: https://www.youtube.com/watch?v=XjOtGgxaX5g
  	$("#user_span2").html( x );
}