57 lines
2.2 KiB
JavaScript
57 lines
2.2 KiB
JavaScript
// @ts-check
|
|
import { MinesweeperRules } from '../core/minesweeper/rules.js'
|
|
import { toMove } from './replay-common.js'
|
|
|
|
/**
|
|
* @typedef {import('../core/minesweeper/rules.js').MoveEvent} MnswprMoveEvent
|
|
* @typedef {import('../core/minesweeper/board.js').Layout} Layout
|
|
* @typedef {{ mine: boolean, adjacent: number, status: 'hidden' | 'flagged' | 'revealed' }} BoardCell
|
|
* @typedef {{ rows: number, cols: number, phase: string, revealedSafe: number, cells: BoardCell[][] }} BoardState
|
|
*/
|
|
|
|
/**
|
|
* The full-board state reducer for Minesweeper — mnswpr's implementation of the
|
|
* replay engine's `StateReducer<MnswprMoveEvent, BoardState>` seam (replay-05).
|
|
* Given the ordered slice of move-events played so far, it reconstructs the
|
|
* COMPLETE board at that point: every cell's mine/adjacent/status plus the phase.
|
|
*
|
|
* Like the progress reducer, it takes the board as closure input and replays the
|
|
* moves through the pure core rules — so reveals flood, chords open their
|
|
* neighbors, and flags toggle — giving an exact reconstruction at any event
|
|
* index. Statelessly a function of the slice, so the engine can jump (seek) to
|
|
* any position and rebuild the board there.
|
|
*
|
|
* @param {Layout} layout - the recorded board (as produced by `generateBoard`)
|
|
* @returns {(events: { event: MnswprMoveEvent }[]) => BoardState}
|
|
*/
|
|
export function createStateReducer(layout) {
|
|
return function state(events) {
|
|
let s = MinesweeperRules.fromLayout(layout)
|
|
for (const record of events) {
|
|
const move = toMove(record.event)
|
|
if (move) s = MinesweeperRules.apply(s, move).state
|
|
}
|
|
return toBoard(s)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Project a core game state into a plain, render-ready 2D board snapshot.
|
|
* @param {import('../core/minesweeper/rules.js').State} s
|
|
* @returns {BoardState}
|
|
*/
|
|
function toBoard(s) {
|
|
const { rows, cols } = s.config
|
|
/** @type {BoardCell[][]} */
|
|
const cells = []
|
|
for (let r = 0; r < rows; r++) {
|
|
/** @type {BoardCell[]} */
|
|
const row = []
|
|
for (let c = 0; c < cols; c++) {
|
|
const cell = s.grid.at(r, c)
|
|
row.push({ mine: cell.mine, adjacent: cell.adjacent, status: cell.status })
|
|
}
|
|
cells.push(row)
|
|
}
|
|
return { rows, cols, phase: s.phase, revealedSafe: s.revealedSafe, cells }
|
|
}
|