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.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// 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}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package rack
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRackBasics(t *testing.T) {
|
||||
r := New(26)
|
||||
if !r.Empty() || r.Total() != 0 {
|
||||
t.Fatal("new rack not empty")
|
||||
}
|
||||
|
||||
r.Add(0) // a
|
||||
r.Add(0)
|
||||
r.Add(2) // c
|
||||
r.AddBlank()
|
||||
|
||||
if r.Count(0) != 2 {
|
||||
t.Errorf("Count(a) = %d, want 2", r.Count(0))
|
||||
}
|
||||
if !r.Has(2) || r.Has(1) {
|
||||
t.Errorf("Has c=%v b=%v, want true,false", r.Has(2), r.Has(1))
|
||||
}
|
||||
if r.Blanks() != 1 {
|
||||
t.Errorf("Blanks = %d, want 1", r.Blanks())
|
||||
}
|
||||
if r.Total() != 4 {
|
||||
t.Errorf("Total = %d, want 4", r.Total())
|
||||
}
|
||||
|
||||
r.Remove(0)
|
||||
if r.Count(0) != 1 {
|
||||
t.Errorf("after Remove, Count(a) = %d, want 1", r.Count(0))
|
||||
}
|
||||
r.RemoveBlank()
|
||||
if r.Blanks() != 0 {
|
||||
t.Errorf("after RemoveBlank, Blanks = %d, want 0", r.Blanks())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRackCloneIndependent(t *testing.T) {
|
||||
r := New(26)
|
||||
r.Add(0)
|
||||
cp := r.Clone()
|
||||
cp.Add(0)
|
||||
cp.AddBlank()
|
||||
if r.Count(0) != 1 || r.Blanks() != 0 {
|
||||
t.Errorf("mutating clone changed original: a=%d blanks=%d", r.Count(0), r.Blanks())
|
||||
}
|
||||
if cp.Count(0) != 2 || cp.Blanks() != 1 {
|
||||
t.Errorf("clone wrong: a=%d blanks=%d", cp.Count(0), cp.Blanks())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user