Making the Board Talk Back
You can turn data into a picture. Now you'll make the picture respond. A click becomes a change to the array, the array re-renders, and your mark appears. This is the moment the game loop closes.
Where we are in the loop
Last lesson you built state → render. Today adds the rest:
state → render → input → update state → render again
You already own "update state" (board[i] = "x") and
"render" (you wrote it). The single genuinely new skill is
input: detecting a click and knowing
which cell it hit.
The new idea: events
The browser is constantly noticing things the user does: clicks,
key presses, mouse moves. Each one is an event.
Your code doesn't go looking for clicks; instead you
register interest and the browser calls you back when
it happens. That's
addEventListener:
someElement.addEventListener("click", () => {
// this runs every time someElement is clicked
});
Two arguments: the event name as a string
("click"), and a function to run when it fires.
That function is called a callback — you
hand it over and the browser calls it back later.
The real problem: which cell?
A click handler is easy. The interesting question is connecting the cell that was clicked back to its slot in the array. The cell needs to remember its own index.
Here's the clean trick. In your render loop you already build
each cell one at a time, and you know its position. Switch to a
counting loop so you have the index i, and stamp it
onto the cell:
function render() {
boardEl.innerHTML = "";
for (let i = 0; i < board.length; i++) {
const cell = document.createElement("div");
cell.className = "cell";
cell.textContent = board[i];
cell.addEventListener("click", () => {
handleClick(i); // this cell knows its own index
});
boardEl.appendChild(cell);
}
}
We swapped for...of for a counting loop only
because we now need the index i, not just the
value. Each cell gets its own click listener that
remembers its own i. Cell 0's listener
knows it's 0; cell 4's knows it's 4. (This works because of a
JavaScript feature called a closure — the inline
function "remembers" the i from the loop turn that
created it. Worth a follow-up question if you're curious.)
The handler: update state, then re-render
function handleClick(i) {
// ignore clicks on a taken cell
if (board[i] !== "") return;
board[i] = "x"; // 1. update the state
render(); // 2. redraw from the new state
}
That's the whole game loop in two lines: change the data,
redraw. Notice what's not here — we never reach
into the cell and set its text directly. We change the array and
let render() do its job. The screen stays a perfect
mirror of the state. That discipline is what will keep the game
sane as it grows.
if (board[i] !== "") return; stops a player
from overwriting a square that's already filled. In games
this is called validating the move: the rules live in the
handler, between input and state change. Almost every game
rule you ever write lives right here.
Live demo
Click any empty square. Every click marks an x and
re-renders. Watch the state readout track the array underneath.
Build it yourself
Open your script.js from Lesson 1 and make three
changes:
-
Start with an empty board: nine
""strings. -
Change
render()'s loop to the counting version above, and add theaddEventListenerline inside it. - Add the
handleClick(i)function.
Save, refresh, and click around. You should be planting x's wherever you click, and clicking a taken square should do nothing.
Once x's are landing: add a console.log(i) as
the first line of handleClick, open your
browser's developer console (Cmd+Option+J in
Chrome), and click each square once. Confirm the indices
print 0 through 8 in the layout you expect — top-left
is 0, bottom-right is 8. Seeing the index light up on click
is the proof that input is wired to the right array slot.
What you just learned
- Events: the browser calls your callback when something happens. You register interest; you don't poll.
- Giving a cell identity: each cell's listener remembers its own index, so a click maps to one array slot.
- The full loop: input → update state → render. You never touch the DOM directly to show a move; you change data and redraw.
- Move validation: game rules live in the handler, between the click and the state change.
x. Lesson 3 adds turns —
tracking whose move it is and alternating x and o. It's a tiny
change to handleClick, and it introduces a second
piece of state beyond the board itself.