// 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 = { N: "north", S: "south", E: "east", W: "west" }; const VOICES: Record = { 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; }