Build a Tic-Tac-Toe Game - Build a Tic-Tac-Toe Game

Tell us what’s happening:

Passing all tests except to display the winner.

// running tests
10. The game should display a message indicating the winner to be X or O.
// tests completed

It does display the winner when the condition is right, in the same way it shows a draw (passes that test). “X has won!”

It must have something to do with React state but I’m not really sure.

This thread does indicate there might be a browser issue (I am using Firefox)

https://forum.freecodecamp.org/t/build-a-tic-tac-toe-game-build-a-tic-tac-toe-game/751680/5

Your code so far

<!-- file: index.html -->

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Tic-Tac-Toe</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.26.5/babel.min.js"></script>
    <script
      data-plugins="transform-modules-umd"
      type="text/babel"
      src="index.jsx"
    ></script>
    <link rel="stylesheet" href="styles.css" />
</head>

<body>
    <div id="root"></div>
    <script
      data-plugins="transform-modules-umd"
      type="text/babel"
      data-presets="react"
      data-type="module"
    >
      import { Board } from './index.jsx';
      ReactDOM.createRoot(document.getElementById('root')).render(<Board />);
    </script>
</body>

</html>

/* file: styles.css */

.board {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
  grid-template-rows: repeat(3, 1fr); /* 3 equal rows */
  gap: 5px;
  width: 300px;
  height: 300px;
}

button {
  width: 100%;
  height: 100%;
  font-size: 2rem;
  cursor: pointer;
}

#reset {
  width: 95px;
}

#finish {
  width: 100%;
  height: 100%;
  font-size: 2rem;
  display: none;
}


/* file: index.jsx */

const { useState, useRef } = React;

export function Board() {
  const initialGrid = [
    ["", "", ""],
    ["", "", ""],
    ["", "", ""]
      ];
  const [turn, setTurn] = useState("X")
  const [grid, setGrid] = useState(initialGrid)
  const [winner, setWinner] = useState(false)
  const [moves, setMoves] = useState(0)
  
  const fin = useRef(null);

  const finish = (result) => {
    if (result == "tie") {
      fin.current.innerText = "It's a tie!"
    } else {
      fin.current.innerText = `${result} has won!`
    }

    fin.current.style.display = "block"
  
  }

  const handleClick = (x,y) => {
    

    setMoves(moves + 1)
    
    setGrid(prev => {
      let na = [...prev]
      na[x][y] = turn
      checkWin(na)
      return na
      })

    const checkWin = (array) => {
      //check for win
      
      //check row
      let win = true;
      for (let yy=0; yy<=2; yy++) {
        
        if (array[x][yy] != turn) win = false;
      }
      
      if (win) {
        setWinner(true)
        finish(turn)
        return}

      //check col
      win = true;
      for (let xx=0; xx<=2; xx++) {
        
        if (array[xx][y] != turn) win = false;
      }
      
           if (win) {
        setWinner(true)
        finish(turn)
        return}

      //check diags
      if (x == 0 && y == 1) return;
      if (x == 2 && y == 1) return;
      if (x == 1 && y == 0) return;
      if (x == 1 && y == 2) return;
      win = true;
      let yy = 0;
      for (let xx=0; xx<=2; xx++) {
        //console.log("checking",xx,yy,array[xx][yy])
        if (array[xx][yy] != turn) win = false;
        yy++;
      }
          if (win) {
        setWinner(true)
        finish(turn)
        return}

      win = true;
      yy = 2;
      for (let xx=0; xx<=2; xx++) {
        //console.log("checking",xx,yy,array[xx][yy])
        if (array[xx][yy] != turn) win = false;
        yy--;
      }
            if (win) {
        setWinner(true)
        finish(turn)
        return}
    }

    console.log(moves)
    if (moves == 8) finish("tie")

  }

  
  return (
  <>
    <div className="board">

      {
        grid.map((row,x) => {
          return row.map((square, y) => {
            
          
          return <button 
            onClick={(e) => {
              if (e.target.innerText != "") return;
              console.log(winner)
              if (winner) return;
              handleClick(x,y);
              
              setTurn(prev => prev == "X"? "O" : "X")
              
              }}
            key={x+y} 
            className="square">{square}</button>

          })
        })

      }
      
    <button onClick={() => {
      setMoves(0)
      fin.current.style.display = "none"
      setWinner(false)
      setGrid(initialGrid);
      setTurn("X");
    }


    } id="reset">Reset</button>
    </div>
    <p id="finish" ref={fin}></p>
    </>
    
    
  )

}

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0

Challenge Information:

Build a Tic-Tac-Toe Game - Build a Tic-Tac-Toe Game

I tried in Chrome. At first it seemed to hang:

But finally (after 20 seconds) it failed even more tests in Chrome…

// running tests
7. All subsequent clicks of a button.square element should alternate between displaying X and O within the element.
8. Clicking on an already used button.square element should result in no change.
9. Clicking on a button.square element after the game has ended should result in no change.
10. The game should display a message indicating the winner to be X or O.
11. The game should display a message indicating a draw.
// tests completed

But it still seems to work

Microsoft Edge browser (which I never use so it has zero extensions) has the same result as Chrome, tests 7-11 all fail.

Firefox only test 10 fails.

I tried using visibility: hidden; / visible for the #finish div which holds the result, but test 10 still failed.

I also tried hardcoding

fin.current.innerText = `X has won!`

and

fin.current.innerText = `O has won!`

result strings instead of using “${result} has won”

Ok, I got it. :+1:

I used the state variable winner in the div that displays the result. This way that UI is updated when the variable updates.

My mistake was to manipulate the DOM using refs. I thought since refs was part of React it would handle it correctly, but I think it’s just a fallback to manipulate the DOM which you shouldn’t do in React.

But you have to ask… does it work in Chrome and MS Edge?

No, it does not.

// running tests
7. All subsequent clicks of a button.square element should alternate between displaying X and O within the element.
8. Clicking on an already used button.square element should result in no change.
9. Clicking on a button.square element after the game has ended should result in no change.
10. The game should display a message indicating the winner to be X or O.
11. The game should display a message indicating a draw.
// tests completed