Hashed seat tokens, ledgered inputs, refusable moves, disk-checked codes, host-only bots, and a drift script
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
ddb18cb08a
commit
c934bbe434
@@ -87,9 +87,8 @@ export const game: GameSpec<State, Input> = {
|
||||
view: (state, viewer) => (viewer === SPECTATOR ? structuredClone(state) : structuredClone(state)),
|
||||
// A function of the state alone: the seed, the round and the seat, so a replay draws the same pick.
|
||||
botInput: (state, seat) => ({ pick: 1 + Math.floor(random(state.rng + state.rounds.length * 7919 + state.seats.indexOf(seat)) * HIGHEST) }),
|
||||
cleanInput: (raw) => {
|
||||
const pick = Number((raw as { pick?: unknown })?.pick);
|
||||
return { pick: Number.isInteger(pick) && pick >= 1 && pick <= HIGHEST ? pick : 1 };
|
||||
},
|
||||
cleanInput: (raw) => ({ pick: Number((raw as { pick?: unknown })?.pick) }),
|
||||
validate: (_state, _seat, input) =>
|
||||
Number.isInteger(input.pick) && input.pick >= 1 && input.pick <= HIGHEST ? null : `Name a number from 1 to ${HIGHEST}.`,
|
||||
nameOf: (state, seat) => state.players[seat]?.name ?? seat
|
||||
};
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
// inputs at a time, ask who still has to move, and hand each viewer the
|
||||
// view they are allowed to see. A new game implements GameSpec once, in
|
||||
// src/lib/game/index.ts, and the rest of the kit works unchanged.
|
||||
//
|
||||
// The shape is simultaneous rounds: every seat that needs input submits, then
|
||||
// the round resolves. Turn-at-a-time games fit too (only one seat needs input
|
||||
// at once). A game of single commands with out-of-turn interruptions, like
|
||||
// Wiz-War, does not fit this contract and needs its own server.
|
||||
|
||||
/** A seat at the table: a single letter from GameSpec.seatIds. */
|
||||
export type SeatId = string;
|
||||
@@ -43,6 +48,8 @@ export interface GameSpec<State, Input> {
|
||||
botInput(state: State, seat: SeatId): Input;
|
||||
/** Only the shapes the engine understands get through; the engine validates the rest. */
|
||||
cleanInput(raw: unknown): Input;
|
||||
/** Why this seat may not make this move now, or null when it may. The server answers with the reason. */
|
||||
validate?(state: State, seat: SeatId, input: Input): string | null;
|
||||
/** A seat's name from the state, for the hall and the chronicle. */
|
||||
nameOf(state: State, seat: SeatId): string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user