Build tic-tac-toe
mediumState & Data+210 XP
Build a 3x3 tic-tac-toe board. Players alternate, X first.
After each move, check whether the player who just moved has three in a row — any row, column, or diagonal — and if so, declare them the winner and stop accepting moves.
Examples
// X: 0, O: 4, X: 1, O: 3, X: 2
// X | X | X
// -----------
// O | O | .
// -----------
// . | . | .
// → status becomes "X wins!"; further clicks on empty cells are ignored.The winner-check runs after every move, not just the ninth.
const WINS = [
[0,1,2],[3,4,5],[6,7,8], // rows
[0,3,6],[1,4,7],[2,5,8], // columns
[0,4,8],[2,4,6], // diagonals
];
function winner(cells) {
for (const [a,b,c] of WINS) {
if (cells[a] && cells[a] === cells[b] && cells[a] === cells[c]) {
return cells[a];
}
}
return null;
}Pre-compute the 8 winning triples once; derive the winner from the board rather than tracking it in state.
What it has to do
- Nine cells, `data-testid="cell-0"` through `data-testid="cell-8"` (row-major: 0,1,2 is the top row).
- A `data-testid="status"` element mentions whose turn it is (containing "X" or "O") before the game ends.
- Clicking an empty cell places the current player's mark and passes the turn.
- Clicking an already-filled cell does nothing — no mark change, no turn change.
- Once a player completes a line, the status becomes exactly "X wins!" or "O wins!", and no further clicks place a mark anywhere on the board.
Notes
- Derive both the current player and winner from `cells` — don't track them in separate state slots that can drift out of sync.
- A tie needs its own branch: no winner AND no empty cells → "Draw". Not tested here but trivial to add once the winner check exists.
- Lock the board with a single early return in the click handler: `if (winner || cells[i]) return;` — one guard, both no-op cases handled.
- A `key` prop on cells is unnecessary — the cells are a fixed-length array rendered by index. The React warning only matters for dynamic lists.
Stuck? The react course covers the ideas this problem is built on.
import { useState } from "react"; export default function App() { const [cells, setCells] = useState(Array(9).fill(null)); const [xIsNext, setXIsNext] = useState(true); // TODO: after each move, check whether the player who just moved has // three in a row (any row, column or diagonal) and if so, lock the // board and show "X wins!" / "O wins!" via data-testid="status". function handleClick(index) { if (cells[index]) return; const next = [...cells]; next[index] = xIsNext ? "X" : "O"; setCells(next); setXIsNext(!xIsNext); } return ( <div style={{ fontFamily: "sans-serif", padding: 16 }}> <p data-testid="status">{xIsNext ? "X" : "O"}'s turn</p> <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 48px)", gap: 4 }}> {cells.map((cell, index) => ( <button key={index} data-testid={`cell-${index}`} onClick={() => handleClick(index)} style={{ height: 48, fontSize: 24 }} > {cell} </button> ))} </div> </div> ); }
- 1.Nine cells, `data-testid="cell-0"` through `data-testid="cell-8"` (row-major: 0,1,2 is the top row).
- 2.A `data-testid="status"` element mentions whose turn it is (containing "X" or "O") before the game ends.
- 3.Clicking an empty cell places the current player's mark and passes the turn.
- 4.Clicking an already-filled cell does nothing — no mark change, no turn change.
- 5.Once a player completes a line, the status becomes exactly "X wins!" or "O wins!", and no further clicks place a mark anywhere on the board.
5 automated assertions run against your rendered component. The exact assertion names are hidden so you build to the behaviour, not to the test.
5 tests · +210 XP
Discussion
Sign in to postSign in to join the conversation.Sign in
No comments yet. Be the first to say something.