aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/table.go
blob: b1beab9b4a0da6c28a16edbdfcf0c620a23db67b (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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package casino

import (
	"math/rand"
)

// tableGame is a multiplayer table game implementation. Table routes every
// player interaction through this interface so new games plug in without
// changing the dispatch layer.
type tableGame interface {
	join(name string) []Event
	leave(name string) []Event
	bet(name, spot string, amount int, wallet Wallet) []Event
	tick() []Event
	action(name, action string) []Event
	rebet(name string) (spot string, amount int)
	markDisconnected(name string) []Event
	hasParticipant(name string) bool
	refundAll() []Event
	idle() bool
	status(name string) []Event
	overview() string
	// seats and state expose the live occupancy and lifecycle phase for the
	// look command's table-details view. They are read-only.
	seats() []string
	state() string
	// wagers is the game's live list of bets the player could make right
	// now (the "list" command's data; empty when nothing is worth naming,
	// which also suppresses the "list" hint). hints is the full live hint
	// list, finished with the shared "rules" and (multi-wager) "list".
	wagers(name string) []WagerRow
	hints(name string) []MenuCommand
	// base returns the table identity shared with the Table handle.
	base() *tableBase
}

// tableBase is the identity every table game shares with its Table handle.
// The Table and the embedded tableGame hold the SAME *tableBase, so the room
// ID has a single source of truth: a rekey updates it once and every event the
// game builds follows, with no second field to keep in sync.
type tableBase struct {
	RoomID int
	Config TableConfig
}

func (b *tableBase) event(message string, public bool) Event {
	return Event{RoomID: b.RoomID, TableID: b.Config.ID, Type: EventResult, Message: message, Public: public, Color: "casino_action"}
}

func (b *tableBase) private(name, message string) Event {
	e := b.event(message, false)
	e.PrivateTo = name
	return e
}

func (b *tableBase) playerAction(name, publicMessage, privateMessage string) Event {
	e := b.event(publicMessage, true)
	e.PrivateTo = name
	e.PrivateMessage = privateMessage
	return e
}

// betMenuEvent builds the shared "place your bet" menu event for a table
// game; only the command list differs per game. The felt already names the
// game and its limits, so the menu carries just the balance and commands.
func (b *tableBase) betMenuEvent(name string, commands []MenuCommand) Event {
	e := b.private(name, "")
	e.Color = "casino_menu"
	e.Menu = &MenuView{ShowBalance: true, Commands: commands}
	return e
}

// Table is the manager-facing handle for a multiplayer table. It only holds
// routing state; all game logic lives in the tableGame implementation. It
// shares one *tableBase with the game, so RoomID/Config are a single source of
// truth for both.
type Table struct {
	*tableBase
	game tableGame
}

// newTable builds the table game for cfg.Game, or nil when the game has no
// multiplayer table implementation.
func newTable(roomID int, cfg TableConfig, rng *rand.Rand) *Table {
	cfg = cfg.Normalize()
	def, ok := tableGameDefFor(cfg.Game)
	if !ok {
		return nil
	}
	game := def.new(roomID, cfg, rng)
	if game == nil {
		return nil
	}
	// The Table and the game share the game's base, so RoomID/Config are a
	// single source of truth for both.
	return &Table{tableBase: game.base(), game: game}
}

func (t *Table) join(name string) []Event  { return t.game.join(name) }
func (t *Table) leave(name string) []Event { return t.game.leave(name) }

func (t *Table) bet(name, spot string, amount int, wallet Wallet) []Event {
	return t.game.bet(name, spot, amount, wallet)
}

func (t *Table) tick() []Event                      { return t.game.tick() }
func (t *Table) action(name, action string) []Event { return t.game.action(name, action) }
func (t *Table) rebet(name string) (string, int)    { return t.game.rebet(name) }

func (t *Table) markDisconnected(name string) []Event { return t.game.markDisconnected(name) }
func (t *Table) hasParticipant(name string) bool      { return t.game.hasParticipant(name) }

// refundAll resolves every committed wager at this table (graceful
// shutdown): stakes return to their players through the wallet.
func (t *Table) refundAll() []Event { return t.game.refundAll() }

// idle on the Table wrapper delegates to the game.
func (t *Table) idle() bool { return t.game.idle() }

// Status is a read-only view: it never restarts a deadline or changes a wager.
func (t *Table) Status(name string) []Event { return t.game.status(name) }
func (t *Table) Overview() string           { return t.game.overview() }

// Seats lists the seated players in seat order for the look command.
func (t *Table) Seats() []string { return t.game.seats() }

// State is the table's live lifecycle phase for the look command.
func (t *Table) State() string { return t.game.state() }

// RulesText is this table's full rule text (the "table rules" wording).
func (t *Table) RulesText() string { return rulesTextOnTable(t.Config) }

// HelpTopic is the help page the "rules" command opens while seated here.
func (t *Table) HelpTopic() string { return t.Config.HelpTopic() }

// Wagers is the game's live list of bets the player could make right now.
// Empty when nothing is worth naming (a lone "bet <amount>" wager, a
// committed round) — the same signal that suppresses the "list" hint.
func (t *Table) Wagers(name string) []WagerRow { return t.game.wagers(name) }

// Hints is the game's live hint list, finished with the shared "rules" and
// (multi-wager) "list" entries so look and the menus advertise both.
func (t *Table) Hints(name string) []MenuCommand { return t.game.hints(name) }