The family list: every game of the Kestrel's Nest in one file, shown in each game's about panel
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
6dce3b82f0
commit
e846ddedc4
@@ -68,3 +68,12 @@ The contract is simultaneous rounds: every seat that needs input submits,
|
|||||||
then the round resolves; turn-at-a-time games fit as the case where one
|
then the round resolves; turn-at-a-time games fit as the case where one
|
||||||
seat needs input at a time. A game of single commands with out-of-turn
|
seat needs input at a time. A game of single commands with out-of-turn
|
||||||
interruptions, Wiz-War's shape, does not fit and keeps its own server.
|
interruptions, Wiz-War's shape, does not fit and keeps its own server.
|
||||||
|
|
||||||
|
## The family
|
||||||
|
|
||||||
|
`family.json` lists every game of the Kestrel's Nest: slug, name, address,
|
||||||
|
one-line pitch, player count and origin. Each game ships a copy as
|
||||||
|
`static/family.json` (the deploy script takes the kit's latest when the kit
|
||||||
|
is beside the game) and its about panel lists the others under *More games
|
||||||
|
at the Kestrel's Nest*. Add a game here, then deploy the games; nothing
|
||||||
|
else needs to know.
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"family": "the Kestrel's Nest",
|
||||||
|
"games": [
|
||||||
|
{
|
||||||
|
"slug": "wizwar",
|
||||||
|
"name": "Wiz-War",
|
||||||
|
"url": "https://wizwar.kestrelsnest.social",
|
||||||
|
"pitch": "Steal treasure. Blast through walls. Outwit your friends.",
|
||||||
|
"players": "2 to 6",
|
||||||
|
"origin": "Tom Jolly's board game, sixth edition, 1993"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "waving-hands",
|
||||||
|
"name": "Waving Hands",
|
||||||
|
"url": "https://hands.kestrelsnest.social",
|
||||||
|
"pitch": "Duelling wizards. Two hands each. The right run of gestures is a spell.",
|
||||||
|
"players": "2 to 12",
|
||||||
|
"origin": "Richard Bartle's pencil-and-paper duel, 1977"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -4,6 +4,10 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
HOST="${1:?usage: deploy.sh <droplet-ip-or-host>}"
|
HOST="${1:?usage: deploy.sh <droplet-ip-or-host>}"
|
||||||
|
|
||||||
|
# The family list is the kit's; a game deploys with the kit's latest copy when the kit is beside it.
|
||||||
|
KIT_FAMILY="$(dirname "$0")/../../game-kit/family.json"
|
||||||
|
[ -f "$KIT_FAMILY" ] && cp "$KIT_FAMILY" static/family.json
|
||||||
|
|
||||||
npm run check
|
npm run check
|
||||||
npm run check:server
|
npm run check:server
|
||||||
npm test
|
npm test
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// bot, open a table, join by code, the ledger of your tables, and the
|
// bot, open a table, join by code, the ledger of your tables, and the
|
||||||
// about panel with the rules, the guide and a way to reach the keeper.
|
// about panel with the rules, the guide and a way to reach the keeper.
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { otherGames, type FamilyGame } from '$lib/family';
|
||||||
import { allSeats, doubtSeat, forgetSeat, rememberSeat, ServerError, trustSeat, type Report } from '$lib/net/client';
|
import { allSeats, doubtSeat, forgetSeat, rememberSeat, ServerError, trustSeat, type Report } from '$lib/net/client';
|
||||||
import { api, NAME_MAX, playerName, rememberName, Room } from '$lib/net/room.svelte';
|
import { api, NAME_MAX, playerName, rememberName, Room } from '$lib/net/room.svelte';
|
||||||
import { unreadTalk } from '$lib/net/talk';
|
import { unreadTalk } from '$lib/net/talk';
|
||||||
@@ -17,6 +18,11 @@
|
|||||||
let seats = $state(allSeats());
|
let seats = $state(allSeats());
|
||||||
let games = $state<Record<string, RoomView<State> | null>>({});
|
let games = $state<Record<string, RoomView<State> | null>>({});
|
||||||
let aboutOpen = $state(false);
|
let aboutOpen = $state(false);
|
||||||
|
/** The other games of the Kestrel's Nest, for the about panel. */
|
||||||
|
let family = $state<FamilyGame[]>([]);
|
||||||
|
$effect(() => {
|
||||||
|
void otherGames('__SLUG__').then((g) => (family = g));
|
||||||
|
});
|
||||||
/** Reports this browser's seats have filed, with the keeper's replies. */
|
/** Reports this browser's seats have filed, with the keeper's replies. */
|
||||||
let reports = $state<Report[]>([]);
|
let reports = $state<Report[]>([]);
|
||||||
let answers = $state<Record<string, string>>({});
|
let answers = $state<Record<string, string>>({});
|
||||||
@@ -228,6 +234,14 @@
|
|||||||
<p>It is free and keeps no accounts. A game between people lives on a small server as an append-only ledger of moves, so it can be replayed from its first move, and each player is shown only what the rules let them see.</p>
|
<p>It is free and keeps no accounts. A game between people lives on a small server as an append-only ledger of moves, so it can be replayed from its first move, and each player is shown only what the rules let them see.</p>
|
||||||
<h3>Send word</h3>
|
<h3>Send word</h3>
|
||||||
<p>A rule read wrong, a bug, a game you would like to tell of: write to <a href="mailto:eric@ericwagoner.com">eric@ericwagoner.com</a>, <a href="https://bsky.app/profile/kestrelsnest.social" target="_blank" rel="noreferrer">@kestrelsnest.social</a> on Bluesky, or <a href="https://toots.kestrelsnest.social/@eric" target="_blank" rel="noreferrer">@eric@toots.kestrelsnest.social</a> on Mastodon. The keeper of this hall roosts at <a href="https://kestrelsnest.social" target="_blank" rel="noreferrer">kestrelsnest.social</a>.</p>
|
<p>A rule read wrong, a bug, a game you would like to tell of: write to <a href="mailto:eric@ericwagoner.com">eric@ericwagoner.com</a>, <a href="https://bsky.app/profile/kestrelsnest.social" target="_blank" rel="noreferrer">@kestrelsnest.social</a> on Bluesky, or <a href="https://toots.kestrelsnest.social/@eric" target="_blank" rel="noreferrer">@eric@toots.kestrelsnest.social</a> on Mastodon. The keeper of this hall roosts at <a href="https://kestrelsnest.social" target="_blank" rel="noreferrer">kestrelsnest.social</a>.</p>
|
||||||
|
{#if family.length}
|
||||||
|
<h3>More games at the Kestrel's Nest</h3>
|
||||||
|
<ul class="family">
|
||||||
|
{#each family as g (g.slug)}
|
||||||
|
<li><a href={g.url}>{g.name}</a>: {g.pitch} <span class="muted">{g.players} players; {g.origin}.</span></li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
<p class="made">__NAME__ is its designer's. This page was made by Eric and a very enthusiastic AI, 2026.</p>
|
<p class="made">__NAME__ is its designer's. This page was made by Eric and a very enthusiastic AI, 2026.</p>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@@ -529,4 +543,12 @@
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--bone-faint);
|
color: var(--bone-faint);
|
||||||
}
|
}
|
||||||
|
.family {
|
||||||
|
margin: 0.4rem 0 0.8rem 1.2rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.family li + li {
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// The other games of the Kestrel's Nest, from the family list the kit keeps
|
||||||
|
// (family.json, one copy shipped with every game), so a player who liked one
|
||||||
|
// table can find the next. This game is left out of its own list.
|
||||||
|
|
||||||
|
export interface FamilyGame {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
pitch: string;
|
||||||
|
players: string;
|
||||||
|
origin: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function otherGames(self: string): Promise<FamilyGame[]> {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/family.json', { cache: 'no-cache' });
|
||||||
|
if (!res.ok) return [];
|
||||||
|
const data = (await res.json()) as { games?: FamilyGame[] };
|
||||||
|
return (data.games ?? []).filter((g) => g.slug !== self);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"family": "the Kestrel's Nest",
|
||||||
|
"games": [
|
||||||
|
{
|
||||||
|
"slug": "wizwar",
|
||||||
|
"name": "Wiz-War",
|
||||||
|
"url": "https://wizwar.kestrelsnest.social",
|
||||||
|
"pitch": "Steal treasure. Blast through walls. Outwit your friends.",
|
||||||
|
"players": "2 to 6",
|
||||||
|
"origin": "Tom Jolly's board game, sixth edition, 1993"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "waving-hands",
|
||||||
|
"name": "Waving Hands",
|
||||||
|
"url": "https://hands.kestrelsnest.social",
|
||||||
|
"pitch": "Duelling wizards. Two hands each. The right run of gestures is a spell.",
|
||||||
|
"players": "2 to 12",
|
||||||
|
"origin": "Richard Bartle's pencil-and-paper duel, 1977"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user