8881214213
Mechanical, behaviour-preserving removal of Stage N / TODO-N / phase (RN) references from comments, doc-comments, service READMEs, the current-state docs (ARCHITECTURE, FUNCTIONAL+_ru, TESTING, UI_DESIGN), config-file comments, and the .fbs/.proto schema comments. PLAN.md / PRERELEASE.md / CLAUDE.md keep the stage history. - Rename the only stage-named identifiers: registerStage8 -> registerSocialOps, registerStage11 -> registerLinkOps (gateway transcode). - Split stage6_test.go: TestEmailLoginFlow -> email_test.go, TestGuestAutoMatchLeavesNoStats (+ provisionGuest) -> account_test.go. - Regenerated proto bindings (push.pb.go, telegram_grpc.pb.go) from the de-staged .proto comments; FB Go/TS bindings unchanged (flatc strips schema comments). go build/vet/gofmt clean across modules; integration typecheck and pnpm check green.
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
// Draft (client-side composition) serialization, kept pure for unit tests. The server stores
|
|
// the JSON opaquely; only the client interprets {rack_order, board_tiles}. The rack
|
|
// order is a comma-joined permutation of the server rack's indices, in the player's visual
|
|
// order; the board tiles are the tiles laid but not yet submitted.
|
|
|
|
import type { Tile } from './model';
|
|
import type { PendingTile } from './placement';
|
|
|
|
interface DraftData {
|
|
rack_order: string;
|
|
board_tiles: Tile[];
|
|
}
|
|
|
|
/** serializeDraft builds the JSON to persist: the rack order and the pending board tiles. */
|
|
export function serializeDraft(rackOrder: number[], pending: PendingTile[]): string {
|
|
const data: DraftData = {
|
|
rack_order: rackOrder.join(','),
|
|
board_tiles: pending.map((p) => ({ row: p.row, col: p.col, letter: p.letter, blank: p.blank })),
|
|
};
|
|
return JSON.stringify(data);
|
|
}
|
|
|
|
/** parseDraft decodes a stored draft, or null when empty or malformed. */
|
|
export function parseDraft(json: string): { rackOrder: number[]; tiles: Tile[] } | null {
|
|
if (!json) return null;
|
|
try {
|
|
const d = JSON.parse(json) as Partial<DraftData>;
|
|
const rackOrder = String(d.rack_order ?? '')
|
|
.split(',')
|
|
.filter((s) => s !== '')
|
|
.map(Number);
|
|
const tiles = Array.isArray(d.board_tiles) ? d.board_tiles : [];
|
|
return { rackOrder, tiles };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* validRackOrder returns order when it is a permutation of [0, len), else null — so a stale
|
|
* order (the rack changed since the draft was saved) is ignored and the server order is kept.
|
|
*/
|
|
export function validRackOrder(order: number[], len: number): number[] | null {
|
|
if (order.length !== len) return null;
|
|
const seen = new Set<number>();
|
|
for (const i of order) {
|
|
if (!Number.isInteger(i) || i < 0 || i >= len || seen.has(i)) return null;
|
|
seen.add(i);
|
|
}
|
|
return order;
|
|
}
|
|
|
|
/**
|
|
* liveDraftTiles drops saved tiles whose cell is now occupied on the committed board (the
|
|
* opponent has since played there) — the position-only reconcile after a refresh.
|
|
*/
|
|
export function liveDraftTiles(tiles: Tile[], occupied: (row: number, col: number) => boolean): Tile[] {
|
|
return tiles.filter((t) => !occupied(t.row, t.col));
|
|
}
|