import { Card } from '../cards/Card'; import { PlayerId } from './GameState'; export enum ActionType { /** Lay an attacking card — either the opening card of a turn or an addition. */ Attack = 'attack', /** Beat one attacking card. */ Defend = 'defend', /** Give up the defence and pick the whole table up. */ Take = 'take', /** Declare that nothing more will be added this turn. */ Pass = 'pass', } export interface AttackAction { readonly type: ActionType.Attack; readonly player: PlayerId; readonly card: Card; } export interface DefendAction { readonly type: ActionType.Defend; readonly player: PlayerId; readonly card: Card; /** Index into `GameState.table` of the slot being beaten. */ readonly slot: number; } export interface TakeAction { readonly type: ActionType.Take; readonly player: PlayerId; } export interface PassAction { readonly type: ActionType.Pass; readonly player: PlayerId; } export type Action = AttackAction | DefendAction | TakeAction | PassAction; export const Actions = { attack(player: PlayerId, card: Card): AttackAction { return { type: ActionType.Attack, player, card }; }, defend(player: PlayerId, card: Card, slot: number): DefendAction { return { type: ActionType.Defend, player, card, slot }; }, take(player: PlayerId): TakeAction { return { type: ActionType.Take, player }; }, pass(player: PlayerId): PassAction { return { type: ActionType.Pass, player }; }, }; export function describeAction(action: Action): string { switch (action.type) { case ActionType.Attack: return `${action.player} attacks with ${action.card.code}`; case ActionType.Defend: return `${action.player} beats slot ${action.slot} with ${action.card.code}`; case ActionType.Take: return `${action.player} takes`; case ActionType.Pass: return `${action.player} passes`; } } /** Thrown when an action is not permitted in the current state. */ export class IllegalActionError extends Error { constructor(action: Action, reason: string) { super(`Illegal action (${describeAction(action)}): ${reason}`); this.name = 'IllegalActionError'; } }