aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/blackjack.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/blackjack.go')
-rw-r--r--internal/casino/blackjack.go62
1 files changed, 47 insertions, 15 deletions
diff --git a/internal/casino/blackjack.go b/internal/casino/blackjack.go
index 39146f7..0a0551c 100644
--- a/internal/casino/blackjack.go
+++ b/internal/casino/blackjack.go
@@ -19,8 +19,12 @@ const (
)
type blackjackHand struct {
- cards []card
- bet int
+ cards []card
+ bet int
+ // stakes are the wallet handles of the wager records committed to this
+ // hand (the opening bet plus each double or split wager). The hand's
+ // settlement returns their total; the linkage lets a refund skip them.
+ stakes []string
fromSplit bool
doubled bool
stood bool
@@ -53,6 +57,7 @@ type blackjackPlayer struct {
Participant
hands []*blackjackHand
insurance int
+ insuranceStakeID string
declinedInsurance bool
lastBet int
}
@@ -264,8 +269,10 @@ func (t *blackjackTable) action(name, action string) []Event {
if len(hand.cards) != 2 || hand.doubled || hand.surrendered || fromSplitAces(hand) || (hand.fromSplit && !t.Rules.AllowsDoubleAfterSplit()) {
return []Event{t.private(name, "You can only double a qualifying hand.")}
}
- if _, err := p.Wallet.Wager(hand.bet); err != nil {
+ if wager, err := p.Wallet.Wager(hand.bet); err != nil {
return []Event{t.private(name, walletErrorMessage(err, "You don't have enough chips and credits to double that hand."))}
+ } else {
+ hand.stakes = append(hand.stakes, wager.ID)
}
hand.bet *= 2
hand.doubled = true
@@ -306,8 +313,10 @@ func (t *blackjackTable) insuranceAction(name, action string) []Event {
if amount <= 0 {
return []Event{t.private(name, "Your bet is too small to insure.")}
}
- if _, err := p.Wallet.Wager(amount); err != nil {
+ if wager, err := p.Wallet.Wager(amount); err != nil {
return []Event{t.private(name, walletErrorMessage(err, "You don't have enough chips and credits for insurance."))}
+ } else {
+ p.insuranceStakeID = wager.ID
}
p.insurance = amount
events = append(events, t.playerAction(name, fmt.Sprintf("%s takes insurance.", name), fmt.Sprintf("You place %d credits on insurance.", amount)))
@@ -352,11 +361,12 @@ func (t *blackjackTable) splitHand(name string, p *blackjackPlayer, hand *blackj
if hand.cards[0].rank == "A" && hand.fromSplit && !t.Rules.AllowsResplitAces() {
return []Event{t.private(name, "Aces cannot be re-split at this table.")}
}
- if _, err := p.Wallet.Wager(hand.bet); err != nil {
+ wager, err := p.Wallet.Wager(hand.bet)
+ if err != nil {
return []Event{t.private(name, walletErrorMessage(err, "You don't have enough chips and credits to split that hand."))}
}
- left := &blackjackHand{cards: []card{hand.cards[0]}, bet: hand.bet, fromSplit: true}
- right := &blackjackHand{cards: []card{hand.cards[1]}, bet: hand.bet, fromSplit: true}
+ left := &blackjackHand{cards: []card{hand.cards[0]}, bet: hand.bet, fromSplit: true, stakes: hand.stakes}
+ right := &blackjackHand{cards: []card{hand.cards[1]}, bet: hand.bet, fromSplit: true, stakes: []string{wager.ID}}
left.cards = append(left.cards, t.shoe.draw())
right.cards = append(right.cards, t.shoe.draw())
index := t.activeHand
@@ -392,7 +402,7 @@ func (t *blackjackTable) startRound() []Event {
if !p.HasBet {
continue
}
- p.hands = []*blackjackHand{{cards: []card{t.shoe.draw(), t.shoe.draw()}, bet: p.Wager.Total}}
+ p.hands = []*blackjackHand{{cards: []card{t.shoe.draw(), t.shoe.draw()}, bet: p.Wager.Total, stakes: []string{p.Wager.ID}}}
if p.hands[0].bet/2 <= 0 {
// A half-bet that rounds to zero cannot be insured.
p.declinedInsurance = true
@@ -553,12 +563,12 @@ func (t *blackjackTable) settle() []Event {
for _, hand := range p.hands {
result, payout := settleHand(hand, dealerTotal, dealerBlackjack, t.Rules)
if payout > 0 && p.Wallet != nil {
- if err := p.Wallet.PayCredits(payout); err != nil {
+ if err := p.Wallet.PayStakeCover(payout, hand.stakes); err != nil {
// The payout could not be made durable. The journal
- // record stays open: recovery re-applies the owed
- // amount. The win is not announced as paid, and the
- // stakes stay open so recovery settles them against
- // the payout record.
+ // record stays open listing the hand's stakes as
+ // covered: recovery re-applies the owed payout (which
+ // returns those stakes) and refunds must not pay them
+ // again. The win is not announced as paid.
payoutFailed = true
publicResult, _ := blackjackResultMessages(p.Name, result, payout)
settlement := t.playerAction(p.Name, publicResult, payoutFailedMessage())
@@ -570,6 +580,15 @@ func (t *blackjackTable) settle() []Event {
events = append(events, settlement)
continue
}
+ // The hand was paid: its stakes are resolved immediately
+ // so a later sibling-hand payout failure cannot cause a
+ // refund of a stake this settlement already returned.
+ t.resolveHandStakes(p.Wallet, hand)
+ } else if payout == 0 && p.Wallet != nil {
+ // A lost hand resolves its stakes even when a sibling
+ // hand's payout is still in flight: the stake was
+ // consumed by the rules of the game.
+ t.resolveHandStakes(p.Wallet, hand)
}
publicResult, privateResult := blackjackResultMessages(p.Name, result, payout)
settlement := t.playerAction(p.Name, publicResult, privateResult)
@@ -586,13 +605,16 @@ func (t *blackjackTable) settle() []Event {
insurancePayout, _ = MulPayout(p.insurance, 1+t.Rules.InsurancePayout)
}
if insurancePayout > 0 {
- if err := p.Wallet.PayCredits(insurancePayout); err != nil {
+ if err := p.Wallet.PayStakeCover(insurancePayout, []string{p.insuranceStakeID}); err != nil {
payoutFailed = true
events = append(events, t.private(p.Name, payoutFailedMessage()))
} else {
+ p.Wallet.ResolveStake(p.insuranceStakeID)
events = append(events, t.private(p.Name, fmt.Sprintf("Insurance pays %d credits.", insurancePayout)))
}
} else {
+ // Insurance lost: its stake was consumed by the rules.
+ p.Wallet.ResolveStake(p.insuranceStakeID)
events = append(events, t.private(p.Name, "Insurance pays 0 credits."))
}
}
@@ -648,7 +670,17 @@ func (t *blackjackTable) resetRound() {
t.window.close()
t.timer.stop()
for _, p := range t.players.ordered() {
- p.HasBet, p.Wager, p.hands, p.insurance, p.declinedInsurance = false, Wager{}, nil, 0, false
+ p.HasBet, p.Wager, p.hands, p.insurance, p.insuranceStakeID, p.declinedInsurance = false, Wager{}, nil, 0, "", false
+ }
+}
+
+// resolveHandStakes settles every stake wager a hand committed. Nil-safe.
+func (t *blackjackTable) resolveHandStakes(wallet Wallet, hand *blackjackHand) {
+ if wallet == nil {
+ return
+ }
+ for _, stakeID := range hand.stakes {
+ wallet.ResolveStake(stakeID)
}
}