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!