d7337d24ea
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.
100 lines
3.5 KiB
Go
100 lines
3.5 KiB
Go
package notify
|
|
|
|
// The structs below are the wire-agnostic inputs the domain services hand to the
|
|
// enriched event constructors. Keeping them here — rather than importing the wire
|
|
// schema into game/lobby/social — preserves the package boundary: notify owns the
|
|
// FlatBuffers encoding, while the domain only fills in already-resolved values (seat
|
|
// display names, alphabet-index racks). Each mirrors the matching scrabblefb table.
|
|
|
|
// SeatStanding is one seat's public standing inside a GameSummary (mirrors
|
|
// scrabblefb.SeatView).
|
|
type SeatStanding struct {
|
|
Seat int
|
|
AccountID string
|
|
DisplayName string
|
|
Score int
|
|
HintsUsed int
|
|
IsWinner bool
|
|
}
|
|
|
|
// GameSummary is the shared, non-private game state embedded in enriched events
|
|
// (mirrors scrabblefb.GameView). LastActivityUnix is the lobby sort key: the current
|
|
// turn's start for an active game, the finish time once finished.
|
|
type GameSummary struct {
|
|
ID string
|
|
Variant string
|
|
DictVersion string
|
|
Status string
|
|
Players int
|
|
ToMove int
|
|
TurnTimeoutSecs int
|
|
MultipleWordsPerTurn bool
|
|
// VsAI marks an honest-AI game (the opponent is shown as 🤖, chat/nudge/add-friend off).
|
|
VsAI bool
|
|
MoveCount int
|
|
EndReason string
|
|
Seats []SeatStanding
|
|
LastActivityUnix int64
|
|
// Kind is the game's origin for the active-game limits (0 unknown, 1 vs_ai, 2 random, 3 friends);
|
|
// it rides live events so a lobby patch keeps the per-kind count correct.
|
|
Kind int
|
|
// NoRepeatWords forbids laying a word that is already on the board (the "Эрудит" rule, pinned
|
|
// per game); it rides live events so the client's local move preview scores like the server.
|
|
NoRepeatWords bool
|
|
}
|
|
|
|
// AlphabetLetter is one variant alphabet entry (a display-only row) embedded in an
|
|
// initial PlayerState so a client seeing a variant for the first time can render its
|
|
// rack (mirrors scrabblefb.AlphabetEntry).
|
|
type AlphabetLetter struct {
|
|
Index int
|
|
Letter string
|
|
Value int
|
|
}
|
|
|
|
// PlayerState is a player's full initial view of a game — the shared summary plus
|
|
// their private rack and budgets (mirrors scrabblefb.StateView). Rack carries wire
|
|
// alphabet indices (a blank is the sentinel index 255). Alphabet is set only when the
|
|
// recipient may not have cached the variant yet (match_found / game_started).
|
|
type PlayerState struct {
|
|
Game GameSummary
|
|
Seat int
|
|
Rack []int
|
|
BagLen int
|
|
HintsRemaining int
|
|
Alphabet []AlphabetLetter
|
|
}
|
|
|
|
// AccountRef is a referenced account with its display name resolved (mirrors
|
|
// scrabblefb.AccountRef).
|
|
type AccountRef struct {
|
|
AccountID string
|
|
DisplayName string
|
|
}
|
|
|
|
// InvitationInvitee is one invited player's seat and response inside an
|
|
// InvitationSummary (mirrors scrabblefb.InvitationInvitee).
|
|
type InvitationInvitee struct {
|
|
AccountID string
|
|
DisplayName string
|
|
Seat int
|
|
Response string
|
|
}
|
|
|
|
// InvitationSummary is a friend-game invitation carried by the NotifyInvitation event so
|
|
// the client adds it to its lobby list without a refetch (mirrors scrabblefb.Invitation).
|
|
type InvitationSummary struct {
|
|
ID string
|
|
Inviter AccountRef
|
|
Invitees []InvitationInvitee
|
|
Variant string
|
|
TurnTimeoutSecs int
|
|
HintsAllowed bool
|
|
HintsPerPlayer int
|
|
MultipleWordsPerTurn bool
|
|
DropoutTiles string
|
|
Status string
|
|
GameID string
|
|
ExpiresAtUnix int64
|
|
}
|