From 72b2b21912dd7510bdf24465c1135d177f4f5d6a Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Wed, 30 Sep 2026 14:19:20 +0700 Subject: [PATCH] fix(wordle): stop giveup from recording a second loss on exhausted rounds --- internal/modules/wordle/handlers.go | 9 ++++++++- internal/modules/wordle/handlers_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/internal/modules/wordle/handlers.go b/internal/modules/wordle/handlers.go index 1e39bd5..0dde494 100644 --- a/internal/modules/wordle/handlers.go +++ b/internal/modules/wordle/handlers.go @@ -169,7 +169,8 @@ func (s *state) handleNew(ctx context.Context, b *bot.Bot, update *models.Update } // handleGiveup is /wordle_giveup — reveals answer for the current round. -// Idempotent on already-finished rounds (parrots the same answer back). +// Calls on a finished round (solved, given up, or out of guesses) echo the +// answer without touching stats. func (s *state) handleGiveup(ctx context.Context, b *bot.Bot, update *models.Update) error { msg := update.Message if msg == nil { @@ -193,6 +194,12 @@ func (s *state) handleGiveup(ctx context.Context, b *bot.Bot, update *models.Upd if g.Giveup { return chathelper.Reply(ctx, b, msg, fmt.Sprintf("Already gave up — %s.", strings.ToUpper(g.Target))) } + // A round lost by running out of guesses already recorded its loss in + // handleWordle; giving up on it must not count a second one. + if isFinished(g) { + return chathelper.Reply(ctx, b, msg, + fmt.Sprintf("Current round is over. Use /wordle_new to start another. Answer was %s.", strings.ToUpper(g.Target))) + } g.Giveup = true if err := saveGame(ctx, s.games, subject, g); err != nil { return err diff --git a/internal/modules/wordle/handlers_test.go b/internal/modules/wordle/handlers_test.go index 9ff5326..2827281 100644 --- a/internal/modules/wordle/handlers_test.go +++ b/internal/modules/wordle/handlers_test.go @@ -190,3 +190,27 @@ func TestWordle_GroupSubjectIsChatID(t *testing.T) { t.Errorf("user-keyed game leaked despite group context: %+v", gUser) } } + +// Running out of guesses already records the loss, so a later /wordle_giveup +// on that round must not count a second one. +func TestWordleGiveup_OutOfGuessesDoesNotRecordSecondLoss(t *testing.T) { + rb, games := installWordle(t, 0, "", "") + g := &GameState{Target: "crane", StartedAt: 1} + for range MaxGuesses { + g.Guesses = append(g.Guesses, GuessRecord{Word: "slate", Results: CompareWords("slate", "crane")}) + } + if err := saveGame(context.Background(), games, "1", g); err != nil { + t.Fatalf("seed game: %v", err) + } + + rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(1, "/wordle_giveup")) + if got := rb.LastSent().Text(); !strings.Contains(got, "Current round is over") || !strings.Contains(got, "CRANE") { + t.Fatalf("/wordle_giveup on exhausted round: %q", got) + } + + rb.Reset() + rb.Bot.ProcessUpdate(context.Background(), testutil.NewPrivateMessage(1, "/wordle_stats")) + if got := rb.LastSent().Text(); !strings.Contains(got, "Played: 0") { + t.Fatalf("giveup on exhausted round recorded stats: %q", got) + } +}