feat(offline): hotseat creation roster + host-participate flow
NewGame offline 'with friends' now builds a local pass-and-play game: - lib/roster.ts: keep-last-valid name + roster->seats (unit-tested). - NewGame.svelte: master host-PIN gate (rows disabled until set), 'are you playing too?' prompt seating the host at row 0, 2-4 player rows with inline name validation + optional per-seat PIN, add/remove (remove behind the master PIN), variant + multiple-words, Start. - PinPad: verification via a verify(pin) callback (UI-held lock at creation, source-held in-game) instead of a lock prop. - i18n: hotseat.* (en + ru); the offline mode selector is now shown offline.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
// Pure helpers for the offline hotseat (pass-and-play) creation roster: keeping a last-valid name as
|
||||
// a row is edited, and turning the finished roster into engine seats. Kept out of the component so it
|
||||
// is unit-testable; the form itself is exercised by the e2e layer.
|
||||
|
||||
import { validDisplayName } from './profileValidation';
|
||||
import type { PinLock } from './pin';
|
||||
import type { Seat } from './localgame/serialize';
|
||||
|
||||
/** RosterRow is one editable player row: the live input, the last valid name kept when the input
|
||||
* goes invalid, and the optional per-seat PIN lock. */
|
||||
export interface RosterRow {
|
||||
name: string;
|
||||
committed: string;
|
||||
pin?: PinLock;
|
||||
}
|
||||
|
||||
/** commitName returns the new last-valid name: the trimmed input when it is a valid display name,
|
||||
* otherwise prevCommitted unchanged — so editing a set name into an invalid one keeps the old one. */
|
||||
export function commitName(input: string, prevCommitted: string): string {
|
||||
return validDisplayName(input) ? input.trim() : prevCommitted;
|
||||
}
|
||||
|
||||
/** rosterReady reports whether the roster can start a game: 2-4 rows, each with a committed name. */
|
||||
export function rosterReady(rows: RosterRow[]): boolean {
|
||||
return rows.length >= 2 && rows.length <= 4 && rows.every((r) => r.committed !== '');
|
||||
}
|
||||
|
||||
/** buildSeats turns committed roster rows into engine seats — all human, account-less (local
|
||||
* players sharing a device), carrying each row's optional PIN. */
|
||||
export function buildSeats(rows: RosterRow[]): Seat[] {
|
||||
return rows.map((r): Seat => ({ kind: 'human', name: r.committed, pin: r.pin }));
|
||||
}
|
||||
Reference in New Issue
Block a user