diff --git a/database/crud/wordle.py b/database/crud/wordle.py index a918256..a978fdc 100644 --- a/database/crud/wordle.py +++ b/database/crud/wordle.py @@ -9,6 +9,7 @@ from database.schemas import WordleGuess, WordleWord __all__ = [ "get_active_wordle_game", + "get_wordle_guesses", "make_wordle_guess", "set_daily_word", "reset_wordle_games", @@ -23,6 +24,12 @@ async def get_active_wordle_game(session: AsyncSession, user_id: int) -> list[Wo return guesses +async def get_wordle_guesses(session: AsyncSession, user_id: int) -> list[str]: + """Get the strings of a player's guesses""" + active_game = await get_active_wordle_game(session, user_id) + return list(map(lambda g: g.guess.lower(), active_game)) + + async def make_wordle_guess(session: AsyncSession, user_id: int, guess: str): """Make a guess in your current game""" guess_instance = WordleGuess(user_id=user_id, guess=guess) @@ -62,7 +69,6 @@ async def set_daily_word(session: AsyncSession, word: str, *, forced: bool = Fal await reset_wordle_games(session) elif forced: current_word.word = word - current_word.day = datetime.date.today() session.add(current_word) await session.commit() @@ -76,3 +82,4 @@ async def reset_wordle_games(session: AsyncSession): """Reset all active games""" statement = delete(WordleGuess) await session.execute(statement) + await session.commit() diff --git a/database/crud/wordle_stats.py b/database/crud/wordle_stats.py index 9d7a77d..71c123e 100644 --- a/database/crud/wordle_stats.py +++ b/database/crud/wordle_stats.py @@ -3,6 +3,7 @@ from datetime import date from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from database.crud.users import get_or_add_user from database.schemas import WordleStats __all__ = ["get_wordle_stats", "complete_wordle_game"] @@ -13,6 +14,8 @@ async def get_wordle_stats(session: AsyncSession, user_id: int) -> WordleStats: If no entry is found, it is first created """ + await get_or_add_user(session, user_id) + statement = select(WordleStats).where(WordleStats.user_id == user_id) stats = (await session.execute(statement)).scalar_one_or_none() if stats is not None: diff --git a/database/utils/caches.py b/database/utils/caches.py index 35165a5..25c9cb5 100644 --- a/database/utils/caches.py +++ b/database/utils/caches.py @@ -8,6 +8,8 @@ from database.crud import links, memes, ufora_courses, wordle __all__ = ["CacheManager", "LinkCache", "UforaCourseCache"] +from database.schemas import WordleWord + class DatabaseCache(ABC): """Base class for a simple cache-like structure @@ -118,10 +120,12 @@ class UforaCourseCache(DatabaseCache): class WordleCache(DatabaseCache): """Cache to store the current daily Wordle word""" + word: WordleWord + async def invalidate(self, database_session: AsyncSession): word = await wordle.get_daily_word(database_session) if word is not None: - self.data = [word.word] + self.word = word class CacheManager: diff --git a/didier/cogs/games.py b/didier/cogs/games.py index a98fe24..6114c77 100644 --- a/didier/cogs/games.py +++ b/didier/cogs/games.py @@ -4,10 +4,11 @@ import discord from discord import app_commands from discord.ext import commands -from database.constants import WORDLE_GUESS_COUNT, WORDLE_WORD_LENGTH -from database.crud.wordle import get_active_wordle_game, make_wordle_guess +from database.constants import WORDLE_WORD_LENGTH +from database.crud.wordle import get_wordle_guesses, make_wordle_guess +from database.crud.wordle_stats import complete_wordle_game from didier import Didier -from didier.data.embeds.wordle import WordleEmbed, WordleErrorEmbed +from didier.data.embeds.wordle import WordleEmbed, WordleErrorEmbed, is_wordle_game_over class Games(commands.Cog): @@ -31,14 +32,13 @@ class Games(commands.Cog): embed = WordleErrorEmbed(message=f"Guess must be 5 characters, but `{guess}` is {len(guess)}.").to_embed() return await interaction.followup.send(embed=embed) - word = self.client.database_caches.wordle_word.data[0].lower() + word_instance = self.client.database_caches.wordle_word.word async with self.client.postgres_session as session: - guesses_instances = await get_active_wordle_game(session, interaction.user.id) - guesses = list(map(lambda g: g.guess, guesses_instances)) + guesses = await get_wordle_guesses(session, interaction.user.id) # Trying to guess with a complete game - if (len(guesses) == WORDLE_GUESS_COUNT and guess) or word in guesses: + if is_wordle_game_over(guesses, word_instance.word): embed = WordleErrorEmbed( message="You've already completed today's Wordle.\nTry again tomorrow!" ).to_embed() @@ -58,9 +58,14 @@ class Games(commands.Cog): # just append locally guesses.append(guess) - embed = WordleEmbed(guesses=guesses, word=word).to_embed() + embed = WordleEmbed(guesses=guesses, word=word_instance).to_embed() await interaction.followup.send(embed=embed) + # After responding to the interaction: update stats in the background + game_over = is_wordle_game_over(guesses, word_instance.word) + if game_over: + await complete_wordle_game(session, interaction.user.id, word_instance.word in guesses) + async def setup(client: Didier): """Load the cog""" diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index 2e62044..64f5501 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -141,8 +141,8 @@ class Tasks(commands.Cog): async def reset_wordle_word(self, forced: bool = False): """Reset the daily Wordle word""" async with self.client.postgres_session as session: - word = await set_daily_word(session, random.choice(tuple(self.client.wordle_words)), forced=forced) - self.client.database_caches.wordle_word.data = [word] + await set_daily_word(session, random.choice(tuple(self.client.wordle_words)), forced=forced) + await self.client.database_caches.wordle_word.invalidate(session) @reset_wordle_word.before_loop async def _before_reset_wordle_word(self): diff --git a/didier/data/embeds/wordle.py b/didier/data/embeds/wordle.py index 4e0d52e..959ad62 100644 --- a/didier/data/embeds/wordle.py +++ b/didier/data/embeds/wordle.py @@ -5,10 +5,22 @@ import discord from overrides import overrides from database.constants import WORDLE_GUESS_COUNT, WORDLE_WORD_LENGTH +from database.schemas import WordleWord from didier.data.embeds.base import EmbedBaseModel from didier.utils.types.datetime import int_to_weekday, tz_aware_now -__all__ = ["WordleEmbed", "WordleErrorEmbed"] +__all__ = ["is_wordle_game_over", "WordleEmbed", "WordleErrorEmbed"] + + +def is_wordle_game_over(guesses: list[str], word: str) -> bool: + """Check if the current game is over or not""" + if not guesses: + return False + + if len(guesses) == WORDLE_GUESS_COUNT: + return True + + return word.lower() in guesses def footer() -> str: @@ -31,17 +43,17 @@ class WordleEmbed(EmbedBaseModel): """Embed for a Wordle game""" guesses: list[str] - word: str + word: WordleWord def _letter_colour(self, guess: str, index: int) -> WordleColour: """Get the colour for a guess at a given position""" - if guess[index] == self.word[index]: + if guess[index] == self.word.word[index]: return WordleColour.CORRECT wrong_letter = 0 wrong_position = 0 - for i, letter in enumerate(self.word): + for i, letter in enumerate(self.word.word): if letter == guess[index] and guess[i] != guess[index]: wrong_letter += 1 @@ -90,23 +102,13 @@ class WordleEmbed(EmbedBaseModel): return emojis - def _is_game_over(self) -> bool: - """Check if the current game is over or not""" - if not self.guesses: - return False - - if len(self.guesses) == WORDLE_GUESS_COUNT: - return True - - return self.word.lower() in self.guesses - @overrides def to_embed(self, **kwargs) -> discord.Embed: only_colours = kwargs.get("only_colours", False) colours = self.colour_code_game() - embed = discord.Embed(colour=discord.Colour.blue(), title="Wordle") + embed = discord.Embed(colour=discord.Colour.blue(), title=f"Wordle #{self.word.word_id + 1}") emojis = self._colours_to_emojis(colours) rows = [" ".join(row) for row in emojis] @@ -117,8 +119,8 @@ class WordleEmbed(EmbedBaseModel): rows[i] += f" ||{guess.upper()}||" # If the game is over, reveal the word - if self._is_game_over(): - rows.append(f"\n\nThe word was **{self.word.upper()}**!") + if is_wordle_game_over(self.guesses, self.word.word): + rows.append(f"\n\nThe word was **{self.word.word.upper()}**!") embed.description = "\n\n".join(rows) embed.set_footer(text=footer()) diff --git a/tests/test_database/test_crud/test_wordle.py b/tests/test_database/test_crud/test_wordle.py index f3aeb2a..4b0e950 100644 --- a/tests/test_database/test_crud/test_wordle.py +++ b/tests/test_database/test_crud/test_wordle.py @@ -121,13 +121,12 @@ async def test_make_wordle_guess(postgres: AsyncSession, user: User): guess = "guess" await crud.make_wordle_guess(postgres, test_user_id, guess) - wordle_guesses = await crud.get_active_wordle_game(postgres, test_user_id) - assert list(map(lambda x: x.guess, wordle_guesses)) == [guess] + assert crud.get_wordle_guesses(postgres, test_user_id) == [guess] other_guess = "otherguess" await crud.make_wordle_guess(postgres, test_user_id, other_guess) - wordle_guesses = await crud.get_active_wordle_game(postgres, test_user_id) - assert list(map(lambda x: x.guess, wordle_guesses)) == [guess, other_guess] + await crud.get_active_wordle_game(postgres, test_user_id) + assert crud.get_wordle_guesses(postgres, test_user_id) == [guess, other_guess] @pytest.mark.postgres