diff options
Diffstat (limited to 'internal/casino/settlement_test.go')
| -rw-r--r-- | internal/casino/settlement_test.go | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/internal/casino/settlement_test.go b/internal/casino/settlement_test.go new file mode 100644 index 0000000..9c110d6 --- /dev/null +++ b/internal/casino/settlement_test.go @@ -0,0 +1,252 @@ +package casino + +import ( + "strings" + "testing" +) + +type recoveringSettlementWallet struct { + testWallet + fail bool +} + +func (w *recoveringSettlementWallet) PayCredits(n int, ids ...string) error { + if w.fail { + return ErrWalletPersistence + } + return w.testWallet.PayCredits(n, ids...) +} + +func receiptLines(events []Event, name string) []SettlementLine { + var lines []SettlementLine + for _, e := range events { + if e.PrivateTo == name { + lines = append(lines, e.Settlement...) + } + } + return lines +} +func receiptTotal(lines []SettlementLine) (stake, returned int) { + for _, line := range lines { + if !line.Unresolved { + stake += line.Stake + returned += line.Returned + } + } + return +} + +func TestCasinoReceiptBlackjackSplitsInsuranceAndNatural(t *testing.T) { + tb := uxBlackjack() + p := tb.players.get("alice") + p.Wallet = &testWallet{} + p.hands = []*blackjackHand{ + {bet: 100, cards: []card{{rank: "A"}, {rank: "K"}}}, + {bet: 200, cards: []card{{rank: "10"}, {rank: "9"}}}, + {bet: 100, cards: []card{{rank: "10"}, {rank: "7"}}}, + } + p.insurance = 50 + lines := receiptLines(tb.settle(), "alice") + if len(lines) != 4 { + t.Fatalf("%+v", lines) + } + if lines[0].Odds != "3:2" || lines[0].Returned != 250 || lines[1].Returned != 400 || lines[2].Result != "push" || lines[3].Odds != "2:1" { + t.Fatalf("%+v", lines) + } + stake, paid := receiptTotal(lines) + if stake != 450 || paid != 750 { + t.Fatalf("stake=%d paid=%d", stake, paid) + } +} + +func TestCasinoReceiptRouletteAndBaccarat(t *testing.T) { + t.Run("roulette", func(t *testing.T) { + tb := newRouletteTable(1, TableConfig{ID: "r", Game: GameRoulette}, newTestRand()) + w := &testWallet{credits: 100} + tb.join("alice") + tb.bet("alice", "17", 10, w) + lines := receiptLines(tb.settle(), "alice") + if len(lines) != 1 || lines[0].Odds != "35:1" || lines[0].Returned != w.paid { + t.Fatalf("%+v paid=%d", lines, w.paid) + } + }) + t.Run("baccarat", func(t *testing.T) { + tb := newBaccaratTestTable(TableConfig{ID: "b", Game: GameBaccarat}) + w := &testWallet{credits: 100} + tb.join("alice") + events := tb.bet("alice", "banker", 10, w) + for i := 0; i < 30 && len(receiptLines(events, "alice")) == 0; i++ { + events = append(events, tb.tick()...) + } + lines := receiptLines(events, "alice") + if len(lines) != 1 || lines[0].Odds != "0.95:1" || lines[0].Returned != w.paid { + t.Fatalf("%+v paid=%d", lines, w.paid) + } + }) +} + +func TestCasinoReceiptCrapsActiveAndOddsRefund(t *testing.T) { + tb := crapsFixture() + w := &testWallet{credits: 1000} + tb.join("alice") + tb.bet("alice", "pass", 100, w) + tb.bet("alice", "field", 25, w) + lines := receiptLines(tb.resolveRoll(3, 3), "alice") + if len(lines) != 2 { + t.Fatalf("%+v", lines) + } + stake, paid := receiptTotal(lines) + if stake != 25 || paid != 0 { + t.Fatalf("%+v", lines) + } + active := 0 + for _, line := range lines { + if line.Unresolved { + active += line.Stake + } + } + if active != 100 { + t.Fatal(lines) + } + tb.bet("alice", "odds pass", 120, w) + lines = receiptLines(tb.resolveRoll(3, 3), "alice") + if len(lines) != 2 { + t.Fatalf("%+v", lines) + } + if lines[0].Odds != "1:1" || lines[1].Odds != "6:5" { + t.Fatal(lines) + } + stake, paid = receiptTotal(lines) + if stake != 220 || paid != 464 { + t.Fatalf("%+v", lines) + } + b := &crapsBet{kind: "come", number: 6, stake: Wager{Total: 10}, odds: Wager{Total: 60}} + lines = tb.crapsSettlementLines(b, -1, 1, true, false) + if lines[0].Result != "loss" || lines[1].Result != "push" || lines[1].Returned != 60 { + t.Fatal(lines) + } +} + +func TestCasinoReceiptCrapsPartialFailure(t *testing.T) { + tb := crapsFixture() + tb.join("alice") + w := &recoveringSettlementWallet{testWallet: testWallet{credits: 1000}} + tb.bet("alice", "pass", 100, w) + tb.bet("alice", "field", 25, w) + w.fail = true + events := tb.resolveRoll(3, 3) + lines := receiptLines(events, "alice") + if len(lines) != 1 || !lines[0].Unresolved { + t.Fatal(lines) + } + found := false + for _, e := range events { + found = found || e.SettlementNote != "" + } + if !found { + t.Fatal("failed wager warning lost") + } +} + +func TestCasinoReceiptSlotFeatureAndDurability(t *testing.T) { + m := newMachine(1, MachineConfig{ID: "s", Game: GameSlots}, newTestRand()) + w := &recoveringSettlementWallet{testWallet: testWallet{credits: 100}} + m.join("alice") + m.beginSpinLocked(10, w, "", "") + m.spin = &SlotSpin{ScatterCount: 3, Payout: 20} + lines := receiptLines(m.finishSpin(), "alice") + if len(lines) != 1 || !lines[0].Unresolved || w.paid != 0 { + t.Fatal(lines, w.paid) + } + m.freeSpins = 0 + m.spin = &SlotSpin{Payout: 5} + lines = receiptLines(m.finishSpin(), "alice") + if len(lines) != 1 || lines[0].Unresolved || lines[0].Stake != 10 || lines[0].Returned != 25 || w.paid != 25 { + t.Fatal(lines, w.paid) + } + // A failed zero-return save must not announce a settled loss. + m.beginSpinLocked(10, w, "", "") + m.spin = &SlotSpin{} + w.fail = true + events := m.finishSpin() + if len(receiptLines(events, "alice")) != 0 || len(w.open) != 1 || !hasMessage(events, payoutFailedMessage()) { + t.Fatalf("events=%+v open=%v", events, w.open) + } +} + +func TestCasinoReceiptVideoPokerAndPaiGow(t *testing.T) { + t.Run("poker", func(t *testing.T) { + tb, w, _ := pokerTestCabinet(t, VideoPokerConfig{Cabinet: CabinetClassic}) + pokerDeal(t, tb, w) + p := tb.players["alice"] + p.cards = pokerHand("Ah Kh Qh Jh 10h") + p.held = [5]bool{true, true, true, true, true} + stake := p.wager.Total + lines := receiptLines(tb.drawLocked("alice", p), "alice") + got, paid := receiptTotal(lines) + if len(lines) != p.hands || got != stake || paid != w.paid || paid == 0 || lines[0].Odds != "799:1" { + t.Fatalf("%+v stake=%d paid=%d", lines, stake, w.paid) + } + }) + t.Run("pai gow", func(t *testing.T) { + tb := pgTestTable(true) + w := &testWallet{credits: 1000} + tb.join("alice") + tb.bet("alice", "fortune", 5, w) + tb.bet("alice", "main", 100, w) + p := tb.player("alice") + p.split = pgHouse(p.cards) + p.ready = true + lines := receiptLines(tb.prepareSettlementLocked(), "alice") + stake, paid := receiptTotal(lines) + if len(lines) != 2 || stake != 105 || paid != w.paid || lines[0].Odds != "19:20" { + t.Fatalf("%+v paid=%d", lines, w.paid) + } + }) +} + +func TestCasinoReceiptPaiGowRetryAnnouncesExactlyOnce(t *testing.T) { + tb := pgTestTable(false) + w := &recoveringSettlementWallet{testWallet: testWallet{credits: 1000}} + tb.join("alice") + tb.bet("alice", "fortune", 5, w) + tb.bet("alice", "main", 100, w) + p := tb.player("alice") + p.split, p.ready = pgHouse(p.cards), true + w.fail = true + if lines := receiptLines(tb.prepareSettlementLocked(), "alice"); len(lines) != 0 { + t.Fatal(lines) + } + if !p.showdown.Pending { + t.Fatal("missing payment-pending status") + } + w.fail = false + lines := receiptLines(tb.payLocked(), "alice") + if len(lines) != 2 || p.showdown.Pending || !p.showdown.Paid { + t.Fatal(lines, p.showdown) + } + paid := w.paid + if len(receiptLines(tb.payLocked(), "alice")) != 0 || w.paid != paid { + t.Fatal("duplicate settlement on retry") + } +} + +func TestCasinoReceiptOddsAreProfitNotReturn(t *testing.T) { + for _, tc := range []struct { + num, den int + want string + }{{35, 1, "35:1"}, {3, 2, "3:2"}, {144, 120, "6:5"}, {0, 5, "0:1"}} { + if got := profitOdds(tc.num, tc.den); got != tc.want { + t.Fatalf("%s != %s", got, tc.want) + } + } + w, _ := rouletteWagerFor("orphelins", false, true) + if got := rouletteSettlementOdds(w, "0"); got != "Varies" { + t.Fatal(got) + } + for _, line := range crapsFixture().crapsSettlementLines(&crapsBet{kind: "buy", number: 4, stake: Wager{Total: 20}}, 2, 1, false, false) { + if !strings.Contains(line.Odds, "fee") || line.Returned != 59 { + t.Fatal(line) + } + } +} |
