From 5be9a3bb92eae59f6082a14098b942e4b22db793 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:05:57 +0000 Subject: [PATCH 1/3] Initial plan From 4276c7a8b1017548ae8d79bbe7d9d95540fb53a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:11:53 +0000 Subject: [PATCH 2/3] feat: add distinct response when a word has already been cited - Add `already_cited` flag to `GuessResult` in game/state.py - Detect duplicate normalized words in `submit_guess()` before appending - Send a specific message in bot.py when a word was already suggested - Add `TestAlreadyCited` tests in test_game_state.py (7 tests) - Add 3 new tests in test_commands.py for the already-cited bot response Co-authored-by: FlorentPoinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- bot/bot.py | 6 ++++ game/state.py | 6 +++- tests/test_commands.py | 47 ++++++++++++++++++++++++++++++ tests/test_game_state.py | 63 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/bot/bot.py b/bot/bot.py index 5855499..290c9e5 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -195,6 +195,12 @@ async def guess(self, ctx: commands.Context, word: str = "") -> None: if result.is_found: await ctx.send(f"🎉 {ctx.author.name} found the word '{word}'!") + elif result.already_cited: + if result.entry.score is not None: + pct = int(result.entry.score * 100) + await ctx.send(f"'{word}' has already been suggested ({pct}% similarity).") + else: + await ctx.send(f"'{word}' has already been suggested.") elif result.entry.score is not None: pct = int(result.entry.score * 100) await ctx.send(f"'{word}': {pct}% similarity") diff --git a/game/state.py b/game/state.py index 976ca70..8891b91 100644 --- a/game/state.py +++ b/game/state.py @@ -55,10 +55,13 @@ class GuessResult: Attributes: entry: The :class:`GuessEntry` that was recorded. is_found: ``True`` if this guess matched the target word. + already_cited: ``True`` if the same word had already been submitted + earlier in this round (by any player). """ entry: GuessEntry is_found: bool + already_cited: bool class GameState: @@ -118,6 +121,7 @@ def submit_guess(self, user: str, word: str) -> GuessResult: normalized = clean_word(word) target_normalized = clean_word(self._target_word) + already_cited = any(e.normalized_word == normalized for e in self._history) found = normalized == target_normalized score: float | None @@ -139,7 +143,7 @@ def submit_guess(self, user: str, word: str) -> GuessResult: if found: self._is_found = True - return GuessResult(entry=entry, is_found=found) + return GuessResult(entry=entry, is_found=found, already_cited=already_cited) # ------------------------------------------------------------------ # State inspection diff --git a/tests/test_commands.py b/tests/test_commands.py index 5e65d20..1e08d50 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -374,6 +374,53 @@ async def test_guess_unknown_word_reports_vocabulary_miss(self): message = ctx.send.call_args[0][0] assert "vocabulary" in message.lower() + async def test_guess_already_cited_word_with_score_sends_distinct_message(self): + bot = _make_bot(cooldown=0) + bot._game_state = GameState(scorer=_FakeScorer()) + bot._game_state.start_new_game("chat", Difficulty.EASY) + ctx = _make_ctx() + ctx.author.name = "alice" + # First submission of "chien" + await _guess_fn(bot, ctx, "chien") + # Second submission of the same word + ctx2 = _make_ctx() + ctx2.author.name = "bob" + await _guess_fn(bot, ctx2, "chien") + message = ctx2.send.call_args[0][0] + assert "already" in message.lower() + assert "%" in message + + async def test_guess_already_cited_word_without_score_sends_distinct_message(self): + bot = _make_bot(cooldown=0) + bot._game_state.start_new_game("chat", Difficulty.EASY) + ctx = _make_ctx() + ctx.author.name = "alice" + # No scorer, so non-exact guess produces score=None + await _guess_fn(bot, ctx, "unknownword") + ctx2 = _make_ctx() + ctx2.author.name = "bob" + await _guess_fn(bot, ctx2, "unknownword") + message = ctx2.send.call_args[0][0] + assert "already" in message.lower() + + async def test_guess_exact_match_not_reported_as_already_cited(self): + """A winning guess should show the win message even if the word was cited before.""" + bot = _make_bot(cooldown=0) + bot._game_state = GameState(scorer=_FakeScorer()) + bot._game_state.start_new_game("chat", Difficulty.EASY) + ctx = _make_ctx() + ctx.author.name = "alice" + # Someone submits the winning word first (game won) + await _guess_fn(bot, ctx, "chat") + # Another user submits the same winning word again + ctx2 = _make_ctx() + ctx2.author.name = "bob" + await _guess_fn(bot, ctx2, "chat") + message = ctx2.send.call_args[0][0] + # Should show winner message, not "already cited" + assert "bob" in message.lower() + assert "chat" in message.lower() + # --------------------------------------------------------------------------- # event_error diff --git a/tests/test_game_state.py b/tests/test_game_state.py index ba68e74..ef9b7b0 100644 --- a/tests/test_game_state.py +++ b/tests/test_game_state.py @@ -157,6 +157,69 @@ def test_guess_result_contains_entry(self): assert result.entry.user == "alice" +# --------------------------------------------------------------------------- +# TestAlreadyCited +# --------------------------------------------------------------------------- + + +class TestAlreadyCited: + def test_first_guess_is_not_already_cited(self): + """The first submission of a word should not be marked as already cited.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + result = gs.submit_guess("alice", "chien") + assert not result.already_cited + + def test_repeated_word_is_already_cited(self): + """A word submitted a second time by any player should be already cited.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "chien") + result = gs.submit_guess("bob", "chien") + assert result.already_cited + + def test_same_user_repeated_word_is_already_cited(self): + """A word submitted twice by the same user should be already cited.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "chien") + result = gs.submit_guess("alice", "chien") + assert result.already_cited + + def test_different_word_is_not_already_cited(self): + """A word not yet in history should not be marked as already cited.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "chien") + result = gs.submit_guess("bob", "maison") + assert not result.already_cited + + def test_already_cited_normalised_match(self): + """Words that normalise to the same form should be detected as already cited.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "Chien") + result = gs.submit_guess("bob", "CHIEN") + assert result.already_cited + + def test_already_cited_word_still_appended_to_history(self): + """Even already-cited guesses should be recorded in history.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "chien") + gs.submit_guess("bob", "chien") + assert gs.attempt_count == 2 + + def test_already_cited_resets_on_new_game(self): + """After starting a new game, previously cited words are no longer cited.""" + gs = _make_state() + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "chien") + gs.start_new_game("maison", Difficulty.EASY) + result = gs.submit_guess("bob", "chien") + assert not result.already_cited + + # --------------------------------------------------------------------------- # TestGameStateLeaderboard # --------------------------------------------------------------------------- From 51387a11c8b80a82b31c44969ec73b0e52970ea1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 21:05:03 +0000 Subject: [PATCH 3/3] fix: exclude already-cited words from history and top guesses Already-cited words are no longer appended to _history, so they no longer appear in top_guesses() and the overlay leaderboard. - game/state.py: skip _history.append() when already_cited=True - tests: assert duplicates NOT appended; fix test_top_guesses_respects_n to use distinct words; add test_already_cited_word_excluded_from_top_guesses Co-authored-by: FlorentPoinsaut <1256948+FlorentPoinsaut@users.noreply.github.com> --- game/state.py | 3 ++- tests/test_game_state.py | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/game/state.py b/game/state.py index 8891b91..aa2fcdf 100644 --- a/game/state.py +++ b/game/state.py @@ -138,7 +138,8 @@ def submit_guess(self, user: str, word: str) -> GuessResult: normalized_word=normalized, score=score, ) - self._history.append(entry) + if not already_cited: + self._history.append(entry) if found: self._is_found = True diff --git a/tests/test_game_state.py b/tests/test_game_state.py index ef9b7b0..6070d30 100644 --- a/tests/test_game_state.py +++ b/tests/test_game_state.py @@ -202,13 +202,13 @@ def test_already_cited_normalised_match(self): result = gs.submit_guess("bob", "CHIEN") assert result.already_cited - def test_already_cited_word_still_appended_to_history(self): - """Even already-cited guesses should be recorded in history.""" + def test_already_cited_word_not_appended_to_history(self): + """Already-cited guesses should not be recorded again in history.""" gs = _make_state() gs.start_new_game("chat", Difficulty.EASY) gs.submit_guess("alice", "chien") gs.submit_guess("bob", "chien") - assert gs.attempt_count == 2 + assert gs.attempt_count == 1 def test_already_cited_resets_on_new_game(self): """After starting a new game, previously cited words are no longer cited.""" @@ -247,10 +247,20 @@ def test_top_guesses_respects_n(self): """top_guesses(n) should return at most n entries.""" gs = _make_state(with_scorer=True) gs.start_new_game("chat", Difficulty.EASY) - for i in range(5): - gs.submit_guess(f"user{i}", "ch") + for word in ["a", "ab", "abc", "abcd", "abcde"]: + gs.submit_guess("alice", word) assert len(gs.top_guesses(n=3)) == 3 + def test_already_cited_word_excluded_from_top_guesses(self): + """A word submitted a second time should not appear twice in the leaderboard.""" + gs = _make_state(with_scorer=True) + gs.start_new_game("chat", Difficulty.EASY) + gs.submit_guess("alice", "chien") + gs.submit_guess("bob", "chien") + top = gs.top_guesses() + normalized_words = [e.normalized_word for e in top] + assert normalized_words.count("chien") == 1 + def test_score_stored_in_entry_with_scorer(self): """When a scorer is provided, GuessEntry.score should be set.""" gs = _make_state(with_scorer=True)