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
|
package casino
import (
"fmt"
"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
// 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 heading for a table
// game; only the command list differs per game.
func (b *tableBase) betMenuEvent(name string, commands []MenuCommand) Event {
heading := fmt.Sprintf("%s | Limits %s", b.Config.Label(), betRangeText(b.Config.MinBet, b.Config.MaxBet))
e := b.private(name, heading)
e.Color = "casino_menu"
e.Menu = &MenuView{Heading: heading, 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() }
// 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) }
|