diff options
| author | historia <[not public]> | 2026-09-19 03:15:59 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-19 03:15:59 -0400 |
| commit | 0e60567a7a0addb9421769ee7be7853bfa8788cb (patch) | |
| tree | 7645112db0e4eae95b8a83c1e4f9aab38bede655 /internal/casino/config_validate.go | |
| parent | cc33072eced3d5460512c09c42ec4531c807d7af (diff) | |
| download | thehouseoficarus-0e60567a7a0addb9421769ee7be7853bfa8788cb.tar.gz | |
refactor: simplifcation, change how rooms are stored in player yaml, remove legacy code
Diffstat (limited to 'internal/casino/config_validate.go')
| -rw-r--r-- | internal/casino/config_validate.go | 77 |
1 files changed, 63 insertions, 14 deletions
diff --git a/internal/casino/config_validate.go b/internal/casino/config_validate.go index 6181883..9ad1776 100644 --- a/internal/casino/config_validate.go +++ b/internal/casino/config_validate.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math" + "sort" "strings" ) @@ -21,8 +22,8 @@ func (c MachineConfig) Validate() error { return fmt.Errorf("unsupported slots variant %q", c.Variant) } } - if c.SpinTicks < 0 { - return fmt.Errorf("spin_ticks must not be negative") + if c.SpinTicks < 0 || c.SpinTicks > maxSpinTicks { + return fmt.Errorf("spin_ticks must be between 0 and %d", maxSpinTicks) } if !finiteNonnegative(c.Payout) || c.Payout > 1 { return fmt.Errorf("payout must be finite and between 0 and 1 (0 uses the default)") @@ -37,16 +38,40 @@ func (c TableConfig) Validate() error { if err := validateIdentityAndBets(c.ID, c.MinBet, c.MaxBet); err != nil { return err } - if c.MaxPlayers < 0 || c.BettingTicks < 0 { - return fmt.Errorf("max_players and betting_ticks must not be negative") + if c.MaxPlayers < 0 || c.MaxPlayers > maxTablePlayers { + return fmt.Errorf("max_players must be between 0 and %d", maxTablePlayers) + } + if c.BettingTicks < 0 || c.BettingTicks > maxBettingTicks { + return fmt.Errorf("betting_ticks must be between 0 and %d", maxBettingTicks) } def, ok := tableGameDefFor(c.Game) if !ok { return fmt.Errorf("unsupported table game %q", c.Game) } + if foreign := foreignRuleBlocks(c); len(foreign) > 0 { + return fmt.Errorf("table game %q has a rule block for a different game (%s); remove it or set game to match", c.Game, strings.Join(foreign, ", ")) + } return def.validate(c) } +// foreignRuleBlocks names every game-specific rule block present on c that +// does not belong to c.Game, in a deterministic order. Each game registers its +// own block in tableGames, so a new game widens this automatically instead of +// needing another hand-maintained switch. +func foreignRuleBlocks(c TableConfig) []string { + var foreign []string + for game, def := range tableGames { + if game == c.Game || def.hasRuleBlock == nil || def.ruleBlockName == "" { + continue + } + if def.hasRuleBlock(c) { + foreign = append(foreign, def.ruleBlockName) + } + } + sort.Strings(foreign) + return foreign +} + // normalizeBlackjackTable applies the blackjack rule defaults. It is the // game's Normalize entry in tableGames. func normalizeBlackjackTable(c TableConfig) TableConfig { @@ -66,8 +91,8 @@ func validateBlackjackTable(c TableConfig) error { if err := validateShoe(b.Decks, b.ShuffleAt); err != nil { return err } - if b.MaxSplitHands < 0 || b.ActionTicks < 0 { - return fmt.Errorf("max_split_hands and action_ticks must not be negative") + if b.MaxSplitHands < 0 || b.ActionTicks < 0 || b.ActionTicks > maxActionTicks { + return fmt.Errorf("max_split_hands must not be negative and action_ticks must be between 0 and %d", maxActionTicks) } if !finiteNonnegative(b.BlackjackPayout) || !finiteNonnegative(b.InsurancePayout) { return fmt.Errorf("blackjack_payout and insurance_payout must be finite and nonnegative") @@ -130,6 +155,22 @@ func finiteNonnegative(n float64) bool { return !math.IsNaN(n) && !math.IsInf(n, 0) && n >= 0 } +// hasBlackjackRuleBlock reports whether c carries any explicit blackjack rule. +// Used by foreignRuleBlocks to reject a block authored for another game. +func hasBlackjackRuleBlock(c TableConfig) bool { + b := c.Blackjack + return b.Decks != 0 || b.ShuffleAt != 0 || b.BlackjackPayout != 0 || + b.DealerHitSoft17 != nil || b.MaxSplitHands != 0 || b.ResplitAces != nil || + b.DoubleAfterSplit != nil || b.DoubleSplitBlackjack != nil || + b.InsurancePayout != 0 || b.Surrender != nil || b.ActionTicks != 0 || + b.SplitUnlikeTenValues != nil +} + +func hasBaccaratRuleBlock(c TableConfig) bool { + b := c.Baccarat + return b.Decks != 0 || b.ShuffleAt != 0 || b.TiePayout != 0 || b.BankerPayout != 0 +} + func validateShoe(decks int, shuffleAt float64) error { // A generous resource limit prevents malformed content from allocating an // unbounded shoe. Zero retains the existing eight-deck default. @@ -158,6 +199,18 @@ const ( maxBankerPayout = 0.99 ) +// Authoring limits for tick-based windows. A typo such as betting_ticks: +// 2000000 would otherwise publish a table that holds its betting window open +// for days; while the window is open a committed stake cannot be withdrawn, so +// the bettor is trapped. These are generous relative to normal play (a tick is +// 600ms). +const ( + maxTablePlayers = 64 + maxBettingTicks = 3600 + maxActionTicks = 3600 + maxSpinTicks = 3600 +) + // maxSlotSymbolLen is the fixed width of one reel cell in the renderer // (" " + symbol + padding + " "); a longer id would make the renderer's // width padding negative and panic. @@ -187,6 +240,9 @@ const baccaratRoundCards = 6 // the geometry here keeps that gate meaningful. func (p SlotProfile) Validate() error { profile := p.Normalize() + if len(profile.Symbols) == 0 { + return fmt.Errorf("slot profile has no symbols") + } if profile.Rows != slotGridRows || profile.Reels != slotGridReels { return fmt.Errorf("unsupported slot geometry %dx%d: the runtime evaluator supports %dx%d only", profile.Rows, profile.Reels, slotGridRows, slotGridReels) @@ -265,7 +321,7 @@ func machineSlotProfile(c MachineConfig) SlotProfile { } // ValidateRoomConfigs checks the shared room-local ID namespace as well as -// individual definitions. Legacy slots under casino_tables remain supported. +// individual definitions. // // It also enforces one table/machine per game per room: a room is addressed // by game name ("play blackjack"), so a second entry for the same game (or @@ -291,13 +347,6 @@ func ValidateRoomConfigs(tables []TableConfig, machines []MachineConfig) error { addressable[key] = id } for _, c := range tables { - if c.Game == GameSlots { - // A slots "table" is instantiated as the room's slots machine. - legacy := LegacySlotMachine(c) - check("legacy slots", c.ID, legacy.Validate()) - checkAddressable(strings.ToLower(string(GameSlots)), c.ID) - continue - } check("table", c.ID, c.Validate()) checkAddressable(strings.ToLower(string(c.Game)), c.ID) } |
