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:
+16
-5
@@ -40,8 +40,17 @@ func perpendicular(d Direction) Direction {
|
||||
// dir under ruleset rs. It validates geometry — the tiles lie on one line, on empty
|
||||
// squares, and form a single contiguous run together with existing tiles — but does not
|
||||
// check the dictionary or board connectivity; ValidatePlay layers those on top. tiles
|
||||
// need not be sorted.
|
||||
// need not be sorted. It evaluates under standard rules; use EvaluateOpts to apply optional
|
||||
// rule variations.
|
||||
func Evaluate(b *board.Board, rs *rules.Ruleset, dir Direction, tiles []Placement) (Move, error) {
|
||||
return EvaluateOpts(b, rs, dir, tiles, PlayOptions{})
|
||||
}
|
||||
|
||||
// EvaluateOpts is Evaluate with optional rule variations (PlayOptions). With
|
||||
// opts.IgnoreCrossWords set, the play's perpendicular cross-words are neither formed nor
|
||||
// scored: the returned move carries no Cross words and its Score counts only the main word
|
||||
// (plus any all-tiles bonus).
|
||||
func EvaluateOpts(b *board.Board, rs *rules.Ruleset, dir Direction, tiles []Placement, opts PlayOptions) (Move, error) {
|
||||
if len(tiles) == 0 {
|
||||
return Move{}, errors.New("scrabble: empty play")
|
||||
}
|
||||
@@ -78,10 +87,12 @@ func Evaluate(b *board.Board, rs *rules.Ruleset, dir Direction, tiles []Placemen
|
||||
}
|
||||
|
||||
move := Move{Dir: dir, Tiles: ts, Main: main, Score: main.Score}
|
||||
for _, t := range ts {
|
||||
if cw, ok := crossWord(b, rs, dir, t); ok {
|
||||
move.Cross = append(move.Cross, cw)
|
||||
move.Score += cw.Score
|
||||
if !opts.IgnoreCrossWords {
|
||||
for _, t := range ts {
|
||||
if cw, ok := crossWord(b, rs, dir, t); ok {
|
||||
move.Cross = append(move.Cross, cw)
|
||||
move.Score += cw.Score
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(ts) == rs.RackSize {
|
||||
|
||||
Reference in New Issue
Block a user