Ian Watkins iankwatkins
Lesson 3 · Game Dev Fundamentals

Taking Turns

Right now every click is an x. A real game has to remember whose turn it is. Today you add a second piece of state alongside the board, and you learn the most important rule about ordering your code: when to flip the turn.

The new idea: state beyond the board

Until now, the board array held the entire game. But "whose turn is it?" is a fact the board can't tell you reliably. It needs its own home. That's our first taste of a real principle:

Game state is more than the playing field

A game's state is everything you'd need to perfectly resume it: the board, the current player, the score, whether it's over. Each of these is a variable. Tic-tac-toe's second variable is the current player.

Add it at the top, next to board:

let currentPlayer = "x";   // x always goes first

Using it, then flipping it

Two changes to handleClick. First, place the current player's mark instead of a hardcoded "x". Second, after a successful move, switch to the other player:

function handleClick(i) {
  if (board[i] !== "") return;        // taken? bail

  board[i] = currentPlayer;             // place this player's mark

  // flip to the other player
  currentPlayer = currentPlayer === "x" ? "o" : "x";

  render();
}

The ternary, decoded

That one line is JavaScript's conditional (ternary) operator. Read it as a question:

currentPlayer = (currentPlayer === "x") ? "o" : "x";
//             condition          if true  if false

"Is the current player x? If yes, the new value is o. Otherwise, x." It's the same as this longer form — use whichever reads clearer to you:

if (currentPlayer === "x") {
  currentPlayer = "o";
} else {
  currentPlayer = "x";
}

The ordering trap (this is the real lesson)

Order matters enormously here, and it's a perfect example of a bug that looks fine. Watch what happens if you flip the player before placing the mark:

// WRONG ORDER
currentPlayer = currentPlayer === "x" ? "o" : "x";  // flip first
board[i] = currentPlayer;                                // place AFTER flip

X clicks, but the flip already happened, so you place an o. The mark on screen is always the wrong player. The guard clause still works, clicks still register, the board still fills — it just plays wrong. Use the value, then change it. Read before you advance. That sequencing instinct will save you in every game you build, especially when turns, scores, and timers all update in one handler.

Show whose turn it is

A player can't see currentPlayer in a variable. Surface it. Add a status line above the board in index.html:

<div id="status"></div>
<div id="board"></div>

And update it inside render(), since render is your single source of truth for what's on screen:

const statusEl = document.querySelector("#status");

function render() {
  // ... existing cell-drawing loop ...
  statusEl.textContent = "Turn: " + currentPlayer.toUpperCase();
}
Why put it in render()

Same discipline as the board: render() paints everything the player sees, from current state. You change state, call render() once, and the whole screen — board and status — updates together. One place to look when something's wrong.

Live demo

Turns alternate. Watch the status line and the state readout track currentPlayer flipping after each move.

Build it yourself

  1. Add let currentPlayer = "x"; at the top, by board.
  2. In handleClick: place currentPlayer instead of "x", then flip with the ternary.
  3. Add the <div id="status"> to your HTML and the status line to render().
Your task

First make it work: alternate x and o, with the status showing whose turn it is. Then deliberately break it — move the flip line above board[i] = currentPlayer, refresh, and play a few moves. Watch the wrong marks land. Predict what you'll see before you click, then confirm. Put it back when you've seen it. Breaking it on purpose is how the ordering rule stops being a sentence and becomes instinct.

What you just learned

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