mediumReact Native#133

Tic-Tac-Toe (Ta-Te-Ti)

Prompt

Implement the win-detection logic for Tic-Tac-Toe.

Write a function checkWinner(board) that takes a flat array of 9 cells (X, O, or null) and returns "X", "O", or null. Winning combinations: 3 in a row, column, or diagonal.

Solution

const WINNERS = [
  [0,1,2], [3,4,5], [6,7,8],
  [0,3,6], [1,4,7], [2,5,8],
  [0,4,8], [2,4,6],
]

function checkWinner(board) {
  for (const [a, b, c] of WINNERS) {
    if (board[a] && board[a] === board[b] && board[a] === board[c]) return board[a]
  }
  return null
}
Mentor's take

The lesson here is rules as data. The eight winning lines are a lookup table, and checkWinner shrinks to "for each line, are all three cells the same non-null mark?" Compare that to the loop-free alternative — nested row/column/diagonal scans — which is more code, harder to audit, and hides an off-by-one in every direction. When a rule set is small and finite, enumerating it beats computing it.

Two details do the real work:

  • board[a] && is the null guard. Without it, a line of three empty cells satisfies null === null === null and the function declares null the "winner" — which is falsy, so the bug hides until someone checks winner !== null. Truthiness guards that happen to work are still worth calling out explicitly.
  • Flat array of 9, not a 3x3 matrix. The win table indexes directly into it, board[i] maps one-to-one onto the 9 pressables, and updates are a single mapboard.map((cell, idx) => idx === i ? player : cell) — keeping every move an immutable copy the component can setState.

The natural follow-up is generalizing to N-in-a-row on an M×M board, where the table explodes and you do switch to directional scanning from the last move. Knowing where the table approach stops scaling is part of the answer.

Red flag: win detection inside the tap handler mixed with setState. Derive the winner from the board (checkWinner(board)) — stored winner flags desync the moment undo or replay arrives.

Say it: "Win lines are data, the check is one loop with a null guard, and the winner is always derived from the board — never stored beside it."