From da420cd15208599b6493d579a7b8201179cf2525 Mon Sep 17 00:00:00 2001 From: Liz Conlan Date: Tue, 3 Mar 2026 00:56:56 +0000 Subject: [PATCH 1/5] Add HTML5 boilerplate with CSS reset and JS skeleton Assisted by claude-sonnet-4-6 --- index.html | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..2016ae0 --- /dev/null +++ b/index.html @@ -0,0 +1,47 @@ + + + + + + Tic-Tac-Toe + + + + + +
+

Tic-Tac-Toe

+
+ + + + From 2a560af66596c375f3a507100834e9895b61127f Mon Sep 17 00:00:00 2001 From: Liz Conlan Date: Tue, 3 Mar 2026 00:57:30 +0000 Subject: [PATCH 2/5] Add game board UI: grid, status bar, restart button Assisted by claude-sonnet-4-6 --- index.html | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 2016ae0..b3d9b98 100644 --- a/index.html +++ b/index.html @@ -20,12 +20,102 @@ align-items: center; justify-content: center; - /* Readable base font with good contrast */ + /* System font stack — readable and accessible */ font-family: system-ui, -apple-system, sans-serif; font-size: 1rem; background: #f5f5f5; color: #1a1a1a; } + + /* ── App wrapper ──────────────────────────────────────────── */ + #app { + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + padding: 2rem 1rem; + width: 100%; + max-width: 400px; + } + + /* ── Title ────────────────────────────────────────────────── */ + h1 { + font-size: 2rem; /* 32px — prominent but not overwhelming */ + font-weight: 700; + letter-spacing: 0.03em; + color: #1a1a1a; + } + + /* ── Status bar ───────────────────────────────────────────── */ + /* Communicates current turn and game result to the player. + min-height prevents layout shift when text changes. */ + #status { + font-size: 1.25rem; /* 20px — large enough to read at a glance */ + font-weight: 500; + min-height: 2rem; + text-align: center; + color: #333; + } + + /* ── Board (3×3 CSS Grid) ─────────────────────────────────── */ + /* Using CSS Grid for a clean, equal-cell layout. + gap creates the visible grid lines without extra elements. */ + .board { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 4px; + background: #444; /* gap colour acts as grid lines */ + border: 4px solid #444; + border-radius: 4px; + width: 100%; + max-width: 360px; + } + + /* ── Cell ─────────────────────────────────────────────────── */ + /* Each cell must be at least 100×100px per spec. + aspect-ratio keeps cells square as the container resizes. */ + .cell { + background: #fff; + min-width: 100px; + min-height: 100px; + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 2.5rem; /* Large mark — readable at a glance */ + font-weight: 700; + cursor: pointer; + user-select: none; /* Prevent accidental text selection on rapid clicks */ + transition: background 0.1s ease; + } + + /* ── Restart button ───────────────────────────────────────── */ + /* Styled consistently with the board colour scheme. + Using a neutral dark colour that contrasts well (4.5:1+) on the button. */ + #restart { + font-family: inherit; /* Match body font — form elements don't inherit by default */ + font-size: 1rem; + font-weight: 600; + padding: 0.6rem 2rem; + border: 2px solid #444; + border-radius: 4px; + background: #fff; + color: #1a1a1a; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease; + } + + #restart:hover, + #restart:focus-visible { + background: #444; + color: #fff; + outline: none; + } + + #restart:focus-visible { + outline: 3px solid #0078d4; /* Keyboard-focus ring for accessibility */ + outline-offset: 2px; + } @@ -33,14 +123,23 @@

Tic-Tac-Toe

+ + +

Player X's turn

+ + +
+ + +
From 2dc5fc056e2b3bb9fcb1db2d6f1877b71da74e6d Mon Sep 17 00:00:00 2001 From: Liz Conlan Date: Tue, 3 Mar 2026 00:58:50 +0000 Subject: [PATCH 3/5] Add pure game logic: initState, makeMove, checkWinner, checkDraw Assisted by claude-sonnet-4-6 --- index.html | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index b3d9b98..a6d3f54 100644 --- a/index.html +++ b/index.html @@ -135,12 +135,149 @@

Tic-Tac-Toe

From e53519312873784d630d13eb7395549a5deb8f3c Mon Sep 17 00:00:00 2001 From: Liz Conlan Date: Tue, 3 Mar 2026 00:59:37 +0000 Subject: [PATCH 4/5] Add UI controller: render, cell clicks, restart wiring Assisted by claude-sonnet-4-6 --- index.html | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/index.html b/index.html index a6d3f54..19dccc9 100644 --- a/index.html +++ b/index.html @@ -278,6 +278,91 @@

Tic-Tac-Toe

console.log('Game engine self-test: all assertions passed'); })(); + + // ═══════════════════════════════════════════════════════════ + // UI CONTROLLER — all DOM access lives here + // Reads game state produced by the engine and reflects it + // in the DOM. Also wires up all user interaction. + // ═══════════════════════════════════════════════════════════ + + // Cache DOM references once — querying the DOM on every render + // is unnecessary overhead when the elements never change. + const boardEl = document.getElementById('board'); + const statusEl = document.getElementById('status'); + const restartEl = document.getElementById('restart'); + + // Application state — single mutable reference, replaced (not mutated) + // on every move so the engine's immutability contract is respected. + let state = initState(); + + /** + * render — synchronises the DOM with the current game state. + * Called after every state change (move or restart). + * Rebuilds cell elements each time for simplicity; at 9 cells + * this is negligible overhead. + */ + function render(s) { + // ── Board cells ──────────────────────────────────────── + boardEl.innerHTML = ''; // clear previous cells + + s.board.forEach((mark, index) => { + const cell = document.createElement('div'); + cell.className = 'cell'; + cell.setAttribute('role', 'gridcell'); + cell.setAttribute('aria-label', `Cell ${index + 1}${mark ? ', ' + mark : ''}`); + + if (mark) { + cell.textContent = mark; + cell.classList.add('taken', mark.toLowerCase()); // .x or .o for colour coding + } + + // Highlight winning cells + if (s.winningCells.includes(index)) { + cell.classList.add('winner'); + } + + // Disable interaction: occupied cells OR game over + // Using pointer-events + aria-disabled rather than a real