diff options
author | 2021-12-19 20:27:34 -0500 | |
---|---|---|
committer | 2022-02-09 18:13:37 -0500 | |
commit | 8c7baf05a82cf53813ed3eabc197abf7a0d98a63 (patch) | |
tree | d60f5f27275c3cc9db29c8f966c0110586ef20f0 | |
parent | Fix bugs after testing and add TODO comments (diff) |
refactor
-rw-r--r-- | bot/exts/events/trivianight/_game.py | 31 | ||||
-rw-r--r-- | bot/exts/events/trivianight/_questions.py | 36 | ||||
-rw-r--r-- | bot/exts/events/trivianight/_scoreboard.py | 36 | ||||
-rw-r--r-- | bot/exts/events/trivianight/trivianight.py | 27 |
4 files changed, 90 insertions, 40 deletions
diff --git a/bot/exts/events/trivianight/_game.py b/bot/exts/events/trivianight/_game.py index 7f2e48dc..db303c58 100644 --- a/bot/exts/events/trivianight/_game.py +++ b/bot/exts/events/trivianight/_game.py @@ -3,7 +3,6 @@ from random import randrange from string import ascii_uppercase from typing import Iterable, Optional, TypedDict - DEFAULT_QUESTION_POINTS = 10 DEFAULT_QUESTION_TIME = 10 @@ -56,7 +55,8 @@ class Question: @property def answers(self) -> list[tuple[str, str]]: - """The possible answers for this answer. + """ + The possible answers for this answer. This is a property that returns a list of letter, answer pairs. """ @@ -119,7 +119,11 @@ class TriviaNightGame: def __init__(self, data: list[QuestionData]) -> None: self._questions = [Question(q) for q in data] + # A copy of the questions to keep for `.trivianight list` + self._all_questions = list(self._questions) self.current_question: Optional[Question] = None + self._points = {} + self._speed = {} def __iter__(self) -> Iterable[Question]: return iter(self._questions) @@ -135,7 +139,7 @@ class TriviaNightGame: if number is not None: try: - question = [q for q in self._questions if q.number == number][0] + question = [q for q in self._all_questions if q.number == number][0] except IndexError: raise ValueError(f"Question number {number} does not exist.") else: @@ -156,3 +160,24 @@ class TriviaNightGame: self.current_question.stop() self.current_question = None + + def list_questions(self) -> None: + """ + List all the questions. + + This method should be called when `.trivianight list` is called to display the following information: + - Question number + - Question description + - Visited/not visited + """ + formatted_string = "" + + spaces = max(len(q.description) for q in self._all_questions) + + for question in self._all_questions: + visited, not_visited = ":checkmark:", ":x:" + formatted_string += f"`Q{question.number}: {question.description}" \ + f"{' ' * (spaces - len(question.description))}|`" \ + f" {visited if question not in self._all_questions else not_visited}\n" + + return formatted_string diff --git a/bot/exts/events/trivianight/_questions.py b/bot/exts/events/trivianight/_questions.py index 9a2cb7d2..7fb6dedf 100644 --- a/bot/exts/events/trivianight/_questions.py +++ b/bot/exts/events/trivianight/_questions.py @@ -1,7 +1,5 @@ -from random import choice, randrange +from random import choice from string import ascii_uppercase -from time import perf_counter -from typing import Optional, TypedDict, Union import discord from discord import Embed, Interaction @@ -10,7 +8,7 @@ from discord.ui import Button, View from bot.constants import Colours, NEGATIVE_REPLIES from . import UserScore -from ._game import Question, AlreadyUpdated, QuestionClosed +from ._game import AlreadyUpdated, Question, QuestionClosed from ._scoreboard import Scoreboard @@ -110,7 +108,7 @@ class QuestionView(View): return question_embed - def end_question(self) -> Embed: + def end_question(self, scoreboard: Scoreboard) -> Embed: """ Ends the question and displays the statistics on who got the question correct, awards points, etc. @@ -120,7 +118,6 @@ class QuestionView(View): guesses = self.question.stop() labels = ascii_uppercase[:len(self.question.answers)] - correct = [label for (label, description) in self.question.answers if description == self.question.correct] answer_embed = Embed( title=f"The correct answer for Question {self.question.number} was..", @@ -135,9 +132,13 @@ class QuestionView(View): for answer_choice in labels } - for idx, (answer, percent) in enumerate(answers_chosen.items()): + answers_chosen = dict( + sorted(list(answers_chosen.items()), key=lambda item: item[1], reverse=True) + ) + + for answer, percent in answers_chosen.items(): # Setting the color of answer_embed to the % of people that got it correct via the mapping - if idx == 0: + if dict(self.question.answers)[answer[0]] == self.question.correct: # Maps the % of people who got it right to a color, from a range of red to green percentage_to_color = [0xFC94A1, 0xFFCCCB, 0xCDFFCC, 0xB0F5AB, 0xB0F5AB] answer_embed.color = percentage_to_color[round(percent * 100) // 25] @@ -149,4 +150,23 @@ class QuestionView(View): inline=False ) + # Assign points to users + for user_id, answer in guesses.items(): + if dict(self.question.answers)[answer[0]] == self.question.correct: + scoreboard.assign_points( + UserScore(int(user_id)), + points=(1 - (answer[-1] / self.question.time) / 2) * self.question.max_points, + speed=answer[-1] + ) + elif answer[-1] <= 2: + scoreboard.assign_points( + UserScore(int(user_id)), + points=-(1 - (answer[-1] / self.question.time) / 2) * self.question.max_points + ) + else: + scoreboard.assign_points( + UserScore(int(user_id)), + points=0 + ) + return answer_embed diff --git a/bot/exts/events/trivianight/_scoreboard.py b/bot/exts/events/trivianight/_scoreboard.py index 40f93475..babd1bd6 100644 --- a/bot/exts/events/trivianight/_scoreboard.py +++ b/bot/exts/events/trivianight/_scoreboard.py @@ -16,8 +16,6 @@ class ScoreboardView(View): def __init__(self, bot: Bot): super().__init__() self.bot = bot - self.points = {} - self.speed = {} async def create_main_leaderboard(self) -> Embed: """ @@ -142,19 +140,31 @@ class ScoreboardView(View): class Scoreboard: """Class for the scoreboard for the Trivia Night event.""" - def __setitem__(self, key: UserScore, value: dict): - if value.get("points") and key.user_id not in self.view.points.keys(): - self.view.points[key.user_id] = value["points"] - elif value.get("points"): - self.view.points[key.user_id] += self.view.points[key.user_id] - - if value.get("speed") and key.user_id not in self.view.speed.keys(): - self.view.speed[key.user_id] = [1, value["speed"]] - elif value.get("speed"): - self.view.speed[key.user_id] = [ - self.view.speed[key.user_id][0] + 1, self.view.speed[key.user_id][1] + value["speed"] + def __init__(self, bot: Bot): + self.view = ScoreboardView(bot) + self._points = {} + self._speed = {} + + def assign_points(self, user: UserScore, *, points: int = None, speed: float = None) -> None: + """ + Assign points or deduct points to/from a certain user. + + This method should be called once the question has finished and all answers have been registered. + """ + if points is not None and user.user_id not in self._points.keys(): + self._points[user.user_id] = points + elif points is not None: + self._points[user.user_id] += self._points[user.user_id] + + if speed is not None and user.user_id not in self._speed.keys(): + self._speed[user.user_id] = [1, speed] + elif speed is not None: + self._speed[user.user_id] = [ + self._speed[user.user_id][0] + 1, self._speed[user.user_id][1] + speed ] async def display(self) -> tuple[Embed, View]: """Returns the embed of the main leaderboard along with the ScoreboardView.""" + self.view.points = self._points + self.view.speed = self._speed return await self.view.create_main_leaderboard(), self.view diff --git a/bot/exts/events/trivianight/trivianight.py b/bot/exts/events/trivianight/trivianight.py index 1465a03d..f158ec0c 100644 --- a/bot/exts/events/trivianight/trivianight.py +++ b/bot/exts/events/trivianight/trivianight.py @@ -4,7 +4,6 @@ from random import choice from typing import Optional from discord import Embed -from discord.colour import Color from discord.ext import commands from bot.bot import Bot @@ -12,7 +11,7 @@ from bot.constants import Colours, NEGATIVE_REPLIES, POSITIVE_REPLIES, Roles from ._game import TriviaNightGame from ._questions import QuestionView -from ._scoreboard import Scoreboard, ScoreboardView +from ._scoreboard import Scoreboard # The ID you see below is the Events Lead role ID TRIVIA_NIGHT_ROLES = (Roles.admin, 78361735739998228) @@ -24,6 +23,8 @@ class TriviaNightCog(commands.Cog): def __init__(self, bot: Bot): self.bot = bot self.game: Optional[TriviaNightGame] = None + self.scoreboard: Optional[Scoreboard] = None + self.question_closed: asyncio.Event = None @commands.group(aliases=["tn"], invoke_without_command=True) async def trivianight(self, ctx: commands.Context) -> None: @@ -91,12 +92,16 @@ class TriviaNightCog(commands.Cog): raise commands.BadArgument("Invalid JSON") self.game = TriviaNightGame(serialized_json) + self.question_closed = asyncio.Event() success_embed = Embed( title=choice(POSITIVE_REPLIES), description="The JSON was loaded successfully!", color=Colours.soft_green ) + + self.scoreboard = Scoreboard(self.bot) + await ctx.send(embed=success_embed) @trivianight.command(aliases=('next',)) @@ -140,6 +145,7 @@ class TriviaNightCog(commands.Cog): duration = next_question.time * percentage await asyncio.sleep(duration) + if int(duration) > 1: # It is quite ugly to display decimals, the delay for requests to reach Discord # cause sub-second accuracy to be quite pointless. @@ -150,7 +156,7 @@ class TriviaNightCog(commands.Cog): await asyncio.sleep(duration) break - await ctx.send(embed=question_view.end_question()) + await ctx.send(embed=question_view.end_question(self.scoreboard)) await message.edit(embed=question_embed, view=None) self.game.end_question() @@ -172,14 +178,7 @@ class TriviaNightCog(commands.Cog): )) return - # TODO: Because of how the game currently works, only the questions left will be able to - # be gotten. Iterate through self.game: - # - # for question in self.game: - # # This is an instance of Question from _game.py - # print(question.description) - - question_list = self.questions.list_questions() + question_list = self.game.list_questions() if isinstance(question_list, Embed): await ctx.send(embed=question_list) return @@ -211,10 +210,7 @@ class TriviaNightCog(commands.Cog): await ctx.send(embed=error_embed) return - # TODO: We need to tell the 'trivianight next' command that the game has ended, if it is still - # running that means it is currently counting down waiting to end the question. Use an asyncio.Event and - # asyncio.wait(self.lock.wait(), timeout=duration) as opposed to asyncio.sleep(duration). - self.game.end_question() + self.ongoing_question = False @trivianight.command() @commands.has_any_role(*TRIVIA_NIGHT_ROLES) @@ -245,7 +241,6 @@ class TriviaNightCog(commands.Cog): await ctx.send(embed=error_embed) return - # TODO: Refactor the scoreboard after the game simplification. scoreboard_embed, scoreboard_view = await self.scoreboard.display() await ctx.send(embed=scoreboard_embed, view=scoreboard_view) self.game = None |