package engine import ( "math/rand" "scrabble-solver/rules" ) // blankTile marks a blank tile in a hand or in the bag, matching the // scrabble-solver convention (selfplay) so a hand of these bytes interoperates // with the solver's rack helpers. const blankTile byte = 0xff // Bag is the shuffled draw pile for one game. Unlike the solver's self-play bag // it supports returning tiles, which an exchange needs. It is seeded once, so a // game's draws are reproducible from its seed and the sequence of operations. // Bag is not safe for concurrent use; the owning Game serialises access. type Bag struct { tiles []byte rng *rand.Rand } // NewBag fills a bag from the ruleset's tile counts and blanks and shuffles it // with seed. Letters are stored as alphabet-index bytes and blanks as blankTile. func NewBag(rs *rules.Ruleset, seed int64) *Bag { var tiles []byte for i, n := range rs.Counts { for range n { tiles = append(tiles, byte(i)) } } for range rs.Blanks { tiles = append(tiles, blankTile) } b := &Bag{tiles: tiles, rng: rand.New(rand.NewSource(seed))} b.shuffle() return b } // Len returns the number of tiles left in the bag. func (b *Bag) Len() int { return len(b.tiles) } // Draw removes up to n tiles from the bag and returns them in a fresh slice. // Drawing more than remain returns all of them; drawing from an empty bag // returns an empty slice. func (b *Bag) Draw(n int) []byte { if n > len(b.tiles) { n = len(b.tiles) } out := make([]byte, n) copy(out, b.tiles[len(b.tiles)-n:]) b.tiles = b.tiles[:len(b.tiles)-n] return out } // Return puts tiles back into the bag and reshuffles, as when a player exchanges // tiles. The tiles must use the same encoding as Draw (alphabet indices and // blankTile). func (b *Bag) Return(tiles []byte) { b.tiles = append(b.tiles, tiles...) b.shuffle() } // shuffle randomises the remaining tiles with the bag's own RNG, keeping draws // deterministic for a given seed and sequence of operations. func (b *Bag) shuffle() { b.rng.Shuffle(len(b.tiles), func(i, j int) { b.tiles[i], b.tiles[j] = b.tiles[j], b.tiles[i] }) }