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
|
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
setRoom(roomID int)
idle() bool
}
// tableBase carries the identity every table game needs to build events.
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
}
// Table is the manager-facing handle for a multiplayer table. It only holds
// routing state; all game logic lives in the tableGame implementation.
type Table struct {
RoomID int
Config TableConfig
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()
t := &Table{RoomID: roomID, Config: cfg}
switch cfg.Game {
case GameBlackjack:
t.game = newBlackjackTable(roomID, cfg, rng)
case GameBaccarat:
t.game = newBaccaratTable(roomID, cfg, rng)
default:
return nil
}
return t
}
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() }
// setRoom updates the table game's room for event routing after a room-ID
// rekey. Games keep their state; only their location moves.
func (b *tableBase) setRoom(roomID int) { b.RoomID = roomID }
// idle reports whether no player is seated (or mid-round): only an idle
// table may have its live rules replaced by new room configuration.
func (t *blackjackTable) idle() bool {
t.mu.Lock()
defer t.mu.Unlock()
return t.players.count() == 0
}
// idle reports whether no player is seated (or mid-round).
func (t *baccaratTable) idle() bool {
t.mu.Lock()
defer t.mu.Unlock()
return t.players.count() == 0
}
// idle on the Table wrapper delegates to the game.
func (t *Table) idle() bool { return t.game.idle() }
|