A seat's view carries its own move in waiting and its move in the last round it played

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-29 12:15:05 -04:00
co-authored by Claude Opus 5.5
parent 927e49cf79
commit 8e35d3a3a6
4 changed files with 28 additions and 2 deletions
+4 -1
View File
@@ -132,7 +132,10 @@ if it were never there.
- `seatsFor(options)`: fewer seats for some table options.
- `settled(input)`: whether an input is final. A game with drafts answers
false until the player says it is done; a draft may be replaced, and the
round waits for every seat's settled input.
round waits for every seat's settled input. A seat's view carries its
own input in waiting (`yourInput`), so a page reopened shows the draft,
and its input in the last round it played (`yourLastInput`), so the page
can tell it what became of its own choice.
- `tally(state)`: the game's own lines for the tally, from a finished game.
- `preferences`, `options`: the preferences panel's settings and the
host's table choices (above).
+13
View File
@@ -63,4 +63,17 @@ describe('rooms', () => {
rooms.submit(room, bo, { pick: 1, final: true }, 1);
expect(() => rooms.submit(room, bo, { pick: 2 }, 1)).toThrow(/not your move/);
});
it("shows a seat its own move in waiting and its last move played, and nobody else's", () => {
const { rooms, room, ann, bo } = table();
rooms.submit(room, ann, { pick: 5 }, 0);
expect(rooms.view(room, 'A').yourInput).toEqual({ pick: 5 });
expect(rooms.view(room, 'B').yourInput).toBeNull();
expect(rooms.view(room, '').yourInput).toBeNull();
rooms.submit(room, bo, { pick: 2 }, 0);
expect(rooms.view(room, 'A').yourInput).toBeNull();
expect(rooms.view(room, 'A').yourLastInput).toEqual({ pick: 5 });
expect(rooms.view(room, 'B').yourLastInput).toEqual({ pick: 2 });
expect(rooms.view(room, '').yourLastInput).toBeNull();
});
});
+7 -1
View File
@@ -48,6 +48,8 @@ export interface Room<State> {
state: State | null;
/** Inputs received for the round in progress, by seat. */
pending: Record<SeatId, unknown>;
/** Each seat's input in the last round it played, kept through rounds it had no part in; each seat is shown only its own. */
played: Record<SeatId, unknown>;
chat: ChatLine[];
/** Rounds played so far; a move names the count it was made against. */
resolved: number;
@@ -192,7 +194,7 @@ export class Rooms<State, Input> {
/** A table with nobody seated yet. */
private blank(id: string, size: number, createdAt: number, rematchOf: string | null, expected: string[], options: TableOptions, seq = 0): Room<State> {
return { id, size, createdAt, seats: [], galleryTalk: false, challenge: null, expected, rematch: null, rematchOf, options, over: false, state: null, pending: {}, chat: [], resolved: 0, seq, updatedAt: createdAt };
return { id, size, createdAt, seats: [], galleryTalk: false, challenge: null, expected, rematch: null, rematchOf, options, over: false, state: null, pending: {}, played: {}, chat: [], resolved: 0, seq, updatedAt: createdAt };
}
/** Open a table with its first seat taken; a rematch names the table it follows and holds seats for its players. */
@@ -418,6 +420,7 @@ export class Rooms<State, Input> {
/** The view for a seat, or for the gallery when the seat is SPECTATOR. */
view(room: Room<State>, seatId: SeatId): RoomView<State> {
const seated = seatId !== SPECTATOR;
return {
roomId: room.id,
seq: room.seq,
@@ -430,6 +433,8 @@ export class Rooms<State, Input> {
resolved: room.resolved,
over: room.state ? this.game.over(room.state) : null,
state: room.state ? this.game.view(room.state, seatId) : null,
yourInput: seated && seatId in room.pending ? room.pending[seatId] : null,
yourLastInput: seated && seatId in room.played ? room.played[seatId] : null,
chat: room.chat,
audience: this.gallery.get(room.id) ?? 0,
galleryTalk: room.galleryTalk,
@@ -512,6 +517,7 @@ export class Rooms<State, Input> {
case 'turn':
if (!room.state) return;
room.state = this.game.resolve(room.state, line.inputs as Record<SeatId, Input>);
room.played = { ...room.played, ...(line.inputs as Record<SeatId, unknown>) };
room.pending = {};
room.resolved += 1;
break;
+4
View File
@@ -29,6 +29,10 @@ export interface RoomView<State> {
turn: number | null;
over: Outcome | null;
state: State | null;
/** This seat's input for the round in progress, as the server holds it (a draft, or a settled move); null for none, and for the gallery. */
yourInput: unknown;
/** This seat's input in the last round it played, for telling it what became of its own choice; null for none, and for the gallery. */
yourLastInput: unknown;
chat: ChatLine[];
/** How many watch from the Peanut Gallery. */
audience: number;