1
\$\begingroup\$

During 2021 I was learning to code but I left it because I didn't have enough time and energy. Today I started my come back to coding so I created a simple tic-tac-toe game.

I didn't use objects, classes and related stuff because my main goal was to write something simple and completeable in 1 to 2 hours. I had to google some syntax that i forgot but I don't think it is a big thing. Didn't search for advice about the game itself ("how to do this", etc.).

So, it will be great to have some general advice, pointers, next steps and so on.

Game can be played here

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="main.js"></script>
    <link rel="stylesheet" href="styles/normalize.css">
    <link rel="stylesheet" href="styles/styles.css">
    <title>Tic-Tac-Toe vanilla JS</title>
</head>
<body>
    <div id="container">
        <div class="cell" id="cell-1"></div>
        <div class="cell" id="cell-2"></div>
        <div class="cell" id="cell-3"></div>
        <div class="cell" id="cell-4"></div>
        <div class="cell" id="cell-5"></div>
        <div class="cell" id="cell-6"></div>
        <div class="cell" id="cell-7"></div>
        <div class="cell" id="cell-8"></div>
        <div class="cell" id="cell-9"></div>
    </div>
</body>
</html>

main.js

// WAIT FOR THE DOM

window.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM fully loaded and parsed');
    main();
});

// STATE

const p1Class = 'player1' // Defined in styles.css
const p2Class = 'player2' // Defined in styles.css

let turnFlag = true // true: player1, false: player2
let playerClass = p1Class;
let cells = null
let cell = null
let moves = 0

// MAIN FUNCTION

function main() {

    // GET CELLS FROM DOM
    cells = document.querySelectorAll(".cell")
    cells.forEach( cell => cell.addEventListener('click', turn) )

    function turn() {
        console.log(`Now playing: ${playerClass}`)
        cell = this; // currently clicked cell

        if (!cell.hasAttribute('data-disabled')) {
            paintCell();
            if (checkWinner() || checkDraw()) {
                restartGame ()
            }
            else {
                changeTurn();
            }
        }
    };
};

// FUNCTIONS

function changeTurn() {
    turnFlag = !turnFlag
    //if (turnFlag == true) {playerClass = p1Class}
    //else {playerClass = p2Class}
    playerClass = turnFlag ? p1Class : p2Class
}

function paintCell() {
    console.log('cell clicked: ' + cell.id)
    cell.classList.add(playerClass)
    cell.setAttribute('data-disabled', 'true')
    moves += 1
}

function checkWinner() {
    function containsPlayerClass(cell) {
        return cell.classList.contains(playerClass)
    }

    // CHECK COMPLETION FOR ROWS, COLUMNS AND DIAGS

    function checkRows() {

        if ( containsPlayerClass(cells[0]) && containsPlayerClass(cells[1]) && containsPlayerClass(cells[2]) ) {
            return true
        }

        else if ( containsPlayerClass(cells[3]) && containsPlayerClass(cells[4]) && containsPlayerClass(cells[5]) ) {
            return true
        }

        else if ( containsPlayerClass(cells[6]) && containsPlayerClass(cells[7]) && containsPlayerClass(cells[8]) ) {
            return true
        }

        else {
            return false
        }

        // if (cells[3].classList.contains(playerClass) && cells[4].classList.contains(playerClass) && cells[5].classList.contains(playerClass)) {
        //     rowsFlag[1] = true
        // }
        // if (cells[6].classList.contains(playerClass) && cells[7].classList.contains(playerClass) && cells[8].classList.contains(playerClass)) {
        //     rowsFlag[2] = true
        // }
    }
  
    function checkColumns() {

        if ( containsPlayerClass(cells[0]) && containsPlayerClass(cells[3]) && containsPlayerClass(cells[6]) ) {
            return true
        }

        else if ( containsPlayerClass(cells[1]) && containsPlayerClass(cells[4]) && containsPlayerClass(cells[7]) ) {
            return rowsFlag = true
        }

        else if ( containsPlayerClass(cells[2]) && containsPlayerClass(cells[5]) && containsPlayerClass(cells[8]) ) {
            return rowsFlag = true
        }

        else {
            return rowsFlag = false
        }
    }

    function checkDiags() {

        if ( containsPlayerClass(cells[0]) && containsPlayerClass(cells[4]) && containsPlayerClass(cells[8]) ) {
            return true
        }

        else if ( containsPlayerClass(cells[2]) && containsPlayerClass(cells[4]) && containsPlayerClass(cells[6]) ) {
            return true
        }

        else {
            return false
        }

    }

    // EVALUATE IF WE HAVE A WINNER

    if ( checkRows() || checkColumns() || checkDiags() ) {
        alert(`YOU WON ${playerClass}`);
        return true
    }
    else {
        return false
    }
}

function checkDraw() {

    if (moves == 9) {
        alert(`DRAW`);
        return true;
    }
    else {
        return false
    }
}

function restartGame() {
    turnFlag = true
    playerClass = p1Class;
    cells.forEach( cell => cell.classList.remove('player1') )
    cells.forEach( cell => cell.classList.remove('player2') )
    cells.forEach( cell => cell.removeAttribute('data-disabled') )
    moves = 0
    console.clear()
    console.log('GAME RESTARTED')
}

styles.css

.player1 {
    background-color: turquoise;
}

.player2 {
    background-color: brown;
}

Thanks in advance.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Overall

Looks like it works! That's pretty good. Getting something correct and working is most of the job of a programmer. Be proud.

Globals and Functions

Stop storing data like who's marked a square only in the DOM. Move game info to a javascript object, a 3x3 array. It's better style (and slightly faster too).

Right now, you have 5 globals. Try removing them one at a time, starting with playerClass. Globals are widely considered bad style. Instead, learn how to pass variables like into functions that need them.

Hint: Learn about constants to get rid of two of them.

UI

Improve the UI a little. Add "X" and "O" into the squares. Indicate which player's turn it is. Draw the winning or drawing square before the alert shows. Use an on-screen display instead of an alert. Draw a happy victory penguin. Draw a line through the victory squares.

Program defensively

Change if (moves == 9) to if (moves >= 9).

\$\endgroup\$
1
  • \$\begingroup\$ So much thanks Zachary! I'm going to change the things you're pointing... And thanks for the UI homework (: \$\endgroup\$
    – nabla-f
    Apr 4, 2022 at 5:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.