256999b42c
- Rename module to gitea.iliadenisov.ru/developer/scrabble-solver so it can be consumed as a versioned dependency (no go.work replace / CI clone). - De-internalize wordlist and dictdawg as public packages. - Remove cmd/builddict, dictprep/, the dictionaries submodule and the dawg Makefile: the word-list parsing and DAWG build now live in the separate scrabble-dictionary repository, which publishes the DAWG set as a release artifact. - internal/dict loads the committed dawg/en_sowpods.dawg fixture for cmd/stress. - Update README/CLAUDE docs accordingly.
49 lines
2.8 KiB
Markdown
49 lines
2.8 KiB
Markdown
# scrabble-solver — project guide
|
||
|
||
A Go library that, given a dictionary, a board position and a rack, returns every legal
|
||
play ranked by score, and scores/validates arbitrary plays. The move generator is the
|
||
**DAWG** algorithm (Appel & Jacobson) over `github.com/iliadenisov/dafsa` — a bit-packed,
|
||
minimised DAWG with a compact ≤63-symbol alphabet. A GADDAG generator was also built,
|
||
measured by self-play, and **removed**: DAWG won for this scoring-solver workload
|
||
(~7× smaller, comparable speed) — see `RESULTS.md`.
|
||
|
||
Module `scrabble-solver`, Go 1.26. Rulesets: English Scrabble, Russian Scrabble, and
|
||
Russian **Эрудит** (`rules` package); Эрудит has no Ё tile and folds Ё→Е in its dictionary.
|
||
|
||
## Layout
|
||
|
||
- `scrabble/` — the public API: `Solver` (`NewSolver`, `GenerateMoves`, `ScorePlay`,
|
||
`ValidatePlay`), the `Move`/`Placement`/`Word` types, the DAWG generator and scoring.
|
||
- `board/`, `rack/`, `rules/` — board grid (+ transpose), rack as per-letter counts,
|
||
and rulesets (geometry, premium layout, tile values/counts, alphabet, bonus):
|
||
`rules.English()`, `rules.RussianScrabble()`, `rules.Erudit()`.
|
||
- `dictdawg/`, `wordlist/` — **public** helpers: `dictdawg` (build/load/serialise DAWGs
|
||
over dafsa), `wordlist` (encode/filter/sort/dedupe + `FoldYo`). Imported by the separate
|
||
`scrabble-dictionary` repo that builds and publishes the DAWG set.
|
||
- `internal/` — `encoding`, `graph`, `dict` (loads the committed `dawg/en_sowpods.dawg`
|
||
for `cmd/stress`).
|
||
- `cmd/stress`, `selfplay/` — the self-play stress harness behind `RESULTS.md`.
|
||
- `dawg/` — **committed** dictionaries: `en_sowpods.dawg`, `ru_scrabble.dawg`,
|
||
`ru_erudit.dawg` (Ё→Е folded). The word-list sources and build pipeline live in the
|
||
separate [`scrabble-dictionary`](https://gitea.iliadenisov.ru/developer/scrabble-dictionary)
|
||
repo (which publishes the DAWG set as a release artifact); these committed copies are
|
||
test fixtures.
|
||
|
||
## Build & test
|
||
|
||
go test ./... # all packages green; also run go vet ./... and gofmt
|
||
|
||
Scoring and move generation are validated against **real tournament games** in GCG format
|
||
(`scrabble/gcg_test.go` + `scrabble/testdata/*.gcg`, including the 700+ club): for every
|
||
move the test checks the score, the running total, and that the generator actually
|
||
produces the played move with that score — canonical play, not invented cases.
|
||
|
||
## Key facts
|
||
|
||
- Compact byte encoding: low 6 bits = alphabet index; `0x80` = blank/wildcard (board, rack
|
||
and output bytes only — never inside the graph). The public API is byte-indexed.
|
||
- DAWG is the production generator; the GADDAG was removed after measurement.
|
||
- Detailed docs: `ALGORITHM.md` (the algorithm — single source of truth), `PLAN.md`
|
||
(design and decisions), `RESULTS.md` (DAWG-vs-GADDAG). The RU word-list pipeline and the
|
||
DAWG build now live in the `scrabble-dictionary` repo.
|