Code viewer for World: Space Pong
const CLOCKTICK 	= 100;				// 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 = 30;					// number of squares along side of world	   
const NOBOXES =  Math.trunc ( (gridsize * gridsize) / 10 );

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


const startRadiusConst	 	= MAXPOS * 0.8 ;		// distance from centre to start the camera at
const skyboxConst			= MAXPOS * 3;
const maxRadiusConst 		= MAXPOS * 100 ;		// maximum distance from camera we will render things  

const show3d = false;



//--- 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_LEFT2 		= 0;		   
const ACTION_RIGHT2 	= 1;
const ACTION_UP2 		= 2;		 
const ACTION_DOWN2 		= 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; }
}







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

var playerOneScore = 0;
var playerTwoScore = 0;
var bounces = 0;

// 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 myPaddle, enemyPaddle, ball;		  

// enemy and agent position on squares
var si, sj, ai, aj , bi, bj;

var direct = 0;


var goodsteps;

var self = this;

var audio = new Audio('/uploads/strelki2/bounce1.mp3');
var error = new Audio('/uploads/strelki2/error.mp3');

// 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] = false;
  }
 }
}


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

 if ( ( ai == i ) && ( aj == j ) ) return true;
 if ( ( si == i ) && ( sj == j ) ) return true;
 
 if ( GRID[i+2][j] === true ) return true;
 if ( GRID[i-2][j] === true ) return true;// fixed object
 return false;
}


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

function initSkybox() 
{


  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
}


function loadTextures()
{

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

 var loader2 = new THREE.TextureLoader();
 loader2.load ( '/uploads/strelki2/pink.jpg',	function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		myPaddle.material =  new THREE.MeshBasicMaterial( { map: thetexture } );
	} ); 

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

	} );
	
	var loader4 = new THREE.TextureLoader();
 loader4.load ( '/uploads/strelki2/blue.jpg',	function ( thetexture ) {			 
		thetexture.minFilter = THREE.LinearFilter;
		ball.material =  new THREE.MeshBasicMaterial( { map: thetexture } );
	} ); 

}
 
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] = true;		// set up data structure, whether using Three.js or not
   }
}


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] == true )
   {
 	 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 paintWalls ( material )		// paint blank boxes  
{
 for ( var i = 0; i < WALLS.length; i++ )
 { 
   if ( WALLS[i] )  WALLS[i].material = material;
 }
}

// --- ball functions -----------------------------------

function drawBall()		// given ei, ej, draw it 
{
  var x = translate ( bi * squaresize );   	
  var z = translate ( bj * squaresize );   	
  var y =  0;
  var dir =  0;
  var velocity

 ball.position.x = x;
 ball.position.y = y;
 ball.position.z = z;
 ball.direction = dir
 threeworld.scene.add(ball);
}

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

 if ( ( ai == i ) && ( aj == j ) ) return true;
 if ( ( si == i ) && ( sj == j ) ) return true;
 
 if ( GRID[i+2][j] === true ) return true;
 if ( GRID[i-2][j] === true ) return true;// fixed object
 return false;
}

var ballDeltaX = -1;
var ballDeltaY = 3;
var diameter = 3;

function processBallMovement() {
    // --- the coordinates of the ball after it moves------
	var nextBallLeft = bi + ballDeltaX;
    var nextBallRight = bi + diameter + ballDeltaX;
    var nextBallTop = bj + ballDeltaY;
    var nextBallBottom = bj + diameter + ballDeltaY;
    
    

	// --- what happens when the ball bounces of the top or bottom side-------------
 	if(nextBallTop < 1 || nextBallBottom > gridsize){
 	    if (audio.duration > 0 && !audio.paused) { // takes care of the sound when two bounces happen in quick succession 
        audio.stop();
        audio.play();
    
        }
 	    audio.play();
 	    ballDeltaY *= -1; // changes the direction of the ball
 	    bounces++;
 	    
 	}
 	// --- what happens when the ball bounces of the left side-------------
 	if (nextBallLeft < 1) { 
 	    if (audio.duration > 0 && !audio.paused) {
        audio.stop();
        audio.play();
    
        }
 	    audio.play();
        ballDeltaX *= -1; // changes the direction of the ball 
        bounces++;
        
    }
    
    // --- what happens when the ball bounces of the right side-------------
    if (nextBallRight > gridsize) { 
        if (audio.duration > 0 && !audio.paused) {
        audio.stop();
        audio.play();
    
        }
        audio.play();
        ballDeltaX *= -1; // changes the direction of the ball 
        bounces++
        
    }
    // --- check if the ball hits players pad (controlled by user)
    if(bj==27){
        if(((bi != ai) && (bi != (ai+1)) && (bi != (ai+2)) && (bi != (ai-1)) && (bi != (ai-2)))){
            error.play();
            playerTwoScore++ // adds to platers score
        }
    }
    
    // updates the location of the ball
	bi+=ballDeltaX; 
	bj+=ballDeltaY;
}   

    
    
function drawEnemyPaddle()		// given ei, ej, draw it 
{
  var x = translate ( si * squaresize );   	
  var z = translate ( sj * squaresize );   	
  var y =  0;	
 
 enemyPaddle.position.x = x;
 enemyPaddle.position.y = y;
 enemyPaddle.position.z = z;
 threeworld.scene.add(enemyPaddle);
}



function initLogicalEnemy()
{
 si = 7;		// this square will be free 
 sj = 1;		// (bug) use Math.trunc or else you get a bad square number if gridsize is odd
}

function initLogicalBall()
{
 bi = gridsize/2;		// this square will be free 
 bj = gridsize/2;		// (bug) use Math.trunc or else you get a bad square number if gridsize is odd
}

function initThreeBall()
{
        
 var sphereMaterial = new THREE.MeshLambertMaterial({color: 0xD43001});

// Create a ball with sphere geometry
    ball = new THREE.Mesh(
    new THREE.SphereGeometry(100,50,50), sphereMaterial);
    drawBall();
}


function initThreeEnemy()
{
 var shape    = new THREE.BoxGeometry( 500, squaresize, squaresize );			 
 enemyPaddle = new THREE.Mesh( shape );
 enemyPaddle.material.color.setHex( BLANKCOLOR  );
 drawEnemyPaddle();
}


function moveLogicalEnemy()
{ 
 // --- follows the location of the ball on the X axis    
 var i;
 if ( si < bi ) i = randomintAtoB(si, si+2.5); 
 if ( si == bi ) i = si; 
 if ( si > bi ) i = randomintAtoB(si-2.5, si);
 // --- check if the ball hits the oponents pad
 if(bj==3){
    if(((bi != si) && (bi != (si+1)) && (bi != (si+2)) && (bi != (si-1)) && (bi != (si-2)))){
        error.play();
        playerOneScore++ // adds to computers score
    }
}
 
 
 if ( ! occupied(i, sj+2))
 {
  si = i;
 }
}


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

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


function initLogicalAgent()
{
// start at same place every time:
 ai = gridsize/2 - 5;
 aj = 28;
}

function initThreeAgent()
{
 var shape    = new THREE.BoxGeometry( 500, squaresize, squaresize );			 
 myPaddle = new THREE.Mesh( shape );
 myPaddle.material.color.setHex( BLANKCOLOR );	
 drawMyPaddle(); 		  
}

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++;

 if ( ! occupied(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	); 
}
 


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

var bounces;


 
function   updateStatus()		 
{
 var score = self.getScore();
 var status =  "You: " + playerOneScore +
    " Computer: " + playerTwoScore+"     --------------------> Player with highest score wins"; 

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






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


// must have this public variable:

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




this.newRun = function() 
{

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

  this.endCondition = false;
	step = 0;


  // define logical data structure for the World, even if no graphical representation:

 	initGrid();
	initLogicalWalls(); 
	initLogicalAgent();
	initLogicalEnemy();
	initLogicalBall();


  // if Three.js graphical representation:
  if ( true  )
  {
	  threeworld.init3d ( startRadiusConst, maxRadiusConst, SKYCOLOR  ); 

	// Set up blank objects first:
        initSkybox();
        

	  initThreeWalls();
	  initThreeAgent();
	  initThreeEnemy();
	  initThreeBall();

	// 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();			// will return sometime later, but can go ahead and render now	
	  document.onkeydown = keyHandler;
  }		
};



this.getState = function()
{
 var x = [ ai, aj, si, sj, bi, bj ];
  return ( x );  
};

var win = new Audio('/uploads/jordanm/win.mp3');
var lose = new Audio('/uploads/jordanm/lose.mp3');


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

  moveLogicalEnemy();
  processBallMovement();

  if ( true  )
  {
   drawMyPaddle();
   drawEnemyPaddle();
   drawBall();
   updateStatus();
   
   // ends the game when either computer or user reaches 10 points 
   if(playerOneScore === 5){
       win.play();
       this.endCondition = true;
   } 
   if(playerTwoScore === 5){
        lose.play();
        this.endCondition = true;
    }
  }
};



this.endRun = function()
{
    if ( true  )
     {
            if(playerTwoScore>playerOneScore){
                
                $("#user_span6").html( " &nbsp; <font color=red> <B> Computer has Won </B> </font>");
                
            }else if((playerTwoScore<playerOneScore)){
                
                $("#user_span6").html( " &nbsp; <font color=red> <B> Congratulations. You have Won </B> </font>"  );
            }else{
                $("#user_span6").html( " &nbsp; <font color=red> <B> Its a draw </B> </font>"  );
            }
     }
};


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


}