Files
Pete/internal/games/cards/cards_test.go
prosolis 8310b30439 games: a deck you can seed and a blackjack you can replay
The first two pieces of games.parodia.dev, both pure: no HTTP, no timers, no
euros, nothing that knows a player's name.

cards/ is the shared deck gogobee never had — blackjack carried its own, UNO
carried another, hold'em leaned on a third-party one. The RNG is threaded rather
than the package global, so a hand is reproducible from its seed. That's what
makes the engine testable, and what lets a disputed hand be dealt again exactly
as it fell.

blackjack/ is ApplyMove(state, move) -> (state, events, error), where an error
means the move was illegal and nothing else. gogobee's engine *was* the message
sender, so its errors meant "the send failed"; there was no seam to test against.
State is a plain value, so a hand survives a redeploy.

House terms match the Matrix table — six decks, 3:2, dealer hits soft 17 — plus a
5% rake. The rake comes off winnings only: a push returns the stake untouched and
a loss is never charged for the privilege.
2026-07-13 22:44:45 -07:00

103 lines
2.2 KiB
Go

package cards
import "testing"
func TestNewDeck_IsAFullShoe(t *testing.T) {
d := NewDeck(6)
if len(d) != 312 {
t.Fatalf("six decks hold %d cards, want 312", len(d))
}
seen := map[Card]int{}
for _, c := range d {
seen[c]++
}
if len(seen) != 52 {
t.Fatalf("%d distinct cards, want 52", len(seen))
}
for c, n := range seen {
if n != 6 {
t.Fatalf("%s appears %d times in a six-deck shoe, want 6", c, n)
}
}
}
func TestNewDeck_ClampsToAtLeastOne(t *testing.T) {
if len(NewDeck(0)) != 52 {
t.Fatal("a zero-deck shoe should still hold one deck")
}
}
func TestShuffle_SameSeedSameOrder(t *testing.T) {
a, b := NewDeck(1), NewDeck(1)
a.Shuffle(NewRNG(99, 1))
b.Shuffle(NewRNG(99, 1))
for i := range a {
if a[i] != b[i] {
t.Fatalf("same seed diverged at %d: %s vs %s", i, a[i], b[i])
}
}
// And a different seed must not give the same order, or the RNG isn't wired up.
c := NewDeck(1)
c.Shuffle(NewRNG(100, 1))
same := true
for i := range a {
if a[i] != c[i] {
same = false
break
}
}
if same {
t.Fatal("a different seed produced an identical shuffle")
}
}
func TestShuffle_KeepsEveryCard(t *testing.T) {
d := NewDeck(1)
d.Shuffle(NewRNG(4, 4))
seen := map[Card]bool{}
for _, c := range d {
seen[c] = true
}
if len(d) != 52 || len(seen) != 52 {
t.Fatalf("shuffle lost cards: %d cards, %d distinct", len(d), len(seen))
}
}
func TestDraw_TakesFromTheTopAndRunsOut(t *testing.T) {
d := NewDeck(1)
top := d[0]
c, ok := d.Draw()
if !ok || c != top {
t.Fatalf("drew %s (ok=%v), want the top card %s", c, ok, top)
}
if len(d) != 51 {
t.Fatalf("deck has %d cards after one draw, want 51", len(d))
}
for len(d) > 0 {
d.Draw()
}
if _, ok := d.Draw(); ok {
t.Fatal("an empty deck kept dealing")
}
}
func TestCard_String(t *testing.T) {
tests := []struct {
card Card
want string
}{
{Card{Ace, Spades}, "A♠"},
{Card{10, Hearts}, "10♥"},
{Card{King, Clubs}, "K♣"},
{Card{Rank: 99, Suit: Spades}, "??"},
}
for _, tc := range tests {
if got := tc.card.String(); got != tc.want {
t.Errorf("String() = %q, want %q", got, tc.want)
}
}
if !(Card{Ace, Hearts}).Red() || (Card{Ace, Spades}).Red() {
t.Error("Red() disagrees about which suits are red")
}
}