import type { CardColor, CardValue, Hand, StashData } from '../types'; export function computePlayable(hand: Hand, ledColor: CardColor | null): Set { const keys = Object.keys(hand); if (!ledColor) return new Set(keys); const ledKeys = keys.filter((k) => hand[k].color === ledColor); if (ledKeys.length > 0) return new Set(ledKeys); const heartKeys = keys.filter((k) => hand[k].color === 'HEARTS'); if (heartKeys.length > 0) return new Set(heartKeys); return new Set(keys); } const VALUE_ORDER: CardValue[] = ['C7', 'C8', 'C9', 'C10', 'LOWER', 'UPPER', 'KING', 'ACE']; /** Seat that wins a completed 4-card trick — mirrors Stash.get_winner in the * engine: HEARTS (červeň) is the permanent trump, otherwise the highest card * of the led colour wins. Safe to call on a partial stash (returns the current * leader among the cards played so far). */ export function stashWinner(stash: StashData): number { const led = stash.cards[String(stash.first_player)]; if (!led) return stash.first_player; let winner = stash.first_player; let best = led; for (let i = 0; i < 4; i++) { const c = stash.cards[String(i)]; if (!c) continue; if (c.color === led.color || c.color === 'HEARTS') { if (c.color === best.color) { if (VALUE_ORDER.indexOf(c.value) >= VALUE_ORDER.indexOf(best.value)) { best = c; winner = i; } } else if (c.color === 'HEARTS') { best = c; winner = i; } } } return winner; } /** The bid the last guesser may not make: the four bids must not sum to the * number of tricks in the round (mirrors Round.add_player_guess in the engine). * Returns null while earlier players are still guessing. */ export function forbiddenGuess(cardsInRound: number, guesses: Record): number | null { if (Object.keys(guesses).length !== 3) return null; const alreadySum = Object.values(guesses).reduce((a, b) => a + b, 0); return cardsInRound - alreadySum; }