aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino_ux.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-30 20:50:45 -0400
committerhistoria <[not public]>2026-09-30 20:50:45 -0400
commite3ff06fc79682673194c8913f0feb9e90920e7d0 (patch)
tree86e25670b5bd02d9868130afcc7a69ec1b7b9ed1 /internal/game/casino_ux.go
parenta5b9c1052c4640ba519e608e0c97e603ce6aa5ef (diff)
downloadthehouseoficarus-e3ff06fc79682673194c8913f0feb9e90920e7d0.tar.gz
feat: look, rules, and list commands updated for casino games
Diffstat (limited to 'internal/game/casino_ux.go')
-rw-r--r--internal/game/casino_ux.go141
1 files changed, 138 insertions, 3 deletions
diff --git a/internal/game/casino_ux.go b/internal/game/casino_ux.go
index a2e7330..2881a49 100644
--- a/internal/game/casino_ux.go
+++ b/internal/game/casino_ux.go
@@ -8,6 +8,8 @@ import (
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
type casinoChoice struct {
@@ -104,6 +106,8 @@ func (g *Game) executeTableStatus(sess *net.Session, args []string, _ string) {
t, playing := g.Casino.Participation(sess.Player.Name)
if !playing || t == nil {
if len(args) > 0 && args[0] == "help" {
+ // Machine players get their cabinet's guide; everyone else the
+ // shared casino one.
topic := "casino"
if playing {
topic = "slots"
@@ -115,17 +119,19 @@ func (g *Game) executeTableStatus(sess *net.Session, args []string, _ string) {
return
}
if len(args) > 0 && args[0] == "rules" {
+ // The felt and the exact, per-table wording stay under table rules;
+ // bare rules opens the game's own help page.
sess.WriteLine(g.renderCasinoRules(sess, t.Config))
return
}
if len(args) > 0 && args[0] == "help" {
- g.doHelp(sess, string(t.Config.Game))
+ g.doHelp(sess, t.HelpTopic())
} else {
g.emitCasinoEvents(t.Status(sess.Player.Name))
if t.Config.Game == casino.GameRoulette {
sess.WriteLine(g.rouletteReminder(sess, t.Config))
} else {
- sess.WriteLine(g.colorize(sess, "command", "Learn to play: table help / help casino. Exact payouts: table rules."))
+ sess.WriteLine(g.colorize(sess, "command", "Learn to play: rules. Exact payouts: table rules."))
}
}
if len(args) > 0 {
@@ -133,8 +139,10 @@ func (g *Game) executeTableStatus(sess *net.Session, args []string, _ string) {
// (insurance, split, rebet, etc.) that may not be legal right now.
g.emitCasinoEvents(t.Status(sess.Player.Name))
commands := []casino.MenuCommand{
+ {Command: "rules", Desc: "Help page for this game variant"},
+ {Command: "list", Desc: "Every bet available right now, with payouts"},
+ {Command: "table rules", Desc: "This table’s exact rules and payouts"},
{Command: "option casino_display compact", Desc: "Compact action hints (detailed for full command menus)"},
- {Command: "table rules", Desc: "Review this table’s rules"},
{Command: "help casino", Desc: "Quick guides to all casino games"},
}
sess.WriteLine(g.renderCasinoCommands(sess, "", "Table controls", commands))
@@ -236,6 +244,14 @@ func (g *Game) casinoTimeText(text string) string {
})
}
+// betRangeWords renders the wager range in words for one-line hints.
+func betRangeWords(minBet, maxBet int) string {
+ if maxBet <= 0 || maxBet >= casino.DefaultMaxBet {
+ return fmt.Sprintf("%d or more", minBet)
+ }
+ return fmt.Sprintf("%d-%d", minBet, maxBet)
+}
+
func (g *Game) casinoBalance(sess *net.Session) string {
if sess.Player == nil {
return ""
@@ -243,3 +259,122 @@ func (g *Game) casinoBalance(sess *net.Session) string {
p := sess.Player
return fmt.Sprintf("Available %s (%s chips + %s credits)", g.colorize(sess, "casino_chips", strconv.Itoa(casinoAffordable(p))), g.colorize(sess, "casino_chips", strconv.Itoa(countChips(p))), g.colorize(sess, "casino_credits", strconv.Itoa(p.Credits)))
}
+
+// executeRules is the seated table's own help entry point: a variant's page
+// (Simplified Craps gets its own short page, not a section of the craps
+// manual), slots help at a machine, and the room's own game while merely
+// standing in a casino room. Explicit topics pass straight through to help.
+func (g *Game) executeRules(sess *net.Session, args []string, rawInput string) {
+ if sess.Player == nil {
+ return
+ }
+ if len(args) > 0 {
+ g.doHelp(sess, strings.Join(args, " "))
+ return
+ }
+ if g.Casino == nil {
+ g.doHelp(sess, "casino")
+ return
+ }
+ if topic := g.casinoRulesTopic(sess.Player.RoomID, sess.Player.Name); topic != "" {
+ g.doHelp(sess, topic)
+ return
+ }
+ // Not in a casino game or room: the shared guide still answers.
+ g.doHelp(sess, "casino")
+}
+
+// casinoRulesTopic picks the help page "rules" should open: the seated
+// table's variant page first, then a machine's game, then the room's own
+// casino games (so standing in the craps pit answers about craps). Mixed
+// games, or no casino content at all, fall back to the shared guide.
+func (g *Game) casinoRulesTopic(roomID int, name string) string {
+ if table, playing := g.Casino.Participation(name); playing && table != nil {
+ return table.HelpTopic()
+ }
+ if machine, ok := g.Casino.MachineFor(name); ok {
+ return string(machine.Config.Game)
+ }
+ room, err := g.World.LoadRoom(roomID)
+ if err != nil {
+ return ""
+ }
+ topic := ""
+ for _, cfg := range room.CasinoTables {
+ page := cfg.HelpTopic()
+ if topic == "" {
+ topic = page
+ } else if topic != page {
+ return "casino"
+ }
+ }
+ for _, m := range room.CasinoMachines {
+ page := string(m.Game)
+ if topic == "" {
+ topic = page
+ } else if topic != page {
+ return "casino"
+ }
+ }
+ return topic
+}
+
+// executeList shows the bets a casino game accepts right now — the game's
+// own state-driven list, from the come-out pass line to the travelling come
+// bets and one-roll props. Everywhere else (shops, plain rooms) "list"
+// keeps its shop meaning.
+func (g *Game) executeList(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ if p == nil {
+ return
+ }
+ if len(args) > 0 || g.Casino == nil {
+ g.executeShopList(sess, args, rawInput)
+ return
+ }
+ if machine, ok := g.Casino.MachineFor(p.Name); ok {
+ sess.WriteLine(g.colorize(sess, "casino_action", "A "+casinoGameLabel(machine.Config.Game)+" takes one wager: bet <amount> (or bet max), then spin or autospin."))
+ return
+ }
+ if table, playing := g.Casino.Participation(p.Name); playing && table != nil {
+ g.showCasinoWagerList(sess, table)
+ return
+ }
+ g.executeShopList(sess, args, rawInput)
+}
+
+// showCasinoWagerList renders the game's live wager list. Two or more rows
+// become a brief table; a single row is one line; an empty list means the
+// only bet is the ordinary amount-only wager.
+func (g *Game) showCasinoWagerList(sess *net.Session, t *casino.Table) {
+ rows := t.Wagers(sess.Player.Name)
+ switch {
+ case len(rows) == 1:
+ r := rows[0]
+ sess.WriteLine(g.colorize(sess, "casino_action", "Your only wager: bet <amount> on "+r.Bet+" — "+r.Payout+" — "+r.Win+"."))
+ case len(rows) == 0:
+ sess.WriteLine(g.colorize(sess, "casino_action", "The only wager here is bet <amount>") + " (" + betRangeWords(t.Config.MinBet, t.Config.MaxBet) + ").")
+ default:
+ commands := make([]casino.MenuCommand, 0, len(rows))
+ for _, r := range rows {
+ commands = append(commands, casino.MenuCommand{Command: "bet <amount> on " + r.Bet, Desc: r.Payout + " | " + r.Win})
+ }
+ sess.WriteLine(g.renderCasinoCommands(sess, g.casinoBalance(sess), t.Config.Label()+" — available bets", commands))
+ }
+}
+
+// showCasinoGameLook appends the live game state and hint menu for the
+// table the player is seated at, so look reads room first, then the table,
+// then the commands that matter now. Machines are skipped: their menus
+// arrive with every spin.
+func (g *Game) showCasinoGameLook(sess *net.Session, p *player.Player, room *world.Room) {
+ if g.Casino == nil || p == nil {
+ return
+ }
+ table, playing := g.Casino.Participation(p.Name)
+ if !playing || table == nil || table.RoomID != room.ID {
+ return
+ }
+ sess.WriteLine("")
+ g.emitCasinoEvents(table.Status(p.Name))
+}