aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-08-03 14:08:09 -0400
committerhistoria <[not public]>2026-08-03 14:08:09 -0400
commit612e429de438821ae8e099d6b54f87c11ff6c1b5 (patch)
treead6f9edf74339b9b50f9c7c2cd314ee7b6e04b2c /internal/game
parentf51424c90dbce7191a31b6e675818c985f0f4539 (diff)
downloadthehouseoficarus-612e429de438821ae8e099d6b54f87c11ff6c1b5.tar.gz
refactor: casino game slop refactor
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/casino.go360
-rw-r--r--internal/game/casino_test.go80
-rw-r--r--internal/game/cmd_option.go20
-rw-r--r--internal/game/cmd_registry.go6
-rw-r--r--internal/game/game.go4
-rw-r--r--internal/game/render_help.go2
6 files changed, 419 insertions, 53 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go
index c6fb63b..801c887 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -4,7 +4,9 @@ import (
"fmt"
"strconv"
"strings"
+ "unicode/utf8"
+ "thehouseoficarus/internal/cardart"
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/color"
"thehouseoficarus/internal/net"
@@ -17,6 +19,9 @@ type casinoWallet struct {
}
func (w casinoWallet) Wager(amount int) (casino.Wager, bool) {
+ if amount <= 0 {
+ return casino.Wager{}, false
+ }
chips := countChips(w.p)
if chips > amount {
chips = amount
@@ -119,23 +124,46 @@ func (g *Game) executeBet(sess *net.Session, args []string, rawInput string) {
g.emitCasinoEvents(g.Casino.StartMachine(machine.RoomID, machine.Config.ID, p.Name, casinoWallet{g: g, p: p}))
return
}
- if len(args) == 1 && args[0] == "max" {
- g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, machine.Config.MaxBet))
- return
- }
if len(args) == 1 {
+ if args[0] == "max" {
+ g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, machine.Config.MaxBet))
+ return
+ }
if amount, err := strconv.Atoi(args[0]); err == nil && amount > 0 {
g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, amount))
return
}
}
- g.emitCasinoEvents(g.Casino.StartMachine(machine.RoomID, machine.Config.ID, p.Name, casinoWallet{g: g, p: p}))
+ sess.WriteLine("Use bet, bet <amount>, or bet max.")
return
}
if len(args) == 0 {
+ if table, playing := g.Casino.Participation(p.Name); playing && table != nil && table.Config.Game == casino.GameBlackjack {
+ amount := g.Casino.RebetAmount(table.RoomID, table.Config.ID, p.Name)
+ g.emitCasinoEvents(g.Casino.Bet(table.RoomID, table.Config.ID, p.Name, amount, casinoWallet{g: g, p: p}))
+ return
+ }
sess.WriteLine("Bet how much?")
return
}
+ if table, playing := g.Casino.Participation(p.Name); playing && table != nil && table.Config.Game == casino.GameBlackjack {
+ amount := 0
+ switch args[0] {
+ case "min":
+ amount = table.Config.MinBet
+ case "max":
+ amount = table.Config.MaxBet
+ default:
+ var err error
+ amount, err = strconv.Atoi(args[0])
+ if err != nil {
+ sess.WriteLine("Use bet, bet <amount>, bet min, or bet max.")
+ return
+ }
+ }
+ g.emitCasinoEvents(g.Casino.Bet(table.RoomID, table.Config.ID, p.Name, amount, casinoWallet{g: g, p: p}))
+ return
+ }
amount, err := strconv.Atoi(args[0])
if err != nil || amount <= 0 {
sess.WriteLine("Your bet must be a positive whole number.")
@@ -161,6 +189,19 @@ func (g *Game) executeAutoBet(sess *net.Session, args []string, rawInput string)
sess.WriteLine("Autobet is only available at games without player choices.")
}
+func (g *Game) executeBlackjackAction(sess *net.Session, action string) {
+ p := sess.Player
+ if p == nil {
+ return
+ }
+ table, playing := g.Casino.Participation(p.Name)
+ if !playing || table == nil {
+ sess.WriteLine("You're not playing a multiplayer casino game.")
+ return
+ }
+ g.emitCasinoEvents(g.Casino.Action(table.RoomID, table.Config.ID, p.Name, action))
+}
+
func (g *Game) CasinoTick() {
if g.Casino != nil {
g.emitCasinoEvents(g.Casino.Tick())
@@ -169,7 +210,7 @@ func (g *Game) CasinoTick() {
func (g *Game) emitCasinoEvents(events []casino.Event) {
for _, event := range events {
- if event.Message == "" {
+ if event.Message == "" && event.Menu == nil {
continue
}
category := event.Color
@@ -182,48 +223,51 @@ func (g *Game) emitCasinoEvents(events []casino.Event) {
continue
}
message := event.Message
- if category == "casino_menu" && sess.Player != nil && sess.Player.Name == event.PrivateTo {
- message = g.casinoMenuBalance(sess, message)
- }
if event.Slot != nil && sess.Player != nil {
message += "\n" + renderSlotSnapshot(event.Slot, g.colorMode(sess), sess.Player.OptionBool("unicode"))
}
+ if event.Blackjack != nil && sess.Player != nil {
+ message += "\n" + renderBlackjackSnapshot(event.Blackjack, g.colorMode(sess), sess.Player.OptionBool("unicode"), sess.Player.OptionInt("card_size"), sess.Player.OptionString("card_style"), g.CardArt)
+ }
sess.WriteLine(g.colorize(sess, category, message))
}
}
- if event.PrivateMessage != "" && event.PrivateTo != "" {
- g.charsMu.Lock()
- sess := g.loggedInChars[event.PrivateTo]
- g.charsMu.Unlock()
- if sess != nil {
- privateMessage := event.PrivateMessage
- if category == "casino_menu" {
- privateMessage = g.casinoMenuBalance(sess, privateMessage)
- }
- sess.WriteLine(g.colorize(sess, category, privateMessage))
- if category == "casino_menu" {
- g.writePrompt(sess)
- }
- }
+ if event.PrivateTo == "" {
+ continue
}
- if event.PrivateTo != "" {
- g.charsMu.Lock()
- sess := g.loggedInChars[event.PrivateTo]
- g.charsMu.Unlock()
- if sess != nil && event.PrivateMessage == "" {
- privateMessage := event.Message
- if category == "casino_menu" {
- privateMessage = g.casinoMenuBalance(sess, privateMessage)
- }
- sess.WriteLine(g.colorize(sess, category, privateMessage))
- if category == "casino_menu" {
- g.writePrompt(sess)
- }
- }
+ text := event.PrivateMessage
+ if text == "" {
+ text = event.Message
+ }
+ if text == "" && event.Menu == nil {
+ continue
+ }
+ g.charsMu.Lock()
+ sess := g.loggedInChars[event.PrivateTo]
+ g.charsMu.Unlock()
+ if sess == nil {
+ continue
+ }
+ privateMessage := g.casinoPrivateMessage(sess, category, event.Menu, text)
+ if event.Blackjack != nil && sess.Player != nil {
+ privateMessage += "\n" + renderBlackjackSnapshot(event.Blackjack, g.colorMode(sess), sess.Player.OptionBool("unicode"), sess.Player.OptionInt("card_size"), sess.Player.OptionString("card_style"), g.CardArt)
+ }
+ sess.WriteLine(g.renderCasinoMessage(sess, category, event.Menu, privateMessage))
+ if category == "casino_menu" {
+ g.writePrompt(sess)
}
}
}
+// 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 && menu.Kind != casino.MenuSlotCommands {
+ return message
+ }
+ return g.colorize(sess, category, message)
+}
+
func casinoEventColor(eventType casino.EventType) string {
if eventType == casino.EventPayout {
return "casino_win"
@@ -231,14 +275,230 @@ func casinoEventColor(eventType casino.EventType) string {
return "casino_action"
}
-func (g *Game) casinoMenuBalance(sess *net.Session, message string) string {
- if sess == nil || sess.Player == nil {
+func (g *Game) casinoPrivateMessage(sess *net.Session, category string, menu *casino.MenuView, message string) string {
+ if category != "casino_menu" || menu == nil {
return message
}
chips := g.colorize(sess, "casino_chips", fmt.Sprintf("%d", countChips(sess.Player)))
credits := g.colorize(sess, "casino_credits", fmt.Sprintf("%d", sess.Player.Credits))
- line := fmt.Sprintf("You have %s chips and %s credits.", chips, credits)
- return strings.Replace(message, "Slot machine commands:", line+"\n\nSlot machine commands:", 1)
+ balance := fmt.Sprintf("You have %s chips and %s credits.", chips, credits)
+ switch menu.Kind {
+ case casino.MenuBlackjackBet:
+ command := func(value string) string { return g.colorize(sess, "casino_command", value) }
+ return fmt.Sprintf("%s\n\nNew hand! You can %s, %s (inc. min/max), or %s.",
+ balance, command("bet"), command("bet <amount>"), command("stop"))
+ case casino.MenuBlackjackTurn:
+ commands := make([]string, len(menu.Actions))
+ for i, action := range menu.Actions {
+ commands[i] = g.colorize(sess, "casino_command", action)
+ }
+ return menu.Title + "\nActions: " + strings.Join(commands, ", ")
+ case casino.MenuSlotCommands:
+ if message == "" {
+ return balance + "\n\n" + menu.Body
+ }
+ return message + "\n\n" + balance + "\n\n" + menu.Body
+ }
+ return message
+}
+
+func renderBlackjackSnapshot(snapshot *casino.BlackjackSnapshot, mode string, unicode bool, size int, style string, art *cardart.Store) string {
+ if snapshot == nil {
+ return ""
+ }
+ size = blackjackCardSize(unicode, size)
+ var lines []string
+ dealerTitle := "Dealer:"
+ if snapshot.DealerRevealed {
+ dealerTitle = "Dealer (" + snapshot.DealerScore + "):"
+ } else if snapshot.DealerShownScore != "" {
+ dealerTitle = "Dealer (" + snapshot.DealerShownScore + "):"
+ }
+ lines = append(lines, blackjackCardLine(mode, dealerTitle))
+ lines = append(lines, blackjackCardsLine(mode, snapshot.Dealer, size, unicode, style, art)...)
+ for _, player := range snapshot.Players {
+ lines = append(lines, blackjackCardLine(mode, fmt.Sprintf("%s (%s):", player.Name, player.Score)))
+ lines = append(lines, blackjackCardsLine(mode, player.Cards, size, unicode, style, art)...)
+ }
+ return strings.Join(lines, "\n")
+}
+
+func blackjackCardLine(mode, text string) string {
+ spec := color.NoColor()
+ spec.Fg = 39
+ return color.Render(mode, spec, text)
+}
+
+func blackjackCardsLine(mode string, cards []casino.BlackjackCardView, size int, unicode bool, style string, art *cardart.Store) []string {
+ if len(cards) == 0 {
+ return nil
+ }
+ if size == 1 {
+ parts := make([]string, len(cards))
+ for i, card := range cards {
+ parts[i] = colorizeCardSymbol(mode, card)
+ }
+ return []string{strings.Join(parts, " ")}
+ }
+ artLines := make([][]string, len(cards))
+ for i, card := range cards {
+ artLines[i] = blackjackCardArt(card, size, unicode, style, art)
+ }
+ lines := make([]string, size)
+ for row := 0; row < size; row++ {
+ var lineParts []string
+ for i, card := range artLines {
+ lineParts = append(lineParts, colorizeCardLine(mode, card[row], cards[i], row, size, style))
+ }
+ lines[row] = strings.Join(lineParts, " ")
+ }
+ return lines
+}
+
+func blackjackCardSize(unicode bool, size int) int {
+ if !unicode {
+ return 5
+ }
+ if size == 1 || size == 5 || size == 7 || size == 9 {
+ return size
+ }
+ return 7
+}
+
+var blackjackSuitSymbols = map[string]string{"hearts": "♥", "spades": "♠", "diamonds": "♦", "clubs": "♣"}
+
+func blackjackSymbol(card casino.BlackjackCardView) string {
+ if card.Hidden {
+ return "?"
+ }
+ return card.Rank + blackjackSuitSymbols[card.Suit]
+}
+
+func blackjackCardArt(card casino.BlackjackCardView, size int, unicode bool, style string, art *cardart.Store) []string {
+ if card.Hidden {
+ return generatedCardArt("?", "", size, unicode, style)
+ }
+ key := card.Rank + strings.ToUpper(card.Suit[:1])
+ if configured := art.Art(key, size); len(configured) == size {
+ return configured
+ }
+ return generatedCardArt(card.Rank, card.Suit, size, unicode, style)
+}
+
+func generatedCardArt(rank, suit string, size int, unicode bool, style string) []string {
+ if size < 5 {
+ size = 5
+ }
+ border := "-"
+ left, right := "+", "+"
+ if unicode {
+ border, left, right = "─", "┌", "┐"
+ }
+ bottomLeft, bottomRight := left, right
+ if unicode {
+ bottomLeft, bottomRight = "└", "┘"
+ }
+ vertical := "|"
+ if unicode {
+ vertical = "│"
+ }
+ if unicode && style == "light" {
+ lines := []string{strings.Repeat("▀", size)}
+ lines = append(lines, generatedCardBody(rank, suit, size, unicode, vertical)...)
+ return append(lines, strings.Repeat("▄", size))
+ }
+ lines := []string{left + strings.Repeat(border, size-2) + right}
+ lines = append(lines, generatedCardBody(rank, suit, size, unicode, vertical)...)
+ lines = append(lines, bottomLeft+strings.Repeat(border, size-2)+bottomRight)
+ return lines
+}
+
+func generatedCardBody(rank, suit string, size int, unicode bool, vertical string) []string {
+ inner := size - 2
+ lines := make([]string, 0, size-2)
+ for row := 1; row < size-1; row++ {
+ content := strings.Repeat(" ", inner)
+ if row == 1 {
+ content = rank + strings.Repeat(" ", inner-len(rank))
+ }
+ if row == (size-1)/2 {
+ suitText := ""
+ if suit != "" {
+ suitText = suitLetter(suit, unicode)
+ }
+ suitWidth := utf8.RuneCountInString(suitText)
+ leftPadding := (inner - suitWidth) / 2
+ content = strings.Repeat(" ", leftPadding) + suitText + strings.Repeat(" ", inner-leftPadding-suitWidth)
+ }
+ if row == size-2 {
+ content = strings.Repeat(" ", inner-len(rank)) + rank
+ }
+ lines = append(lines, vertical+content+vertical)
+ }
+ return lines
+}
+
+func suitLetter(suit string, unicode bool) string {
+ if !unicode {
+ return strings.ToUpper(suit[:1])
+ }
+ return blackjackSuitSymbols[suit]
+}
+
+func colorizeCardLine(mode, line string, card casino.BlackjackCardView, row, size int, style string) string {
+ red := card.Suit == "hearts" || card.Suit == "diamonds"
+ rankColor := 27
+ if red {
+ rankColor = 196
+ }
+ suitColor := 27
+ if red {
+ suitColor = 196
+ }
+ rankLine := row == 1 || row == size-2
+ light := style == "light"
+ bodyBackground := light && row > 0 && row < size-1
+ var builder strings.Builder
+ for _, r := range line {
+ if strings.ContainsRune("┌─┐└┘│┬┴├┤┼╔╗╚╝═║╦╩╠╣╬▀▄+|-", r) {
+ spec := color.ColorSpec{Fg: 15}
+ if bodyBackground {
+ spec.Bg = 15
+ }
+ builder.WriteString(color.Render(mode, spec, string(r)))
+ } else if r == ' ' {
+ if bodyBackground {
+ builder.WriteString(color.Render(mode, color.ColorSpec{Fg: 15, Bg: 15}, " "))
+ } else {
+ builder.WriteRune(r)
+ }
+ } else {
+ value := suitColor
+ if rankLine {
+ value = rankColor
+ }
+ spec := color.ColorSpec{Fg: value}
+ if bodyBackground {
+ spec.Bg = 15
+ }
+ builder.WriteString(color.Render(mode, spec, string(r)))
+ }
+ }
+ return builder.String()
+}
+
+func colorizeCardSymbol(mode string, card casino.BlackjackCardView) string {
+ if card.Hidden {
+ return color.Render(mode, color.ColorSpec{Fg: 15}, "?")
+ }
+ rankColor, suitColor := 27, 27
+ if card.Suit == "hearts" || card.Suit == "diamonds" {
+ rankColor, suitColor = 196, 196
+ }
+ runes := []rune(blackjackSymbol(card))
+ rankLength := len([]rune(card.Rank))
+ return color.Render(mode, color.ColorSpec{Fg: rankColor}, string(runes[:rankLength])) +
+ color.Render(mode, color.ColorSpec{Fg: suitColor}, string(runes[rankLength:]))
}
func renderSlotSnapshot(snapshot *casino.SlotSnapshot, mode string, unicode bool) string {
@@ -295,23 +555,17 @@ func colorSlotSymbol(mode, symbol string) string {
spec.Fg = 250
switch symbol {
case "straw":
- spec = color.NoColor()
- spec.Gradient = []int{220, 226}
+ spec.Fg = 220
case "stick":
- spec = color.NoColor()
- spec.Gradient = []int{34, 46}
+ spec.Fg = 46
case "brick":
- spec = color.NoColor()
- spec.Gradient = []int{160, 196}
+ spec.Fg = 196
case "hat":
- spec = color.NoColor()
- spec.Gradient = []int{129, 201}
+ spec.Fg = 201
case "wolf":
- spec = color.NoColor()
- spec.Gradient = []int{27, 39}
+ spec.Fg = 39
case "scatter":
- spec = color.NoColor()
- spec.Gradient = []int{196, 220, 46, 51, 129, 201}
+ spec.Fg = 226
default:
spec.Dim = true
}
diff --git a/internal/game/casino_test.go b/internal/game/casino_test.go
index b2b37c4..b74baab 100644
--- a/internal/game/casino_test.go
+++ b/internal/game/casino_test.go
@@ -6,8 +6,45 @@ import (
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
+type casinoTestConn struct {
+ data []byte
+}
+
+func (c *casinoTestConn) ReadMessage() (string, error) { return "", nil }
+func (c *casinoTestConn) Write(b []byte) (int, error) {
+ c.data = append(c.data, b...)
+ return len(b), nil
+}
+func (c *casinoTestConn) Close() error { return nil }
+func (c *casinoTestConn) SetEcho(bool) error { return nil }
+
+func TestExecuteBetRejectsInvalidMachineArgs(t *testing.T) {
+ g := &Game{Casino: casino.NewManager(1)}
+ cfg := casino.MachineConfig{ID: "slots", Game: casino.GameSlots}
+ g.Casino.JoinMachine(1, cfg, "alice")
+ conn := &casinoTestConn{}
+ sess := &net.Session{Player: player.New("alice"), Conn: conn}
+ g.loggedInChars = map[string]*net.Session{"alice": sess}
+ for _, args := range [][]string{{"banana"}, {"-5"}, {"10", "extra"}} {
+ conn.data = nil
+ g.executeBet(sess, args, "bet "+strings.Join(args, " "))
+ if !strings.Contains(string(conn.data), "Use bet, bet <amount>, or bet max.") {
+ t.Fatalf("bet %v: expected usage error, got %q", args, string(conn.data))
+ }
+ }
+ // A bare bet at a machine reaches the spin path instead of erroring.
+ g.Casino.SetMachineBet(1, "slots", "alice", 10)
+ conn.data = nil
+ g.executeBet(sess, nil, "bet")
+ if !strings.Contains(string(conn.data), "You don't have enough chips and credits") {
+ t.Fatalf("bare bet should attempt a spin, got %q", string(conn.data))
+ }
+}
+
func TestRenderSlotSnapshotColorsWordSymbols(t *testing.T) {
snapshot := &casino.SlotSnapshot{StoppedReels: 1}
snapshot.Grid[0][0] = "scatter"
@@ -27,3 +64,46 @@ func TestRenderSlotSnapshotColorsWordSymbols(t *testing.T) {
t.Fatalf("slot output lacks the Unicode frame: %q", output)
}
}
+
+func TestRenderBlackjackSnapshotUsesCompactASCIIArt(t *testing.T) {
+ snapshot := &casino.BlackjackSnapshot{
+ Dealer: []casino.BlackjackCardView{{Rank: "?", Hidden: true}, {Rank: "K", Suit: "hearts"}},
+ Players: []casino.BlackjackPlayerView{{Name: "alice", Score: "20", Cards: []casino.BlackjackCardView{{Rank: "5", Suit: "clubs"}, {Rank: "Q", Suit: "spades"}}}},
+ }
+ plain := renderBlackjackSnapshot(snapshot, "none", false, 5, "dark", nil)
+ if !strings.Contains(plain, "+---+") || !strings.Contains(plain, "Dealer:") || !strings.Contains(plain, "alice (20):") {
+ t.Fatalf("unexpected blackjack art: %q", plain)
+ }
+ output := renderBlackjackSnapshot(snapshot, "xterm256", false, 5, "dark", nil)
+ if !strings.Contains(output, "\033[38;5;") {
+ t.Fatalf("expected colored blackjack art: %q", output)
+ }
+ if strings.ContainsAny(output, "│─┌┐└┘") {
+ t.Fatalf("blackjack art contains Unicode borders: %q", output)
+ }
+ for _, line := range strings.Split(output, "\n") {
+ if color.VisibleLen(line) > 80 {
+ t.Fatalf("blackjack line exceeds console width: %d", color.VisibleLen(line))
+ }
+ }
+ unicodeOutput := renderBlackjackSnapshot(snapshot, "none", true, 7, "dark", nil)
+ if !strings.Contains(unicodeOutput, "│5 │") || !strings.Contains(unicodeOutput, "│ 5│") || !strings.Contains(unicodeOutput, "│ ♣ │") {
+ t.Fatalf("unexpected Unicode card alignment: %q", unicodeOutput)
+ }
+ coloredUnicode := renderBlackjackSnapshot(snapshot, "xterm256", true, 7, "dark", nil)
+ if !strings.Contains(coloredUnicode, "\033[38;5;15m") || !strings.Contains(coloredUnicode, "\033[38;5;27m") {
+ t.Fatalf("expected white borders and blue card art: %q", coloredUnicode)
+ }
+ light := renderBlackjackSnapshot(snapshot, "xterm256", true, 7, "light", nil)
+ if !strings.Contains(light, "\033[48;5;15m") {
+ t.Fatalf("expected white card background in light style: %q", light)
+ }
+ lightLines := strings.Split(light, "\n")
+ if strings.Contains(lightLines[1], "\033[48;5;15m") || strings.Contains(lightLines[7], "\033[48;5;15m") {
+ t.Fatalf("card edges should not have a background: %q", light)
+ }
+ lightPlain := renderBlackjackSnapshot(snapshot, "none", true, 7, "light", nil)
+ if !strings.Contains(lightPlain, "▀▀▀▀▀▀▀") || !strings.Contains(lightPlain, "▄▄▄▄▄▄▄") {
+ t.Fatalf("expected half-height light card edges: %q", lightPlain)
+ }
+}
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
index 55a04de..d35ca12 100644
--- a/internal/game/cmd_option.go
+++ b/internal/game/cmd_option.go
@@ -62,6 +62,14 @@ func (g *Game) doOption(sess *net.Session, input string) {
}
value := strings.ToLower(parts[1])
+ if def.Name == "card_size" {
+ switch value {
+ case "1", "5", "7", "9":
+ default:
+ sess.WriteLine("Invalid value for card_size: use 1, 5, 7, or 9.")
+ return
+ }
+ }
parsed, ok := parseOptionValue(def, value)
if !ok {
valid := formatValidValues(def)
@@ -73,11 +81,17 @@ func (g *Game) doOption(sess *net.Session, input string) {
p.Options = make(map[string]any)
}
p.Options[def.Name] = parsed
+ if def.Name == "unicode" && parsed == false {
+ p.Options["card_size"] = 5
+ }
if sess.Account.Options == nil {
sess.Account.Options = make(map[string]any)
}
sess.Account.Options[def.Name] = parsed
+ if def.Name == "unicode" && parsed == false {
+ sess.Account.Options["card_size"] = 5
+ }
acc, err := g.AccountStore.LoadAccount(sess.Account.Name)
if err == nil {
@@ -85,6 +99,9 @@ func (g *Game) doOption(sess *net.Session, input string) {
acc.Options = make(map[string]any)
}
acc.Options[def.Name] = parsed
+ if def.Name == "unicode" && parsed == false {
+ acc.Options["card_size"] = 5
+ }
g.AccountStore.SaveAccount(acc)
}
sess.WriteLine(fmt.Sprintf("%s set to %s.", def.Name, formatOptionValue(p, def)))
@@ -119,6 +136,9 @@ func formatValidValues(def *player.OptionDef) string {
}
return "string"
case player.OptInt:
+ if def.Name == "card_size" {
+ return "1/5/7/9"
+ }
return "num"
}
return ""
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index 4c41216..db6cd96 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -28,6 +28,12 @@ var commandRegistry = map[string]commandDef{
"spin": {(*Game).executeBet, ClassActive},
"autobet": {(*Game).executeAutoBet, ClassActive},
"autospin": {(*Game).executeAutoBet, ClassActive},
+ "hit": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeBlackjackAction(s, "hit") }, ClassActive},
+ "stand": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeBlackjackAction(s, "stand") }, ClassActive},
+ "double": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeBlackjackAction(s, "double") }, ClassActive},
+ "split": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeBlackjackAction(s, "split") }, ClassActive},
+ "insurance": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeBlackjackAction(s, "insurance") }, ClassActive},
+ "surrender": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeBlackjackAction(s, "surrender") }, ClassActive},
"style": {(*Game).executeStyle, ClassInstant},
"look": {(*Game).executeLook, ClassInstant},
"l": {(*Game).executeLook, ClassInstant},
diff --git a/internal/game/game.go b/internal/game/game.go
index cac0ec9..23e0635 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -2,11 +2,13 @@ package game
import (
"fmt"
+ "path/filepath"
"sort"
"strings"
"sync"
"time"
+ "thehouseoficarus/internal/cardart"
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/combat"
"thehouseoficarus/internal/config"
@@ -42,6 +44,7 @@ type Deps struct {
Ticks *engine.Engine
ColorConfig *config.ColorsConfig
ConstantColorConfig *config.ColorsConfig
+ CardArt *cardart.Store
DataDir string
}
@@ -91,6 +94,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, constantColorConfig *
Ticks: engine.New(),
ColorConfig: colorConfig,
ConstantColorConfig: constantColorConfig,
+ CardArt: cardart.Load(filepath.Join(dataDir, "cards.yaml")),
DataDir: dataDir,
},
GlobalFlags: NewGlobalFlagStore(),
diff --git a/internal/game/render_help.go b/internal/game/render_help.go
index 8af3096..afd5b86 100644
--- a/internal/game/render_help.go
+++ b/internal/game/render_help.go
@@ -57,6 +57,8 @@ var commandList = []cmdEntry{
{"bet", "Active", "Place a casino wager"},
{"autobet / autospin", "Active", "Automatically repeat a casino wager"},
{"spin", "Active", "Spin the current casino wager"},
+ {"hit / stand / double / split", "Active", "Play a blackjack hand"},
+ {"insurance / surrender", "Active", "Use blackjack table options"},
{"mine", "Active", "Mine rocks (Mining)"},
{"mix", "Active", "Mix potions (Pharmacy)"},
{"mods / modlist", "Instant", "List available science modules"},