Search code examples
javascripthtmlcssonclicktic-tac-toe

How to display an X on a board created using for loops by clicking on a square


I'm a beginner so please be merciful :) I'm trying to make a tic tac toe game with html, css and pure JS. I'm stuck at trying to display an "X" symbol (or "O" ) while clicking on a square on my board (i used for loops for it, as i used it before for tetris game). I tried event listeners but couldn't get it to work, probably i'm doing something wrong :P So anyway my question is: How to display a symbol on a board by clicking on it ?

<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="tic-tac-toe.css">
    <title>TIC TAC TOE IN JS</title>
    <h1 class='header'>TIC TAC TOE in JS</h1>
  </head>
<body>
    <div class='canvas'>
        <canvas id="canvas" width='300px' height="300px"></canvas>
    </div>
    <div class='scores'>
        <div>Player 1 Wins: <span id="score-x">0</span></div>
        <div>Ties: <span id="score-tie">0</span> </div>
        <div>Player 2 Wins: <span id="score-o">0</span> </div>
    </div>

<script src="./tictactoe.js"></script>

</body>

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

//CONSTS
const row = 3;
const col = 3;
const vacant = "white";
const sq = 100;
let w = canvas.clientWidth/3;
let h = canvas.clientHeight/3;


//draw a square
function drawSquare(x, y, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x*sq, y*sq, sq, sq);
    ctx.strokeStyle = 'black';
    ctx.strokeRect(x*sq, y*sq, sq, sq);
}

    //Create board

let board = [ ];
for(let r = 0; r < row; r++){
    board[r] = [ ];
        for(let c = 0; c < col; c++) {
            board[r][c] = vacant;
        }
}
    //Draw Board

function drawBoard() {
    for(r = 0; r < row; r++) {
        for(c = 0; c < col; c++){
            drawSquare(c, r, board[r][c]);
        }
    }
}
drawBoard();

//players
const players = ['X', 'O']
const player1 = players[0];
const player2 = players[1];

//let the starting player be selected at random
const spot = board[r-1][c-1];

function setup() {
    let randomPlayer = Math.floor(Math.random() *2 );
    let currentPlayer = "";
    if (randomPlayer == 0){
        currentPlayer = player1;
        alert('Player 1 starts the game !')
    }else {
        currentPlayer = player2;
        alert('Player 2 starts the game !')
    };
}
function drawSymbol () {
    let x = w * r + w/2;
    let y = h * c + h/2;
    let xr = w/4;
    spot.addEventListener('click', (x, y, xr) => {
    line(x-xr, y-xr, x+xr, y+xr);
    line(x+xr, y-xr, x-xr, y+xr);
})
}


setup();
//Check if player clicked on the board 

//Place X

//Place O

UPDATED CODE

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

//Variables
let row = 3;
let col = 3;
const vacant = "white";
let sq = 100;
let w = canvas.clientWidth/3;
let h = canvas.clientHeight/3;
let currentPlayer = "";

//players
const players = ['X', 'O']
const player1 = players[0];
const player2 = players[1];


//draw a square
function drawSquare(x, y, color) {
    ctx.fillStyle = color;
    ctx.fillRect(x*sq, y*sq, sq, sq);
    ctx.strokeStyle = 'black';
    ctx.strokeRect(x*sq, y*sq, sq, sq);
}

//Create board
let board = [ ];
for(let r = 0; r < row; r++){
    board[r] = [ ];
        for(let c = 0; c < col; c++) {
            board[r][c] = vacant;
        }
}
//Draw Board
function drawBoard() {
    for(r = 0; r < row; r++) {
        for(c = 0; c < col; c++){
            drawSquare(c, r, board[r][c]);
        }
    }
}
drawBoard();



// sets up the game
function setup() {

    //let the starting player be selected at random
    let randomPlayer = Math.floor(Math.random() *2 );
    if (randomPlayer == 0){
        currentPlayer = player1;
        alert('Player 1 starts the game !')
    }else {
        currentPlayer = player2;
        alert('Player 2 starts the game !')
    };

    function calculatePos () {
        // checks for click position
        canvas.addEventListener('click', event => {

            // get the position of the board relative to the page
            let {left, top} = canvas.getBoundingClientRect();

            // get the position of the click relative to the page
            let mouseX = event.clientX - left;
            let mouseY = event.clientY - top;

            // calculate which square is being clicked 
            let newRow = Math.floor(mouseY / sq);
            let newCol = Math.floor(mouseX / sq);
            drawSymbol(newRow, newCol)
        })  
    }




calculatePos();
}
setup();
// listens for clicks on the canvas to draw the symbol
function drawSymbol (newRow, newCol) {
    let x = w * newRow + w/2;
    let y = h * newCol + h/2;
    let xr = w/4;
    if(currentPlayer == players[0]) {
        canvas.addEventListener('click', () => {
            ctx.lineWidth = 5;
            ctx.strokeStyle = 'black';
            ctx.beginPath();
            ctx.moveTo(x-xr, y-xr);
            ctx.lineTo(x+xr, y+xr);
            ctx.moveTo(x+xr, y-xr);
            ctx.lineTo(x-xr, y+xr);
            ctx.stroke();
    }) 
    } else {
            canvas.addEventListener('click', () => {
                ctx.lineWidth = 5;
                ctx.strokeStyle = 'black';
                ctx.beginPath();
                ctx.arc(x , y, 40, 0, 2 * Math.PI);
                ctx.stroke();
    }
)}
}
drawSymbol(r, c);

Solution

  • Looks good so far! One way to accomplish this is to add the event listener to the entire canvas element. An appropriate place to add it is in your setup function; that way, as soon as the board is setup, your canvas will be listening for clicks.

    Now, if the entire canvas is listening for clicks, you will need a way to determine which square has been clicked. This is not too difficult, but there are some snags. Let's imagine trying to determine this for a "1 dimensional board":

    "The board"
    +---------+---------+---------+
    

    Suppose a click occurs somewhere on it:

    +---------+---------+---------+
                 ^click
    |------------| <-clickPos = the distance between the click and the edge of the board
    

    How can we determine which cell it occurred in? If we know the size of each cell, and the location of the click, it can be done with division:

    const clickedCell = Math.floor(clickPos / cellSize);
    

    We floor this value in order to produce an integral value (it will be 0 for the first cell, 1 for the second, etc.).

    Here is where the "gotcha" shows up: when a click event occurs, it is easy to extract the location of the click relative to the page, but this is not necessarily what you want.

    Here's a new "board" that reflects this:

    |            +---------+---------+---------+
    ^ edge                    ^click
    of page                   |
    |-------------------------| <-this distance is easy to extract from the event
                 |------------| <-this is the distance you want
    |------------| <-this is the distance between the edge of the board and the edge of the page
    

    As the diagram shows, the click position that we care about is actually not the one recorded in the event. In order to determine that, we need to subtract the position of the click from the position of the left edge of the board.

    Here's how that looks in JavaScript:

    canvas.addEventListener('mouseup', event => {
      const { left, top } = canvas.getBoundingClientRect();
      //      ^^^^  ^^^ These give us the position of the board relative to the page
      const mouseX = event.clientX - left;
      const mouseY = event.clientY - top;
      //                   ^^^^^^^ These give us the position of the click relative to the page
    
      const row = Math.floor(mouseY / sq);
      const col = Math.floor(mouseX / sq);
    
      // Now draw your "X" or "O" here, using the `row` and `col` values
      // calculated above to determine where to place it
    });
    

    As a reminder, you only need to add that event listener once; once it's been added, it will call the function that we've provided every time a click occurs. It might make sense to break up the position-calculating work into a separate function, etc. but you will figure this out as you go. Hope this helps; keep it up!