The host begins at every table size and may send a bot away
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
457a536f58
commit
ddb18cb08a
@@ -5,6 +5,7 @@
|
||||
// POST /api/rooms/:id/join {name} take the next seat
|
||||
// POST /api/rooms/:id/bot {token} seat a bot (any seated player may, before the game begins)
|
||||
// POST /api/rooms/:id/begin {token} the host begins with the players seated so far
|
||||
// POST /api/rooms/:id/unseat {token, seat} the host sends a bot away before the game begins
|
||||
// GET /api/rooms/:id?token= the view for that seat; without a token, the gallery's view
|
||||
// POST /api/rooms/:id/turn {token, input} this seat's move
|
||||
// POST /api/rooms/:id/say {token, text} table talk, from a seat to the whole room
|
||||
@@ -193,6 +194,10 @@ async function handle(req: IncomingMessage, res: ServerResponse): Promise<void>
|
||||
rooms.addBot(room, token);
|
||||
return send(res, 200, rooms.view(room, rooms.seatOf(room, token).id));
|
||||
}
|
||||
if (action === 'unseat') {
|
||||
rooms.unseat(room, token, body.seat);
|
||||
return send(res, 200, rooms.view(room, rooms.seatOf(room, token).id));
|
||||
}
|
||||
if (action === 'begin') {
|
||||
rooms.begin(room, token);
|
||||
return send(res, 200, rooms.view(room, rooms.seatOf(room, token).id));
|
||||
|
||||
@@ -148,7 +148,7 @@ export class Rooms<State, Input> {
|
||||
return this.sit(room, name, false);
|
||||
}
|
||||
|
||||
/** The host, who opened the table, begins once enough are seated. A full table begins by itself. */
|
||||
/** The host, who opened the table, begins once enough are seated, however full the table. */
|
||||
begin(room: Room<State>, token: string | undefined): void {
|
||||
const seat = this.seatOf(room, token);
|
||||
if (seat.id !== room.seats[0]?.id) throw new RoomError('Only the player who opened the table may begin.', 403);
|
||||
@@ -170,12 +170,22 @@ export class Rooms<State, Input> {
|
||||
if (room.seats.length >= room.size) throw new RoomError('Every seat at this table is taken.', 409);
|
||||
const name = cleanName(rawName);
|
||||
if (room.seats.some((s) => s.name.toLowerCase() === name.toLowerCase())) throw new RoomError('Another player here already has that name.', 409);
|
||||
const seat: Seat = { id: this.game.seatIds[room.seats.length], name, token: bot ? '' : newId(18), bot };
|
||||
const id = this.game.seatIds.find((s) => !room.seats.some((taken) => taken.id === s))!;
|
||||
const seat: Seat = { id, name, token: bot ? '' : newId(18), bot };
|
||||
this.commit(room, { t: 'seat', id: seat.id, name, token: seat.token, bot });
|
||||
if (room.seats.length === room.size) this.commit(room, { t: 'start', seed: newSeed(), rules: this.game.currentRules });
|
||||
return seat;
|
||||
}
|
||||
|
||||
/** The host sends a bot away before the game begins. People leave by not coming back. */
|
||||
unseat(room: Room<State>, token: string | undefined, seatId: unknown): void {
|
||||
const host = this.seatOf(room, token);
|
||||
if (host.id !== room.seats[0]?.id) throw new RoomError('Only the player who opened the table may send a bot away.', 403);
|
||||
if (room.state) throw new RoomError('The game has begun; the table is set.', 409);
|
||||
const seat = room.seats.find((s) => s.id === seatId);
|
||||
if (!seat || !seat.bot) throw new RoomError('Only a bot can be sent away.', 400);
|
||||
this.commit(room, { t: 'unseat', id: seat.id });
|
||||
}
|
||||
|
||||
/** Record a seat's move; resolve the round once nobody else is awaited. */
|
||||
submit(room: Room<State>, token: string | undefined, raw: unknown): void {
|
||||
const seat = this.seatOf(room, token);
|
||||
@@ -287,6 +297,9 @@ export class Rooms<State, Input> {
|
||||
case 'seat':
|
||||
room.seats.push({ id: line.id, name: line.name, token: line.token, bot: line.bot });
|
||||
break;
|
||||
case 'unseat':
|
||||
room.seats = room.seats.filter((s) => s.id !== line.id);
|
||||
break;
|
||||
case 'start':
|
||||
room.state = this.game.create(Object.fromEntries(room.seats.map((s) => [s.id, s.name])), line.seed, line.rules ?? 1);
|
||||
break;
|
||||
|
||||
@@ -9,6 +9,8 @@ import type { SeatId } from '../../src/lib/game/spec';
|
||||
export type LedgerLine =
|
||||
| { t: 'room'; id: string; seats: number; createdAt: number }
|
||||
| { t: 'seat'; id: SeatId; name: string; token: string; bot: boolean }
|
||||
/** A bot sent away by the host before the game began; its seat id is free again. */
|
||||
| { t: 'unseat'; id: SeatId }
|
||||
/** Ledgers from before revisions were recorded resolve under rules 1. */
|
||||
| { t: 'start'; seed: number; rules?: number }
|
||||
| { t: 'turn'; at: number; inputs: Record<SeatId, unknown> }
|
||||
|
||||
Reference in New Issue
Block a user