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,82 @@
|
||||
package rules
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEnglishConsistency(t *testing.T) {
|
||||
rs := English()
|
||||
if err := rs.Validate(); err != nil {
|
||||
t.Fatalf("Validate: %v", err)
|
||||
}
|
||||
if rs.Rows != 15 || rs.Cols != 15 {
|
||||
t.Errorf("board = %dx%d, want 15x15", rs.Rows, rs.Cols)
|
||||
}
|
||||
if rs.Size() != 26 {
|
||||
t.Errorf("alphabet size = %d, want 26", rs.Size())
|
||||
}
|
||||
if rs.Center != 7*15+7 {
|
||||
t.Errorf("centre = %d, want %d", rs.Center, 7*15+7)
|
||||
}
|
||||
|
||||
letters := 0
|
||||
for _, c := range rs.Counts {
|
||||
letters += c
|
||||
}
|
||||
if letters != 98 {
|
||||
t.Errorf("sum(Counts) = %d, want 98", letters)
|
||||
}
|
||||
if rs.Blanks != 2 || letters+rs.Blanks != 100 {
|
||||
t.Errorf("bag = %d letters + %d blanks, want 98+2=100", letters, rs.Blanks)
|
||||
}
|
||||
|
||||
points := 0
|
||||
for i := range rs.Values {
|
||||
points += rs.Values[i] * rs.Counts[i]
|
||||
}
|
||||
if points != 187 {
|
||||
t.Errorf("total bag points = %d, want 187", points)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnglishPremiums(t *testing.T) {
|
||||
rs := English()
|
||||
|
||||
spot := []struct {
|
||||
r, c int
|
||||
want Premium
|
||||
}{
|
||||
{0, 0, TW}, {0, 7, TW}, {0, 14, TW}, {7, 0, TW}, {14, 7, TW},
|
||||
{7, 7, DW}, {1, 1, DW}, {4, 4, DW},
|
||||
{1, 5, TL}, {5, 5, TL}, {9, 9, TL},
|
||||
{0, 3, DL}, {3, 0, DL}, {6, 6, DL},
|
||||
{0, 1, None}, {7, 1, None},
|
||||
}
|
||||
for _, s := range spot {
|
||||
if got := rs.Premium(s.r, s.c); got != s.want {
|
||||
t.Errorf("Premium(%d,%d) = %d, want %d", s.r, s.c, got, s.want)
|
||||
}
|
||||
}
|
||||
|
||||
// Census of premium squares for the standard board.
|
||||
census := map[Premium]int{}
|
||||
for i := range rs.Rows * rs.Cols {
|
||||
census[rs.PremiumAt(i)]++
|
||||
}
|
||||
want := map[Premium]int{None: 164, DL: 24, TL: 12, DW: 17, TW: 8}
|
||||
for p, n := range want {
|
||||
if census[p] != n {
|
||||
t.Errorf("premium %d count = %d, want %d", p, census[p], n)
|
||||
}
|
||||
}
|
||||
|
||||
// The standard board is symmetric under transpose and 180° rotation.
|
||||
for r := range rs.Rows {
|
||||
for c := range rs.Cols {
|
||||
if rs.Premium(r, c) != rs.Premium(c, r) {
|
||||
t.Errorf("not transpose-symmetric at (%d,%d)", r, c)
|
||||
}
|
||||
if rs.Premium(r, c) != rs.Premium(rs.Rows-1-r, rs.Cols-1-c) {
|
||||
t.Errorf("not 180°-symmetric at (%d,%d)", r, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user