Every token on the board can be read as its card

"What does a rosebush do again?" now has an in-game answer: press and
hold any square (450ms) to open the card behind whatever occupies it —
creature, terrain, dropped object, warp token, or boobytrap marker —
in the view-only inspector corner. A plain click that claims no game
action peeks too: out of turn, every tap reads; on your turn, taps
beyond your reach read instead of doing nothing, and clicking an
opponent's creature with nothing selected reads its card. Priority
when a square holds several stories: creature, then terrain, then the
topmost dropped object.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 10:39:56 -04:00
co-authored by Claude Fable 5
parent a9d40d73ac
commit 97a3305d07
2 changed files with 59 additions and 3 deletions
+35 -2
View File
@@ -267,7 +267,8 @@
}
function clickCell(cell: { x: number; y: number }) {
if (!view || !isYourTurn) return;
if (!view) return;
if (!isYourTurn) { peekAt(cell); return; }
if (pendingCellFor && selectedCard) {
// Stage 2 of teleport-opponent: destination chosen.
dispatch({
@@ -371,6 +372,34 @@
return;
}
}
// The click claimed no action — offer the square's card instead.
peekAt(cell);
}
/** Card ids behind the terrain tokens, for tap-to-read. */
const CONTENT_CARD: Record<string, string> = {
stone: "fill-square-with-stone", thornbush: "thornbush", rosebush: "rosebush",
ooze: "killer-ooze", dust: "dust-cloud", slime: "fill-square-with-slime",
tacks: "handful-of-tacks", pit: "create-pit", safe: "safe",
};
/** Show the card behind whatever occupies this square (view-only). */
function peekAt(cell: { x: number; y: number }): boolean {
if (!view) return false;
const key = `${cell.x},${cell.y}`;
const creature = view.creatures.find((c) => c.position.x === cell.x && c.position.y === cell.y);
const content = view.squareContents[key];
const objects = view.groundObjects[key] ?? [];
const cardId =
creature ? creature.kind
: content ? CONTENT_CARD[content.kind]
: objects.length > 0 ? objects[objects.length - 1]!.cardId
: view.dimWarps.some((w) => (w.a.x === cell.x && w.a.y === cell.y) || (w.b.x === cell.x && w.b.y === cell.y)) ? "dimensional-warp"
: view.boobytraps.some((t) => t.cells.some((c) => c.x === cell.x && c.y === cell.y)) ? "boobytrap"
: null;
if (!cardId) return false;
peekCard = { instanceId: `peek-${cardId}`, cardId };
return true;
}
function clickWarp(cell: { x: number; y: number }, side: Side) {
@@ -395,9 +424,10 @@
}
function clickCreature(creatureId: string) {
if (!view || !isYourTurn) return;
if (!view) return;
const creature = view.creatures.find((c) => c.id === creatureId);
if (!creature) return;
if (!isYourTurn) { peekAt(creature.position); return; }
if (selectedCard && (CREATURE_TARGET_CARDS.has(selectedCard.cardId) || cardDef(selectedCard.cardId).cardType === "attack")) {
dispatch({
type: "cast", instanceId: selectedCard.instanceId,
@@ -416,6 +446,8 @@
const mine = creature.controllerId === view.you || creature.kind === "democratic-monster";
if (mine) {
selectedCreature = selectedCreature === creatureId ? null : creatureId;
} else {
peekAt(creature.position);
}
}
@@ -845,6 +877,7 @@
onCreatureClick={clickCreature}
onWarpClick={clickWarp}
markedCell={tradeFrom ?? pendingSectorFrom}
onCellPeek={(c) => peekAt(c)}
{litCells}
/>
</section>