aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-08-03 16:42:59 -0400
committerhistoria <[not public]>2026-08-03 16:42:59 -0400
commitadda27dba5bf7bf1dee62159f0cbc0bd90734751 (patch)
tree2a68c0bad42c3561cfcd7a32eee52f7ff7eb14f9 /internal
parentfe15f2ffeb5685b056650a61aa0e23965ef51e95 (diff)
downloadthehouseoficarus-adda27dba5bf7bf1dee62159f0cbc0bd90734751.tar.gz
fix: blackjack game formatting
Diffstat (limited to 'internal')
-rw-r--r--internal/casino/blackjack.go6
-rw-r--r--internal/casino/blackjack_test.go25
-rw-r--r--internal/casino/machine.go20
-rw-r--r--internal/casino/manager.go4
-rw-r--r--internal/casino/manager_test.go7
-rw-r--r--internal/game/casino.go6
-rw-r--r--internal/game/casino_test.go7
7 files changed, 54 insertions, 21 deletions
diff --git a/internal/casino/blackjack.go b/internal/casino/blackjack.go
index 80a4d15..ff334c9 100644
--- a/internal/casino/blackjack.go
+++ b/internal/casino/blackjack.go
@@ -399,7 +399,7 @@ func (t *blackjackTable) startRound() []Event {
p.declinedInsurance = true
}
}
- roundEvent := t.event("New blackjack hand!", true)
+ roundEvent := t.event("", true)
roundEvent.Blackjack = t.snapshot(false)
events := []Event{roundEvent}
if t.dealer.cards[1].rank == "A" {
@@ -528,7 +528,7 @@ func (t *blackjackTable) settle() []Event {
t.dealer.cards = append(t.dealer.cards, t.shoe.draw())
}
dealerTotal, _ := t.dealer.total()
- dealerEvent := t.event("Dealer's hand:", true)
+ dealerEvent := t.event("", true)
dealerEvent.Blackjack = t.snapshot(true)
events := []Event{dealerEvent}
dealerBlackjack := t.dealerBlackjack()
@@ -662,7 +662,7 @@ func (t *blackjackTable) menu() string {
func handScore(hand *blackjackHand) string {
total, soft := hand.total()
- if !soft {
+ if !soft || total == 21 {
return fmt.Sprintf("%d", total)
}
return fmt.Sprintf("%d or %d", total-10, total)
diff --git a/internal/casino/blackjack_test.go b/internal/casino/blackjack_test.go
index f1bef99..89b5d06 100644
--- a/internal/casino/blackjack_test.go
+++ b/internal/casino/blackjack_test.go
@@ -19,6 +19,18 @@ func TestBlackjackHandTreatsAceAsElevenWhenPossible(t *testing.T) {
}
}
+func TestBlackjackHandScoreShowsBlackjackAs21(t *testing.T) {
+ if got := handScore(&blackjackHand{cards: []card{{rank: "A"}, {rank: "K"}}}); got != "21" {
+ t.Fatalf("blackjack score=%q, want 21", got)
+ }
+ if got := handScore(&blackjackHand{cards: []card{{rank: "A"}, {rank: "6"}}}); got != "7 or 17" {
+ t.Fatalf("soft 17 score=%q, want 7 or 17", got)
+ }
+ if got := handScore(&blackjackHand{cards: []card{{rank: "10"}, {rank: "8"}}}); got != "18" {
+ t.Fatalf("hard 18 score=%q, want 18", got)
+ }
+}
+
func TestBlackjackSettlementRules(t *testing.T) {
rules := (BlackjackConfig{}).Normalize()
natural := &blackjackHand{bet: 10, cards: []card{{rank: "A"}, {rank: "K"}}}
@@ -46,7 +58,7 @@ func TestBlackjackTableStartsAfterAllPlayersBet(t *testing.T) {
t.Fatal("round started before all players bet")
}
events := table.bet("bob", "", 10, wb)
- if !hasMessage(events, "New blackjack hand") {
+ if !hasSnapshot(events) {
t.Fatalf("round did not start after all players bet: %+v", events)
}
}
@@ -197,7 +209,7 @@ func TestBlackjackBettingIgnoresDisconnected(t *testing.T) {
table.markDisconnected("bob")
w := &testWallet{credits: 100}
events := table.bet("alice", "", 10, w)
- if !hasMessage(events, "New blackjack hand") {
+ if !hasSnapshot(events) {
t.Fatalf("disconnected player stalled the round: %+v", events)
}
}
@@ -226,6 +238,15 @@ func hasMessage(events []Event, fragment string) bool {
return false
}
+func hasSnapshot(events []Event) bool {
+ for _, event := range events {
+ if event.Blackjack != nil {
+ return true
+ }
+ }
+ return false
+}
+
func contains(value, fragment string) bool {
return strings.Contains(value, fragment)
}
diff --git a/internal/casino/machine.go b/internal/casino/machine.go
index 887e154..0ee4cde 100644
--- a/internal/casino/machine.go
+++ b/internal/casino/machine.go
@@ -64,7 +64,7 @@ func (m *Machine) join(name string) []Event {
return m.menuEvents("You resume your place at the slot machine.")
}
-func (m *Machine) setBet(name string, amount int) []Event {
+func (m *Machine) setBet(name string, amount int, wallet Wallet) []Event {
m.mu.Lock()
defer m.mu.Unlock()
if !m.owns(name) {
@@ -79,8 +79,9 @@ func (m *Machine) setBet(name string, amount int) []Event {
if amount < m.Config.MinBet || amount > m.Config.MaxBet {
return m.private(fmt.Sprintf("Slot machine bets must be between %d and %d.", m.Config.MinBet, m.Config.MaxBet))
}
- m.bet = amount
- return m.menuEvents(fmt.Sprintf("Your slot machine bet is set to %d.", amount))
+ return m.beginSpinLocked(name, amount, wallet,
+ fmt.Sprintf("%s sets the bet to %d and pulls the lever.", name, amount),
+ fmt.Sprintf("Bet set to %d. You pull the lever on the slots machine.", amount))
}
func (m *Machine) start(name string, wallet Wallet) []Event {
@@ -98,15 +99,22 @@ func (m *Machine) start(name string, wallet Wallet) []Event {
if m.bet <= 0 {
return m.private("Set a wager first with 'bet <amount>'.")
}
- if _, ok := wallet.Wager(m.bet); !ok {
+ return m.beginSpinLocked(name, m.bet, wallet,
+ fmt.Sprintf("%s pulls the lever on the slots machine.", name),
+ "You pull the lever on the slots machine.")
+}
+
+func (m *Machine) beginSpinLocked(name string, amount int, wallet Wallet, publicMessage, privateMessage string) []Event {
+ if _, ok := wallet.Wager(amount); !ok {
return m.private("You don't have enough chips and credits for that bet.")
}
- m.spinBet = m.bet
+ m.bet = amount
+ m.spinBet = amount
m.player.Wallet = wallet
m.spin = generateSlotSpinWithProfile(m.rng, m.spinBet, m.profile)
m.reel, m.clock, m.suspense = 0, 0, false
m.phase = machineSpinning
- return m.playerAction(fmt.Sprintf("%s pulls the lever on the slots machine.", name), "You pull the lever on the slots machine.")
+ return m.playerAction(publicMessage, privateMessage)
}
func (m *Machine) enableAutoBet(name string, wallet Wallet) []Event {
diff --git a/internal/casino/manager.go b/internal/casino/manager.go
index c876226..60bf14d 100644
--- a/internal/casino/manager.go
+++ b/internal/casino/manager.go
@@ -97,14 +97,14 @@ func (m *Manager) JoinMachine(roomID int, cfg MachineConfig, name string) []Even
return m.EnsureMachine(roomID, cfg).join(name)
}
-func (m *Manager) SetMachineBet(roomID int, machineID, name string, amount int) []Event {
+func (m *Manager) SetMachineBet(roomID int, machineID, name string, amount int, wallet Wallet) []Event {
m.mu.Lock()
machine := m.machines[tableKey(roomID, machineID)]
m.mu.Unlock()
if machine == nil {
return nil
}
- return machine.setBet(name, amount)
+ return machine.setBet(name, amount, wallet)
}
func (m *Manager) StartMachine(roomID int, machineID, name string, wallet Wallet) []Event {
diff --git a/internal/casino/manager_test.go b/internal/casino/manager_test.go
index f0809be..818f397 100644
--- a/internal/casino/manager_test.go
+++ b/internal/casino/manager_test.go
@@ -87,8 +87,13 @@ func TestMachineAutobetCountdownAndStop(t *testing.T) {
m := NewManager(1)
cfg := MachineConfig{ID: "slots", Game: GameSlots, SpinTicks: 1}
m.JoinMachine(1, cfg, "alice")
- m.SetMachineBet(1, "slots", "alice", 10)
w := &testWallet{credits: 100}
+ if events := m.SetMachineBet(1, "slots", "alice", 10, w); len(events) == 0 {
+ t.Fatalf("setting a bet did not start a spin: %+v", events)
+ }
+ for m.MachineBusy("alice") {
+ m.Tick()
+ }
if events := m.EnableMachineAutoBet(1, "slots", "alice", w); len(events) != 0 {
t.Fatalf("enabling autobet emitted events before countdown: %+v", events)
}
diff --git a/internal/game/casino.go b/internal/game/casino.go
index fc400a7..97372f4 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -126,11 +126,11 @@ func (g *Game) executeBet(sess *net.Session, args []string, rawInput string) {
}
if len(args) == 1 {
if args[0] == "max" {
- g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, machine.Config.MaxBet))
+ g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, machine.Config.MaxBet, casinoWallet{g: g, p: p}))
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))
+ g.emitCasinoEvents(g.Casino.SetMachineBet(machine.RoomID, machine.Config.ID, p.Name, amount, casinoWallet{g: g, p: p}))
return
}
}
@@ -227,7 +227,7 @@ func (g *Game) CasinoTick() {
func (g *Game) emitCasinoEvents(events []casino.Event) {
for _, event := range events {
- if event.Message == "" && event.Menu == nil {
+ if event.Message == "" && event.Menu == nil && event.Blackjack == nil && event.Baccarat == nil && event.Slot == nil {
continue
}
category := event.Color
diff --git a/internal/game/casino_test.go b/internal/game/casino_test.go
index d7b5ae9..0394437 100644
--- a/internal/game/casino_test.go
+++ b/internal/game/casino_test.go
@@ -36,12 +36,11 @@ func TestExecuteBetRejectsInvalidMachineArgs(t *testing.T) {
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)
+ // A bet amount at a machine sets the wager and immediately spins.
conn.data = nil
- g.executeBet(sess, nil, "bet")
+ g.executeBet(sess, []string{"10"}, "bet 10")
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))
+ t.Fatalf("bet 10 should attempt an auto-spin and fail at the empty wallet, got %q", string(conn.data))
}
}