Skip to content

Add custom-dimensions config panel (PR 2 of #12)#15

Merged
mellonis merged 7 commits into
masterfrom
feat/board-config-panel
May 18, 2026
Merged

Add custom-dimensions config panel (PR 2 of #12)#15
mellonis merged 7 commits into
masterfrom
feat/board-config-panel

Conversation

@mellonis

Copy link
Copy Markdown
Owner

PR 2 of two staged PRs for #12 — adds the Custom… button, the modal config panel with sliders, the cascade-flip animation on every new game, and a small hit-test debug overlay.

Summary

  • Engine extensionMinesweeper.preview(cols, rows, mines) static factory returns a fully-revealed Snapshot using a placeMines helper extracted from #getMinesSet; new getRevealedSnapshot() instance method exposes the running game's mines without triggering #lose.
  • PreviewRenderer widget draws an entire revealed Snapshot with fit-to-bounds scaling; replaces the per-cell Button render path while the panel is open.
  • FlipAnimation widget plays a per-cell scaleY squish in a diagonal cascade from top-left, swapping the source sprite for the target at mid-flip.
  • ConfigPanel HTML modal with three range sliders (cols 5–40, rows 5–40, mines 1..cols*rows-9). Sliders auto-snap mines to the classic-density "ideal" count when cols/rows change; user can re-tune freely. 1Hz auto-shuffle while idle, paused during drag.
  • Game gains pause / resume / setPreviewSnapshot / applyConfigSave / setPickOverlay. Whole canvas (chrome + field) resizes to match the slider so the preview always renders at native CELL_SIZE. Aim reticle is hidden during pause and during the new-game cascade.
  • New-game cascade plays on initial load, preset clicks, and the smiley reset using a clear-field source (all flat revealed-empty cells flipping up into raised unrevealed). No mines or numbers leak.
  • Hit-test debug overlay — a checkbox in the panel blits the WidgetManager's internal color-keyed pick canvas onto the main canvas; state persists across panel open/close.
  • Mobile — preset buttons stack vertically at viewports ≤600px with full-width and bigger tap-target padding.

Commit map

  1. c223533 Add Minesweeper.preview, getRevealedSnapshot, and preview/flip widgets
  2. 1c538cf Add custom-dimensions config panel
  3. 22681a8 Add cascade flip animation to panel open and save
  4. ca9e6f2 Add hit-test pick-canvas debug overlay toggle
  5. 87cd391 Animate cells on every new game start
  6. 20fd12c Auto-snap mines to classic density when cols or rows change
  7. 4c4c2a9 Stack preset buttons vertically on narrow viewports

Test plan

  • npm run build clean (no type errors)
  • npm run lint clean
  • npm test — 30/30 passing (11 new tests cover Minesweeper.preview shape, validation, all-mines edge, 1×1 case, and getRevealedSnapshot non-mutation)
  • Browser-verified at 1400×900 desktop: preset clicks, Custom… open, slider drag with chrome resize, Save (close-flip animation), Cancel (instant snap back), auto-shuffle ticks, hit-test overlay toggle, aim hide during cascade + show after.
  • Mobile preset stacking verified via injected CSS (browser window can't shrink below OS minimum, so the actual media-query trigger needs a real mobile device or browser devtools).
  • Out of scope here — Responsive field for small viewports: scale-then-pan with minimap #13's job: at fractional CSS-scale on narrow viewports the rendering shows the same uneven pixel sizes as Add level preset buttons + fix mobile canvas hit-test/aspect-ratio #14. Fix is responsive cell-size + pan-with-snap.

mellonis added 7 commits May 18, 2026 22:53
Extract #getMinesSet into a module-level placeMines helper shared by both
the live-game first-reveal path and a new static Minesweeper.preview
factory that returns a fully-revealed Snapshot for arbitrary cols/rows/
mines. Add getRevealedSnapshot() instance method that exposes the
running game's mines and neighbour counts without triggering #lose.

Introduce two widgets that the upcoming config panel will mount on the
field area:

* PreviewRenderer — single Canvas widget that draws an entire revealed
  Snapshot with fit-to-bounds scaling. Replaces the per-cell Button
  render path while the panel is open.
* FlipAnimation — per-cell scaleY squish that transitions cells from
  one snapshot to another in a diagonal cascade from top-left.

Both widgets are unwired in this commit; later commits will mount them
from Game.

Refs #12.
Fourth Custom… button in the presets row opens an HTML modal panel
with three range sliders (cols 5–40, rows 5–40, mines 1..cols*rows-9).
While the panel is open:

* The running Minesweeper is paused (timer freezes at its last value).
  Aim reticle is hidden — the 45s idle-show path is also guarded so
  the aim doesn't reappear under the panel.
* Game mounts PreviewRenderer on the field area showing the running
  game's revealed snapshot, then the slider-driven preview snapshot
  as the user drags.
* When dims change, the whole canvas (chrome + field) resizes so the
  preview always renders at native CELL_SIZE — no tiny-cell preview
  squashed into the old field bounds.
* A 1Hz auto-shuffle re-randomizes the preview while idle; paused
  while a slider is being actively dragged.
* Save (or any preset click) discards the panel and starts a fresh
  game at the chosen dims. Cancel restores the stashed game and
  resumes its timer.

Panel is positioned bottom-center on narrow viewports, right-center
on >=900px so the canvas stays visible.

Refs #12.
When the panel opens, FlipAnimation transitions cells from the running
game's current view to the fully-revealed snapshot. Each cell flips
with a scaleY squish whose midpoint swaps the source sprite for the
target; per-cell start times stagger diagonally from the top-left,
producing a ~500ms cascade.

The same animation plays in reverse on Save: from the preview's last
visible snapshot into the fresh game's all-unrevealed cells. Cancel
stays instant (the close-flip semantics don't fit when preview dims
differ from the restored game's dims).

Slider interaction during a flip cancels the in-flight animation —
the user's intent is preview, not waiting out the cascade.

Refs #12.
A checkbox in the config panel turns on a debug overlay that blits
WidgetManager's internal color-keyed pick canvas onto the main canvas.
The pick canvas is the offscreen buffer the hit-test reads to resolve
mouse positions to widgets — each interactive widget gets a unique
RGB-encoded color (`#1`, `#2`, ...). The overlay exposes the
literal data the mechanism sees.

State persists across panel open/close so the overlay can stay on while
playing. Non-interactive widgets (chrome panels, counters) don't
participate in the pick canvas and stay clear.

Refs #12.
Initial load, preset clicks, and the smiley reset now play the same
cascade-flip the panel save uses. The source snapshot is a clear
field (all-revealed-empty cells, no mines or numbers) — cells appear
as flat squares that flip up into the raised unrevealed state, with
no spoilers in the cascade.

Aim reticle is hidden for the duration of the flip — it would
otherwise peek through the gaps between flipping cells — and revealed
again in the flip's onComplete callback. Fresh-game detection in the
render loop also gates on flipAnimation === null so it can't race
the cascade.
Changing the cols or rows slider also updates the mines slider to the
"ideal" mine count for the new field size, computed by linearly
interpolating density between the three classic levels:

* 9×9 → 12.3% density (matches Beginner's 10 mines)
* 16×16 → 15.6% density (matches Intermediate's 40)
* 30×16 → 20.6% density (matches Expert's 99)

Sizes outside the beginner–expert range clamp to the nearest density.
The mines slider stays freely tunable afterwards — the auto-snap only
fires when cols or rows changes, so a user can dial the mine count up
or down without it being clobbered.
At viewports <=600px wide, the presets row switches to a column with
each button at 100% width and a bigger touch-target padding (12px
vertical). The row stretches across the available width via
align-self: stretch overriding #root's align-items: center.

On wider viewports the buttons keep the original horizontal layout.
@mellonis mellonis merged commit ba30316 into master May 18, 2026
1 check passed
@mellonis mellonis deleted the feat/board-config-panel branch May 18, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant