Files
Ilia Denisov 15c7959d96 Implement Scrabble move generator (DAWG) with English and Russian rules
A Go library that returns every legal play ranked by score and scores or
validates plays, using the Appel-Jacobson DAWG algorithm over
github.com/iliadenisov/dafsa v1.1.0.

- DAWG move generation (across / down / both), full tournament scoring with a
  per-tile breakdown; public Solver: GenerateMoves (ranked), ScorePlay,
  ValidatePlay.
- Rulesets: English Scrabble, Russian Scrabble, Эрудит (parameterizable Ruleset).
- cmd/builddict (build the DAWG from the dictionaries submodule), cmd/stress
  (self-play benchmark), selfplay engine; brute-force test oracle.
- A GADDAG was implemented, benchmarked and removed (the DAWG was smaller and
  faster for a scoring solver); see RESULTS.md and ALGORITHM.md.
2026-06-01 16:07:32 +02:00

58 lines
1.9 KiB
Go

// Package rack represents a player's rack as per-letter tile counts plus blanks.
package rack
// Rack holds tile counts: one slot per alphabet letter index plus a final slot for
// blanks. Like a Go slice or map, a Rack value shares its underlying storage with its
// copies; use Clone for an independent rack. The move generator mutates a single Rack
// in place (removing a tile, recursing, putting it back).
type Rack struct {
counts []int
}
// New returns an empty rack for an alphabet of the given size.
func New(alphabetSize int) Rack {
return Rack{counts: make([]int, alphabetSize+1)}
}
func (r Rack) blankIdx() int { return len(r.counts) - 1 }
// Count returns how many tiles of the given letter index are on the rack.
func (r Rack) Count(letter byte) int { return r.counts[letter] }
// Has reports whether at least one tile of the given letter index is on the rack.
func (r Rack) Has(letter byte) bool { return r.counts[letter] > 0 }
// Blanks returns the number of blank tiles on the rack.
func (r Rack) Blanks() int { return r.counts[r.blankIdx()] }
// Total returns the number of tiles on the rack, blanks included.
func (r Rack) Total() int {
n := 0
for _, c := range r.counts {
n += c
}
return n
}
// Empty reports whether the rack holds no tiles.
func (r Rack) Empty() bool { return r.Total() == 0 }
// Add puts a tile of the given letter index onto the rack.
func (r Rack) Add(letter byte) { r.counts[letter]++ }
// AddBlank puts a blank tile onto the rack.
func (r Rack) AddBlank() { r.counts[r.blankIdx()]++ }
// Remove takes one tile of the given letter index off the rack.
func (r Rack) Remove(letter byte) { r.counts[letter]-- }
// RemoveBlank takes one blank tile off the rack.
func (r Rack) RemoveBlank() { r.counts[r.blankIdx()]-- }
// Clone returns an independent copy of the rack.
func (r Rack) Clone() Rack {
c := make([]int, len(r.counts))
copy(c, r.counts)
return Rack{counts: c}
}