Files
wizwar6e/packages/web/src/hints.ts
T
Eric WagonerandClaude Opus 5.5 c64ff70ed4 The table speaks plainer, and keeps its whimsy
Wording throughout the app, the server's messages and the engine's
refusals trades its stacked dashes for sentences, while the maze still
trusts you to honor its curses, the gallery still listens, and the
clockwork still learns its best tricks from the humans at the table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:29:07 -04:00

102 lines
3.9 KiB
TypeScript

// The council of three: each automaton temperament reads YOUR view — the
// same cards and board you see, nothing more — and says what it would do
// in your robes. Purely advisory; nothing is dispatched.
import { automatonCommand, cardDef, type Command, type GameView } from "@wizwar/engine";
import type { AutomatonStyle } from "@wizwar/engine";
const DIRECTION: Record<string, string> = { N: "north", S: "south", E: "east", W: "west" };
const VOICES: Record<AutomatonStyle, { name: string; glyph: string; mood: string }> = {
hunter: { name: "The Hunter", glyph: "🏹", mood: "eyes on the gold" },
berserker: { name: "The Berserker", glyph: "🔥", mood: "eyes on the blood" },
worrier: { name: "The Worrier", glyph: "🛡", mood: "eyes on the exits" },
};
function nameOf(view: GameView, instanceId: string): string {
const c = view.yourHand.find((c) => c.instanceId === instanceId);
return c ? cardDef(c.cardId).name : "a card";
}
function describe(view: GameView, cmd: Command): string {
switch (cmd.type) {
case "move":
return `step ${DIRECTION[cmd.direction]}`;
case "playNumberForMovement":
return `play the ${nameOf(view, cmd.instanceId)} for extra movement`;
case "cast": {
const card = nameOf(view, cmd.instanceId);
const at =
cmd.target?.kind === "player" ? ` at ${cmd.target.playerId}`
: cmd.target?.kind === "cell" ? ` at the square (${cmd.target.cell.x}, ${cmd.target.cell.y})`
: cmd.target?.kind === "edge" ? ` on the wall line beside (${cmd.target.cell.x}, ${cmd.target.cell.y})`
: cmd.target?.kind === "creature" ? " on the monster"
: "";
const num = cmd.numberInstanceIds?.length
? ` with ${cmd.numberInstanceIds.map((id) => nameOf(view, id)).join(" and ")}`
: "";
return `cast ${card}${at}${num}`;
}
case "counteract":
return `answer with ${nameOf(view, cmd.instanceId)}`;
case "pass":
return "let it resolve, since nothing in hand is worth spending on this";
case "punch":
return `punch ${cmd.targetId}`;
case "punchWall":
return "punch the wall";
case "pickUpTreasure":
return "pick up the treasure here (that ends the turn's actions)";
case "pickUpObject":
return "pick up the object here (that ends the turn's actions)";
case "dropTreasure":
return "drop the treasure here";
case "dropObject":
return `drop ${nameOf(view, cmd.instanceId)} here`;
case "warpStep":
return "step through the warp underfoot";
case "testIllusion":
return "test that shimmering wall, since doubting it is free";
case "moveCreature":
return `march the monster ${DIRECTION[cmd.direction]}`;
case "creatureAttack":
return `set the monster on ${cmd.targetId}`;
case "tearTreasure":
return `wrest the treasure from ${cmd.targetId}'s arms`;
case "setAmbush":
return `lay an ambush with ${nameOf(view, cmd.instanceId)}`;
case "discard":
return `shed ${cmd.instanceIds.map((id) => nameOf(view, id)).join(", ")} to make room`;
case "endTurn":
return cmd.draw > 0 ? `end the turn and draw ${cmd.draw}` : "end the turn";
default:
return "bide";
}
}
export interface TableHint {
name: string;
glyph: string;
mood: string;
advice: string;
}
/** What each temperament would do from this seat, phrased for a novice. */
export function tableHints(view: GameView): TableHint[] {
const styles: AutomatonStyle[] = ["hunter", "berserker", "worrier"];
const out: TableHint[] = [];
for (const s of styles) {
const v = VOICES[s];
let advice: string;
try {
const cmd = automatonCommand(view, s, "archmage");
advice = cmd ? `I would ${describe(view, cmd)}.` : "Nothing calls for us right now. Wait for your moment.";
} catch (e) {
console.warn("hint council choked:", e);
advice = "…I have no counsel here.";
}
out.push({ name: v.name, glyph: v.glyph, mood: v.mood, advice });
}
return out;
}