Declaring a Winner

Check for winning combinations and display when someone wins.

The Winning Logic

A player wins when they have 3 marks in a row — horizontally, vertically, or diagonally. There are exactly 8 winning combinations:

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],  // top row
    [3, 4, 5],  // middle row
    [6, 7, 8],  // bottom row
    [0, 3, 6],  // left column
    [1, 4, 7],  // middle column
    [2, 5, 8],  // right column
    [0, 4, 8],  // diagonal
    [2, 4, 6],  // anti-diagonal
  ];

  for (const [a, b, c] of lines) {
    if (squares[a] &&
        squares[a] === squares[b] &&
        squares[a] === squares[c]) {
      return squares[a];  // Return 'X' or 'O'
    }
  }
  return null;  // No winner yet
}

Add this function at the top of your file, before the components.

Using Computed for the Status

Now we can use computed to create a derived value for the status message. A computed value automatically updates when its dependencies change:

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

function calculateWinner(squares) {
  // ... (the function from above)
}

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

  // Computed value that depends on squares and xIsNext
  const status = computed(() => {
    const winner = calculateWinner(squares());
    if (winner) {
      return `Winner: ${winner}`;
    }
    return `Next player: ${xIsNext() ? 'X' : 'O'}`;
  });

  function handleClick(i) {
    // Don't allow moves if game is over or square is filled
    if (calculateWinner(squares()) || squares()[i]) {
      return;
    }

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

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

How Computed Works

The status computed value:

  1. Runs its function and tracks which signals it reads (squares and xIsNext)
  2. Caches the result
  3. Re-runs only when one of those signals changes
  4. Triggers UI updates automatically

Automatic Optimization

Because status is computed, What only recalculates it when squares or xIsNext changes. If you had other signals in your app, changing them wouldn't affect the status computation at all.

Complete Game Code

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

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2], [3, 4, 5], [6, 7, 8],
    [0, 3, 6], [1, 4, 7], [2, 5, 8],
    [0, 4, 8], [2, 4, 6],
  ];
  for (const [a, b, c] of lines) {
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}

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);

  const status = computed(() => {
    const winner = calculateWinner(squares());
    if (winner) return `Winner: ${winner}`;
    return `Next player: ${xIsNext() ? 'X' : 'O'}`;
  });

  function handleClick(i) {
    if (calculateWinner(squares()) || squares()[i]) return;
    const next = [...squares()];
    next[i] = xIsNext() ? 'X' : 'O';
    squares.set(next);
    xIsNext.set(!xIsNext());
  }

  return (
    <>
      <div className="status">{status}</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 is now fully playable!

  • X and O take turns
  • Can't overwrite squares or play after game ends
  • Shows the winner when someone wins

For the final step, we'll add the ability to go back in time and see previous moves.