I just wrapped a wild sprint upgrading the ancient projects/minesweeper/index.html Minesweeper clone into something I’d actually show to a friend. The prompt read like a dare, and I took it personally. Here’s how it felt to wrestle the board into shape, tests, auto-player, cookies and all.

Getting the board to behave

The original file was one massive, monolithic <script> tag. Breaking it into modules felt like finally opening windows in a stuffy room. Spinning up MinesweeperUI let me orchestrate the DOM without losing my mind:

const ui = new MinesweeperUI(document);
ui.init();

That two-line handshake now kicks off a cascade of well-behaved event bindings, from difficulty toggles to download/import buttons.【F:projects/minesweeper/scripts/main.js†L1-L7】【F:projects/minesweeper/scripts/ui.js†L34-L78】

The UI refactor wasn’t just tidying. Wiring _applyRevealResult to feed status messages, trigger timers, and juggle face sprites meant juggling a dozen side effects. More than once I muttered at the screen while stepping through this branchy beast:

if (result.action === 'mine') {
  this._handleLoss();
  return;
}
if (this.game.status === GameStatus.WON) {
  this._handleWin(fromAuto ? 'auto' : this.mode);
} else if (guessed) {
  this._setFace('uncertain');
}

Watching the wrong emoji flash at the wrong time at 1 a.m. was peak debugging angst.【F:projects/minesweeper/scripts/ui.js†L209-L234】

Persistence pains and eventual triumph

Cookies were off the table, so I leaned on localStorage via a ScoreRepository. The merge logic had to honor existing heroes while welcoming imports, so I taught it to normalize incoming entries and slice to the top ten:

const merged = {
  human: sortScores([
    ...data.human,
    ...(Array.isArray(incoming.human) ? incoming.human.map(normalizeEntry) : []),
  ]).slice(0, 10),
  auto: sortScores([
    ...data.auto,
    ...(Array.isArray(incoming.auto) ? incoming.auto.map(normalizeEntry) : []),
  ]).slice(0, 10),
};

The first time I imported a file and silently nuked my leaderboard, I just stared at the console wondering why I do this to myself. Eventually the merge function stopped gaslighting me.【F:projects/minesweeper/scripts/scoreboard.js†L57-L104】

Writing tests with attitude

Node’s built-in test runner turned out to be the perfect soapbox for my late-night feelings. Exhibit A from game.test.js:

// This test is savage about regressions, because face-planting on click one is unacceptable.
assert.notEqual(game.field[0][0], MINE);

And yes, I absolutely left notes about my own mistakes:

// I can still feel the debugging angst from when I forgot this order of operations.

Future me deserves to remember the pain, and future regressions will get a swift smackdown.【F:tests/game.test.js†L1-L34】

The scoreboard suite guards against lost legends, while the auto-player test ensures my AI buddy can actually win a handcrafted board.【F:tests/scoreboard.test.js†L1-L41】【F:tests/autoPlayer.test.js†L1-L23】

Making the computer play itself

Implementing AutoPlayer felt like building a tiny deduction engine. The constraint solver splits the frontier into components, enumerates valid assignments, and even picks the least-risky guess when logic runs dry. The heart of it lives in _enumerateComponent:

search(0);
if (assignments.length === 0) {
  return null;
}
const mineTotals = Array(totalCells).fill(0);
assignments.forEach(assignment => {
  assignment.forEach((value, index) => {
    mineTotals[index] += value;
  });
});

When I finally watched the auto mode cruise through beginner and intermediate boards, I actually fist-pumped. The suspense of waiting for assignments.length to be non-zero was brutal, but so worth it.【F:projects/minesweeper/scripts/autoPlayer.js†L166-L296】

Lessons learned and self-evaluation

I learned (again) that refactoring is emotional labor. Modularizing the code gave me room to think; layering persistence, import/export, and automation forced me to confront every implicit assumption. Did I do a good job? I’m proud of the structure, the tests, and the fact that the leaderboards survive page reloads. But I’d love to tighten up the auto-player’s heuristics and give the UI a dark mode pass.

What’s next for me

After this minesweeper odyssey, I want to prototype a Sokoban-style puzzle with similar persistence tricks, maybe even explore procedural level generation. Beyond games, my broader ambition is to write tools that help people teach themselves—interactive tutorials, creative coding sandboxes, anything that shortens the feedback loop between curiosity and insight. One day I’d like to curate a digital playground of small, smart toys that nudge folks toward learning and laughter.

Until then, I’m savoring this win—and keeping my debugging comments sharp for the next late-night refactor.