package engine import ( "maps" "slices" "testing" "gitea.iliadenisov.ru/developer/scrabble-solver/rules" ) // allTiles returns the full multiset of tiles a bag is filled from, in ruleset // order (letters then blanks). func allTiles(rs *rules.Ruleset) []byte { var ts []byte for i, n := range rs.Counts { for range n { ts = append(ts, byte(i)) } } for range rs.Blanks { ts = append(ts, blankTile) } return ts } // TestBagDeterministic checks that two bags with the same seed draw identically. func TestBagDeterministic(t *testing.T) { rs := rules.English() a, b := NewBag(rs, 42), NewBag(rs, 42) if a.Len() != b.Len() { t.Fatalf("len mismatch: %d vs %d", a.Len(), b.Len()) } for a.Len() > 0 { if da, db := a.Draw(3), b.Draw(3); !slices.Equal(da, db) { t.Fatalf("same seed drew differently: %v vs %v", da, db) } } } // TestBagReturnConservesMultiset checks Len accounting and that Return puts the // exact tiles back, leaving the bag's multiset unchanged. func TestBagReturnConservesMultiset(t *testing.T) { rs := rules.English() want := tileCounts(allTiles(rs)) total := len(allTiles(rs)) b := NewBag(rs, 7) if b.Len() != total { t.Fatalf("new bag len = %d, want %d", b.Len(), total) } drawn := b.Draw(rs.RackSize) if b.Len() != total-rs.RackSize { t.Fatalf("after draw len = %d, want %d", b.Len(), total-rs.RackSize) } b.Return(drawn) if b.Len() != total { t.Fatalf("after return len = %d, want %d", b.Len(), total) } if got := tileCounts(b.Draw(b.Len())); !maps.Equal(got, want) { t.Fatalf("multiset changed across draw/return") } } // TestBagDrawAll returns everything once the bag is exhausted and never panics. func TestBagDrawAll(t *testing.T) { rs := rules.English() b := NewBag(rs, 1) all := b.Draw(b.Len() + 10) // asking for more than present returns all if len(all) != len(allTiles(rs)) { t.Fatalf("drew %d, want %d", len(all), len(allTiles(rs))) } if b.Len() != 0 { t.Fatalf("bag len = %d, want 0", b.Len()) } if got := b.Draw(1); len(got) != 0 { t.Fatalf("draw from empty bag returned %d tiles", len(got)) } }