# 3. Make the server the referee

Before adding network traffic, write rules that are easy to read and test. The browser may ask to play square 4. It may not declare “I won”, choose the other player's identity, or replace the board.

<!-- source: src/game.ts -->

The first distinct account takes X; the second takes O. Joining again with the same account is idempotent. `move()` verifies membership, a second player, an unfinished game, the current revision, the correct turn and an empty square—in that order—before changing anything.

The revision is a move number, not a clock. If an old click arrives after the board changed, it is rejected instead of being applied to a different turn. Both validation and mutation are synchronous, so another socket callback cannot interleave halfway through a move in this single Node process.

`snapshot()` copies arrays. A consumer cannot edit the game's internal board by changing a returned snapshot. `Games` caps room creation at 100; a full tutorial server fails explicitly instead of growing forever.

## Run the rules

```sh
npm run test:coverage
```

The unit tests exercise every winning line, both winning marks, a draw, illegal turns, stale revisions, invalid cells, nonmember access and capacity. They enforce 100% coverage of **game.ts**. Real-network tests run alongside them; that number is not a claim about every branch of the authentication or browser code.

**Checkpoint:** the referee works without a browser. Rooms and seats survive a disconnected socket, but not a server restart. That is a deliberate teaching boundary, not persistence.
