Three more cautions: the un-grabbed chest, the biting ground, the starved draw
Ending a turn on a treasure nobody picked up now asks first (unless the auto-grab preference is about to take it, or the grab would be refused — laden arms, weakness, spent actions, glue, a locked safe, or your own home where the chest already scores; the view learns gluedCells and openSafes so the check is honest). Stepping onto ground that bites — thornbush, tacks, pit, ooze — warns with the exact price, unless you are mist. And the sticky draw picker no longer quietly starves anyone: a draw below what would fit asks too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6e1ccb0195
commit
ef921a0c2b
@@ -57,6 +57,10 @@ export interface GameView {
|
|||||||
/** Duration spells in play (public knowledge). */
|
/** Duration spells in play (public knowledge). */
|
||||||
sustained: SustainedEffect[];
|
sustained: SustainedEffect[];
|
||||||
squareContents: Record<string, SquareContent>;
|
squareContents: Record<string, SquareContent>;
|
||||||
|
/** Cells whose contents are glued down (public: the cast was seen). */
|
||||||
|
gluedCells: Record<string, true>;
|
||||||
|
/** Safes standing open (their combination entered this turn). */
|
||||||
|
openSafes: string[];
|
||||||
groundObjects: Record<string, CardInstance[]>;
|
groundObjects: Record<string, CardInstance[]>;
|
||||||
doorStates: Record<string, "jammed" | "removed">;
|
doorStates: Record<string, "jammed" | "removed">;
|
||||||
/** Accumulated attack damage per edge (public — cracks show). */
|
/** Accumulated attack damage per edge (public — cracks show). */
|
||||||
@@ -144,6 +148,8 @@ export function viewFor(state: GameState, playerId: PlayerId): GameView {
|
|||||||
pendingDiscard: state.pendingDiscard,
|
pendingDiscard: state.pendingDiscard,
|
||||||
sustained: state.sustained.map((s) => ({ ...s })),
|
sustained: state.sustained.map((s) => ({ ...s })),
|
||||||
squareContents: { ...state.squareContents },
|
squareContents: { ...state.squareContents },
|
||||||
|
gluedCells: { ...state.gluedCells },
|
||||||
|
openSafes: [...state.openSafes],
|
||||||
groundObjects: Object.fromEntries(
|
groundObjects: Object.fromEntries(
|
||||||
Object.entries(state.groundObjects).map(([k, v]) => [k, [...v]]),
|
Object.entries(state.groundObjects).map(([k, v]) => [k, [...v]]),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -63,7 +63,8 @@
|
|||||||
if (!prefs.cautions || reasons.length === 0) go();
|
if (!prefs.cautions || reasons.length === 0) go();
|
||||||
else caution = { reasons, go };
|
else caution = { reasons, go };
|
||||||
}
|
}
|
||||||
/** A move with the delivery check: stepping off your home still laden. */
|
/** A move with its regret checks: stepping off your home still laden,
|
||||||
|
* or stepping onto ground that bites. */
|
||||||
function tryMove(direction: Side, over?: boolean) {
|
function tryMove(direction: Side, over?: boolean) {
|
||||||
const reasons: string[] = [];
|
const reasons: string[] = [];
|
||||||
if (view && me && me.carriedTreasureId && cellKey(me.position) === cellKey(me.home)) {
|
if (view && me && me.carriedTreasureId && cellKey(me.position) === cellKey(me.home)) {
|
||||||
@@ -72,6 +73,22 @@
|
|||||||
? `you stand on your home with ${t.owner}'s treasure in your arms — drop it first and it scores`
|
? `you stand on your home with ${t.owner}'s treasure in your arms — drop it first and it scores`
|
||||||
: "you stand on your home with your own treasure in your arms — drop it first and it is safe");
|
: "you stand on your home with your own treasure in your arms — drop it first and it is safe");
|
||||||
}
|
}
|
||||||
|
if (view && me) {
|
||||||
|
const misted = view.sustained.some((e) => e.cardId === "mist-body" && e.targetId === view.you);
|
||||||
|
const steps = over ? 2 : 1;
|
||||||
|
const dest = { x: me.position.x + (direction === "E" ? steps : direction === "W" ? -steps : 0),
|
||||||
|
y: me.position.y + (direction === "S" ? steps : direction === "N" ? -steps : 0) };
|
||||||
|
const ground = view.squareContents[cellKey(dest)]?.kind;
|
||||||
|
if (!misted && ground) {
|
||||||
|
const bite: Record<string, string> = {
|
||||||
|
thornbush: "the thorns end your turn, cost you the NEXT one, and prick for 1",
|
||||||
|
tacks: "the tacks bite for 3",
|
||||||
|
pit: "the pit: roll a 1 and you fall in, hurt and stuck",
|
||||||
|
ooze: "the ooze burns 1 — and may drop you flat, treasure and all",
|
||||||
|
};
|
||||||
|
if (bite[ground]) reasons.push(bite[ground]);
|
||||||
|
}
|
||||||
|
}
|
||||||
withCaution(reasons, () => dispatch({ type: "move", direction, ...(over ? { over: true } : {}) }));
|
withCaution(reasons, () => dispatch({ type: "move", direction, ...(over ? { over: true } : {}) }));
|
||||||
}
|
}
|
||||||
function playFx(events: Parameters<typeof scheduleFx>[0]) {
|
function playFx(events: Parameters<typeof scheduleFx>[0]) {
|
||||||
@@ -919,6 +936,25 @@
|
|||||||
? `${t.owner}'s treasure is still in your arms — drop it here and it scores`
|
? `${t.owner}'s treasure is still in your arms — drop it here and it scores`
|
||||||
: "your own treasure is still in your arms — drop it here and it is safe");
|
: "your own treasure is still in your arms — drop it here and it is safe");
|
||||||
}
|
}
|
||||||
|
// Treasure underfoot un-grabbed — unless the auto-grab preference (or
|
||||||
|
// this caution's own "do it anyway") is about to handle it, or the
|
||||||
|
// grab would be refused (laden, weakened, actions spent, glue, safe,
|
||||||
|
// or your own home where a stolen chest is already scoring).
|
||||||
|
const here = cellKey(me.position);
|
||||||
|
const autoWillGrab = prefs.autoGrab && treasuresHere.length === 1;
|
||||||
|
const weakened = view.sustained.some((e) => e.cardId === "weakness" && e.targetId === view.you);
|
||||||
|
const safed = view.squareContents[here]?.kind === "safe" &&
|
||||||
|
view.squareContents[here]!.createdBy !== view.you && !view.openSafes.includes(here);
|
||||||
|
if (treasuresHere.length > 0 && !autoWillGrab && !me.carriedTreasureId &&
|
||||||
|
!view.turn.actionsEnded && !weakened && !view.gluedCells[here] && !safed &&
|
||||||
|
here !== cellKey(me.home)) {
|
||||||
|
reasons.push(`a treasure lies at your feet, un-grabbed`);
|
||||||
|
}
|
||||||
|
// The draw picker is sticky: a 0 or 1 chosen turns ago quietly starves.
|
||||||
|
const drawRoom = Math.min(2, room);
|
||||||
|
if (drawCount < drawRoom) {
|
||||||
|
reasons.push(`drawing only ${drawCount} when ${drawRoom} would fit`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
withCaution(reasons, () => {
|
withCaution(reasons, () => {
|
||||||
// Preference: a lone grabbable treasure underfoot is picked up on the
|
// Preference: a lone grabbable treasure underfoot is picked up on the
|
||||||
|
|||||||
Reference in New Issue
Block a user