Code viewer for World: Tic-Tac-Toe
//By Scott Brady(20387871) and Nacé van Wyk (2046546)
// Adapted from https://editor.p5js.org/cs4all/sketches/Bk3TOJzlE
// positioning information from https://github.com/processing/p5.js/wiki/Positioning-your-canvas

//---- normal P5 code -------------------------------------------------------

AB.world.newRun = function() {
    AB.socketStart(); // starts socket at game start
    AB.runReady = false; // sets run ready to false to allow for splash screen and to press start 
};

function splashScreen(text) {
    // let text = "Welcome to Tic Tac Toe. The aim of the game is to fill a row, diagonal or column with your symbol, X or O. First to do so is the winner.";        
    return (text); // display welcome message on splash screen 
}

AB.newSplash( splashScreen("Welcome to Tic Tac Toe. The aim of the game is to fill a row, diagonal or column with your symbol, X or O. First to do so is the winner.") ); // calls splash screen function (makes it display)
document.getElementById("splashbutton").id = "startsplash";

$("#startsplash").click (function () { // when start button is clicked splash screen goes away 
   AB.removeSplash();
   AB.backgroundMusic ( MUSICFILE ); // Start the background music when the user interacts with the page
   AB.runReady = true; // game is now ready to run (place symbols)
  if (AB.socket) {
    if (AB.socket.connected) {
        console.log("SOCKET CONNECTED");
        // AB.socketOut("TESTING SOCKETS!!!");
        }
    }
});

//constants used in game
let board; 
let p1;
let p2;
let turn;
let currentTurnDiv;
let turnText = "";
let cnv;
let height;

let data = { //dictionary to store information to be sent via websocket
};

const POP_SOUND = '/uploads/mrprice/mixkit-game-ball-tap-2073.wav'; //constant to store sound
const MUSICFILE = '/uploads/mrprice/elevator_music.mp3';
const MAX_TEXT_SIZE = 200;

function setup() { // function called by p5 when game stats up
    height = windowHeight - 200;
    p1 = new Player("X"); //define player 1
    p2 = new Player("O"); //define player 2
    var cnv = createCanvas(height, height); //square that reps board 
    var x = (windowWidth - width) / 2; //x canvas position
    var y = (windowHeight - height) / 2; //y canvas position
    cnv.position(x, y); //canvas position
    currentTurnDiv = createDiv('').size(180, 100); //defines player turn aka who goes also background
    board = new Board(p1, p2); //instance on board class
    activePlayer = p1;
}

function draw() { // called automatically by p5 after setup 
    background(color("yellow")); // background colour of the win screen
    AB.hideRunHeader(); // hides ancient brain controls 
    board.display(); // dispays board
    // style player turn text
    currentTurnDiv.style('font-size', '50px');
    currentTurnDiv.style('font-family', 'sans-serif');
    currentTurnDiv.style('padding-top', '25px');
    currentTurnDiv.style('margin', 'auto');
    currentTurnDiv.style('width', '100%');
    currentTurnDiv.style('height', '100%');
    currentTurnDiv.style('color', 'white');
    currentTurnDiv.style('text-align', 'center');
    // set the currentTurnDiv background image
    currentTurnDiv.style('background-image', 'url(/uploads/mrprice/bg.jpg)');
    currentTurnDiv.style('background-size', 'cover');
    currentTurnDiv.style('background-position', 'center');
    currentTurnDiv.style('background-repeat', 'no-repeat');
    currentTurnDiv.style('background-attachment', 'fixed');
}

function playSound(instance) { // function to play sound
    if (instance == "pop") { //if pop is passed to playSound function
        var sound = new Audio(POP_SOUND); // gets sound from aound file stored variable 
        sound.play(); // sound plays
    }
}

function mousePressed() { //automatically called by p5 when mouse is pressed
    // check if mouse is in the board and game is ready to run 
    if (mouseX > 0 && mouseX < height && mouseY > 0 && mouseY < height && AB.runReady === true) {
        if (!board.winState) { // if the game has not been won 
            if (board.turn === "X") { // if it is player x's turn 
                playSound("pop"); // play the sound 
                p1.select(board); // places player piece - calls select function on player one passes in bpard and updates the board
            } else if (board.turn === "O") { // if it is player o's turn
                playSound("pop"); // same as above
                p2.select(board); // same as above
            }
            board.toggleTurn(); // switches player turn ie x to o 
        } else {
            // someone has won start a new game
            board.newGame();
            AB.socketOut("startOver");
        }
    }
}

// board attributes & methods
class Board {
    constructor(p1, p2) { // defines attributes of the board
        // empty array to hokd the board - gets filled in newGame function
        this.cells = []; // little blocks on the board
        this.cellSize = (width - 1) / 3; // size of little blocks

        this.p1 = p1; // player one
        this.p2 = p2; //player two
        this.turn = this.p1.symbol; //initialises starting turn 

        this.winState = false; // game hasn't been won
        this.resultText = ""; // text that will be updated and displayed depending on game result
        this.newGame(); // new game function
    }

    display() { //displays the board 
        // dynamically set text size based on window height
        // on small displays make it smaller, large displays make it bigger
        let textSz = 100 / MAX_TEXT_SIZE *(height / 2) 
        let cellSize = this.cellSize; // sets little blocks size
        if (this.winState) {
            // someone has won, display the result
            textSize(textSz * 0.25);
            textAlign(CENTER);
            text(this.resultText, width / 2, height / 3); // display result text 
            text("Click inside this yellow\nsquare to play again", width / 2, height / 2); // prompts user for new game
            var bc = color("teal");
            fill(bc); // text colour teal
        } else {
            // nobody has won yet, display the board
            this.cells.forEach(function (element) {
                // draw the cell
                strokeWeight(25); //adds border for semi-3d appearance
                stroke('rgba(0, 80, 80, 0.8)'); // colour of stroke
                var c = color('teal'); // cell colour
                fill(c);
                rect(element.row * cellSize, element.col * cellSize, cellSize, cellSize); //creates each cell

                c = color('yellow'); // set piece colour
                fill(c);
                strokeWeight(10); // adds border to piece
                stroke('rgba(173, 167, 0, 0.8)'); // colour of border
                textSize(textSz); // size of piece
                textAlign(CENTER);// center piece in little block
                // draw the X or O
                // centre the text in the cell
                text(element.symbol, element.row * cellSize + cellSize / 2, element.col * cellSize + cellSize / 2 + (textSz / 2.8)); // creates elements
                strokeWeight(0); // resets stroke to zero to avoid unnecessary strokes
            });
        }

    }

    // update the board after a turn takes place
    update(row, col, symbol) {
        let turn = this.turn;

        this.cells.forEach(function (element) {
            // element.col is the column and element.row is the row
            // element.symbol is the X or O and element.val is the value
            // element is the cell object
            // if the cell's row and column match the row and column of the move
            if (element.row === row && element.col === col && element.val === 0) {
                // update the cell's text and value
                element.symbol = symbol;
                if (turn === "X") {
                    // if it's X's turn, the value is 1
                    element.val = 1;
                    data["element"] = element;
                } else {
                    // if it's O's turn, the value is -1
                    element.val = -1;
                    data["element"] = element;
                }
            }
        });
        // check if the game has been won and who has won
        // update the result label accordingly
        let result = this.checkResult();
        if (result === "X") {
            this.winState = true;
            this.resultText = "X wins!";
        } else if (result === "O") {
            this.winState = true;
            this.resultText = "O wins!";
        } else if (result === "tie") {
            this.winState = true;
            this.resultText = "It's a tie!";
        }
    }

    toggleTurn() {
        // if it's X's turn, make it O's turn
        if (this.turn == p1.symbol) {
            data["currentTurn"] = p2.symbol;
            this.turn = p2.symbol;
            activePlayer = p2;
            turnText = "Turn: " + p2.symbol;
        } else {
            // if it's O's turn, make it X's turn
            data["currentTurn"] = p1.symbol;
            this.turn = p1.symbol;
            activePlayer = p1;
            turnText = "Turn: " + p1.symbol;
        }
        currentTurnDiv.html(turnText);
        data["turnText"] = turnText;
        AB.socketOut(data);
    }


    // check if someone has won
    checkResult() {
        let winner;
        let p1 = this.p1;
        let p2 = this.p2;
        
        let rowSums = new Array(3);
        let colSums = new Array(3);
        let diagSums = new Array(3);
        // numOpen is the number of open cells
        let numOpen = 9;

        // initialize the rowSums, colSums, and diagSums arrays
        for (let i = 0; i < 3; i++) {
            rowSums[i] = 0;
            colSums[i] = 0;
            diagSums[i] = 0;
        }

        // loop through the cells
        this.cells.forEach(function (element) {
            // if the cell is open, decrement numOpen
            rowSums[element.row] += element.val;
            colSums[element.col] += element.val;
            numOpen -= abs(element.val);

        
            if (abs(element.row - element.col) === 0) {
                // add the cell's value to the main diagonal sum
                diagSums[0] += element.val;
            }
            if (abs(element.row - element.col) === 2 || (element.row == 1 && element.col == 1)) {
                // add the cell's value to the secondary diagonal sum
                diagSums[1] += element.val;
            }
        });

        // check if any of the sums are 3 or -3
        rowSums.forEach(function (element) {
            if (element === 3) {
                winner = p1.symbol;
                // if the sum is 3, X won
            }
            if (element === -3) {
                winner = p2.symbol;
                // if the sum is -3, O won
            }
        });

        colSums.forEach(function (element) {
            if (element === 3) {
                winner = p1.symbol;
            }
            if (element === -1 * 3) {
                winner = p2.symbol;
            }
        });

        diagSums.forEach(function (element) {
            if (element === 3) {
                winner = p1.symbol;
            }
            if (element === -1 * 3) {
                winner = p2.symbol;
            }
        });

        // if all the squares are filled and there is no winner, i.e. none of the above confitions have been met, it's a tie
        if (numOpen === 0) {
            winner = "tie";
        }

        return winner;
    }

    // configure the board's state at the beginning of a game
    newGame() {
        console.log("p1 score" + p1.score);
        console.log("p2 score" + p2.score);
        this.winState = false;  // the game is not over
        this.turn = this.p1.symbol; // X goes first
        currentTurnDiv.html("Turn: " + this.p1.symbol);

        // reset the cells
        this.cells = [];

        // create the cells
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                this.cells.push({
                    // the row
                    "row": i,

                    // the column
                    "col": j,

                    // the player's symbol. X or O
                    "symbol": "",

                    // the value
                    "val": 0
                });
            }
        }
    }
}

class Player {
    constructor(symbol) {
        // the player's symbol, X or O
        this.symbol = symbol;
        this.score = 0;
    }

    select(board) {
        // if it's X's turn, the value is 1
        if (board.turn == this.symbol) {
            console.log(board.turn, this.symbol);
            // cx and cy are the coordinates of the mouse
            let cx = int(Math.floor(mouseX / board.cellSize));
            let cy = int(Math.floor(mouseY / board.cellSize));

            // update the square on the board that the mouse is within
            board.update(cx, cy, this.symbol);

            // send the board's state to the other player
            data["row"] = cx;
            data["col"] = cy;
            data["symbol"] = this.symbol;
        }
    }
}

// Sockets
AB.socketIn = function (data) {
    console.log(data.turnText);
    // update the board with the new play
    currentTurnDiv.html(data.turnText);
    board.update(data.row, data.col, data.symbol);
    console.log(data.currentTurn);
    board.turn = data.currentTurn;
    activePlayer = data.activePlayer;
    
    if (data == "startOver") {
        board.newGame();
    }
};

AB.socketUserlist = function ( array ) {
    console.log(array.length);    
    if (array.length > 2) {
        // there are more than 2 players trying to enter the game, which is 2-player only
        // display an error screen
        AB.newSplash (splashScreen("<h3>The game is full. Please wait a moment, and then try again."));
        // update the button from "start" to "retry"
        document.getElementById("splashbutton").innerHTML = "Retry";
        // reload the page when the button is clicked
        document.getElementById("splashbutton").onclick = function () {
            location.reload();
        };
    } else if (array.length === 1) {
        // there is only one player, so don't start the game until a second
        // player joins and the first player hits 'retry'
        AB.newSplash (splashScreen("<h3>Waiting for a matched player. Please wait a moment and try again.</h3>"));
        // update the button from "start" to "retry"
        document.getElementById("splashbutton").innerHTML = "Retry";
        // reload the page when the button is clicked
        document.getElementById("splashbutton").onclick = function () {
            location.reload();
        };
    }
    // ensure that when this function is called again, the user array is not passed
    // this will avoid the error message displaying for all users currently playing and ensure it only displays for
    // the new user trying to join
    AB.socketUserlist = function() {};

};