Code viewer for World: Tic-Tac-Toe (clone by Nial...

// Cloned by Niall Kelly on 29 Nov 2022 from World "Tic-Tac-Toe" by Scott Brady 
// Please leave this clone trail here.
 
// 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() {
    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 
}

function errorSplashScreen() {
    let text = "The game is full. Please try again later.";        
    return (text); // display welcome message on splash screen 
}

AB.newSplash( splashScreen() ); // calls splash screen function (makes it display)

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

AB.world.nextStep = function() { //redundant
}
//constants used in game
let board; 
let p1;
let p2;
let turn;
let currentTurnDiv;
let turnText = "";
var cnv;
let height;
var activePlayer;

var 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';

function setup() { // function called by p5 when game stats up
    height = windowHeight - 300;
    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 centerCanvas() {
//     var x = (windowWidth - width) / 2;
//     var y = (windowHeight - height) / 2;
//     cnv.position(x, y);
// }

// function windowResized() {
//     height = windowHeight - 300
//     resizeCanvas(height, height)
// }

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

// 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 
        let cellSize = this.cellSize; // sets little blocks size
        if (this.winState) {
            // someone has won, display the result
            textSize(40);
            textAlign(CENTER);
            text(this.resultText, width / 2, height / 2); // display result text 
            text("Click anywhere for a new game", width / 2, height - 200); // promts 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 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(168); // 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 + 60); // creates elements
                // text(element.symbol, element.row*cellSize + cellSize/2, element.col*cellSize + cellSize/2 + 20);
                // text(element.symbol, element.row*cellSize + cellSize/2, element.col*cellSize+cellSize/1.5)
                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.totalOnes++;
                    data["element"] = element;
                } else {
                    // if it's O's turn, the value is -1
                    element.val = -1;
                    // data.totalMinusOnes++;
                    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;
            // AB.socketOut(turnText);
        } 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;
            // AB.socketOut(turnText);

        }
        data["activePlayer"] = activePlayer;
        // console.log(turnText);
        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
                // p1.win();
            }
            if (element === -3) {
                winner = p2.symbol;
                // if the sum is -3, O won
                // p2.win();
            }
        });

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

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

        // 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() {
        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;
        }
    }

    // win() {
    //     this.score++
    // }
}

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

// AB.socketUserlist = function ( array ) {
//     console.log(array.length);
//     if (array.length == 2) {
//         AB.newSplash (errorSplashScreen());
//     }
// };