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.
77 lines
2.2 KiB
Go
77 lines
2.2 KiB
Go
// Package dict loads the English test dictionary as a DAWG, preferring the serialized
|
|
// cache under testdata and falling back to building from the dictionaries submodule.
|
|
// Paths are resolved relative to the repository root so it works both from the repo root
|
|
// (commands) and from a package directory (tests).
|
|
package dict
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/iliadenisov/alphabet"
|
|
dawg "github.com/iliadenisov/dafsa"
|
|
|
|
"scrabble-solver/internal/dictdawg"
|
|
"scrabble-solver/internal/wordlist"
|
|
)
|
|
|
|
// MinLen and MaxLen bound playable word lengths (a 15x15 board holds at most 15).
|
|
const (
|
|
MinLen = 2
|
|
MaxLen = 15
|
|
)
|
|
|
|
func exists(p string) bool { _, err := os.Stat(p); return err == nil }
|
|
|
|
// Root returns the repository root by walking up from the working directory to the
|
|
// directory containing go.mod, or "." if none is found.
|
|
func Root() string {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return "."
|
|
}
|
|
for {
|
|
if exists(filepath.Join(dir, "go.mod")) {
|
|
return dir
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
return "."
|
|
}
|
|
dir = parent
|
|
}
|
|
}
|
|
|
|
// DAWGCache and WordlistPath locate the English cache file and source word list,
|
|
// relative to the repository root.
|
|
func DAWGCache() string { return filepath.Join(Root(), "testdata", "sowpods.dawg") }
|
|
func WordlistPath() string { return filepath.Join(Root(), "dictionaries", "english", "sowpods.txt") }
|
|
|
|
// EnglishAvailable reports whether the English dictionary can be loaded (cache or source).
|
|
func EnglishAvailable() bool {
|
|
return exists(DAWGCache()) || exists(WordlistPath())
|
|
}
|
|
|
|
// EnglishWords returns the encoded English word list (from the submodule source).
|
|
func EnglishWords() ([][]byte, error) {
|
|
return wordlist.Read(WordlistPath(), alphabet.Latin(), MinLen, MaxLen)
|
|
}
|
|
|
|
// EnglishDAWG returns the English DAWG, loading the cache if present, otherwise building
|
|
// it from the word list and caching it (best effort).
|
|
func EnglishDAWG() (dawg.Finder, error) {
|
|
if exists(DAWGCache()) {
|
|
return dictdawg.Load(DAWGCache())
|
|
}
|
|
words, err := EnglishWords()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f, err := dictdawg.Build(alphabet.Latin(), words)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = dictdawg.Save(f, DAWGCache())
|
|
return f, nil
|
|
}
|