feat(game): a score a player can see the cause of, and a dead end they can claim

pointsFor now returns the named terms behind a word's total alongside the
total itself, trimmed from the trailing term when the maxPointsPerWord cap
bites, so PlayedWord.parts always sums exactly to points. ClaimDeadEnd lets
the player to act say a syllable has no answer instead of waiting out the
clock; the room checks HasLegalMove and settles it exactly as a timeout
would, or refuses with the clock untouched. dictionary.Store gains a
diacritic-stripped index built at Open, so MoveRejected.suggestion can offer
the one real word a typo differs from by tone marks alone. ReportWord is
validated and rate-limited on the session, logged with the room's own
context, and answered with WordReported.
This commit is contained in:
tiennm99 committed 2026-09-21 01:54:58 +07:00
1 parent 865222d2cd
commit 16fd37978a
12 files changed
+905 -13

No files matched your search

+12 -2
View File
@@ -59,9 +59,19 @@ func welcomeMsg(sessionID, resumeToken, nickname string) *noituv1.ServerMessage
}}}
}
func moveRejectedMsg(reason noituv1.RejectReason, word string, turnSeq uint32) *noituv1.ServerMessage {
// suggestion is REJECT_REASON_NOT_IN_DICTIONARY only: the one real word the
// submission differs from by diacritics alone, or empty when none applies.
func moveRejectedMsg(reason noituv1.RejectReason, word string, turnSeq uint32, suggestion string) *noituv1.ServerMessage {
return &noituv1.ServerMessage{Payload: &noituv1.ServerMessage_MoveRejected{
MoveRejected: &noituv1.MoveRejected{Reason: reason, Word: word, TurnSeq: turnSeq},
MoveRejected: &noituv1.MoveRejected{Reason: reason, Word: word, TurnSeq: turnSeq, Suggestion: suggestion},
}}
}
// wordReportedMsg acknowledges a ReportWord, echoing the word as the server
// recorded it so the player sees that it was heard.
func wordReportedMsg(word string) *noituv1.ServerMessage {
return &noituv1.ServerMessage{Payload: &noituv1.ServerMessage_WordReported{
WordReported: &noituv1.WordReported{Word: word},
}}
}