15c7959d96
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.
68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
// Command builddict converts a word list into a serialized DAWG, cached under testdata
|
|
// for the tests and the benchmark. By default it reads the English SOWPODS list from
|
|
// the dictionaries submodule.
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/iliadenisov/alphabet"
|
|
|
|
"scrabble-solver/internal/dictdawg"
|
|
"scrabble-solver/internal/wordlist"
|
|
)
|
|
|
|
func main() {
|
|
dict := flag.String("dict", "dictionaries/english/sowpods.txt", "word list file (one word per line)")
|
|
out := flag.String("out", "testdata", "output directory")
|
|
name := flag.String("name", "sowpods", "base name for the output file")
|
|
minLen := flag.Int("min", 2, "minimum word length")
|
|
maxLen := flag.Int("max", 15, "maximum word length")
|
|
flag.Parse()
|
|
|
|
idx := alphabet.Latin()
|
|
|
|
t0 := time.Now()
|
|
words, err := wordlist.Read(*dict, idx, *minLen, *maxLen)
|
|
if err != nil {
|
|
log.Fatalf("read %s: %v", *dict, err)
|
|
}
|
|
fmt.Printf("loaded %d words from %s in %s\n", len(words), *dict, time.Since(t0).Round(time.Millisecond))
|
|
|
|
if err := os.MkdirAll(*out, 0o755); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
t := time.Now()
|
|
f, err := dictdawg.Build(idx, words)
|
|
if err != nil {
|
|
log.Fatalf("build dawg: %v", err)
|
|
}
|
|
path := filepath.Join(*out, *name+".dawg")
|
|
if err := dictdawg.Save(f, path); err != nil {
|
|
log.Fatalf("save: %v", err)
|
|
}
|
|
size := int64(0)
|
|
if fi, err := os.Stat(path); err == nil {
|
|
size = fi.Size()
|
|
}
|
|
fmt.Printf("DAWG %d nodes, %s, built+saved in %s -> %s\n",
|
|
f.NumNodes(), humanBytes(size), time.Since(t).Round(time.Millisecond), path)
|
|
}
|
|
|
|
func humanBytes(n int64) string {
|
|
switch {
|
|
case n >= 1<<20:
|
|
return fmt.Sprintf("%.2f MB", float64(n)/(1<<20))
|
|
case n >= 1<<10:
|
|
return fmt.Sprintf("%.1f KB", float64(n)/(1<<10))
|
|
default:
|
|
return fmt.Sprintf("%d B", n)
|
|
}
|
|
}
|