Code — how this page is built

Under the hood

The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.


MinesweeperGameview on GitHub ↗

First-click-safe mine placement, adjacency counts, iterative flood-fill on zeros, flagging via right-click, and win/lose detection.

'use client';

import * as React from 'react';

import { Win98Button } from '@/components/ui/win-98-button';

const COLS = 9;
const ROWS = 9;
const MINES = 10;
const N = COLS * ROWS;
const NUM_COLORS = ['', '#2f6db0', '#6f7d4a', '#c2453a', '#9b5cc0', '#b8772e', '#4c8c7a', '#14110b', '#8a8f99'];

interface Cell {
  mine: boolean;
  rev: boolean;
  flag: boolean;
  adj: number;
}

const neighbors = (i: number) => {
  const r = Math.floor(i / COLS);
  const c = i % COLS;
  const out: number[] = [];
  for (let dr = -1; dr <= 1; dr++)
    for (let dc = -1; dc <= 1; dc++) {
      if (!dr && !dc) continue;
      const nr = r + dr;
      const nc = c + dc;
      if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS) out.push(nr * COLS + nc);
    }
  return out;
};

function makeBoard(safe: number): Cell[] {
  const mineSet = new Set<number>();
  while (mineSet.size < MINES) {
    const idx = Math.floor(Math.random() * N);
    if (idx !== safe && !neighbors(safe).includes(idx)) mineSet.add(idx);
  }
  const board: Cell[] = Array.from({ length: N }, (_, i) => ({
    mine: mineSet.has(i),
    rev: false,
    flag: false,
    adj: 0,
  }));
  board.forEach((cell, i) => {
    if (!cell.mine) cell.adj = neighbors(i).filter((j) => board[j].mine).length;
  });
  return board;
}

export function MinesweeperGame() {
  const [board, setBoard] = React.useState<Cell[] | null>(null);
  const [status, setStatus] = React.useState<'idle' | 'playing' | 'won' | 'lost'>('idle');

  const reset = () => {
    setBoard(null);
    setStatus('idle');
  };

  const reveal = (i: number) => {
    if (status === 'won' || status === 'lost') return;
    let b = board;
    if (!b) {
      b = makeBoard(i);
      setStatus('playing');
    }
    b = b.map((c) => ({ ...c }));
    if (b[i].flag || b[i].rev) return;
    if (b[i].mine) {
      b.forEach((c) => { if (c.mine) c.rev = true; });
      setBoard(b);
      setStatus('lost');
      return;
    }
    const stack = [i];
    while (stack.length) {
      const j = stack.pop()!;
      const cell = b[j];
      if (cell.rev || cell.flag) continue;
      cell.rev = true;
      if (cell.adj === 0) neighbors(j).forEach((k) => { if (!b![k].rev) stack.push(k); });
    }
    const revealed = b.filter((c) => c.rev).length;
    if (revealed === N - MINES) {
      setStatus('won');
      b.forEach((c) => { if (c.mine) c.flag = true; });
    }
    setBoard(b);
  };

  const flag = (e: React.MouseEvent, i: number) => {
    e.preventDefault();
    if (!board || status !== 'playing') return;
    const b = board.map((c) => ({ ...c }));
    if (!b[i].rev) b[i].flag = !b[i].flag;
    setBoard(b);
  };

  const cells: Cell[] = board ?? Array.from({ length: N }, () => ({ mine: false, rev: false, flag: false, adj: 0 }));
  const flags = cells.filter((c) => c.flag).length;

  return (
    <div className="flex flex-col items-center gap-5">
      <div className="flex w-full items-center justify-between font-mono text-sm" style={{ maxWidth: 360 }}>
        <span className="opacity-60">💣 {MINES - flags}</span>
        <span className="font-bold">
          {status === 'won' ? 'cleared!' : status === 'lost' ? 'boom' : ''}
        </span>
      </div>

      <div
        className="grid select-none rounded-md bg-foreground/10 p-1.5"
        style={{ gridTemplateColumns: `repeat(${COLS}, 1fr)`, gap: 3, width: 'min(88vw, 360px)' }}
      >
        {cells.map((c, i) => (
          <button
            key={i}
            type="button"
            onClick={() => reveal(i)}
            onContextMenu={(e) => flag(e, i)}
            className="flex aspect-square items-center justify-center rounded-[3px] text-sm font-bold"
            style={{
              background: c.rev ? (c.mine ? 'var(--accent-red)' : 'rgba(20,17,11,0.05)') : 'var(--tile)',
              color: NUM_COLORS[c.adj] || 'var(--ink)',
              boxShadow: c.rev ? 'none' : 'inset -1px -1px #0003, inset 1px 1px #fff8',
            }}
          >
            {c.flag ? '⚑' : c.rev ? (c.mine ? '💣' : c.adj || '') : ''}
          </button>
        ))}
      </div>

      <div className="flex items-center gap-3">
        <Win98Button onClick={reset} className="h-9 px-5">New game</Win98Button>
        <span className="font-mono text-[11px] uppercase tracking-[0.12em] opacity-45">
          click to reveal · right-click to flag
        </span>
      </div>
    </div>
  );
}
↖ kaspirius