Kerry Clements

Tic Tac Toe

Case study

Overview

A browser-based Tic Tac Toe game built with vanilla HTML, CSS, and JavaScript. This was one of my earliest projects. I returned to it recently to refactor the code and add new features, and the gap between the two versions is a useful marker of how my thinking has developed.

The original code

The original worked, but game state lived in the DOM. Rather than tracking the board in a JavaScript data structure, the code read .innerText back from the squares to figure out what had been played.

Win detection was the most visible symptom of this. Every square had its own block of conditional logic, amounting to over 90 lines of repetitive code. It also contained a bug: if the centre square was the last move to complete the middle row, no win was declared. That combination was missing from the centre square's branch.

The refactor

The fix was to separate state from the DOM. I introduced a board array as the single source of truth and a WIN_COMBINATIONS constant listing all eight winning lines as index triplets. Win detection became a five-line function. The 90-line checkIfWinner was gone and the bug was fixed.

Other changes followed from that foundation. CSS classes replaced inline style manipulation for win and draw highlighting. classList.add and classList.remove replaced classList.toggle, which is fragile if state ever gets out of sync. Loop variables got let. Reset was rewritten to always return to a consistent starting state.

Human vs computer mode

With the state model in place, adding a computer opponent was straightforward. The board array and WIN_COMBINATIONS constant were already everything the computer needed to reason about the game.

I added two difficulty levels, designed around how they feel to play rather than how they work technically:

  • Easy - picks a random empty square. Plays like a parent who sometimes lets their child win.
  • Hard - wins if it can, blocks if it must, otherwise picks randomly. Plays like a decent human opponent who occasionally loses focus.

I considered adding an unbeatable minimax mode but decided against it. Tic Tac Toe is a solved game - perfect play always draws, so an unbeatable opponent is only interesting if you want to prove you can draw every time. Hard mode is more fun.

Accessibility

A Stark WCAG 2.2 AA audit flagged one violation: the <section> wrapping the grid had an implicit ARIA region role but no accessible name. Adding aria-label="Game board" resolved it. The final audit on desktop finished at 97%, zero violations, 76 passed. A separate mobile audit caught a contrast failure on the turn message colour against the pink background, which was resolved by darkening the blue and lightening the background. Two potentials remain flagged but are not visible on Stark's free tier.

Why vanilla JS

This is a vanilla JS project alongside Jobs Done. I could have rewritten it in React but wanted to improve it on its own terms. The same principles apply regardless of framework: state managed separately from the UI, logic that is easy to follow and test, accessibility considered as part of the build.

What's next

  • Vitest unit tests for the game logic
  • Score tracking across games

Try it

tictactoe.kerryclements.com

View the code on GitHub