Ian Watkins iankwatkins
Lesson 2 · Game Dev Fundamentals

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:

The loop, completed today

state → render → inputupdate 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.

Why the guard clause matters

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:

  1. Start with an empty board: nine "" strings.
  2. Change render()'s loop to the counting version above, and add the addEventListener line inside it.
  3. 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.

Your task

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

That's all ten lessons! See your course progress and celebration →