From adda27dba5bf7bf1dee62159f0cbc0bd90734751 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 3 Aug 2026 16:42:59 -0400 Subject: fix: blackjack game formatting --- internal/casino/blackjack.go | 6 +++--- internal/casino/blackjack_test.go | 25 +++++++++++++++++++++++-- internal/casino/machine.go | 20 ++++++++++++++------ internal/casino/manager.go | 4 ++-- internal/casino/manager_test.go | 7 ++++++- internal/game/casino.go | 6 +++--- internal/game/casino_test.go | 7 +++---- 7 files changed, 54 insertions(+), 21 deletions(-) (limited to 'internal') 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 '.") } - 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)) } } -- cgit v1.2.3