The first minute, for a stranger: what the game is, what to do, and how to join
The landing says the pitch before it asks for a name — steal treasure, blast through walls, outwit your friends — and states the goal. The coach says "select a card, then choose its target," which is what the cards actually want. The turn banner counts treasures home beside the moves left. The council speaks once when its three voices agree. A friend arriving on an invite link meets "Join Eric's game," a name field, and a seat, with the gallery an afterthought; the masthead says "invite friends" beside the room code. The first-person view has a toggle beside the board instead of a checkbox in preferences. How to play opens with winning. And a shared moment's card is titled by the turn's story — the treasure carried home, the blow that landed, the spell cast — instead of whose turn it was. 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
a071494f4a
commit
4cc0423846
@@ -36,6 +36,7 @@ import { randomBytes } from "node:crypto";
|
||||
import { readFileSync, existsSync, realpathSync, statSync, createReadStream } from "node:fs";
|
||||
import { extname, join, normalize, sep } from "node:path";
|
||||
import { WebSocketServer, WebSocket } from "ws";
|
||||
import { cardDef } from "@wizwar/engine";
|
||||
import type { Command, PlayerId } from "@wizwar/engine";
|
||||
import {
|
||||
catchUpSteps,
|
||||
@@ -198,11 +199,43 @@ function safeBase(rawHost: string, rawProto: string): string {
|
||||
return `${proto}://${escapeHtml(rawHost)}`;
|
||||
}
|
||||
|
||||
/** The turn's story in one line, for the share card: the deed a
|
||||
* stranger would click to see, chosen by weight — a crown, a treasure
|
||||
* carried home, a blow that landed, a spell cast — before the bare fact
|
||||
* of whose turn it was. */
|
||||
function headlineOf(data: ShareData): string | null {
|
||||
const name = (id: string | null) => { if (!id) return "a spell"; try { return cardDef(id).name; } catch { return id; } };
|
||||
let best: { rank: number; text: string } | null = null;
|
||||
const offer = (rank: number, text: string) => { if (!best || rank > best.rank) best = { rank, text }; };
|
||||
for (const step of data.steps) {
|
||||
for (const e of step.events) {
|
||||
switch (e.type) {
|
||||
case "gameWon": offer(9, e.reason === "treasures" ? `${e.player} wins with two treasures home` : `${e.player} is the last wizard standing`); break;
|
||||
case "playerEliminated": offer(8, e.reason === "killed" ? `${e.player} falls in the maze` : `${e.player} loses both treasures`); break;
|
||||
case "treasureDropped": if (e.onHomeOf === e.player) offer(7, `${e.player} carries a stolen treasure home`); break;
|
||||
case "treasurePickedUp": if (e.owner !== e.player) offer(5, `${e.player} snatches ${e.owner}'s treasure`); break;
|
||||
case "attackResolved": if (e.damageDealt > 0) offer(6, `${e.attacker} hits ${e.defender} with ${name(e.attackCardId)} for ${e.damageDealt}`); else if (e.fullyStopped) offer(4, `${e.defender} stops ${e.attacker}'s ${name(e.attackCardId)} cold`); break;
|
||||
case "punched": offer(3, `${e.attacker} punches ${e.target}`); break;
|
||||
case "spellCast": offer(2, e.target ? `${e.caster} casts ${name(e.cardId)} at ${e.target}` : `${e.caster} casts ${name(e.cardId)}`); break;
|
||||
case "jumpedPit": offer(3, `${e.player} leaps the pit`); break;
|
||||
case "fellInPit": offer(3, `${e.player} falls into the pit`); break;
|
||||
case "wardSprung": offer(4, `${e.owner}'s warded treasure bites ${e.victim}`); break;
|
||||
case "slimeTrapSprung": offer(4, `the slime springs ${name(e.cardId)} on ${e.victim}`); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best ? (best as { text: string }).text : null;
|
||||
}
|
||||
|
||||
function shareHtml(id: string, data: ShareData, rawHost: string, rawProto: string): string {
|
||||
const base = safeBase(rawHost, rawProto);
|
||||
const headline = data.whole ? null : headlineOf(data);
|
||||
const title = data.whole
|
||||
? "The whole tale — a game of Wiz-War, replayed"
|
||||
: `${escapeHtml(data.actor)}'s turn — a Wiz-War instant replay`;
|
||||
: headline
|
||||
? `${escapeHtml(headline)} — a Wiz-War instant replay`
|
||||
: `${escapeHtml(data.actor)}'s turn — a Wiz-War instant replay`;
|
||||
const desc = data.whole
|
||||
? `A full game of Wiz-War, magical combat in a stone labyrinth — every turn through its wizard's own eyes${data.actor ? `, to ${escapeHtml(data.actor)}'s triumph` : ""}. Watch it all, then deal yourself in.`
|
||||
: `Round ${data.round || "?"} of a game of Wiz-War, magical combat in a stone labyrinth. ` +
|
||||
|
||||
Reference in New Issue
Block a user