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

Tell us what’s happening:

I’m currently working on displaying the right symbol to the button within the 3x3 grid (either ‘X’ or ‘O’ depending on the current player). However, I am utilizing a 2-D array to make things more flexible if I ever want to expand on this project in the future (e.g. make it 4x4 or 5x5). I was also told this makes it easier to keep track of positioning to make it easier to recognize a winner pattern.

At the current moment, upon clicking a grid-item it displays either ‘X’ or ‘O’ to the ENTIRE column rather than just that specific button. I have no idea why.

A quick follow-up question here, since this is a 2-D array what exactly should I be using for the key?

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: index.jsx */
const { useState } = React;

export function Board() {
  const [currentPlayer, setCurrentPlayer] = useState("X");

  const [boardSize, setBoardSize] = useState(3);
  const [boxes, setBoxes] = useState(new Array(boardSize).fill(new Array(boardSize).fill(undefined)));
  // const [boxes, setBoxes] = useState(new Array(boardSize).fill(undefined));
  
  const [haveWinner, setHaveWinner] = useState(false);
  const [winner, setWinner] = useState();

  function handleMove(rowIndex, colIndex) {
    const newBoxes = [...boxes];
    
    if (currentPlayer === "X") {
      newBoxes[rowIndex][colIndex] = "X";
      setCurrentPlayer("O");
    } else {
      newBoxes[rowIndex][colIndex] = "O";
      setCurrentPlayer("X");
    }

    setBoxes(newBoxes);

    console.log(newBoxes);

    handleResult();
  }

  function handleReset() {
    const newBoxes = new Array(boardSize).fill(new Array(boardSize).fill(undefined))

    setCurrentPlayer("X");
    setBoxes(newBoxes);
    setHaveWinner(false);
  }

  function handleResult() {
   
  }

  return (
    <div className="main-container">
      <h1>Tic-Tac-Toe</h1>
      <h2>{haveWinner ? `Winner: ${winner}` : `Next Player: ${currentPlayer}`}</h2>
      
      <div className="grid-container">
      {
        boxes.map((item, rowIndex) => (
          item.map((subItem, colIndex) => (
          <button key={colIndex} disabled={!!subItem} onClick={() => handleMove(rowIndex, colIndex)} role="button" className="square">{subItem}</button>
        ))))
      }
      </div>

      <button onClick={handleReset} id="reset" role="button">Reset Game</button>
    </div>
  );
}

/* const arr = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

console.log(arr[0][1]); 

const newArr = arr.map(item => item.map(subItem => subItem + 1)); */


/* file: styles.css */
.main-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}

h1 {
  text-align: center;
}

h2 {
  text-align: center;
  font-weight: normal;
}
.grid-container {
  display: grid;
  grid-template-columns: auto auto auto;
  grid-template-rows: auto auto auto;
  padding: 5px;
  gap: .5rem;
  justify-content: center;
}

.square {
  border: 1px solid black;
  border-radius: 10px;
  width: 5rem;
  height: 5rem;
  padding: 10px;
  font-size: 30px;
  text-align: center;
  background-color: white;
}

.square:hover {
  cursor: pointer;
}

#reset {
  font-size: 20px;
  border: 1px solid black;
  border-radius: 5px;
  margin-top: 2rem;
  padding: 5px;
}

#reset:hover {
  cursor: pointer;
}

#reset:active {
  background-color: red;
}

Visual Reference:

Challenge Information:

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

Hey @xSaberNight,

Check your logic on this line:

const [boxes, setBoxes] = useState(new Array(boardSize).fill(new Array(boardSize).fill(undefined)));

Filling the array this way really creates the same reference to the initial array each time. That’s why when you clicked that first box it fills in every other array because when you reference that initial array within handleMove, it is resulting every other referenced array to be updated.

Instead, you need to have each row be a fresh array.

And the same problem is occurring within handleReset which, if not fixed, will reignite the problem once you hit reset.

Happy Coding!

1 Like

So, to clarify, there shouldn’t be a parent array? Instead it should be 3 separate 1-D arrays? Just curious, so I don’t go back and start changing things incorrectly.

Example:

const rowBoxes1 = new Array(boardSize).fill(undefined)
const rowBoxes2 = new Array(boardSize).fill(undefined)
const rowBoxes3 = new Array(boardSize).fill(undefined)

You can and should have a parent array but how you fill that array is what matters.

1 Like