feat(modules): port wordle module + per-subject locking

Phase 5b of go-port-cloud-run plan. Port 14855-word dictionary
(89 KB, byte-identical to JS source) and four wordle commands
(/wordle, /wordle_new, /wordle_giveup, /wordle_stats).

KV wire-format parity: GameState/Stats JSON match JS shape;
*int64 LastResultAt for null-value compatibility. Two real bugs
caught and fixed: (1) defaultRNG data race in handlers — switched
to math/rand.Intn (mutex-protected package-level); (2) Get→mutate→Put
logical race in groups — added per-subject sync.Mutex map to serialize
access. TTL deferred (Firestore has no expirationTtl equiv — Phase 11 GC).
This commit is contained in:
tiennm99 committed 2026-05-09 09:35:23 +07:00
1 parent 25f06eaaf2
commit 5980897475
16 files changed
+16015 -2

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
package wordle
import (
"github.com/tiennm99/miti99bot-go/internal/modules"
)
// New is the wordle module Factory. Loads the embedded dictionary once,
// captures the per-module KV via closure, and registers all four commands.
func New(deps modules.Deps) modules.Module {
words, set := loadWords()
s := &state{kv: deps.KV, words: words, set: set}
return modules.Module{
Commands: []modules.Command{
{
Name: "wordle",
Visibility: modules.VisibilityPublic,
Description: "Classic wordle — guess the 5-letter word",
Handler: s.handleWordle,
},
{
Name: "wordle_new",
Visibility: modules.VisibilityPublic,
Description: "Start a new round (auto-gives-up any in-progress one)",
Handler: s.handleNew,
},
{
Name: "wordle_giveup",
Visibility: modules.VisibilityPublic,
Description: "Reveal the current wordle answer",
Handler: s.handleGiveup,
},
{
Name: "wordle_stats",
Visibility: modules.VisibilityPublic,
Description: "Show your wordle stats (wins, streak)",
Handler: s.handleStats,
},
},
}
}