aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/casino.go')
-rw-r--r--internal/game/casino.go84
1 files changed, 81 insertions, 3 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go
index 9282653..08695e7 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -563,7 +563,7 @@ func (g *Game) emitCasinoEvents(events []casino.Event) {
// Style prose before composing it with independently styled art.
// Wrapping the finished message leaks casino_action into borders
// and plain spaces between a snapshot's own color resets.
- message := g.colorize(sess, category, g.casinoTimeText(event.Message))
+ message := g.casinoProse(sess, category, g.casinoTimeText(event.Message))
if details {
if rendered := g.renderCasinoSnapshot(sess, event.Snapshot); rendered != "" {
message += "\n" + rendered
@@ -592,7 +592,10 @@ func (g *Game) emitCasinoEvents(events []casino.Event) {
}
privateMessage := g.casinoPrivateMessage(sess, category, event.Menu, g.casinoTimeText(text))
privateMessage = g.renderCasinoMessage(sess, category, event.Menu, privateMessage)
- if rendered := g.renderCasinoSnapshot(sess, event.Snapshot); rendered != "" {
+ rendered := g.renderCasinoSnapshot(sess, event.Snapshot)
+ if header, reminder, ok := g.casinoBoardReminder(sess, category, event.Menu, event.Snapshot, g.casinoTimeText(text)); ok {
+ privateMessage = composeTableThenReminder(header, rendered, reminder)
+ } else if rendered != "" {
privateMessage += "\n" + rendered
}
if event.ShowBalance {
@@ -605,13 +608,66 @@ func (g *Game) emitCasinoEvents(events []casino.Event) {
}
}
+// casinoBoardReminder splits a roulette menu so its wager instructions can be
+// printed under the betting board, which is the table the reminder refers to.
+// Every other game, and compact displays, keep their menu in one block.
+func (g *Game) casinoBoardReminder(sess *net.Session, category string, menu *casino.MenuView, snapshot casino.Snapshot, message string) (header, reminder string, ok bool) {
+ if category != "casino_menu" || menu == nil || sess.Player == nil {
+ return "", "", false
+ }
+ s, isRoulette := snapshot.(*casino.RouletteSnapshot)
+ if !isRoulette || s.Phase == "playing" || s.Phase == "result" {
+ return "", "", false
+ }
+ if sess.Player.OptionString("casino_display") == "compact" && (menu.Repeat || !menu.ShowBalance) {
+ // The compact reminder is already a short block; leave it intact.
+ return "", "", false
+ }
+ balance := ""
+ if menu.ShowBalance {
+ balance = g.casinoBalance(sess)
+ }
+ heading := g.casinoTimeText(menu.Heading)
+ if heading == "" {
+ heading = message
+ }
+ commands := append([]casino.MenuCommand(nil), menu.Commands...)
+ for i := range commands {
+ if commands[i].Cost > casinoAffordable(sess.Player) {
+ commands[i].Desc += " [unavailable: insufficient funds]"
+ }
+ }
+ return g.casinoMenuHeader(sess, balance, heading), g.casinoMenuCommands(sess, commands), true
+}
+
+// composeTableThenReminder joins a menu header, the board graphic, and the
+// wager reminder. The board snapshot already leads with a blank line, so only
+// the reminder needs an extra gap before it.
+func composeTableThenReminder(header, table, reminder string) string {
+ out := header
+ if table != "" {
+ out += "\n" + table
+ }
+ if reminder != "" {
+ out += "\n\n" + reminder
+ }
+ return out
+}
+
// renderCasinoMessage colorizes a casino message unless it is an
// already-composed structured menu.
func (g *Game) renderCasinoMessage(sess *net.Session, category string, menu *casino.MenuView, message string) string {
if category == "casino_menu" && menu != nil {
return message
}
- return g.colorize(sess, category, message)
+ return g.casinoProse(sess, category, message)
+}
+
+// casinoProse colorizes casino event text, expanding inline color tags such as
+// {casino_result_win}WIN{/} against the session's palette. The event's color
+// category is the default for untagged text.
+func (g *Game) casinoProse(sess *net.Session, category, text string) string {
+ return g.expandTagsDefault(sess, g.resolveColor(sess, category), text)
}
func (g *Game) casinoPrivateMessage(sess *net.Session, category string, menu *casino.MenuView, message string) string {
@@ -701,6 +757,22 @@ func (g *Game) renderCasinoSnapshot(sess *net.Session, snapshot casino.Snapshot)
// optional balance line, a heading, and an aligned command list with each
// command in the "command" color and its description in plain text.
func (g *Game) renderCasinoCommands(sess *net.Session, balance, heading string, commands []casino.MenuCommand) string {
+ head := g.casinoMenuHeader(sess, balance, heading)
+ body := g.casinoMenuCommands(sess, commands)
+ switch {
+ case head == "":
+ return body
+ case body == "":
+ return head
+ default:
+ return head + "\n\n" + body
+ }
+}
+
+// casinoMenuHeader formats the balance and heading that introduce a casino
+// menu. It is split from the command list so the roulette board can be drawn
+// between them.
+func (g *Game) casinoMenuHeader(sess *net.Session, balance, heading string) string {
var sb strings.Builder
if balance != "" {
sb.WriteString(balance)
@@ -710,12 +782,18 @@ func (g *Game) renderCasinoCommands(sess *net.Session, balance, heading string,
sb.WriteString(g.colorize(sess, "casino_action", heading))
sb.WriteString("\n\n")
}
+ return strings.TrimRight(sb.String(), "\n")
+}
+
+// casinoMenuCommands formats an aligned, colored command list.
+func (g *Game) casinoMenuCommands(sess *net.Session, commands []casino.MenuCommand) string {
width := 0
for _, c := range commands {
if n := utf8.RuneCountInString(c.Command); n > width {
width = n
}
}
+ var sb strings.Builder
for _, c := range commands {
padded := c.Command + strings.Repeat(" ", width-utf8.RuneCountInString(c.Command))
category := "command"