A table for a dozen wizards, begun by its host
Rooms take up to twelve seats and the host begins the duel with whoever is seated; a full table begins by itself. The lobby is a roster with a bot seat and the host's begin button. The ledger pins the turn and your own columns and scrolls sideways to the rest; the phone bar shows every wizard's health; a wizard who is out watches the rest; each charmed wizard gets their own gesture; an unnamed spell goes at whoever hurt you last, else the weakest opponent; a stab has a target from the start. 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
79947e31ba
commit
58d7db106e
+21
-4
@@ -11,8 +11,9 @@ import { createGame, inDuel, type GameState, type TurnInput, type WizardId } fro
|
||||
import { viewFor, type RoomView } from '../../src/lib/game/view';
|
||||
import type { LedgerLine, Store } from './store';
|
||||
|
||||
const SEAT_IDS: WizardId[] = ['A', 'B', 'C', 'D'];
|
||||
const BOT_NAMES = ['Aldric', 'Morwenna', 'Thessaly', 'Gandric', 'Ysolde', 'Ormund', 'Corwin', 'Isaura'];
|
||||
/** A dozen seats at most: a turn resolves only when every seat has moved, and the slowest sets the pace. */
|
||||
const SEAT_IDS: WizardId[] = 'ABCDEFGHJKLM'.split('');
|
||||
const BOT_NAMES = ['Aldric', 'Morwenna', 'Thessaly', 'Gandric', 'Ysolde', 'Ormund', 'Corwin', 'Isaura', 'Bellamy', 'Hesper', 'Lucan', 'Wynne'];
|
||||
export const MAX_SEATS = SEAT_IDS.length;
|
||||
|
||||
export interface Seat {
|
||||
@@ -55,6 +56,10 @@ function newCode(): string {
|
||||
return [...bytes].map((b) => CODE_ALPHABET[b % CODE_ALPHABET.length]).join('');
|
||||
}
|
||||
|
||||
function newSeed(): number {
|
||||
return (Date.now() ^ (Math.random() * 0xffffffff)) >>> 0;
|
||||
}
|
||||
|
||||
export function normalizeCode(raw: string): string {
|
||||
return raw.trim().toUpperCase();
|
||||
}
|
||||
@@ -109,7 +114,7 @@ export class Rooms {
|
||||
}
|
||||
|
||||
create(name: string, size: number): { room: Room; seat: Seat } {
|
||||
if (!Number.isInteger(size) || size < 2 || size > MAX_SEATS) throw new RoomError(`A duel seats 2 to ${MAX_SEATS} wizards.`);
|
||||
if (!Number.isInteger(size) || size < 2 || size > MAX_SEATS) throw new RoomError(`A table seats 2 to ${MAX_SEATS} wizards.`);
|
||||
let id = newCode();
|
||||
while (this.rooms.has(id)) id = newCode();
|
||||
const room: Room = { id, size, createdAt: Date.now(), seats: [], state: null, pending: {}, seq: 0, updatedAt: Date.now() };
|
||||
@@ -120,11 +125,22 @@ export class Rooms {
|
||||
}
|
||||
|
||||
join(room: Room, name: string): Seat {
|
||||
if (room.state) throw new RoomError('The duel has begun; no more seats are taken.', 409);
|
||||
return this.sit(room, name, false);
|
||||
}
|
||||
|
||||
/** The host, who opened the table, begins the duel once at least two are seated. A full table begins by itself. */
|
||||
begin(room: Room, token: string | undefined): void {
|
||||
const seat = this.seatOf(room, token);
|
||||
if (seat.id !== room.seats[0]?.id) throw new RoomError('Only the wizard who opened the table may begin the duel.', 403);
|
||||
if (room.state) throw new RoomError('The duel has already begun.', 409);
|
||||
if (room.seats.length < 2) throw new RoomError('A duel needs at least two wizards.', 409);
|
||||
this.commit(room, { t: 'start', seed: newSeed() });
|
||||
}
|
||||
|
||||
addBot(room: Room, token: string | undefined): Seat {
|
||||
this.seatOf(room, token);
|
||||
if (room.state) throw new RoomError('The duel has begun; no more seats are taken.', 409);
|
||||
const taken = new Set(room.seats.map((s) => s.name.toLowerCase()));
|
||||
const pool = BOT_NAMES.filter((n) => !taken.has(n.toLowerCase()));
|
||||
const name = pool[Math.floor(Math.random() * pool.length)] ?? 'The Construct';
|
||||
@@ -137,7 +153,7 @@ export class Rooms {
|
||||
if (room.seats.some((s) => s.name.toLowerCase() === name.toLowerCase())) throw new RoomError('Another wizard here already has that name.', 409);
|
||||
const seat: Seat = { id: SEAT_IDS[room.seats.length], 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: (Date.now() ^ (Math.random() * 0xffffffff)) >>> 0 });
|
||||
if (room.seats.length === room.size) this.commit(room, { t: 'start', seed: newSeed() });
|
||||
return seat;
|
||||
}
|
||||
|
||||
@@ -175,6 +191,7 @@ export class Rooms {
|
||||
seq: room.seq,
|
||||
size: room.size,
|
||||
me: seatId,
|
||||
host: room.seats[0]?.id ?? seatId,
|
||||
seats: room.seats.map((s) => ({ id: s.id, name: s.name, bot: s.bot })),
|
||||
waitingOn: waitingOn(room),
|
||||
state: room.state ? viewFor(room.state, seatId) : null
|
||||
|
||||
Reference in New Issue
Block a user