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:
@@ -35,6 +35,10 @@ export interface LocalGameRecord {
|
||||
seed: string;
|
||||
players: number;
|
||||
multipleWords: boolean;
|
||||
/** Forbids laying a word that is already on the board (the Erudit rule). Pinned at creation from
|
||||
* the variant, so a game started before the rule existed (where it is absent, read as false)
|
||||
* replays and scores exactly as it was played. */
|
||||
noRepeatWords?: boolean;
|
||||
dropoutTiles: DropoutTiles;
|
||||
seats: Seat[];
|
||||
/** The dictionary-independent, alphabet-index-space move journal. */
|
||||
@@ -87,6 +91,7 @@ export function serializeGame(game: LocalGame, meta: RecordMeta): LocalGameRecor
|
||||
seed: game.seed.toString(),
|
||||
players: game.playerCount,
|
||||
multipleWords: cfg.multipleWords,
|
||||
noRepeatWords: cfg.noRepeatWords || undefined,
|
||||
dropoutTiles: cfg.dropoutTiles,
|
||||
seats: meta.seats,
|
||||
journal: game.history,
|
||||
@@ -113,6 +118,7 @@ export function replayGame(record: LocalGameRecord, dawg: Dawg): LocalGame {
|
||||
players: record.players,
|
||||
dawg,
|
||||
multipleWords: record.multipleWords,
|
||||
noRepeatWords: record.noRepeatWords,
|
||||
dropoutTiles: record.dropoutTiles,
|
||||
};
|
||||
const game = new LocalGame(opts);
|
||||
|
||||
Reference in New Issue
Block a user