Search code examples
javascriptif-statementforeachaddeventlistenertic-tac-toe

if statement not running || 0r else if inside a forEach loop for tic tac toe game over logic


I am trying to get console.log("game over"), when three specified cells are clicked. The if statement runs the code once but then it doesn't run the || case or the else if.

    "use strict";

     const gameClock = document.querySelector(".clock");
    playerTitle = document.querySelector(".player-title");
    const ticTacToeBoard = document.querySelector(".tic-tac-toe-container");
    const cells = document.querySelectorAll(".cell");
    const startPauseBtn = document.querySelector(".start-btn");
    const resetBtn = document.querySelector(".resetBtn");
    const cellsClicked = [];
    const winningMoves = [
      "box-1",
      "box-2",
      "box-3",
      "box-4",
      "box-5",
      "box-6",
      "box-7",
      "box-8",
      "box-9",
    ];
    let seconds = 0;
    let minutes = 0;
    let displaySeconds = 0;
    let displayMinutes = 0;
    let interval = null;
    let stopWatchStatus = "stopped";

    ticTacToeBoard.classList.add("player-turn");
    //functions

    const playerMove = () => {
      cells.forEach((cell) => {
         const playerEventListener = () => {
          if (ticTacToeBoard.classList.contains("player-turn")) {
            let currentPlayer = document.createElement("p");
            currentPlayer.classList.add("player-1");
            currentPlayer.innerHTML = "X";
            cell.append(currentPlayer);
            playerTitle.innerHTML = "Player: 2";
            ticTacToeBoard.classList.toggle("player-turn");
            cellsClicked.push(cell.getAttribute("id"));
            if (
              cellsClicked[0] === winningMoves[0] &&
              cellsClicked[1] === winningMoves[1] &&
              cellsClicked[2] === winningMoves[2]
            ) {
              console.log("game over");
            } else if (
              cellsClicked[3] === winningMoves[3] &&
              cellsClicked[4] === winningMoves[4] &&
              cellsClicked[5] === winningMoves[5]
             ) {
              console.log("game over");
            } else if (
              cellsClicked[6] === winningMoves[6] &&
              cellsClicked[7] === winningMoves[7] &&
              cellsClicked[8] === winningMoves[8]
            ) {
              console.log("game over");
            } else {
              console.log(false);
              console.log(cellsClicked, winningMoves);
            }
          } else {
            let currentPlayer = document.createElement("p");
            currentPlayer.classList.add("player-2");
            currentPlayer.innerHTML = "O";
            cell.append(currentPlayer);
            playerTitle.innerHTML = "Player: 1";
            ticTacToeBoard.classList.toggle("player-turn");
            if (
              (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-1" &&
                cell.getAttribute("id") === "box-2" &&
                cell.getAttribute("id") === "box-3") ||
              (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-4" &&
                cell.getAttribute("id") === "box-5" &&
                cell.getAttribute("id") === "box-6") ||
               (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-7" &&
                cell.getAttribute("id") === "box-8" &&
                cell.getAttribute("id") === "box-9") ||
              (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-1" &&
                cell.getAttribute("id") === "box-4" &&
                cell.getAttribute("id") === "box-7") ||
              (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-2" &&
                cell.getAttribute("id") === "box-5" &&
                cell.getAttribute("id") === "box-8") ||
              (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-3" &&
                cell.getAttribute("id") === "box-6" &&
                cell.getAttribute("id") === "box-9") ||
              (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-1" &&
                cell.getAttribute("id") === "box-5" &&
                cell.getAttribute("id") === "box-9") ||
               (cell.contains(currentPlayer) &&
                cell.getAttribute("id") === "box-3" &&
                cell.getAttribute("id") === "box-5" &&
                cell.getAttribute("id") === "box-7")
            ) {
               console.log("game over");
             }
          }
        };

        cell.addEventListener("click", playerEventListener, { once: true });
      });
    };
    const stopWatch = () => {
      seconds++;
      if (seconds / 60 === 1) {
        seconds = 0;
        minutes++;
        if (minutes / 60 === 1) {
          minutes = 0;
        }
      }
      if (seconds < 10) {
        displaySeconds = "0" + seconds.toString();
      } else {
        displaySeconds = seconds;
      }
      if (minutes < 10) {
        displayMinutes = "0" + minutes.toString();
      } else {
        displayMinutes = minutes;
       }
      gameClock.innerHTML = `${displayMinutes}:${displaySeconds}`;
    };

    const startStop = () => {
      if (stopWatchStatus === "stopped") {
        interval = setInterval(stopWatch, 1000);
        stopWatchStatus = "started";
        startPauseBtn.textContent = "Pause";
      } else {
        clearInterval(interval);
        stopWatchStatus = "stopped";
        startPauseBtn.textContent = "Start Game";
      }
    };

    const startGame = () => {
      startPauseBtn.addEventListener("click", () => {
        if (startPauseBtn.textContent === "Start Game") {
          startPauseBtn.textContent = "Pause";
          startStop();
         } else if ((startPauseBtn.textContent = "Pause")) {
          startPauseBtn.textContent = "Start Game";
          startStop();
        }
      });
    };

    startGame();
    playerMove();

I tried both matching the cellsClicked and winningMoves index and I tried to see if cell.getAttribute("id") === "box-1", box-2, etc both of them have the same result the first case runs then after that nothing consoles.


Solution

  • Introduction

    There are some problems I spotted in your code and I decided to make some additions to make things more clear. In particular, since I'm holding the game state in the dom, I added some superflous data attributes that will show with no ambiguity and no math to which row/col belong each cell.

    I'm not very proud of how I determines the winning position because I'm sure it could be much more efficient than that... anyway just my 2 cents.

    Problems spotted:

    As I pointed out in the comments, you have an odd strategy in your if conditions checking if the same value matches different values in AND.

    //...
    cell.getAttribute("id") === "box-1" &&
    cell.getAttribute("id") === "box-2" &&
    //...
    

    of course it will always return false.

    Another problem I spotted was in your playerEventListener where you take for granted the variable cell. Inside an event handler, to grab the element firing the event you should use event.target.

    Refactor:

    To simplify the scenario I stripped away part of your logic that wasn't strictly bound to the game itself (the clock, the start/reset buttons, the initialization of the vars in the global scope).

    So here you have 2 main functions: isGameOver and onCellClick.

    onCellClick(event)

    It's the callback passed to addEventListener to listen for the click event on the whole board. I'm using event delegation here so that there's one event handler on the parent element that will be triggered also when the event will bubble from the children cells.

    The whole state of the board is hold inside the dom itself so that each cell now has data attributes indicating: the column, the row, the cell nr, the player marking that cell.

    //the click event handler on the board
    const onCellClick = (event) => {    
      //the clicked cell comes from the passed event argument
      const clickedCell = event.target;
    
      //retrieves the player index having the previous move (1 or 2)
      const prevPlayer = ticTacToeBoard.dataset.previousplayer;
    
      //builds the object for the current player
      const currPlayer = (prevPlayer == 1) ? { i: 2, symb: 'O'} : { i: 1, symb: 'X'};
    
      //shows the move on the tic-tac-toe container
      clickedCell.textContent = currPlayer.symb;  
      playerTitle.innerHTML = `Player: ${currPlayer.i} moved`;                
      
    
      //sets the data-ownedBy as the mark placed by the current player
      clickedCell.dataset.ownedBy = currPlayer.symb;
    
      //if it's game over...
      const gameover = isGameOver(clickedCell);     
      if(gameover){    
        playerTitle.innerHTML = `Player${currPlayer.i} won the game!`;                
        playerTitle.style.background = 'yellow';
        ticTacToeBoard.classList.add('gameover');
      }
    
      //updates the latest move played by
      ticTacToeBoard.dataset.previousplayer = currPlayer.i;                
    }
    

    isGameOver(cell)

    This is called by the click event handler and it will just check, starting from the currenct cell, if there's any winning combination for the current player on the same row, same column or same diagonal(s if any).

    //returns true if there's any winning position on the board
    const isGameOver = (cell) => {  
      
      //returns a selector fetching elements with the data attributes set in o argument
      const getSelector = (o)=>{
        let selector = '.cell';
        if(o.nr)
          selector += `[data-nr="${o.nr}"]`; 
        if(o.row)
          selector += `[data-row="${o.row}"]`;
        if(o.col)
          selector += `[data-col="${o.col}"]`;            
        return selector += `[data-owned-by="${o.symb}"]`;
      };
        
      //the selectors for the winning lines to check for
      const rowSelector = getSelector({row: cell.dataset.row, symb: cell.dataset.ownedBy});
      const colSelector = getSelector({col: cell.dataset.col, symb: cell.dataset.ownedBy});
      const diagSelectors = [];
    
      //if cellnr is odd (only odd cells are into a diagonal)
      if(parseInt(cell.dataset.nr) % 2 == 1){              
        //pushes all the diagonals to which the currNr belongs to
        const diagonals = [];
        if ([1,5,9].includes(parseInt(cell.dataset.nr)))
          diagonals.push([1,5,9]);
        if ([3,5,7].includes(parseInt(cell.dataset.nr)))
          diagonals.push([3,5,7]);                
        //for each of those diagonals
        diagonals.forEach(diagonal =>{
          //adds to diagSelectors the corresponding diagonal selector
          const diagSelector = diagonal.reduce(
            (acc, curr)=>{
              acc += `,${getSelector({nr:curr, symb:cell.dataset.ownedBy})}`;
              return acc;
            },
            ''
          ).substring(1);              
          diagSelectors.push(diagSelector);      
        });
      }        
        
      //returns true if there's any winning position
      if (document.querySelectorAll(rowSelector).length == 3)
        return true
      if (document.querySelectorAll(colSelector).length == 3)                
        return true
      for(const diagSelector of diagSelectors){
        if(document.querySelectorAll(diagSelector).length == 3){
          return true
        }            
      }
    
      //otherwise false
      return false;
    }
    

    Working demo:

    let playerTitle = document.querySelector(".player-title");
    const ticTacToeBoard = document.querySelector(".tic-tac-toe-container");
    
    //returns true if there's any winning position on the board
    const isGameOver = (cell) => {  
      
      //returns a selector fetching elements with the data attributes set in o argument
      const getSelector = (o)=>{
        let selector = '.cell';
        if(o.nr)
          selector += `[data-nr="${o.nr}"]`; 
        if(o.row)
          selector += `[data-row="${o.row}"]`;
        if(o.col)
          selector += `[data-col="${o.col}"]`;            
        return selector += `[data-owned-by="${o.symb}"]`;
      };
        
      //the selectors for the winning lines to check for
      const rowSelector = getSelector({row: cell.dataset.row, symb: cell.dataset.ownedBy});
      const colSelector = getSelector({col: cell.dataset.col, symb: cell.dataset.ownedBy});
      const diagSelectors = [];
    
      //if cellnr is odd (only odd cells are into a diagonal)
      if(parseInt(cell.dataset.nr) % 2 == 1){              
        //pushes all the diagonals to which the currNr belongs to
        const diagonals = [];
        if ([1,5,9].includes(parseInt(cell.dataset.nr)))
          diagonals.push([1,5,9]);
        if ([3,5,7].includes(parseInt(cell.dataset.nr)))
          diagonals.push([3,5,7]);                
        //for each of those diagonals
        diagonals.forEach(diagonal =>{
          //adds to diagSelectors the corresponding diagonal selector
          const diagSelector = diagonal.reduce(
            (acc, curr)=>{
              acc += `,${getSelector({nr:curr, symb:cell.dataset.ownedBy})}`;
              return acc;
            },
            ''
          ).substring(1);              
          diagSelectors.push(diagSelector);      
        });
      }        
        
      //returns true if there's any winning position
      if (document.querySelectorAll(rowSelector).length == 3)
        return true
      if (document.querySelectorAll(colSelector).length == 3)                
        return true
      for(const diagSelector of diagSelectors){
        if(document.querySelectorAll(diagSelector).length == 3){
          return true
        }            
      }
    
      //otherwise false
      return false;
    }
    
    //the click event handler on the board
    const onCellClick = (event) => {    
      //the clicked cell comes from the passed event argument
      const clickedCell = event.target;
    
      if(clickedCell.dataset.ownedBy)
        return;
    
      //retrieves the player index having the previous move (1 or 2)
      const prevPlayer = ticTacToeBoard.dataset.previousplayer;
    
      //builds the object for the current player
      const currPlayer = (prevPlayer == 1) ? { i: 2, symb: 'O'} : { i: 1, symb: 'X'};
    
      //shows the move on the tic-tac-toe container
      clickedCell.textContent = currPlayer.symb;  
      playerTitle.innerHTML = `Player: ${currPlayer.i} moved`;                
      
      //sets the data-ownedBy as the mark placed by the current player
      clickedCell.dataset.ownedBy = currPlayer.symb;
    
      //if it's game over...
      const gameover = isGameOver(clickedCell);     
      if(gameover){    
        playerTitle.innerHTML = `Player${currPlayer.i} won the game!`;                
        playerTitle.style.background = 'yellow';
        ticTacToeBoard.classList.add('gameover');
      }
    
      //updates the latest move played by
      ticTacToeBoard.dataset.previousplayer = currPlayer.i;                
    }
    
    //adds the click event listener to the whole ticTacToeContainer
    ticTacToeBoard.addEventListener("click", onCellClick);
    *{
      box-sizing: border-box;
    }
    
    body, html{
      font-size: 20px;
      margin: 0;
      padding: 0;
      font-family: sans-serif;
    }
    
    .player-title{
      width: 100%;
      text-align: center;
      padding: 1em;    
      font-weight: 600;
    }
    
    .tic-tac-toe-container{
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      margin: 1rem auto;  
      width: fit-content;
      gap: 2px;
    }
    
    .cell{
      outline: solid 2px gray;  
      width: 3em;
      height: 3em;
      cursor: pointer;
      font-weight: 600;
      font-size: 2rem;
      
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    .gameover{
      pointer-events: none;
      background: lightgray;
    }
    <div class="player-title">Player 1, make the first move!</div>
    <div class="tic-tac-toe-container">
      <div id="box-1" class="cell" data-nr="1" data-row="1" data-col="1"></div>
      <div id="box-2" class="cell" data-nr="2" data-row="1" data-col="2"></div>
      <div id="box-3" class="cell" data-nr="3" data-row="1" data-col="3"></div>
      <div id="box-4" class="cell" data-nr="4" data-row="2" data-col="1"></div>
      <div id="box-5" class="cell" data-nr="5" data-row="2" data-col="2"></div>
      <div id="box-6" class="cell" data-nr="6" data-row="2" data-col="3"></div>
      <div id="box-7" class="cell" data-nr="7" data-row="3" data-col="1"></div>
      <div id="box-8" class="cell" data-nr="8" data-row="3" data-col="2"></div>
      <div id="box-9" class="cell" data-nr="9" data-row="3" data-col="3"></div>
    </div>