Skip to content

Engine cleanups (closes #1 #2 #3 #4 #6 #7)#16

Merged
mellonis merged 2 commits into
masterfrom
feat/engine-cleanups
May 19, 2026
Merged

Engine cleanups (closes #1 #2 #3 #4 #6 #7)#16
mellonis merged 2 commits into
masterfrom
feat/engine-cleanups

Conversation

@mellonis

Copy link
Copy Markdown
Owner

Sweeps six of the small engine issues filed back in #1#7. Issue #5 (first-click safety) already landed in earlier work, so it's skipped.

Summary

  • Fix off-by-one in Minesweeper.#checkCol / #checkRow #1 Minesweeper.#checkCol / #checkRow used col > #cols instead of col >= #cols. Valid indices are 0..cols-1; col === cols slipped through the guard and crashed on field access. Same off-by-one in #checkRow. Fix: >>=.
  • Add negative bounds checks in #checkIndex / #checkCol / #checkRow #2 None of #checkIndex / #checkCol / #checkRow rejected negative numbers. Number.isInteger(-1) is true and -1 > cols is false, so field[-1] slipped through to a crash. Added explicit < 0 checks.
  • Pass isGameEnded=true from #lose() to avoid redundant flood-fills #3 #lose() called cell.reveal() with no arg, so each unrevealed empty cell triggered a zero-neighbour flood-fill cascade — already-revealed cells short-circuited, so the visible result was correct, but it ran thousands of redundant recursive calls per loss. Pass true to skip the per-cell cascade.
  • Drop dead isGameOver argument in Minesweeper.reveal() #4 Minesweeper.reveal() passed this.#isGameOver to cell.reveal() after an early-return on this.#isGameOver, so the arg was always false. Dropped.
  • Remove redundant !isMarked check in Cell.mark() #6 Cell.mark()'s second branch had if (!#isRevealed && !#isMarked), but the first branch already returns when #isMarked is true. Dropped the tautological !#isMarked.
  • Make win check O(1) by tracking #revealedCount #7 Win check iterated the entire field on every reveal. Cell.reveal() now returns the count of newly-revealed cells (summing the transitive flood-fill reveals); Minesweeper tracks #revealedCount and wins when it equals cols*rows - mines. cell.isMine via the existing getter replaces the previous boolean return as the lose signal. Two test assertions updated to match the new number return.

Test plan

  • npm run build clean (no type errors)
  • npm run lint clean
  • npm test — 30/30 passing (test assertions for Cell.reveal's return type updated)
  • Manual: tested in browser at http://localhost:5173 prior to push — preset clicks, mid-game reveal, win and lose flows all work as before; no observable behavior change, just less wasted work and tighter validation.

Closes #1, closes #2, closes #3, closes #4, closes #6, closes #7.

mellonis added 2 commits May 19, 2026 09:25
…#1 #2 #3 #4 #6 #7)

* #1 Off-by-one in #checkCol / #checkRow: `col > #cols` → `col >= #cols`.
  Valid columns are 0..cols-1; col === cols previously slipped through
  the guard and then crashed on this.#field[cols].

* #2 Negative bounds in #checkIndex / #checkCol / #checkRow: added
  `< 0` checks alongside the upper bounds. Number.isInteger(-1) is true
  and -1 > cols is false, so negatives also slipped through.

* #3 #lose() now passes cell.reveal(true) so each cell reveals locally
  without triggering its zero-neighbour flood-fill. Result was correct
  before (already-revealed cells short-circuit) but it ran a recursive
  cascade per empty cell.

* #4 Dropped dead `this.#isGameOver` arg passed to cell.reveal() in
  Minesweeper.reveal — the early-return at the top of reveal already
  guarantees #isGameOver is false here.

* #6 Removed redundant `&& !this.#isMarked` from Cell.mark()'s second
  branch — the first branch already returns when #isMarked is true.

* #7 Win check is now O(1). Cell.reveal() returns the count of newly
  revealed cells (summing transitive flood-fill reveals); Minesweeper
  tracks #revealedCount and wins when it equals cols*rows - mines.
  cell.isMine via the existing getter replaces the previous boolean
  return as the lose signal. Tests updated to match the number return.
Covers the regressions the engine-cleanup commit could re-introduce
and exercises code paths previously untested:

* bounds validation — col / row / index out of range (high and low)
  throw with the expected message. Locks in fixes #1 and #2.

* win flow — 3x3 with 8 mines, first-click center is the only
  non-mine, so a single reveal triggers win. Exercises the new
  #revealedCount tracking from #7 without needing Math.random mocking.

* lose flow — 3x3 with 7 mines, Math.random pinned to 0 so the lone
  other non-mine ends up at idx 8 and idx 0 is a mine. After the safe
  first-click center reveal, clicking idx 0 loses; snapshot exposes
  6 mines + 1 explosion. Covers #3's reveal(true) path indirectly.

Test count: 30 → 39. Coverage on minesweeper.ts: 86% → 96% statements.
@mellonis mellonis merged commit 1536075 into master May 19, 2026
1 check passed
@mellonis mellonis deleted the feat/engine-cleanups branch May 19, 2026 06:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant