Code viewer for World: Mighty Spread
// Cloned by Jack on 17 Nov 2022 from World "Websockets Project" by Gareth Hogan 
// Please leave this clone trail here.
 
// Cloned by Gareth Hogan on 12 Nov 2022 from World "Websockets boxes" by Starter user 
// Please leave this clone trail here.

//--------------------------------------------------------------------------------------------------------------
// CONSTS AND VARS
//--------------------------------------------------------------------------------------------------------------

AB.clockTick = 100;  // Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 300; // Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 100;  // Take screenshot on this step. (All resources should have finished loading.) Default 50.

// Source: https://minecraft.fandom.com/wiki/List_of_block_textures
// '/uploads/gareth22/example.jpg'
const FILE_ARRAY = [
    "/uploads/gareth22/dirt.jpg",
    "/uploads/gareth22/diamond.jpg",
    "/uploads/gareth22/redstone.jpg",
    "/uploads/gareth22/redglass.jpg",
    "/uploads/gareth22/blueglass.jpg",
    "/uploads/gareth22/blorb.jpg",
    "/uploads/gareth22/rorb.jpg"
    ];

//skybox array
// http://stemkoski.github.io/Three.js/#skybox
// https://jaxry.github.io/panorama-to-cubemap/
const SKYBOX_ARRAY = [							 
    "/uploads/gareth22/px-min.png",
    "/uploads/gareth22/nx-min.png",
    "/uploads/gareth22/py-min.png",
    "/uploads/gareth22/ny-min.png",
    "/uploads/gareth22/pz-min.png",
    "/uploads/gareth22/nz-min.png"
    ];

const SKYCOLOR 	= 0x000000;	// colour of the sky
const gridsize = 10;	// number of squares along side of world	   
const objectsize = 100; // size of the objects
const NumBoxes = gridsize*gridsize; // calculate the number of boxes that will be in the grid
const MAXPOS = gridsize * objectsize;  // start things within these bounds                    
const startRadiusConst = MAXPOS;	// distance from centre to start the camera at
const maxRadiusConst = MAXPOS * 5 ;  // maximum distance from camera we will render things

// constants used in the grid, each square has one number to show state, you can see what each number means here
const GRID_BLANK = 0;
const GRID_USER1 = 1; 
const GRID_USER2 = 4; 
const GRID_COLOUR1 = 2;
const GRID_COLOUR2 = 3;

// each action has an associated number
const ACTION_LEFT = 0;		   
const ACTION_RIGHT = 1;
const ACTION_UP = 2;		 
const ACTION_DOWN = 3;

// setup camera
ABHandler.MAXCAMERAPOS = MAXPOS; 
ABWorld.drawCameraControls = false; 
AB.drawRunControls = false;
ABHandler.GROUNDZERO = true;	

// Variables to be used
var step; // timer
var textureArray = new Array(FILE_ARRAY.length);
var GRID = new Array(gridsize);
var ai, aj; //player 1
var bi, bj; //player 2 
var player1, player2; // the orb objects

//--------------------------------------------------------------------------------------------------------------
// PRELOAD RESOURCES
//--------------------------------------------------------------------------------------------------------------

function loadResources() // asynchronous file loads - call initScene() when all finished 
{
	for ( var i = 0; i < FILE_ARRAY.length; i++ ) 
	    startFileLoad (i); // launch n asynchronous file loads
}

function startFileLoad(n) // asynchronous file load of texture n 
{
	var loader = new THREE.TextureLoader();

	loader.load(FILE_ARRAY[n], function(thetexture)  	 
	{
		thetexture.minFilter  = THREE.LinearFilter;
		textureArray[n] = thetexture;
		if(asynchFinished()) 
            initScene(); // initialize the scene when finished loading
	});	
}
 
function asynchFinished() // all file loads returned 
{
	for ( var i = 0; i < FILE_ARRAY.length; i++ )
    { 
		if(!textureArray[i]) 
			return false;
    }
	return true;
}

function splashHTML()		// HTML string to be shown in the splash
{
	string = "Colour the floor, QUICKLY! <br><font color=red><b>Red is the arrow keys</b></font> <br><font color=blue><b>Blue is WASD</b></font><br>  Colour the most squares before time is up to win!";
	return(string);
}

AB.newSplash(splashHTML()); // show the splash

AB.splashClick(function()  // when the start is pressed    
{		
    AB.removeSplash(); // remove splash screen 
    
    // ready to start run loop? 
    splashClicked = true;
    step = 0;
    ABWorld.render();
    AB.runReady = true;
    AB.socketOut("Start"); // send out start command to all worlds so they remove the splash
    
    if(asynchFinished()) 
        AB.runReady = true;  // start run loop 
});


//--------------------------------------------------------------------------------------------------------------
// SETUP GRID
//--------------------------------------------------------------------------------------------------------------

function initScene() // called when all textures ready 
{
    var i, j, shape, theobject, borb, rorb; // various variables used in the grid

    for (i = 0; i < gridsize ; i++)  // setting up the lists inside the grid list
        GRID[i] = new Array(gridsize);	
    
    // MAKING CUBES

    for (i = 0; i < gridsize ; i++){
        for (j = 0; j < gridsize ; j++){
            GRID[i][j] = GRID_BLANK; // default cube is blank
            shape = new THREE.BoxGeometry(objectsize, objectsize, objectsize);
            theobject = new THREE.Mesh(shape);
            
            theobject.material = new THREE.MeshBasicMaterial({map: textureArray[0]});  // textureArray[0] is for blank 
              
            theobject.position.copy(translate(i,j)); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
            ABWorld.scene.add(theobject);
        }
    }

    // PLAYER ORBS
    borb = new THREE.SphereGeometry(50,32,16); // blue orb
    rorb = new THREE.SphereGeometry(50,32,16); // red orb
    player1 = new THREE.Mesh(borb);
    player2 = new THREE.Mesh(rorb);
    player1.material = new THREE.MeshBasicMaterial({map: textureArray[5]});   
    player2.material = new THREE.MeshBasicMaterial({map: textureArray[6]}); 
    
    position1 = translateOrb(0,0) // placing it top left
    player1.position.copy(position1)

    position2 = translateOrb(9,9) // placing it bottom right
    player2.position.copy(position2)

    ABWorld.scene.add(player1);
    ABWorld.scene.add(player2);

    //positions of the players
    ai = 0;
    aj = 0;
    bi = 9;
    bj = 9;

    GRID[ai][aj] = GRID_USER1;
    GRID[bi][bj] = GRID_USER2;
    ABWorld.scene.background = new THREE.CubeTextureLoader().load(SKYBOX_ARRAY, function() 
    { 
    ABWorld.render();
    });
    
    AB.removeLoading(); // remove loading screen
}

function translate(i, j) // translte x y coord into a 3D vector	
{
	var v = new THREE.Vector3();
	
	v.y = 0;	
	v.x = (i * objectsize) - (MAXPOS/2);   		 
	v.z = (j * objectsize) - (MAXPOS/2);   	
	
	return v;
}

function translateOrb(i, j)	// translate for the ORB as they need to be one layer above the rest
{
	var v = new THREE.Vector3();
	
	v.y = 100;	
	v.x = (i * objectsize) - (MAXPOS/2);   		 
	v.z = (j * objectsize) - (MAXPOS/2);   	
	
	return v;
}

function occupied(i, j)	// is this square occupied
{
    return(GRID[i][j] == GRID_USER1 || GRID[i][j] == GRID_USER2);
}

//--------------------------------------------------------------------------------------------------------------
// MOVEMENT
//--------------------------------------------------------------------------------------------------------------

// arrow keys : LEFT, UP, RIGHT, DOWN, A, W, D, S
var OURKEYS = [ 37, 38, 39, 40, 65, 87, 68, 83];

function ourKeys(event) { // checks if a key pressed is either WASD or arrow keys
    return(OURKEYS.includes(event.keyCode)); 
}

function keyHandler(event) // what to do when a key is pressed
{
	if(!AB.runReady)return true;
	if(!ourKeys(event)) return true;
	
    // cases for each different key
    // socketsender first sends to other worlds what to do, then does it locally using moveUser
	if(event.keyCode == 65) socketSender(ACTION_LEFT, 1); 
    if(event.keyCode == 87) socketSender(ACTION_DOWN, 1); 
    if(event.keyCode == 68) socketSender(ACTION_RIGHT, 1); 
    if(event.keyCode == 83) socketSender(ACTION_UP, 1); 

	if(event.keyCode == 37) socketSender(ACTION_LEFT, 2);   
    if(event.keyCode == 38) socketSender(ACTION_DOWN, 2); 	 
    if(event.keyCode == 39) socketSender(ACTION_RIGHT, 2); 	 
    if(event.keyCode == 40) socketSender(ACTION_UP, 2);   

	event.stopPropagation(); event.preventDefault(); return false;
}

function moveUser(action, player) // moving the player, takes in the action to do, and which player to do it to		 
{ 
    if(player == 1){ // ai aj is where the player was, i j will be where they move to
        var i = ai;
        var j = aj;
    }
    else if(player == 2){
        var j = bj;
        var i = bi;
    }		 

    if (action == ACTION_LEFT ){
        if(i > 0) i--;
        else return;
    }
    else if (action == ACTION_RIGHT ){
        if(i < 9) i++;
        else return;
    }
    else if (action == ACTION_UP ){
        if(j < 9) j++;
        else return;
    }
    else if (action == ACTION_DOWN){
        if(j > 0) j--;
        else return;
    }

    if (!occupied(i,j)) // else just miss a turn cause you can't move into that space
    {
        if(player == 1){ //diamond
            GRID[ai][aj] = GRID_COLOUR1; // change colour of square you are moving off of
            shape = new THREE.BoxGeometry(objectsize, objectsize, objectsize);
            theobject = new THREE.Mesh(shape);
            theobject.material = new THREE.MeshBasicMaterial({map: textureArray[1]});
            theobject.position.copy(translate(ai,aj)); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
            ABWorld.scene.add(theobject);
            
            position = translateOrb(i,j) // move their orb
            player1.position.copy(position)
        
            GRID[i][j] = GRID_USER1; // new square the player is standing on
            shape = new THREE.BoxGeometry(objectsize, objectsize, objectsize);
            theobject = new THREE.Mesh(shape);
            theobject.material = new THREE.MeshBasicMaterial({map: textureArray[4]}); // where user is
            theobject.position.copy(translate(i,j)); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
            ABWorld.scene.add(theobject);
        }
        else if(player == 2){ //redstone
            GRID[bi][bj] = GRID_COLOUR2;
            shape = new THREE.BoxGeometry(objectsize, objectsize, objectsize);
            theobject = new THREE.Mesh(shape);
            theobject.material = new THREE.MeshBasicMaterial({map: textureArray[2]});
            theobject.position.copy(translate(bi,bj)); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
            ABWorld.scene.add(theobject);
                        
            position = translateOrb(i,j)
            player2.position.copy(position)

            GRID[i][j] = GRID_USER2;
            shape = new THREE.BoxGeometry(objectsize, objectsize, objectsize);
            theobject = new THREE.Mesh(shape);
            theobject.material = new THREE.MeshBasicMaterial({map: textureArray[3]}); // where user is
            theobject.position.copy(translate(i,j)); // translate my (i,j) grid coordinates to three.js (x,y,z) coordinates 
            ABWorld.scene.add(theobject);
        }	

        if(player == 1){ // now they are moved, update the coords where they are
            ai = i;
            aj = j;
        }
        else if(player == 2){
            bi = i;
            bj = j;
        }	
    }
}

//--------------------------------------------------------------------------------------------------------------
// AB WORLD FUNCTIONS
//--------------------------------------------------------------------------------------------------------------

AB.world.newRun = function() 
{
	AB.loadingScreen();
	AB.runReady = false;  
	ABWorld.init3d(startRadiusConst, maxRadiusConst, SKYCOLOR ); 
	loadResources(); // aynch file loads, calls initScene() when it returns 
    document.onkeydown = keyHandler;   
    step = 0;
};

function updatestatus(){ // this shows the timer
    var status = "<font size=5 face=verdana> Time: <br>" + step + "</font>" ; 
    $("#user_span3").html(status);
};

AB.world.nextStep = function()
{
    step++; // each step the timer goes up and is shown
    updatestatus();
};

function countScore(){ // function to calculate score at the end of the game
    scoreP1 = 1;
    scoreP2 = 1;

    for (i = 0; i < gridsize ; i++){
        for (j = 0; j < gridsize ; j++){

            if (GRID[i][j] == 2){
                scoreP1 += 1;
            }
            else if(GRID[i][j] == 3){
                scoreP2 += 1;
            }
        }
    }
    console.log(scoreP1)
    console.log(scoreP2)

    if(scoreP1 > scoreP2){
        winner = "<br> <font color=blue> <B> The winner is ... PLAYER 1 </B> </font>"
    }
    else if (scoreP2 > scoreP1){
        winner = "<br> <font color=red> <B> The winner is ... PLAYER 2 </B> </font>"
    }
    else{
        winner = "<br> <font color=green> <B> The game was a Draw</B> </font>"
    }
    return(winner);
}

AB.world.endRun = function() // at the end of the run
{
    if(step == 300)
    {
       $("#user_span4").html("<font size=5 face=verdana> <B>TIME UP</B> </font>");
    }
    winner = countScore();
    AB.msg(winner, 7) // shows the winner
};

//--------------------------------------------------------------------------------------------------------------
// SOCKETS
//--------------------------------------------------------------------------------------------------------------
AB.socketStart(); // start sockets

AB.socketIn = function(data) // incoming data on socket, i.e. clicks of other player 
{
    if(typeof data === "string") // a string signals to take down the splash for all screens
    {
        AB.removeSplash(); 
        splashClicked = true;
        step = 0;
        ABWorld.render();
        AB.runReady = true;
    }
    else // it will be a list containing the player and move to do
    {
        action = data[0]
        player = data[1]
        moveUser(action, player);
    }
};

function socketSender(action, player) // function to send out the move to do, then do the move locally
{
    data = [action, player];
    AB.socketOut(data);
    moveUser(action, player);
};