Taking Turns

Alternate between X and O, and prevent overwriting existing moves.

Tracking Whose Turn It Is

We need a second signal to track whether it's X's turn or O's turn:

src/main.jsx
function Board() {
  const squares = signal(Array(9).fill(null));
  const xIsNext = signal(true);  // X goes first

  function handleClick(i) {
    const next = [...squares()];

    // Place X or O depending on whose turn it is
    next[i] = xIsNext() ? 'X' : 'O';
    squares.set(next);

    // Toggle the turn
    xIsNext.set(!xIsNext());
  }

  return (
    <div className="board">
      {() => squares().map((value, i) => (
        <Square
          key={i}
          value={value}
          onClick={() => handleClick(i)}
        />
      ))}
    </div>
  );
}

Now when you click, it alternates between X and O. But there's still a problem — you can overwrite existing moves. Let's fix that.

Preventing Overwrites

We should ignore clicks on squares that already have a value:

function handleClick(i) {
  // Don't do anything if square is already filled
  if (squares()[i]) {
    return;
  }

  const next = [...squares()];
  next[i] = xIsNext() ? 'X' : 'O';
  squares.set(next);
  xIsNext.set(!xIsNext());
}

Now clicking a filled square does nothing. The game is playable!

Showing the Current Player

Let's show whose turn it is:

src/main.jsx (updated)
function Board() {
  const squares = signal(Array(9).fill(null));
  const xIsNext = signal(true);

  function handleClick(i) {
    if (squares()[i]) return;

    const next = [...squares()];
    next[i] = xIsNext() ? 'X' : 'O';
    squares.set(next);
    xIsNext.set(!xIsNext());
  }

  return (
    <>
      <div className="status">
        {() => `Next player: ${xIsNext() ? 'X' : 'O'}`}
      </div>
      <div className="board">
        {() => squares().map((value, i) => (
          <Square
            key={i}
            value={value}
            onClick={() => handleClick(i)}
          />
        ))}
      </div>
    </>
  );
}

Notice we wrapped the board in a fragment <>...</> to return multiple elements.

Reactive Status

The status uses {() => ...} to make it reactive. When xIsNext changes, the status text updates automatically.

Complete Code So Far

src/main.jsx
import './styles.css';
import { signal } from 'what-framework';

function Square({ value, onClick }) {
  return (
    <button className="square" onClick={onClick}>
      {value}
    </button>
  );
}

function Board() {
  const squares = signal(Array(9).fill(null));
  const xIsNext = signal(true);

  function handleClick(i) {
    if (squares()[i]) return;

    const next = [...squares()];
    next[i] = xIsNext() ? 'X' : 'O';
    squares.set(next);
    xIsNext.set(!xIsNext());
  }

  return (
    <>
      <div className="status">
        {() => `Next player: ${xIsNext() ? 'X' : 'O'}`}
      </div>
      <div className="board">
        {() => squares().map((value, i) => (
          <Square
            key={i}
            value={value}
            onClick={() => handleClick(i)}
          />
        ))}
      </div>
    </>
  );
}

export default function Game() {
  return (
    <div className="game">
      <h1>Tic-Tac-Toe</h1>
      <Board />
    </div>
  );
}

Checkpoint

Your game should now:

  • Alternate between X and O
  • Show whose turn it is
  • Prevent clicking filled squares

The only thing missing is detecting when someone wins. Let's add that next!