Files
wizwar6e/packages/web/src/hints.ts
T
Eric WagonerandClaude Fable 5 76207c6264 Strength gets its grip: tear a treasure from a wizard's arms
The card's second power was never built. New tearTreasure command: with
STRENGTH on you, in the same square as a carrier, the tear spends your
attack; the victim keeps the treasure only on a D4 roll of 1. Per the
FAQ it is not a pickup (your turn's actions continue), and full or
weakened arms tear it loose onto the floor instead. A torn treasure's
warded owner gets their window, blind grabs flail like blind punches,
and the automatons wrestle too. A new command breaks no stored ledger,
so live games gain the grip at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 13:44:50 -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 — 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 — 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;
}