A table's options and its size are read one way, in spec.ts, by the server, the tally and the replay; a game may seat fewer for some options
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
faa2845e70
commit
6133a2469a
@@ -14,6 +14,7 @@
|
||||
import type { Report, RoomView, Tally } from '$lib/net/view';
|
||||
import type { State } from '$lib/game';
|
||||
import { game } from '$lib/game';
|
||||
import { tableSize } from '$lib/game/spec';
|
||||
|
||||
let name = $state(playerName());
|
||||
let code = $state('');
|
||||
@@ -219,7 +220,7 @@
|
||||
{/if}
|
||||
<button type="button" class="commit primary" disabled={!ready || busy} onclick={() => open(() => Room.createWithBot(name.trim(), options))}>Play now against the bot</button>
|
||||
<div class="ways">
|
||||
<button type="button" class="quiet" disabled={!ready || busy} onclick={() => open(() => Room.create(name.trim(), game.seatIds.length, options))}>Open a table</button>
|
||||
<button type="button" class="quiet" disabled={!ready || busy} onclick={() => open(() => Room.create(name.trim(), tableSize(game, options), options))}>Open a table</button>
|
||||
<form class="joinform" onsubmit={join}>
|
||||
<span class="or">or join one</span>
|
||||
<input type="text" class="code" bind:value={code} maxlength="4" placeholder="CODE" autocapitalize="characters" autocomplete="off" aria-label="room code" />
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { cleanOptions, tableSize, type TableOption } from './spec';
|
||||
|
||||
const options: TableOption[] = [
|
||||
{ key: 'length', label: 'Length', choices: [{ value: 'short', label: 'Short' }, { value: 'long', label: 'Long' }], default: 'short' }
|
||||
];
|
||||
|
||||
describe('table options', () => {
|
||||
it('keeps only declared options and offered choices, the rest at their defaults', () => {
|
||||
expect(cleanOptions({ options }, { length: 'long', colour: 'red' })).toEqual({ length: 'long' });
|
||||
expect(cleanOptions({ options }, { length: 'endless' })).toEqual({ length: 'short' });
|
||||
expect(cleanOptions({ options }, null)).toEqual({ length: 'short' });
|
||||
});
|
||||
|
||||
it('seats as many as the options allow, never more than the seats there are', () => {
|
||||
const seatIds = ['A', 'B', 'C', 'D'];
|
||||
expect(tableSize({ seatIds, options })).toBe(4);
|
||||
const seatsFor = (o: Record<string, string>) => (o.length === 'short' ? 2 : 9);
|
||||
expect(tableSize({ seatIds, options, seatsFor }, { length: 'short' })).toBe(2);
|
||||
expect(tableSize({ seatIds, options, seatsFor }, { length: 'long' })).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -48,6 +48,8 @@ export interface Preference {
|
||||
export interface GameSpec<State, Input> {
|
||||
/** Seats in table order; the table takes at most this many. */
|
||||
seatIds: SeatId[];
|
||||
/** The most seats a table with these options takes, when an option makes it fewer than `seatIds`. */
|
||||
seatsFor?(options: TableOptions): number;
|
||||
/** What the host may choose when opening a table; empty for a game with one way to play. */
|
||||
options?: TableOption[];
|
||||
/** The game's own settings for the preferences panel. */
|
||||
@@ -83,3 +85,24 @@ export interface GameSpec<State, Input> {
|
||||
/** The game's own lines for the hall's tally, counted from a finished game: a label and how many it adds. */
|
||||
tally?(state: State): Record<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A table's options as the game will play them: only the options it
|
||||
* declares, only the choices it offers, the rest at their defaults. The
|
||||
* server, the tally and the ledger replay all read a room line through this,
|
||||
* so each creates the same game from the same ledger.
|
||||
*/
|
||||
export function cleanOptions(game: Pick<GameSpec<unknown, unknown>, 'options'>, raw: unknown): TableOptions {
|
||||
const given = (typeof raw === 'object' && raw !== null ? raw : {}) as Record<string, unknown>;
|
||||
const options: TableOptions = {};
|
||||
for (const o of game.options ?? []) {
|
||||
const v = given[o.key];
|
||||
options[o.key] = typeof v === 'string' && o.choices.some((c) => c.value === v) ? v : o.default;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/** The most seats a table with these options takes. */
|
||||
export function tableSize(game: Pick<GameSpec<unknown, unknown>, 'options' | 'seatIds' | 'seatsFor'>, raw?: unknown): number {
|
||||
return Math.min(game.seatIds.length, game.seatsFor?.(cleanOptions(game, raw)) ?? game.seatIds.length);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user