diff options
Diffstat (limited to 'internal/casino/blackjack.go')
| -rw-r--r-- | internal/casino/blackjack.go | 62 |
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) } } |
