Play now against the bot opens a room the server keeps

Every duel then leaves a ledger and shows in the visitors digest; the
offline duel in the browser remains as a fallback.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141G6xqLeNRYEtviLWSB5Up
This commit is contained in:
Eric Wagoner
2026-09-22 21:56:30 -04:00
co-authored by Claude Fable 5.1
parent 098dadf482
commit 885dcf56e6
2 changed files with 32 additions and 9 deletions
+23 -7
View File
@@ -32,6 +32,20 @@
});
async function playBot() {
busy = true;
error = '';
try {
rememberName(name);
const remote = await Duel.createBotRoom(name.trim());
await goto(`/join/${remote.roomId}`);
} catch (e) {
error = e instanceof Error ? e.message : 'The duel could not be opened.';
} finally {
busy = false;
}
}
async function playOffline() {
rememberName(name);
await goto('/play');
}
@@ -66,7 +80,7 @@
const g = games[held.roomId];
if (g === undefined) return 'looking';
if (g === null) return 'unreachable';
const others = g.seats.filter((s) => s.id !== held.seat).map((s) => s.name);
const others = g.seats.filter((s) => s.id !== held.seat).map((s) => (s.bot ? `${s.name} (the bot)` : s.name));
if (!g.state) return `waiting to start${g.seats.length ? ` · ${g.seats.map((s) => s.name).join(', ')}` : ''}`;
if (g.state.over) {
if (g.state.over.winner === held.seat) return 'you won';
@@ -105,12 +119,14 @@
<span>Your wizard's name</span>
<input type="text" bind:value={name} maxlength={NAME_MAX} placeholder="e.g. Morwenna" autocomplete="nickname" />
</label>
<button type="button" class="reveal primary" disabled={!ready} onclick={playBot}>
{local ? `Continue against ${local.foe.name} (turn ${local.turnNumber})` : 'Play now against the bot'}
</button>
<button type="button" class="reveal primary" disabled={!ready || busy} onclick={playBot}>Play now against the bot</button>
<p class="muted small">
{#if local}
<p class="muted small">Or start afresh from the board's "New duel".</p>
<button type="button" class="link" onclick={playOffline}>Continue your offline duel against {local.foe.name} (turn {local.turnNumber})</button>
{:else}
<button type="button" class="link" onclick={playOffline}>Or play offline, in this browser only</button>
{/if}
</p>
<div class="ways">
<button type="button" class="quiet" disabled={!ready || busy} onclick={create}>Create a duel</button>
<form class="joinform" onsubmit={join}>
@@ -149,8 +165,8 @@
{#if local}
<div class="ledger-row your-move">
<a class="ledger-resume" href="/play">
<span class="ledger-code">bot</span>
<span class="ledger-info">your move · turn {local.turnNumber} · against {local.foe.name}</span>
<span class="ledger-code">here</span>
<span class="ledger-info">offline · your move · turn {local.turnNumber} · against {local.foe.name}</span>
</a>
</div>
{/if}
+7
View File
@@ -794,6 +794,13 @@ export class Duel {
return remoteFrom(seated.view, seated.token);
}
/** A room with the bot already seated: a duel the server keeps, so it can be replayed and seen. */
static async createBotRoom(name: string): Promise<Remote> {
const seated = await api.create(name, 2);
rememberSeat(seated.view.roomId, { seat: seated.seat, token: seated.token });
return remoteFrom(await api.addBot(seated.view.roomId, seated.token), seated.token);
}
static async joinRoom(roomId: string, name: string): Promise<Remote> {
const seated = await api.join(roomId, name);
rememberSeat(roomId, { seat: seated.seat, token: seated.token });