import { Card } from '../cards/Card'; import { Action, Actions } from './Action'; import { GameState, Phase, PlayerId } from './GameState'; import { beats, currentActor, legalActions, unbeatenSlots } from './DurakRules'; /** * What tapping a card means for a player right now, or null if that card cannot * be played. * * This lives in the core rather than in the game screen so it can be tested * without the engine: it is the one piece of interface logic that can silently * turn a legal move into a rejected tap, and it is far too easy to get wrong by * eye. The screen just calls it and submits whatever comes back. * * One tap is the whole interaction. When defending, the attack to beat is worked * out here instead of being chosen separately — the serialised rules leave only * one unanswered attack at a time, and where several somehow exist the lowest * slot this card can beat is taken. */ export function intentFor(state: GameState, player: PlayerId, card: Card): Action | null { if (currentActor(state) !== player) { return null; } if (state.phase === Phase.Defence) { const slot = unbeatenSlots(state) .find((i) => beats(card, state.table[i].attack, state.trumpSuit)); return slot === undefined ? null : Actions.defend(player, card, slot); } const wanted = Actions.attack(player, card); const legal = legalActions(state, player) .some((a) => a.type === wanted.type && (a as { card?: Card }).card === card); return legal ? wanted : null; }