feat(mnswpr): type GameSession.onMove

This commit is contained in:
ayo 2026-07-17 22:56:51 +02:00
parent 310b657e32
commit a26c2c5055
3 changed files with 20 additions and 10 deletions

View file

@ -6,6 +6,7 @@
* Future home: @cozy-games/game-session. * Future home: @cozy-games/game-session.
* *
* @typedef {{ init: Function, apply: Function, status: Function, project: Function, serialize?: Function, deserialize?: Function, toMoveEvent?: Function }} Rules * @typedef {{ init: Function, apply: Function, status: Function, project: Function, serialize?: Function, deserialize?: Function, toMoveEvent?: Function }} Rules
* @typedef {import('../minesweeper/rules.js').MoveEvent} MoveEvent
*/ */
export class GameSession { export class GameSession {
/** /**
@ -56,8 +57,8 @@ export class GameSession {
}>; }>;
_t0: number; _t0: number;
_tEnd: number; _tEnd: number;
/** @type {Set<(event: object) => void>} */ /** @type {Set<(event: MoveEvent) => void>} */
_moveHandlers: Set<(event: object) => void>; _moveHandlers: Set<(event: MoveEvent) => void>;
_seq: number; _seq: number;
/** /**
* Subscribe to typed move-events one per effective move (reveal / flag / * Subscribe to typed move-events one per effective move (reveal / flag /
@ -65,10 +66,10 @@ export class GameSession {
* unsubscribe function. Pure in-process pub/sub: no DOM, no rendering. Requires * unsubscribe function. Pure in-process pub/sub: no DOM, no rendering. Requires
* the rules to implement `toMoveEvent`. * the rules to implement `toMoveEvent`.
* *
* @param {(event: object) => void} handler * @param {(event: MoveEvent) => void} handler
* @returns {() => void} unsubscribe * @returns {() => void} unsubscribe
*/ */
onMove(handler: (event: object) => void): () => void; onMove(handler: (event: MoveEvent) => void): () => void;
/** /**
* Apply a move: stamp it, fold it through the rules, and return the projected * Apply a move: stamp it, fold it through the rules, and return the projected
* view + events + authoritative elapsed time. * view + events + authoritative elapsed time.
@ -79,8 +80,8 @@ export class GameSession {
view: any; view: any;
time: number; time: number;
}; };
/** @param {object} event */ /** @param {MoveEvent} event */
_emitMove(event: object): void; _emitMove(event: MoveEvent): void;
status(): any; status(): any;
view(): any; view(): any;
log(): { log(): {
@ -136,3 +137,11 @@ export type Rules = {
deserialize?: Function; deserialize?: Function;
toMoveEvent?: Function; toMoveEvent?: Function;
}; };
/**
* Layer 1 owns lifecycle, the (injected) clock, and the move log; delegates all
* game meaning to an injected `rules` object. This is where timing authority
* lives: on the client `clock` is `Date.now` (cosmetic); on a server it is the
* server's clock (authoritative). The session never calls a wall clock itself.
* Future home:
*/
export type MoveEvent = import("../minesweeper/rules.js").MoveEvent;

View file

@ -8,6 +8,7 @@
* Future home: @cozy-games/game-session. * Future home: @cozy-games/game-session.
* *
* @typedef {{ init: Function, apply: Function, status: Function, project: Function, serialize?: Function, deserialize?: Function, toMoveEvent?: Function }} Rules * @typedef {{ init: Function, apply: Function, status: Function, project: Function, serialize?: Function, deserialize?: Function, toMoveEvent?: Function }} Rules
* @typedef {import('../minesweeper/rules.js').MoveEvent} MoveEvent
*/ */
export class GameSession { export class GameSession {
/** /**
@ -30,7 +31,7 @@ export class GameSession {
// Move-event emission: subscribers + a monotonic sequence counter (last seq // Move-event emission: subscribers + a monotonic sequence counter (last seq
// assigned; 0 = none yet). Seq is part of the snapshot so it keeps rising // assigned; 0 = none yet). Seq is part of the snapshot so it keeps rising
// across a resume rather than restarting. // across a resume rather than restarting.
/** @type {Set<(event: object) => void>} */ /** @type {Set<(event: MoveEvent) => void>} */
this._moveHandlers = new Set() this._moveHandlers = new Set()
this._seq = 0 this._seq = 0
} }
@ -41,7 +42,7 @@ export class GameSession {
* unsubscribe function. Pure in-process pub/sub: no DOM, no rendering. Requires * unsubscribe function. Pure in-process pub/sub: no DOM, no rendering. Requires
* the rules to implement `toMoveEvent`. * the rules to implement `toMoveEvent`.
* *
* @param {(event: object) => void} handler * @param {(event: MoveEvent) => void} handler
* @returns {() => void} unsubscribe * @returns {() => void} unsubscribe
*/ */
onMove(handler) { onMove(handler) {
@ -75,7 +76,7 @@ export class GameSession {
return { events, view: this.rules.project(state), time: this.elapsed() } return { events, view: this.rules.project(state), time: this.elapsed() }
} }
/** @param {object} event */ /** @param {MoveEvent} event */
_emitMove(event) { _emitMove(event) {
for (const handler of this._moveHandlers) handler(event) for (const handler of this._moveHandlers) handler(event)
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "@cozy-games/mnswpr", "name": "@cozy-games/mnswpr",
"version": "0.5.3", "version": "0.5.4",
"description": "Classic Minesweeper browser game", "description": "Classic Minesweeper browser game",
"author": "Ayo", "author": "Ayo",
"type": "module", "type": "module",