import { Card } from '../cards/Card'; import { PlayerId } from './GameState'; /** * What happened, as opposed to what someone wanted to do. The presentation layer * animates this stream; it is the only thing the view needs in order to stay in * sync with the rules. * * The stream is a god's-eye view — `Dealt` and `Drew` carry real cards. It is * the view's job to show only what the local player may see. Anything that * genuinely must not know (a bot) reads a `PlayerView` instead. */ export enum GameEventType { Dealt = 'dealt', TrumpRevealed = 'trumpRevealed', TurnStarted = 'turnStarted', Attacked = 'attacked', Defended = 'defended', TookDeclared = 'tookDeclared', Passed = 'passed', TableDiscarded = 'tableDiscarded', TableTaken = 'tableTaken', Drew = 'drew', PlayerOut = 'playerOut', GameOver = 'gameOver', } export type GameEvent = | { type: GameEventType.Dealt; player: PlayerId; cards: Card[] } | { type: GameEventType.TrumpRevealed; card: Card } | { type: GameEventType.TurnStarted; turnNumber: number; attacker: PlayerId; defender: PlayerId } | { type: GameEventType.Attacked; player: PlayerId; card: Card; slot: number } | { type: GameEventType.Defended; player: PlayerId; card: Card; slot: number } | { type: GameEventType.TookDeclared; player: PlayerId } | { type: GameEventType.Passed; player: PlayerId } | { type: GameEventType.TableDiscarded; cards: Card[] } | { type: GameEventType.TableTaken; player: PlayerId; cards: Card[] } | { type: GameEventType.Drew; player: PlayerId; cards: Card[]; deckRemaining: number } | { type: GameEventType.PlayerOut; player: PlayerId } | { type: GameEventType.GameOver; loser: PlayerId | null }; export function describeEvent(event: GameEvent): string { switch (event.type) { case GameEventType.Dealt: return `${event.player} was dealt ${event.cards.length} cards`; case GameEventType.TrumpRevealed: return `trump is ${event.card.code}`; case GameEventType.TurnStarted: return `turn ${event.turnNumber}: ${event.attacker} attacks ${event.defender}`; case GameEventType.Attacked: return `${event.player} played ${event.card.code}`; case GameEventType.Defended: return `${event.player} beat slot ${event.slot} with ${event.card.code}`; case GameEventType.TookDeclared: return `${event.player} declared a take`; case GameEventType.Passed: return `${event.player} passed`; case GameEventType.TableDiscarded: return `${event.cards.length} cards discarded`; case GameEventType.TableTaken: return `${event.player} took ${event.cards.length} cards`; case GameEventType.Drew: return `${event.player} drew ${event.cards.length} (${event.deckRemaining} left)`; case GameEventType.PlayerOut: return `${event.player} is out`; case GameEventType.GameOver: return event.loser ? `${event.loser} is the fool` : 'a draw'; } }