aboutsummaryrefslogtreecommitdiff
path: root/internal/game/casino_forfeit_regression_test.go
blob: c0647120c93714767a370e90f46e9c29e99b4391 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package game

// Disconnect-forfeit regressions: a player who disconnects mid-round
// forfeits the committed stake, so startup recovery must NOT refund it; a
// stake left outstanding by a genuine crash still is.

import (
	"testing"

	"thehouseoficarus/internal/casino"
)

// TestDisconnectForfeitsMachineStake: disconnecting mid-spin forfeits the
// stake (the machine frees the seat immediately); recovery must not refund
// it on the next startup.
func TestDisconnectForfeitsMachineStake(t *testing.T) {
	h := newHarness(t)
	h.seedAccount("alice")
	p := h.seedCharacter("alice", "alice")
	h.g.RunOnOwner(func() {
		p.Credits = 100
		h.g.Casino.JoinMachine(1, casino.MachineConfig{ID: "slots", Game: casino.GameSlots, SpinTicks: 50}, "alice")
		h.g.Casino.SetMachineBet(1, "slots", "alice", 10, casinoWallet{g: h.g, p: p})
		// The disconnect path forfeits the in-flight spin.
		h.g.Casino.MarkDisconnected("alice")
	})
	h.barrier()

	restarted := harnessFromDir(t, h.dir)
	loaded, err := restarted.g.AccountStore.LoadCharacter("alice")
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Credits != 90 {
		t.Fatalf("credits after forfeited spin = %d, want 90 (disconnect forfeits)", loaded.Credits)
	}
}

// TestDisconnectForfeitsBlackjackStake: same policy at a table: the seat
// frees up and the stake is not refunded by recovery.
func TestDisconnectForfeitsBlackjackStake(t *testing.T) {
	h := newHarness(t)
	h.seedAccount("alice")
	p := h.seedCharacter("alice", "alice")
	// A natural opening hand (player or dealer blackjack) settles the round
	// inside Bet() itself, before any disconnect can forfeit anything. Deal
	// from deterministic seeds until the opening hand leaves the stake
	// outstanding, so the regression exercises the mid-round forfeit path
	// on every run instead of failing ~5% of the time on a lucky deal.
	committed := false
	for seed := 0; seed < 1000 && !committed; seed++ {
		h.g.RunOnOwner(func() {
			h.g.Casino = casino.NewManager(int64(seed))
			p.Credits = 100
			h.g.Casino.Join(1, casino.TableConfig{ID: "bj", Game: casino.GameBlackjack, MaxPlayers: 2}, "alice")
			h.g.Casino.Bet(1, "bj", "alice", "", 10, casinoWallet{g: h.g, p: p})
			committed = len(p.PendingWagers) == 1
		})
		h.barrier()
	}
	if !committed {
		t.Fatal("no manager seed left the blackjack stake outstanding; deal or settle logic changed")
	}
	h.g.RunOnOwner(func() {
		// The disconnect path forfeits the in-flight hand.
		h.g.Casino.MarkDisconnected("alice")
	})
	h.barrier()

	restarted := harnessFromDir(t, h.dir)
	loaded, err := restarted.g.AccountStore.LoadCharacter("alice")
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Credits != 90 {
		t.Fatalf("credits after forfeited hand = %d, want 90 (disconnect forfeits)", loaded.Credits)
	}
}