Code viewer for World: TowersOfHanoi (Num 1,2,3 t...
/*
Current Bugs
Ai mind doesn't work. 
The code is based off a way to solve it I took from wikepedia "https://en.wikipedia.org/wiki/Tower_of_Hanoi"
Sometimes sound effect doesn't play. I couldn't find a way to delay the pause by a couple of seconds every time.

Problems

The first problem I ran into was being able to print the discs on the rod. I solved this by making each logical disc contain an object that represented it graphically. This made it easy to print by simply iterating through the array of logical discs. 
Since the X Co ordinates also comes from the array it makes it much easier to print as you only have to worry about y axis. 
I ran into the problem of having a good control scheme. Initially I used 1 2 3 to select a rod to take a disc from and 4,5, and 6 to put the disc onto a rod. But that was quite tricky to use(Tested it on friends). I changed this to the current scheme. Still 1, 2, and 3 to select a rod but then left and right to select the destination rod and enter to confirm. I also added in arrows to highlight the selected towers. 

I believe my world is a good recreation of the Tower of Hannoi problem. It is also in itself a difficult problem to solve. This means I didn't have to add any difficulty to it.
*/


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


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

const discs = 5;	   // Number Of discs
const numRods = 3;     // Three rods to hold discs

const MAXPOS = 160;		                // Length of one side used to centre camera.
	
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	 	= 350;		 // distance from centre to start the camera at
const skyboxConst			= 3000;		// where to put skybox 
const maxRadiusConst 		= 6000;		// maximum distance from camera we will render things  


 //Disc Object stores variables needed.
 function Disc(radius, position, object)
 {
	 this.radius = radius;
	 this.position = position;
	 this.object = object;
 }
 
//---- start of World class -------------------------------------------------------
 
function World() { 


// most of World can be private 

var rods = new Array (numRods); //Array of rods. Each rod contains an array.
var initialRod = -1;			//Initialised to -1 so it can be selected by user
var destinationRod = -1;		//Initialised to -1 so it can be selected by user
var moves = 0;					// Number of moves equivalent to score.
var rodObjects = new Array(numRods);	//Hold three js objects. Used to paint the rods.
var circleObjects = new Array(numRods);	//Hold three js objects. Used to paint the rods.
var legalMove = false;					//Boolean that indicates if a move was legal.		
var topArrow = new Array(2);			// Used to hold the top arrow.
var bottomArrow = new Array(2);			// Used to hold the bottom arrow.

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



//-----------Functions to set up logical world.---------------

function initLogicalRods()		//Set up array of discs. All discs are blank to start. They are given a position in array.
{
 for (var i = 0; i < numRods ; i++) 
 {
   rods[i] = new Array(discs);
   
   for(var j = 0; j < discs; j++ )
   {
	    var empty = new Disc(0,j,null);
        rods[i][j] = empty;
   }
 }
}

function initLogicaldiscs()		// Set up initial x discs. They all start on leftmost rod.	
{
  var i = 0;
  var radius = discs * 6;
  for (var j = 0; j < discs ; j++) 
  {
	var disc = new Disc(radius,j);
    rods[i][j] = disc;
    radius = radius - 6;	
  }
}


function initArrow()			//Sets up 2 arrows. 
{
	
	var shape = new THREE.BoxGeometry(5, 24, 3);
	var geometry = new THREE.ConeGeometry(6, 14, 3);
	var cone = new THREE.Mesh( geometry);
	var cube = new THREE.Mesh( shape );
	var cone2 = new THREE.Mesh( geometry);
	var cube2 = new THREE.Mesh( shape );
	cube.material.color.setHex( 0xff0000  );
	cone.material.color.setHex( 0xff0000  );
	
	cube2.material.color.setHex( 0xff0000  );
	cone2.material.color.setHex( 0xff0000  );
	
	cube.position.x = translate(0);   		
    cube.position.z = (0);   	
    cube.position.y = (140);	
	
	cone.position.x = translate(0);   		
    cone.position.z = (0);   	
    cone.position.y = (122);
	cone.rotateZ(3.14159); // Rotates the cone 90 degrees so it looks like an arrow.
	
	topArrow [0] = cube;   // Adds it to an array of objects.
	topArrow [1] = cone;
	   		
    cube2.position.z = (0);   	
    cube2.position.y = (-45);	
	  		
    cone2.position.z = (0);   	
    cone2.position.y = (-30);
	
	bottomArrow [0] = cube2;
	bottomArrow [1] = cone2;
	
	
}

 
// Used to centre co ordinates.
function translate ( x ) 
{
 return ( x - ( MAXPOS/2 ) );
}





//--- skybox ----------------------------------------------------------------------------------------------
// Couldn't find a better skybox but would like to use a different one.
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/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
}


// --- add fixed objects ---------------------------------------- 

//Loads in textures Discs and Rods.
function loadTextures()
{

 var loader1 = new THREE.TextureLoader();
 loader1.load ( '/uploads/andrewt/yellow_wood_texture.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintDiscsYellow( new THREE.MeshBasicMaterial( { map: thetexture } ) );
	} );
 var loader2 = new THREE.TextureLoader();
  loader1.load ( '/uploads/andrewt/plain_wood_texture.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintRods( new THREE.MeshBasicMaterial( { map: thetexture } ) );
	} );
	
 var loader3 = new THREE.TextureLoader();
 loader1.load ( '/uploads/andrewt/blue_wood_texture.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintDiscsBlue( new THREE.MeshBasicMaterial( { map: thetexture } ) );
	} );
var loader4 = new THREE.TextureLoader();
 loader1.load ( '/uploads/andrewt/red_wooden_texture.jpg',		function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		paintArrowsRed( new THREE.MeshBasicMaterial( { map: thetexture } ) );
	} );
}

//Paints all even discs blue
function paintDiscsBlue(material)
{
	for(var i = 0; i < numRods; i++)
    {
     for(var j = 0; j < discs; j++)
	 {
		
		if ( rods[i][j].object === null )
		{
			
		}
		else if(j%2 === 0 )
		{
			
			rods[i][j].object.material = material;
		}
		
	}
  }
}

//Paints all odd discs yellow.
function paintDiscsYellow(material)
{
	for(var i = 0; i < numRods; i++)
    {
     for(var j = 0; j < discs; j++)
	 {
		
		if ( rods[i][j].object === null )
		{
			
		}
		else if(j%2 !== 0 )
		{
			
			rods[i][j].object.material = material;
		}
		
	}
  }
}

//Paints the rods with a wooden texture.
function paintRods(material)
{
  for(var i = 0; i < numRods; i++)
  {
	rodObjects[i].material = material;
	circleObjects[i].material = material;
   
  }
}

//Paints arrows with a red wood texture
function paintArrowsRed(material)
{
  for(var i = 0; i < 2; i++)
  {
	bottomArrow[i].material = material;
	topArrow[i].material = material;
   
  }
}

//Creates the graphical rods and adds them to the world.
function initThreeRods()
{
  for(var i = 0; i < numRods; i++)
  {
    var shape = new THREE.CylinderGeometry(4, 4, 100, 64 );
    var shape2 = new THREE.CylinderGeometry(16,16,2,64);
	var circle = new THREE.Mesh( shape2 );
    var rod  = new THREE.Mesh( shape );
    
    rod.material.color.setHex( 0x000000  );
    circle.material.color.setHex( 0x000000 );
    
	circle.position.x = translate(i * 80);
    circle.position.z = (0);   	
    circle.position.y = (0);	
	
	rod.position.x = translate(i * 80);   		  		
    rod.position.z = (0);   	
    rod.position.y = (50);	
 
	circleObjects[i] = circle;
	rodObjects[i] = rod;
	
    threeworld.scene.add(rod);
	threeworld.scene.add(circle);
  }
}

//Creates the grahpical discs however doesn't add them to the world. Simply puts them in the correct place in an array.
function initThreeDiscs()
{
   var radius = 0;
   for (var i = 0; i < discs ; i++)
   {
	 var rod = rods [0][i];
	 radius = rod.radius;
     var shape = new THREE.CylinderGeometry( radius, radius, 10, 64);
     var thecircle  = new THREE.Mesh( shape );
     thecircle.material.color.setHex( 0xff0000  );			  
  
     thecircle.position.x = translate(0);   		
     thecircle.position.z = (0);   	
     thecircle.position.y = (6 + (15 * i));	
 
	 rods [0][i].object = thecircle;
   }

}

//Prints the top arrow at its correct location. Offset is used to calculat this.
function printTopArrow(offset)
{
	for(var i = 0; i < 2; i++)
	{
		topArrow[i].position.x = translate(offset * 80)
		threeworld.scene.add(topArrow[i]);
	}
}

//Prints the bottom arrow at its correct location. Offset is used to calculat this.
function printBottomArrow(offset)
{
	for(var i = 0; i < 2; i++)
	{
		bottomArrow[i].position.x = translate(offset * 80)
		threeworld.scene.add(bottomArrow[i]);
	}
}

//Prints out the three rod arrays. Uses the object to find where the discs are.
function printThreeRods()
{
  for(var i = 0; i < numRods; i++)
  {
    for(var j = 0; j < discs; j++)
	{
		
		if ( rods[i][j].object == null )
		{
			
		}
		else
		{
			rods[i][j].object.position.x = translate(i * 80);   		// translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates 
			rods[i][j].object.position.z = (0);   	
			rods[i][j].object.position.y = (6 + (15 * j));
			threeworld.scene.add(rods[i][j].object);
		}
		
	}
  }
}

//------------------------Functions to find and move Discs--------------------------------

//Finds the top disc on the specified rod.
function findTopRing(rodNumber)
{
	var i = discs-1;
	var currentRing = rods[rodNumber][i];
	var radius = currentRing.radius;
	while(radius === 0 && i > 0) //Checks if radius is = 0 and i > 0. If i is = 0 the rod is empty.
	{
		i--;
		currentRing = rods[rodNumber][i];
		radius = currentRing.radius;
	}
	currentRing = rods[rodNumber][i]; 
	return currentRing; // Returns a Disc object.
}

//Moves the top ring from one pole to another.
function moveRing(selectedRod, nextRod)
{
	var selectedRing = findTopRing(selectedRod);
	var nextRing = findTopRing(nextRod);
	var tempObject;
	var tempRadius;
	if(selectedRing.object === null)
	{
		
	}
	else if(selectedRing.radius < nextRing.radius || nextRing.radius === 0)
	{
		if(nextRing.radius === 0)
		{
			tempObject = rods[selectedRod][selectedRing.position].object;
			tempRadius = rods[selectedRod][selectedRing.position].radius;
			
			rods[nextRod][nextRing.position].object = tempObject;
			rods[nextRod][nextRing.position].radius = tempRadius;
			
			rods[selectedRod][selectedRing.position].object = null;
			rods[selectedRod][selectedRing.position].radius = 0;
		}
		else	
		{
			tempObject = rods[selectedRod][selectedRing.position].object;
			tempRadius = rods[selectedRod][selectedRing.position].radius;
			
			rods[nextRod][nextRing.position+1].object = tempObject;
			rods[nextRod][nextRing.position+1].radius = tempRadius;
			
			rods[selectedRod][selectedRing.position].object = null;
			rods[selectedRod][selectedRing.position].radius = 0;
		}
		moves++;
		
		legalMove = true;
		musicPlay();
	}
	
	else
	{
		legalMove = false;
	}
}



//Checks what key is entered. User enter 1 2 or 3 to select a rod. They then use left and right and enter to select a rod to move to.
function keyHandler(e)		
// user control 
{
	if (e.keyCode == 49)
	{
		initialRod = 0;
		printTopArrow(0);
	}
	
	if (e.keyCode == 50)
	{
		
		initialRod = 1;
		printTopArrow(1);
	}
	
	if (e.keyCode == 51)
	{
		initialRod = 2;
		printTopArrow(2);
	}
	
	if (e.keyCode == 37)
	{
		if(destinationRod > 0)
		{
			destinationRod--;
			printBottomArrow(destinationRod);
		}
	}
	
	if (e.keyCode == 39)
	{
		if(destinationRod < 2)
		{
			destinationRod++;
			printBottomArrow(destinationRod);
		}
		
	}
			
	
	if(e.keyCode == 13)
	{
		if(initialRod != -1 && destinationRod != -1)
		{
			moveRing(initialRod,destinationRod);
		}
		
	}
}

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


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

//Initialises  Run
this.newRun = function() 
{

  this.endCondition = false;
  
	//initialises logical rods and discs.
	initLogicalRods();
	initLogicaldiscs(); 
	
  if ( true  )
  {
	
	
	if ( show3d )
	{
	 threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 	
	}	     
	else
	{
	 threeworld.init2d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 		     
	}
	
	//Initialises graphics.
	initThreeRods(); 
	initThreeDiscs();
	initArrow();
	printThreeRods();

	//Creates skybox and starts loop for music.
	initSkybox();
 	initMusic();
	musicPause();
	
	// 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;	 
  }

};

//Outdated
this.getState = function()
{
  var x = new Array(numRods)
  
  for(var i = 0; i < numRods; i++)
  {
	  x[i] = findTopRing(i).radius;
  }
  return (x);  
  
};

//Updates location of discs


this.nextStep = function()		 
{
 var a = 4;

	/*
	var firstRod = a[0];
	var secondRod = a[1];
	
	moveRing(firstRod, secondRod);
	*/
	
	printThreeRods();
	
	if(findTopRing(2).position == (discs-1))
	{
		this.endCondition = true;
	}
	
	var status = "Number of moves: " + moves;
	
	if(legalMove)
	{
		musicPause();
	}
	else
	{
		status = status +  "<b> Illegal Move </b> " 
	}
	$("#user_span3").html( status );
	
};

this.endRun = function()
{
 if ( true  )
 {
  if ( this.endCondition)
     $("#user_span6").html( " &nbsp; <font color=red> <B> Congratulations you completed this in  </B> </font>   " + moves + "&nbsp; <font color=red> <B> moves</B> </font>   "  );
  else
	   $("#user_span6").html( " &nbsp; <font color=red> <B> You took too long you made   </B> </font>   " + moves + " &nbsp; <font color=red> <B>  moves</B> </font>   "  );
 }
};

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

// --- music and sound effects ----------------------------------------
// credits: Free Sound Org
// http://www.freesound.org/people/juskiddink/sounds/108615/



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