fix(ui): one-word games must not highlight phantom cross words; review polish
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m25s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m2s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m25s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m2s
Board-highlight bug (reported on the contour): formedGeometry walked cross words
unconditionally, so in a single-word (one-word-per-turn) game a staged tile sitting
next to a committed tile lit up a green "cross word" the engine ignores — and which
need not even be a real word (the reported "БО lights up green in a one-word ПОПА
play"). Gate the cross-word walk on the game's multipleWordsPerTurn flag. The score
(8) was already correct — a premium square under the main word.
Also, from review:
- the turn strip reads the staged play's "WORD+WORD = N" while composing a legal move,
reverting to the turn / result text otherwise;
- the Exchange/Pass dialog shows the bag count ("In the bag: N" / "Bag is empty")
right-aligned in the title row, via a new optional Modal `titleAside`;
- cosmetics: half the turn strip's bottom padding (the plaques below carry their own
top pad); a top gap above the landscape rack (it sat flush under the docked history);
more horizontal padding on tab count badges so a 2-3 digit bag count clears the pill
ends;
- admin console: the game Summary now shows the single-word / multiple-words rule.
Tests: formed single-word case added; full unit (584) + e2e (chromium + webkit, 113
each) green; backend build + adminconsole templates parse. Docs (FUNCTIONAL +_ru,
UI_DESIGN) updated.
This commit is contained in:
+24
-14
@@ -77,12 +77,19 @@ function span(
|
||||
* formedGeometry derives the composed play's word geometry from the board and the staged tiles.
|
||||
* It returns the main word (the maximal run along the play axis through the staged tiles) and the
|
||||
* set of every cell the main word and each cross word (a perpendicular run of two or more cells
|
||||
* through a staged tile) cover. It returns null when nothing is staged, or when the staged tiles
|
||||
* are not on a single line — a shape only an illegal play produces, which the caller never asks
|
||||
* about (it is invoked only for a legal preview). The play axis is fixed by the staged tiles when
|
||||
* two or more are colinear; a lone tile takes whichever axis forms the longer word.
|
||||
* through a staged tile) cover. Under the **single-word rule** (multipleWords = false) cross words
|
||||
* are omitted, matching the engine, which ignores them entirely — a perpendicular run there is
|
||||
* neither validated nor scored (and need not even be a real word), so highlighting it would be
|
||||
* misleading. It returns null when nothing is staged, or when the staged tiles are not on a single
|
||||
* line — a shape only an illegal play produces, which the caller never asks about (it is invoked
|
||||
* only for a legal preview). The play axis is fixed by the staged tiles when two or more are
|
||||
* colinear; a lone tile takes whichever axis forms the longer word.
|
||||
*/
|
||||
export function formedGeometry(board: Board, pending: readonly Cell[]): FormedGeometry | null {
|
||||
export function formedGeometry(
|
||||
board: Board,
|
||||
pending: readonly Cell[],
|
||||
multipleWords: boolean,
|
||||
): FormedGeometry | null {
|
||||
if (pending.length === 0) return null;
|
||||
const filled = filledPredicate(board, pending);
|
||||
const first = pending[0];
|
||||
@@ -115,15 +122,18 @@ export function formedGeometry(board: Board, pending: readonly Cell[]): FormedGe
|
||||
}
|
||||
|
||||
// Cross words: the perpendicular run through each staged tile, kept only when it is a real word
|
||||
// (two or more cells).
|
||||
const cross: Dir = dir === 'H' ? 'V' : 'H';
|
||||
for (const p of pending) {
|
||||
const cs = span(filled, cross, p.row, p.col);
|
||||
if (cs.len < 2) continue;
|
||||
if (cross === 'H') {
|
||||
for (let c = cs.start; c < cs.start + cs.len; c++) cells.add(key(p.row, c));
|
||||
} else {
|
||||
for (let r = cs.start; r < cs.start + cs.len; r++) cells.add(key(r, p.col));
|
||||
// (two or more cells). Skipped under the single-word rule, where the engine ignores cross words,
|
||||
// so a perpendicular run must not be highlighted as if it were a formed word.
|
||||
if (multipleWords) {
|
||||
const cross: Dir = dir === 'H' ? 'V' : 'H';
|
||||
for (const p of pending) {
|
||||
const cs = span(filled, cross, p.row, p.col);
|
||||
if (cs.len < 2) continue;
|
||||
if (cross === 'H') {
|
||||
for (let c = cs.start; c < cs.start + cs.len; c++) cells.add(key(p.row, c));
|
||||
} else {
|
||||
for (let r = cs.start; r < cs.start + cs.len; r++) cells.add(key(r, p.col));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user