The lobby tells a caller what hour it is where the keeper lives, and that other games can go on in another tab
WIZWAR_KEEPER_TZ names the zone (Eastern by default); the room carries it and the client words the hour by the minute. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
31bfcf7bc0
commit
35d805fda3
@@ -147,6 +147,8 @@ function receiveFeedbackImage(req: IncomingMessage, res: ServerResponse, reportI
|
||||
|
||||
/** Calls to the keeper: a real person's phone rings for each. */
|
||||
const challengesPerAddress = new SlidingLimit(3, 60 * 60 * 1000);
|
||||
/** Where the keeper sleeps, so a caller can tell four in the morning from four in the afternoon. */
|
||||
const KEEPER_ZONE = process.env.WIZWAR_KEEPER_TZ ?? "America/New_York";
|
||||
const PUBLIC_URL = (process.env.WIZWAR_PUBLIC_URL ?? "https://wizwar.kestrelsnest.social").replace(/\/$/, "");
|
||||
const reportsPerAddress = new SlidingLimit(6, 60 * 60 * 1000);
|
||||
const MAX_COMMAND_BYTES = 16384; // serialized game command
|
||||
@@ -594,6 +596,7 @@ function roomInfo(room: Room) {
|
||||
expansion: room.expansion,
|
||||
challenge: room.challenge ?? null,
|
||||
keeper: KEEPER,
|
||||
keeperZone: KEEPER_ZONE,
|
||||
colors: Object.fromEntries(room.colorChoices),
|
||||
bots: Object.fromEntries(
|
||||
// A mystery machine keeps its mood only while the game lives: once it
|
||||
|
||||
@@ -1723,6 +1723,23 @@
|
||||
/** The attack on the stack draws its sight line; a cast that resolved
|
||||
* at once (a creation, a curse) draws the line it was accepted on for a
|
||||
* few seconds, so the table can see how the aim was legal. */
|
||||
/** The hour where the keeper lives, in words, kept current by the minute. */
|
||||
let minuteTick = $state(0);
|
||||
$effect(() => {
|
||||
const id = setInterval(() => { minuteTick++; }, 60_000);
|
||||
return () => clearInterval(id);
|
||||
});
|
||||
const keeperHour = $derived.by(() => {
|
||||
void minuteTick;
|
||||
if (!net.keeperZone) return null;
|
||||
try {
|
||||
const hour = Number(new Intl.DateTimeFormat("en-US", { hour: "numeric", hour12: false, timeZone: net.keeperZone }).format(new Date())) % 24;
|
||||
const word = ["twelve", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven"][hour % 12];
|
||||
const part = hour < 5 ? "in the small hours" : hour < 12 ? "in the morning" : hour < 18 ? "in the afternoon" : "in the evening";
|
||||
return `${word} ${part}`;
|
||||
} catch { return null; }
|
||||
});
|
||||
|
||||
let castTrace = $state.raw<SightLine | null>(null);
|
||||
let castTraceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const sightTrace = $derived((view ? stackSightTrace(view) : null) ?? castTrace);
|
||||
@@ -2739,13 +2756,15 @@
|
||||
title="a raven flies to {net.keeper}, who keeps this table">
|
||||
⚔ Challenge {net.keeper}, the keeper</button>
|
||||
<div class="challenge-note">{net.keeper} keeps this table and answers every raven — sometimes within the minute,
|
||||
sometimes after a night's sleep. The seat is held either way.</div>
|
||||
sometimes after a night's sleep. {keeperHour ? `It is ${keeperHour} where ${net.keeper} lives.` : ""} The seat is held either way.</div>
|
||||
</div>
|
||||
{:else if net.challenge}
|
||||
<div class="challenge-note">The raven has flown to {net.keeper}, called by {net.challenge.by}. A seat is held for them:
|
||||
<div class="challenge-note">The raven has flown to {net.keeper}, called by {net.challenge.by}.
|
||||
{keeperHour ? `It is ${keeperHour} there.` : ""} A seat is held for them:
|
||||
if they can come now, they will take it; if they are at supper, or asleep, or off in the world, they will
|
||||
leave a word below when they can. Meanwhile the table is yours — seat a clockwork wizard, invite a friend,
|
||||
or leave the room on the ledger and come back to it later.</div>
|
||||
or open another game in a new tab and play on; this room keeps its place on your ledger until you come
|
||||
back, as long as nobody abandons it.</div>
|
||||
{/if}
|
||||
<!-- Talk before the boards flip: to say when, or to leave a word. -->
|
||||
{@const lobbyTalk = net.log.filter((l) => l.text.startsWith("\u{1F4AC}")).slice(-6)}
|
||||
|
||||
@@ -383,6 +383,8 @@ class Net {
|
||||
challenge = $state<{ by: string; at: string } | null>(null);
|
||||
/** The keeper's name, once the room has said it. */
|
||||
keeper = $state<string | null>(null);
|
||||
/** The keeper's time zone (IANA), for telling a caller what hour it is there. */
|
||||
keeperZone = $state<string | null>(null);
|
||||
view = $state<GameView | null>(null);
|
||||
log = $state<LogLine[]>([]);
|
||||
error = $state<string | null>(null);
|
||||
@@ -541,6 +543,7 @@ class Net {
|
||||
this.expected = msg.expected ?? [];
|
||||
this.challenge = msg.challenge ?? null;
|
||||
if (typeof msg.keeper === "string") this.keeper = msg.keeper;
|
||||
if (typeof msg.keeperZone === "string") this.keeperZone = msg.keeperZone;
|
||||
if (this.you && this.token) {
|
||||
const seat: Seat = { name: this.you, roomId: msg.roomId, token: this.token };
|
||||
localStorage.setItem(SEAT_KEY, JSON.stringify(seat));
|
||||
|
||||
Reference in New Issue
Block a user