aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/table.go
blob: 408e5fe9feb72371b9e398c839b4ad5b48bdb48d (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
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
	// 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("New %s hand! Place your bet.", b.Config.Game)
	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() }