The transferable ideas and the JavaScript traps, on one page. Keep it open while you build; revisit it for your next game.
The game lives in data (state). The screen is a render of that data. Input changes state; you re-render. Never reach into the screen to fake a change. Change the data and redraw, so the screen is always an honest mirror of the state.
clearTimeout).
ifs. Shorter,
readable, and portable to a bigger board.
true. You can still test it for truthiness.
| Part | Job | Example |
|---|---|---|
| State | Everything needed to resume the game. Plain variables. |
board, currentPlayer,
isGameOver, winningLine, scores,
difficulty
|
| render() | Read state, draw the whole screen. |
Rebuild cells from board; show status and score
|
| Input handler | Validate, change state, then render. | handleClick(i) |
| Rules | Guards and checks for what's allowed and what just happened. |
checkWin, isBoardFull, guard
clauses
|
| Reset | Restore every state variable to its start, then render. | resetGame() |
score endings: computer win +10 | human win -10 | draw 0
computer's turn -> take the MAX of the replies (wants high)
opponent's turn -> take the MIN of the replies (assume best play)
base case -> game over: return that ending's score
Recursion here is simulate-check-undo where "check" is another simulate-check-undo, until a finished game returns a real number that bubbles back up the tree. Because it assumes you always play your best, it never walks into a loss.
| Trap | Fix / rule |
|---|---|
arr.push[i] with square brackets |
() calls a function, [] looks up a
property. Use arr.push(i).
|
const el = element.addEventListener(...) |
addEventListener returns
undefined. Split "grab the element" from
"attach the listener."
|
Re-declaring const x in the same scope |
A SyntaxError blanks the entire file (it fails before running). If nothing renders, check the Console for a red SyntaxError. |
Using a variable declared inside { } outside
those braces
|
Block scope: a let/const only
lives inside the braces it was declared in.
|
if (!choice) when 0 is a valid
value
|
0 is "falsy." Use
if (choice === null) when zero is a real,
meaningful value.
|
Passing fn() instead of fn to a
listener or timer
|
Pass the function (no parentheses) to be called later.
fn() runs it now and passes its return value.
|
color: var var(--ink),
transform: uppercase, or a mistyped
#id
|
Invalid CSS is ignored with no error. Inspect the element; bad lines show struck-through in the Styles panel. |
Adding a class with className = "win" |
That replaces all classes. Use
classList.add("win") to add one without wiping
the rest.
|
| Tic-tac-toe | Snake |
|---|---|
state: a board array |
state: the snake body (array of cells) + food position + direction |
input: a click runs handleClick |
input: arrow keys change the direction |
| update: happens on each click |
update: happens on a timer (setInterval), so
the game animates itself
|
| render: rebuild cells from the board | render: draw the snake and food from state |
| rules: win and draw checks | rules: hit a wall or yourself = game over; eat food = grow |
Same skeleton. The only genuinely new idea in Snake is that the update step fires on a clock instead of waiting for input. You already own everything else.