package engine import "fmt" // SetupTile is one tile of a variant's full bag, decoded for the first-move draw // (docs/ARCHITECTURE.md ยง6): its concrete letter (or the blank marker), a blank // flag, and its draw rank. Lower rank wins the draw โ€” a blank ranks above every // letter, and letters rank by alphabet index, so the tile closest to the start of // the alphabet ("A") wins. It is dictionary-independent, built from the variant's // solver ruleset alone. type SetupTile struct { // Letter is the concrete character (the case the solver ruleset emits), or // the blank marker "?" for a blank. Letter string // Blank reports whether the tile is a blank. Blank bool // Rank orders the draw: BlankRank for a blank (best), else the letter's // alphabet index (0 = closest to "A"). Rank int } // BlankRank is the first-move draw rank of a blank: below every letter index, so a // blank always beats a lettered tile, matching the official rule that a blank // supersedes all letters. const BlankRank = -1 // SetupBag returns variant's full tile bag โ€” every lettered tile expanded by its // count, plus one entry per blank โ€” decoded for the first-move seeding draw. The // order is deterministic (alphabet order, blanks last); callers shuffle it with // their own entropy. It needs no dictionary, so it is built from the variant's // ruleset alone and reports ErrUnknownVariant for an unrecognised variant. func SetupBag(v Variant) ([]SetupTile, error) { rs, ok := v.ruleset() if !ok { return nil, fmt.Errorf("%w: %d", ErrUnknownVariant, v) } bag := make([]SetupTile, 0, 128) for i, n := range rs.Counts { ch, err := rs.Alphabet.Character(byte(i)) if err != nil { // An offered variant's alphabet never yields a bad index; skip defensively. continue } for range n { bag = append(bag, SetupTile{Letter: ch, Rank: i}) } } for range rs.Blanks { bag = append(bag, SetupTile{Letter: blankLetter, Blank: true, Rank: BlankRank}) } return bag, nil }