feat(rules): forbid repeating a word already on the board in Erudit
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m48s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m48s
Russian "Эрудит" treats a word laid on the board as belonging to the game: it cannot be laid again. Neither the solver, the backend nor the offline JS port knew the rule, so a player (and the robot) could replay a word freely. Official Scrabble places no such restriction, so both Scrabble variants keep playing unrestricted. The rule applies in two ways. A play whose main word is already on the board is illegal, and is neither accepted nor generated. A play whose perpendicular cross-word is already there stands — that word is incidental to laying the main word — but scores nothing. The set of played words is the game's own move journal, main words and cross-words alike, compared decoded, so a word spelled with a blank is the same word. It lives in the game layer, not the solver: only a game knows its history, and the solver stays stateless and standard-rules. The backend applies it at submit, at the move preview and over generated moves (filtering and re-ranking them, so neither the robot nor the hint can offer a play the engine would then refuse); the client port does the same for the offline engine and for the on-device preview of an online game. The rule is pinned per game (games.no_repeat_words, set from the variant at creation) rather than keyed on the variant, because a game is replayed from its journal on every open. Applied retroactively it would make an already-played repeat illegal — closing that game as a draw — and would rescore a play whose cross-word repeats an earlier word, shifting a live game's totals. Games created before the rule keep playing without it. The flag rides the wire as a trailing field because the client's preview must score the way the server does, and offline games pin the same answer in their own record.
This commit is contained in:
@@ -561,6 +561,24 @@ Key points:
|
||||
it on (standard). For auto-match the rule is part of the matchmaking key, so only players
|
||||
who chose the same rule are paired (the rule field rides every create/enqueue request, so
|
||||
matchmaking stays one uniform path).
|
||||
- **No repeated words (Erudit).** A word laid on the board cannot be laid again — the "Эрудит"
|
||||
rule; official Scrabble has no such restriction, so both Scrabble variants never take it. It
|
||||
applies in two ways: a play whose **main word** is already on the board is illegal, and a play
|
||||
whose **cross-word** is already there stands (it is incidental to laying the main word) but that
|
||||
cross-word scores nothing. The set of played words is the game's own **move journal** — the main
|
||||
word and every cross-word of every play, compared decoded, so a word spelled with a blank is the
|
||||
same word. The rule lives in the **game layer**, not the solver: `backend/internal/engine`
|
||||
(`norepeat.go`) and its client port `ui/src/lib/dict/norepeat.ts` apply it at submit, at the move
|
||||
preview and over generated moves — filtering and re-ranking them, so neither the robot nor the
|
||||
hint can offer a play the engine would then refuse. The solver stays stateless and
|
||||
standard-rules; only a game knows its history.
|
||||
It is **pinned per game** (`games.no_repeat_words`, set from the variant at creation and read
|
||||
back thereafter, never re-derived) rather than keyed on the variant, because a game is replayed
|
||||
from its journal on every open: applied retroactively it would make an already-played repeat
|
||||
illegal — closing that game as a draw (§9.1) — and would rescore a play whose cross-word repeats
|
||||
an earlier word. Games created before the rule therefore keep playing without it. The flag rides
|
||||
the wire (`GameView.no_repeat_words`) because the client's on-device move preview must score the
|
||||
way the server does; offline games pin the same answer in their own record.
|
||||
- **End of game**: the bag is empty **and** a player empties their rack, **or**
|
||||
**6 consecutive scoreless turns** (passes/exchanges), **or** a resignation, or
|
||||
a missed turn. The **per-game turn timeout** is chosen at creation
|
||||
|
||||
+10
-1
@@ -196,7 +196,16 @@ the simplified **single-word rule** — only the word laid along the player's li
|
||||
real word (and it must still cross or extend letters already on the board along that line),
|
||||
and any incidental perpendicular words are ignored and not scored — while on is
|
||||
standard Scrabble. English games are always standard and show no such toggle. In auto-match
|
||||
the choice joins the pairing key, so a player only meets opponents who picked the same rule. Friend games (2–4) are
|
||||
the choice joins the pairing key, so a player only meets opponents who picked the same rule.
|
||||
|
||||
**Erudite forbids repeating a word.** A word laid on the board belongs to the game: it cannot be laid
|
||||
again, and the AI never plays one either. A play whose main word is already on the board is rejected
|
||||
like any illegal move; a play that only forms an already-played word **across** its main word stands —
|
||||
that word is incidental to the move — but scores nothing. The rule is not a choice: every Erudite
|
||||
game takes it, and both Scrabble variants (where the official rules place no such restriction) never
|
||||
do, so the variant's one-line description on **New Game** reads "no repeated words". Games started
|
||||
before the rule existed keep playing without it, so a game in progress never changes rules under its
|
||||
players. Friend games (2–4) are
|
||||
formed by inviting players from the friend list (an invitation, like a friend code,
|
||||
is shareable as a Telegram deep link that opens it directly — on VK, which forwards no payload to the
|
||||
Mini App, the link only opens the app and the recipient enters the copied code by hand): the inviter chooses the
|
||||
|
||||
+10
-1
@@ -202,7 +202,16 @@ e-mail) либо ввод фразы. Активные игры форфейтя
|
||||
этой линии), а случайные перпендикулярные слова игнорируются и не засчитываются;
|
||||
включена — обычный скрэббл. Английские игры всегда по стандартным правилам и тоггл не
|
||||
показывают. В авто-подборе выбор входит в ключ подбора, поэтому игрок сводится только с теми,
|
||||
кто выбрал то же правило. Игры с друзьями (2–4)
|
||||
кто выбрал то же правило.
|
||||
|
||||
**В «Эрудите» слово нельзя повторять.** Выставленное на доску слово принадлежит партии: выставить
|
||||
его ещё раз нельзя, и робот такого хода тоже не делает. Ход, чьё основное слово уже стоит на доске,
|
||||
отклоняется как любой недопустимый; ход, который лишь образует уже выставленное слово **поперёк**
|
||||
основного, засчитывается — такое слово побочно для хода, — но очков не приносит. Правило не
|
||||
выбирается: его берёт каждая партия «Эрудита», и никогда — оба варианта скрэббла (в официальных
|
||||
правилах такого ограничения нет), поэтому однострочное описание варианта на экране **новой игры**
|
||||
содержит «слова без повтора». Партии, начатые до появления правила, продолжают играть без него,
|
||||
так что правила идущей партии не меняются у игроков на ходу. Игры с друзьями (2–4)
|
||||
формируются приглашением игроков из списка друзей (приглашение, как и код друга,
|
||||
можно отправить deep-link'ом в Telegram, который откроет его сразу — на VK, который не передаёт Mini App
|
||||
никакой нагрузки, ссылка лишь открывает приложение, а скопированный код получатель вводит вручную): инициатор
|
||||
|
||||
Reference in New Issue
Block a user