diff options
Diffstat (limited to 'bot/exts/events/trivianight/_game.py')
| -rw-r--r-- | bot/exts/events/trivianight/_game.py | 60 |
1 files changed, 57 insertions, 3 deletions
diff --git a/bot/exts/events/trivianight/_game.py b/bot/exts/events/trivianight/_game.py index 086d0de6..aac745a7 100644 --- a/bot/exts/events/trivianight/_game.py +++ b/bot/exts/events/trivianight/_game.py @@ -1,4 +1,5 @@ import time +from random import randrange from string import ascii_uppercase from typing import Iterable, Optional, TypedDict @@ -25,6 +26,14 @@ UserGuess = tuple[ ] +class QuestionClosed(RuntimeError): + """Exception raised when the question is not open for guesses anymore.""" + + +class AlreadyUpdated(RuntimeError): + """Exception raised when the user has already updated their guess once.""" + + class Question: """Interface for one question in a trivia night game.""" @@ -76,10 +85,10 @@ class Question: def _update_guess(self, user: int, answer: str) -> UserGuess: """Update an already existing guess.""" if self._started is None: - raise RuntimeError("Question is not open for answers.") + raise QuestionClosed("Question is not open for answers.") if self._guesses[user][1] is False: - raise RuntimeError(f"User({user}) has already updated their guess once.") + raise AlreadyUpdated(f"User({user}) has already updated their guess once.") self._guesses[user] = (answer, False, time.perf_counter() - self._started) return self._guesses[user] @@ -90,7 +99,7 @@ class Question: return self._update_guess(user, answer) if self._started is None: - raise RuntimeError("Question is not open for answers.") + raise QuestionClosed("Question is not open for answers.") self._guesses[user] = (answer, True, time.perf_counter() - self._started) return self._guesses[user] @@ -103,3 +112,48 @@ class Question: self._guesses = {} return guesses + + +class TriviaNightGame: + """Interface for managing a game of trivia night.""" + + def __init__(self, data: list[QuestionData]) -> None: + self._questions = [Question(q) for q in data] + self.current_question: Optional[Question] = None + + def __iter__(self) -> Iterable[Question]: + return iter(self._questions) + + def next_question(self, number: str = None) -> Question: + """ + Consume one random question from the trivia night game. + + One question is randomly picked from the list of questions which is then removed and returned. + """ + if self.current_question is not None: + raise RuntimeError("Cannot call next_question() when there is a current question.") + + if number is not None: + try: + question = [q for q in self._questions if q.number == number][0] + except IndexError: + raise ValueError(f"Question number {number} does not exist.") + else: + question = self._questions.pop(randrange(len(self._questions))) + + self.current_question = question + self.current_question.start() + return question + + def end_question(self) -> None: + """ + End the current question. + + This method should be called when the question has been answered, it must be called before + attempting to call `next_question()` again. + """ + if self.current_question is None: + raise RuntimeError("Cannot call end_question() when there is no current question.") + + self.current_question.stop() + self.current_question = None |