fix(engine): single-word rule connects along the play line #57

Merged
developer merged 2 commits from feature/single-word-connectivity into development 2026-06-14 15:02:32 +00:00
Owner

Fixes the contour bug where a single-word-per-turn game accepted plays that merely touched existing tiles sideways without forming a word along their own line (the reported РЮМ/КЕД/ОР placements, where e.g. РЮМ's М abutted Н making the non-word ЕРСАНМ).

Root cause: under IgnoreCrossWords the solver's connected() reused the standard rule's touchesPerpendicular test while cross-words were suppressed, so a purely perpendicular contact counted as a connection (and the generator offered such plays). Standard play was unaffected.

Fix (engine scrabble-solver v1.1.1, PR #3): under the single-word rule the main word must run through an existing tile along the play direction; generation is filtered by the same predicate. The rule is therefore no longer a superset of standard play (it forbids parallel plays the standard rule allows, and admits in-line plays whose cross-words are invalid).

Backend: for a single tile that abuts the board on both axes, playDirection now picks the higher-scoring legal orientation (horizontal on a tie) instead of the geometrically longer one, so a real word is never rejected in favour of a non-word.

Reworked single-word tests (solver + engine), updated ARCHITECTURE/FUNCTIONAL(_ru)/PRERELEASE (row MW2).

Verified locally (against the published v1.1.1): go build/vet/gofmt clean; backend unit + integration (-tags=integration, testcontainers) green.

Fixes the contour bug where a **single-word-per-turn** game accepted plays that merely *touched* existing tiles sideways without forming a word along their own line (the reported РЮМ/КЕД/ОР placements, where e.g. РЮМ's М abutted Н making the non-word ЕРСАНМ). **Root cause:** under `IgnoreCrossWords` the solver's `connected()` reused the standard rule's `touchesPerpendicular` test while cross-words were suppressed, so a purely perpendicular contact counted as a connection (and the generator offered such plays). Standard play was unaffected. **Fix (engine `scrabble-solver` v1.1.1, PR #3):** under the single-word rule the main word must run through an existing tile along the play direction; generation is filtered by the same predicate. The rule is therefore **no longer a superset** of standard play (it forbids parallel plays the standard rule allows, and admits in-line plays whose cross-words are invalid). **Backend:** for a single tile that abuts the board on both axes, `playDirection` now picks the higher-scoring **legal** orientation (horizontal on a tie) instead of the geometrically longer one, so a real word is never rejected in favour of a non-word. Reworked single-word tests (solver + engine), updated `ARCHITECTURE`/`FUNCTIONAL(_ru)`/`PRERELEASE` (row MW2). **Verified locally** (against the published v1.1.1): `go build`/`vet`/`gofmt` clean; backend unit + integration (`-tags=integration`, testcontainers) green.
developer added 1 commit 2026-06-14 12:49:35 +00:00
fix(engine): single-word rule connects along the play line
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Has been skipped
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m11s
ff87a3bf62
Bumps the engine to scrabble-solver v1.1.1, where a single-word-per-turn play
must form its word along its own line through an existing tile: a multi-tile play
that touches the board only perpendicular to itself (the contour РЮМ/КЕД/ОР cases)
no longer connects. For a single tile that abuts the board on both axes the engine
now plays the higher-scoring legal orientation instead of the geometrically longer
one (playDirection), so a real word is never rejected in favour of a non-word.

Reworks the single-word solver/engine tests for the corrected rule (no longer a
superset of standard play) and updates ARCHITECTURE/FUNCTIONAL/PRERELEASE.
developer added 1 commit 2026-06-14 14:57:29 +00:00
feat(game): void unreplayable games as a draw instead of erroring
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 46s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m13s
222eaf730f
A committed move that becomes illegal under a tightened rule (the single-word
connectivity fix) makes engine replay fail, which left such games unopenable —
an empty screen and an 'illegal play' error. Now the first open closes the game
gracefully as a draw (engine.EndAborted -> end_reason 'aborted', no winner),
preserves the journal, and surfaces an impersonal organizer note at the end of
the move history and in the GCG export.

- engine: EndAborted + Abort() (draw, no rack adjustment; winner -1).
- service: replay aborts on ErrIllegalPlay; liveGame persists the void once
  (lazy, on open); GameState re-reads for the settled view.
- store: VoidGame finishes the game and stamps a draw without a journal row.
- migration 00002: allow end_reason 'aborted'.
- ui: organizer note under the history grid; i18n en/ru.
- docs: ARCHITECTURE 6/9.1, FUNCTIONAL(+ru), PRERELEASE MW3.
owner approved these changes 2026-06-14 15:02:14 +00:00
developer merged commit 4f2fc795ec into development 2026-06-14 15:02:32 +00:00
developer deleted branch feature/single-word-connectivity 2026-06-14 15:02:32 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#57