Add custom-dimensions config panel (PR 2 of #12)#15
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Minesweeper.preview(cols, rows, mines)static factory returns a fully-revealed Snapshot using a placeMines helper extracted from#getMinesSet; newgetRevealedSnapshot()instance method exposes the running game's mines without triggering#lose.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.Commit map
c223533Add Minesweeper.preview, getRevealedSnapshot, and preview/flip widgets1c538cfAdd custom-dimensions config panel22681a8Add cascade flip animation to panel open and saveca9e6f2Add hit-test pick-canvas debug overlay toggle87cd391Animate cells on every new game start20fd12cAuto-snap mines to classic density when cols or rows change4c4c2a9Stack preset buttons vertically on narrow viewportsTest plan
npm run buildclean (no type errors)npm run lintcleannpm test— 30/30 passing (11 new tests coverMinesweeper.previewshape, validation, all-mines edge, 1×1 case, andgetRevealedSnapshotnon-mutation)