scrabble: add single-word-per-turn rule via PlayOptions

Add an optional "single word per turn" rule: only the main word along the
play direction is validated and scored; perpendicular cross-words are ignored
— not formed, validated, scored, or used to constrain move generation. The
zero PlayOptions stays standard Scrabble, so existing callers are unchanged.

- PlayOptions{IgnoreCrossWords} threaded through new EvaluateOpts,
  Solver.{ScorePlay,ValidatePlay,GenerateMoves}Opts and the DAWG generator
  (relaxed cross-sets via fullSet, main-word-only scoring).
- connected() tests perpendicular adjacency directly instead of via
  Move.Cross, so an all-new main word touching the board only sideways still
  connects when cross-words are suppressed (behaviour-preserving for standard
  play).
- Tests: focused corner-case suite (solver_opts_test.go) and a single-word
  GCG fixture; the 17 real-game GCG fixtures stay green as the standard-rules
  regression guard.
This commit is contained in:
Ilia Denisov
2026-06-12 01:37:39 +02:00
parent 256999b42c
commit a8a559993d
9 changed files with 299 additions and 39 deletions
+5 -1
View File
@@ -13,7 +13,11 @@ See [`ALGORITHM.md`](ALGORITHM.md) for the algorithm (the single source of truth
- DAWG move generation (across / down / both orientations), with full tournament scoring
(cross-words, premiums, all-tiles bonus) and a per-tile breakdown.
- Public `Solver`: `GenerateMoves` (ranked), `ScorePlay`, `ValidatePlay`.
- Public `Solver`: `GenerateMoves` (ranked), `ScorePlay`, `ValidatePlay`, each with an
`*Opts` variant taking `PlayOptions` for optional rule variations.
- Optional **single word per turn** rule (`PlayOptions{IgnoreCrossWords: true}`): only the
main word is validated and scored — perpendicular cross-words are ignored, including in
move generation. The zero `PlayOptions` is standard Scrabble.
- Rulesets: **English** Scrabble, **Russian** Scrabble, **Эрудит**; `rules.Ruleset` is
fully parameterizable (board, premiums, tile values/counts, blanks, rack, bonus).
- A GADDAG (Gordon) was implemented, benchmarked and then **removed** — for a scoring