The owner's idea for teaching novices: a hints button runs all three temperaments' brains against YOUR view — the same cards and board you see, nothing hidden — and each says what it would do in your robes. The Hunter (eyes on the gold), the Berserker (eyes on the blood), the Worrier (eyes on the exits). Available on your turn and while under fire (counter advice included); purely advisory, computed client-side, nothing dispatched. hints.ts translates any Command into plain words. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
101 lines
3.9 KiB
TypeScript
101 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 "setAmbush":
|
|
return `lay an ambush with ${nameOf(view, cmd.instanceId)}`;
|
|
case "armWard":
|
|
return cmd.armed ? "arm the Ward over the gold" : "stand the Ward down";
|
|
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 {
|
|
advice = "…I have no counsel here.";
|
|
}
|
|
out.push({ name: v.name, glyph: v.glyph, mood: v.mood, advice });
|
|
}
|
|
return out;
|
|
}
|