mirror of
https://github.com/stijndcl/didier.git
synced 2026-04-07 23:55:46 +02:00
WORDLE
This commit is contained in:
parent
ea4181eac0
commit
db499f3742
20 changed files with 350 additions and 101 deletions
2
database/constants.py
Normal file
2
database/constants.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
WORDLE_GUESS_COUNT = 6
|
||||
WORDLE_WORD_LENGTH = 5
|
||||
|
|
@ -1,32 +1,47 @@
|
|||
from typing import Optional
|
||||
|
||||
from database.enums import TempStorageKey
|
||||
from database.mongo_types import MongoCollection
|
||||
from database.schemas.mongo import WordleGame
|
||||
from database.mongo_types import MongoDatabase
|
||||
from database.schemas.mongo import TemporaryStorage, WordleGame
|
||||
from database.utils.datetime import today_only_date
|
||||
|
||||
__all__ = ["get_active_wordle_game", "make_wordle_guess", "start_new_wordle_game"]
|
||||
__all__ = [
|
||||
"get_active_wordle_game",
|
||||
"make_wordle_guess",
|
||||
"start_new_wordle_game",
|
||||
"set_daily_word",
|
||||
"reset_wordle_games",
|
||||
]
|
||||
|
||||
|
||||
async def get_active_wordle_game(collection: MongoCollection, user_id: int) -> Optional[WordleGame]:
|
||||
async def get_active_wordle_game(database: MongoDatabase, user_id: int) -> Optional[WordleGame]:
|
||||
"""Find a player's active game"""
|
||||
return await collection.find_one({"user_id": user_id})
|
||||
collection = database[WordleGame.collection()]
|
||||
result = await collection.find_one({"user_id": user_id})
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
return WordleGame(**result)
|
||||
|
||||
|
||||
async def start_new_wordle_game(collection: MongoCollection, user_id: int) -> WordleGame:
|
||||
async def start_new_wordle_game(database: MongoDatabase, user_id: int) -> WordleGame:
|
||||
"""Start a new game"""
|
||||
collection = database[WordleGame.collection()]
|
||||
game = WordleGame(user_id=user_id)
|
||||
await collection.insert_one(game.dict(by_alias=True))
|
||||
return game
|
||||
|
||||
|
||||
async def make_wordle_guess(collection: MongoCollection, user_id: int, guess: str):
|
||||
async def make_wordle_guess(database: MongoDatabase, user_id: int, guess: str):
|
||||
"""Make a guess in your current game"""
|
||||
collection = database[WordleGame.collection()]
|
||||
await collection.update_one({"user_id": user_id}, {"$push": {"guesses": guess}})
|
||||
|
||||
|
||||
async def get_daily_word(collection: MongoCollection) -> Optional[str]:
|
||||
async def get_daily_word(database: MongoDatabase) -> Optional[str]:
|
||||
"""Get the word of today"""
|
||||
collection = database[TemporaryStorage.collection()]
|
||||
|
||||
result = await collection.find_one({"key": TempStorageKey.WORDLE_WORD, "day": today_only_date()})
|
||||
if result is None:
|
||||
return None
|
||||
|
|
@ -34,16 +49,33 @@ async def get_daily_word(collection: MongoCollection) -> Optional[str]:
|
|||
return result["word"]
|
||||
|
||||
|
||||
async def set_daily_word(collection: MongoCollection, word: str):
|
||||
async def set_daily_word(database: MongoDatabase, word: str, *, forced: bool = False) -> str:
|
||||
"""Set the word of today
|
||||
|
||||
This does NOT overwrite the existing word if there is one, so that it can safely run
|
||||
on startup every time
|
||||
on startup every time.
|
||||
|
||||
In order to always overwrite the current word, set the "forced"-kwarg to True.
|
||||
|
||||
Returns the word that was chosen. If one already existed, return that instead.
|
||||
"""
|
||||
current_word = await get_daily_word(collection)
|
||||
collection = database[TemporaryStorage.collection()]
|
||||
|
||||
current_word = None if forced else await get_daily_word(collection)
|
||||
if current_word is not None:
|
||||
return
|
||||
return current_word
|
||||
|
||||
await collection.update_one(
|
||||
{"key": TempStorageKey.WORDLE_WORD}, {"day": today_only_date(), "word": word}, upsert=True
|
||||
{"key": TempStorageKey.WORDLE_WORD}, {"$set": {"day": today_only_date(), "word": word}}, upsert=True
|
||||
)
|
||||
|
||||
# Remove all active games
|
||||
await reset_wordle_games(database)
|
||||
|
||||
return word
|
||||
|
||||
|
||||
async def reset_wordle_games(database: MongoDatabase):
|
||||
"""Reset all active games"""
|
||||
collection = database[WordleGame.collection()]
|
||||
await collection.drop()
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ from typing import Optional
|
|||
|
||||
from bson import ObjectId
|
||||
from overrides import overrides
|
||||
from pydantic import BaseModel, Field, conlist
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
__all__ = ["MongoBase", "TemporaryStorage", "WordleGame"]
|
||||
|
||||
from database.utils.datetime import today_only_date
|
||||
|
||||
|
||||
class PyObjectId(str):
|
||||
class PyObjectId(ObjectId):
|
||||
"""Custom type for bson ObjectIds"""
|
||||
|
||||
@classmethod
|
||||
|
|
@ -71,12 +71,20 @@ class TemporaryStorage(MongoCollection):
|
|||
class WordleStats(BaseModel):
|
||||
"""Model that holds stats about a player's Wordle performance"""
|
||||
|
||||
guess_distribution: conlist(int, min_items=6, max_items=6) = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0])
|
||||
guess_distribution: list[int] = Field(default_factory=lambda: [0, 0, 0, 0, 0, 0])
|
||||
last_guess: Optional[datetime.date] = None
|
||||
win_rate: float = 0
|
||||
current_streak: int = 0
|
||||
max_streak: int = 0
|
||||
|
||||
@validator("guess_distribution")
|
||||
def validate_guesses_length(cls, value: list[int]):
|
||||
"""Check that the distribution of guesses is of the correct length"""
|
||||
if len(value) != 6:
|
||||
raise ValueError(f"guess_distribution must be length 6, found {len(value)}")
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class GameStats(MongoCollection):
|
||||
"""Collection that holds stats about how well a user has performed in games"""
|
||||
|
|
@ -94,10 +102,18 @@ class WordleGame(MongoCollection):
|
|||
"""Collection that holds people's active Wordle games"""
|
||||
|
||||
day: datetime.date = Field(default_factory=lambda: today_only_date())
|
||||
guesses: conlist(str, min_items=0, max_items=6) = Field(default_factory=list)
|
||||
guesses: list[str] = Field(default_factory=list)
|
||||
user_id: int
|
||||
|
||||
@staticmethod
|
||||
@overrides
|
||||
def collection() -> str:
|
||||
return "wordle"
|
||||
|
||||
@validator("guesses")
|
||||
def validate_guesses_length(cls, value: list[int]):
|
||||
"""Check that the amount of guesses is of the correct length"""
|
||||
if len(value) > 6:
|
||||
raise ValueError(f"guess_distribution must be no longer than 6 elements, found {len(value)}")
|
||||
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from overrides import overrides
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from database.crud import ufora_courses
|
||||
from database.crud import ufora_courses, wordle
|
||||
from database.mongo_types import MongoDatabase
|
||||
|
||||
__all__ = ["CacheManager"]
|
||||
__all__ = ["CacheManager", "UforaCourseCache"]
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class DatabaseCache(ABC):
|
||||
class DatabaseCache(ABC, Generic[T]):
|
||||
"""Base class for a simple cache-like structure
|
||||
|
||||
The goal of this class is to store data for Discord auto-completion results
|
||||
|
|
@ -31,10 +35,10 @@ class DatabaseCache(ABC):
|
|||
self.data.clear()
|
||||
|
||||
@abstractmethod
|
||||
async def refresh(self, database_session: AsyncSession):
|
||||
async def refresh(self, database_session: T):
|
||||
"""Refresh the data stored in this cache"""
|
||||
|
||||
async def invalidate(self, database_session: AsyncSession):
|
||||
async def invalidate(self, database_session: T):
|
||||
"""Invalidate the data stored in this cache"""
|
||||
await self.refresh(database_session)
|
||||
|
||||
|
|
@ -45,7 +49,7 @@ class DatabaseCache(ABC):
|
|||
return [self.data[index] for index, value in enumerate(self.data_transformed) if query in value]
|
||||
|
||||
|
||||
class UforaCourseCache(DatabaseCache):
|
||||
class UforaCourseCache(DatabaseCache[AsyncSession]):
|
||||
"""Cache to store the names of Ufora courses"""
|
||||
|
||||
# Also store the aliases to add additional support
|
||||
|
|
@ -90,14 +94,26 @@ class UforaCourseCache(DatabaseCache):
|
|||
return sorted(list(results))
|
||||
|
||||
|
||||
class WordleCache(DatabaseCache[MongoDatabase]):
|
||||
"""Cache to store the current daily Wordle word"""
|
||||
|
||||
async def refresh(self, database_session: MongoDatabase):
|
||||
word = await wordle.get_daily_word(database_session)
|
||||
if word is not None:
|
||||
self.data = [word]
|
||||
|
||||
|
||||
class CacheManager:
|
||||
"""Class that keeps track of all caches"""
|
||||
|
||||
ufora_courses: UforaCourseCache
|
||||
wordle_word: WordleCache
|
||||
|
||||
def __init__(self):
|
||||
self.ufora_courses = UforaCourseCache()
|
||||
self.wordle_word = WordleCache()
|
||||
|
||||
async def initialize_caches(self, database_session: AsyncSession):
|
||||
async def initialize_caches(self, postgres_session: AsyncSession, mongo_db: MongoDatabase):
|
||||
"""Initialize the contents of all caches"""
|
||||
await self.ufora_courses.refresh(database_session)
|
||||
await self.ufora_courses.refresh(postgres_session)
|
||||
await self.wordle_word.refresh(mongo_db)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue