aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/registry.go
blob: c53fe53312ab002c981cdafa86aab6216b10ae41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package casino

import (
	"fmt"
	"math/rand"
)

// tableGameDef is the single registration point for a multiplayer table game.
// A game's validation, rule defaults, and constructor live together here, so
// adding a game is one entry instead of edits to three independent switches
// (TableConfig.Validate, TableConfig.Normalize, and newTable) that could
// silently drift apart. The rule structs stay on TableConfig; this does not
// introduce a config union.
type tableGameDef struct {
	// validate checks a raw (pre-Normalize) table config.
	validate func(TableConfig) error
	// normalize applies this game's rule defaults to a table config.
	normalize func(TableConfig) TableConfig
	// ruleBlockName is the YAML rule block this game owns (for error messages),
	// and hasRuleBlock reports whether that block carries any explicit value.
	// Together they let TableConfig.Validate reject a block authored for a
	// different game without a hand-maintained switch.
	ruleBlockName string
	hasRuleBlock  func(TableConfig) bool
	// new builds the game. It is called only after validate passes and the
	// shared table config has been normalized.
	new func(roomID int, cfg TableConfig, rng *rand.Rand) tableGame
}

// tableGames is populated in init, not in a var initializer: the constructors
// transitively call TableConfig.Normalize, which consults tableGames, and Go
// rejects that as an initialization cycle when the map is a literal.
var tableGames map[GameName]tableGameDef

func init() {
	tableGames = map[GameName]tableGameDef{
		GameCraps: {
			validate:      validateCraps,
			normalize:     func(c TableConfig) TableConfig { c.Craps = c.Craps.Normalize(); return c },
			ruleBlockName: "craps",
			hasRuleBlock:  func(c TableConfig) bool { return c.Craps != (CrapsConfig{}) },
			new:           func(room int, c TableConfig, rng *rand.Rand) tableGame { return newCrapsTable(room, c, rng) },
		},
		GameRoulette: {
			validate: func(c TableConfig) error {
				if c.Roulette.SpinTicks < 0 || c.Roulette.SpinTicks > maxSpinTicks {
					return fmt.Errorf("roulette spin_ticks must be between 0 and %d", maxSpinTicks)
				}
				if c.Roulette.Racetrack && c.Roulette.DoubleZero {
					return fmt.Errorf("roulette racetrack is only available on single-zero tables; remove racetrack or set double_zero: false")
				}
				return nil
			},
			normalize: func(c TableConfig) TableConfig {
				if c.Roulette.SpinTicks == 0 {
					c.Roulette.SpinTicks = 3
				}
				return c
			},
			ruleBlockName: "roulette",
			hasRuleBlock:  func(c TableConfig) bool { return c.Roulette != (RouletteConfig{}) },
			new:           func(roomID int, cfg TableConfig, rng *rand.Rand) tableGame { return newRouletteTable(roomID, cfg, rng) },
		},
		GameBlackjack: {
			validate:      validateBlackjackTable,
			normalize:     normalizeBlackjackTable,
			ruleBlockName: "blackjack",
			hasRuleBlock:  hasBlackjackRuleBlock,
			new: func(roomID int, cfg TableConfig, rng *rand.Rand) tableGame {
				return newBlackjackTable(roomID, cfg, rng)
			},
		},
		GameBaccarat: {
			validate:      validateBaccaratTable,
			normalize:     normalizeBaccaratTable,
			ruleBlockName: "baccarat",
			hasRuleBlock:  hasBaccaratRuleBlock,
			new: func(roomID int, cfg TableConfig, rng *rand.Rand) tableGame {
				return newBaccaratTable(roomID, cfg, rng)
			},
		},
	}
}

// tableGameDefFor returns the registration for a game, or false when the game
// has no multiplayer table implementation.
func tableGameDefFor(game GameName) (tableGameDef, bool) {
	def, ok := tableGames[game]
	return def, ok
}

// slotVariants maps a machine variant name to the profile that variant plays.
// This is the content seam for a new slot cabinet: a variant is a profile
// lookup, not a new code path through Validate and the machine constructor.
// The map is shared by MachineConfig.Validate and machineSlotProfile so the
// accepted set and the played set cannot disagree.
var slotVariants = map[string]func() SlotProfile{
	"huff_and_even_more_puff": DefaultHuffAndPuffProfile,
}