package casino import "fmt" // betRangeText describes the accepted wager range; the unlimited sentinel is // rendered as "no maximum" instead of a machine-sized integer. func betRangeText(minBet, maxBet int) string { if maxBet == maxBetUnlimited { return fmt.Sprintf("%d or more (no maximum)", minBet) } return fmt.Sprintf("between %d and %d", minBet, maxBet) } type GameName string const ( GameSlots GameName = "slots" GameBlackjack GameName = "blackjack" GameBaccarat GameName = "baccarat" ) type BlackjackConfig struct { Decks int `yaml:"decks,omitempty"` ShuffleAt float64 `yaml:"shuffle_at,omitempty"` BlackjackPayout float64 `yaml:"blackjack_payout,omitempty"` DealerHitSoft17 *bool `yaml:"dealer_hit_soft_17,omitempty"` MaxSplitHands int `yaml:"max_split_hands,omitempty"` ResplitAces *bool `yaml:"resplit_aces,omitempty"` DoubleAfterSplit *bool `yaml:"double_after_split,omitempty"` DoubleSplitBlackjack *bool `yaml:"double_split_blackjack,omitempty"` InsurancePayout float64 `yaml:"insurance_payout,omitempty"` Surrender *bool `yaml:"surrender,omitempty"` ActionTicks int `yaml:"action_ticks,omitempty"` SplitUnlikeTenValues *bool `yaml:"split_unlike_ten_values,omitempty"` } func ruleBool(value *bool, defaultValue bool) bool { if value == nil { return defaultValue } return *value } func (c BlackjackConfig) DealerHitsSoft17() bool { return ruleBool(c.DealerHitSoft17, false) } func (c BlackjackConfig) AllowsResplitAces() bool { return ruleBool(c.ResplitAces, false) } func (c BlackjackConfig) AllowsDoubleAfterSplit() bool { return ruleBool(c.DoubleAfterSplit, true) } func (c BlackjackConfig) CountsDoubleSplitBlackjack() bool { return ruleBool(c.DoubleSplitBlackjack, true) } func (c BlackjackConfig) AllowsSurrender() bool { return ruleBool(c.Surrender, false) } func (c BlackjackConfig) AllowsUnlikeTenSplit() bool { return ruleBool(c.SplitUnlikeTenValues, true) } func (c BlackjackConfig) Normalize() BlackjackConfig { if c.Decks <= 0 { c.Decks = 8 } if c.ShuffleAt <= 0 || c.ShuffleAt >= 1 { c.ShuffleAt = 0.25 } if c.BlackjackPayout <= 0 { c.BlackjackPayout = 1.5 } if c.MaxSplitHands <= 0 { c.MaxSplitHands = 4 } if c.InsurancePayout <= 0 { c.InsurancePayout = 2 } if c.ActionTicks <= 0 { c.ActionTicks = 20 } return c } // BaccaratConfig holds punto banco house rules: shoe composition and payout // multipliers. Banker wins pay BankerPayout:1 (a 5% commission game is 0.95); // tie wins pay TiePayout:1. type BaccaratConfig struct { Decks int `yaml:"decks,omitempty"` ShuffleAt float64 `yaml:"shuffle_at,omitempty"` TiePayout float64 `yaml:"tie_payout,omitempty"` BankerPayout float64 `yaml:"banker_payout,omitempty"` } func (c BaccaratConfig) Normalize() BaccaratConfig { if c.Decks <= 0 { c.Decks = 8 } if c.ShuffleAt <= 0 || c.ShuffleAt >= 1 { c.ShuffleAt = 0.25 } if c.TiePayout <= 0 { c.TiePayout = 8 } if c.BankerPayout <= 0 { c.BankerPayout = 0.95 } return c } type TableConfig struct { ID string `yaml:"id"` Game GameName `yaml:"game"` MaxPlayers int `yaml:"max_players,omitempty"` BettingTicks int `yaml:"betting_ticks,omitempty"` MinBet int `yaml:"min_bet,omitempty"` MaxBet int `yaml:"max_bet,omitempty"` Blackjack BlackjackConfig `yaml:"blackjack,omitempty"` Baccarat BaccaratConfig `yaml:"baccarat,omitempty"` } type MachineConfig struct { ID string `yaml:"id"` Game GameName `yaml:"game"` Variant string `yaml:"variant,omitempty"` MinBet int `yaml:"min_bet,omitempty"` MaxBet int `yaml:"max_bet,omitempty"` SpinTicks int `yaml:"spin_ticks,omitempty"` Payout float64 `yaml:"payout,omitempty"` } func (c MachineConfig) Normalize() MachineConfig { if c.MinBet <= 0 { c.MinBet = 1 } // An unset MaxBet defaults to the economy's bounded cap; the explicit // unlimited sentinel is still honored for authored configurations. if c.MaxBet <= 0 { c.MaxBet = DefaultMaxBet } if c.SpinTicks <= 0 { c.SpinTicks = 2 } if c.Variant == "" { c.Variant = "huff_and_even_more_puff" } if c.Payout <= 0 { c.Payout = 0.97 } return c } func (c TableConfig) Normalize() TableConfig { if c.MaxPlayers <= 0 { c.MaxPlayers = 8 } if c.BettingTicks <= 0 { c.BettingTicks = 20 } if c.MinBet <= 0 { c.MinBet = 1 } if c.MaxBet <= 0 { c.MaxBet = DefaultMaxBet } return c } type Wager struct { Total int Chips int Credits int } type Wallet interface { // Wager debits the stake durably. ErrInsufficientFunds is a normal // rejected bet; ErrWalletPersistence means the debit could not be made // durable and the game must not proceed as if the bet succeeded. Wager(amount int) (Wager, error) // PayCredits pays a settlement durably, returning an error when the // credit could not be persisted (the game must not report a win that // is not durable). PayCredits(amount int) error // ResolveStakes marks every outstanding stake for this player as // settled: the round resolved (won, lost, pushed, or forfeited), so // startup recovery must no longer refund it. A stake whose wager // record remains open at restart is refunded by recovery. ResolveStakes() // RefundOutstanding returns every outstanding stake to the player // (chips as chips, credits through the durable payout path) and // resolves the stakes. It reports the amounts returned. An error // means the refund could not be made durable: the stakes remain open // and startup recovery completes the refund. RefundOutstanding() (chips, credits int, err error) } type EventType string const ( EventJoined EventType = "joined" EventBetting EventType = "betting" EventBetPlaced EventType = "bet_placed" EventRoundStarted EventType = "round_started" EventResult EventType = "result" EventPayout EventType = "payout" EventTimedOut EventType = "timed_out" EventLeft EventType = "left" EventClosed EventType = "closed" ) type MenuKind string const ( MenuSlotCommands MenuKind = "slot_commands" MenuBlackjackBet MenuKind = "blackjack_bet" MenuBlackjackTurn MenuKind = "blackjack_turn" MenuBaccaratBet MenuKind = "baccarat_bet" ) // MenuView carries a structured menu alongside its plain-text Message so the // presentation layer can enrich it (balances, command colors) without parsing // message prefixes. type MenuView struct { Kind MenuKind Body string // command list (MenuSlotCommands) Title string // prompt heading (MenuBlackjackTurn) Actions []string // available commands (MenuBlackjackTurn) Commands []MenuCommand // unified command list for all casino menus } // MenuCommand is one selectable command shown in a casino menu: the typed // command text plus a short human-readable description. type MenuCommand struct { Command string Desc string } type Event struct { RoomID int TableID string Type EventType Player string Message string PrivateMessage string PrivateTo string Color string Public bool Slot *SlotSnapshot Blackjack *BlackjackSnapshot Baccarat *BaccaratSnapshot Menu *MenuView } type SlotSnapshot struct { Grid [3][5]string StoppedReels int } // CardView renders one card for any card-based table game. type CardView struct { Rank string Suit string Hidden bool } type BlackjackPlayerView struct { Name string Cards []CardView Score string } type BlackjackSnapshot struct { Dealer []CardView DealerShownScore string DealerScore string DealerRevealed bool Players []BlackjackPlayerView } // BaccaratBetView lists one participant's wager on a baccarat outcome. type BaccaratBetView struct { Name string Spot string Amount int } type BaccaratSnapshot struct { Player []CardView Banker []CardView PlayerScore string BankerScore string Bets []BaccaratBetView } type Participant struct { Name string Connected bool HasBet bool Wager Wager Wallet Wallet }