From a7d00f8de9a5cfa2b9c76f1a2b39ac861787e24e Mon Sep 17 00:00:00 2001 From: Shom770 <82843611+Shom770@users.noreply.github.com> Date: Tue, 19 Oct 2021 21:43:53 -0400 Subject: add trivianight structure in the main cog --- bot/exts/events/trivianight/_questions.py | 116 +++++++++++++++++++++++++++++ bot/exts/events/trivianight/_scoreboard.py | 81 ++++++++++++++++++++ bot/exts/events/trivianight/questions.py | 115 ---------------------------- bot/exts/events/trivianight/scoreboard.py | 81 -------------------- bot/exts/events/trivianight/trivianight.py | 27 +++++++ 5 files changed, 224 insertions(+), 196 deletions(-) create mode 100644 bot/exts/events/trivianight/_questions.py create mode 100644 bot/exts/events/trivianight/_scoreboard.py delete mode 100644 bot/exts/events/trivianight/questions.py delete mode 100644 bot/exts/events/trivianight/scoreboard.py diff --git a/bot/exts/events/trivianight/_questions.py b/bot/exts/events/trivianight/_questions.py new file mode 100644 index 00000000..ef56ee81 --- /dev/null +++ b/bot/exts/events/trivianight/_questions.py @@ -0,0 +1,116 @@ +from random import choice +from time import perf_counter + +from discord import ButtonStyle, Embed, Interaction +from discord.ui import Button, View + +from bot.constants import Colours, NEGATIVE_REPLIES +from ._scoreboard import Scoreboard + + +class QuestionButton(Button): + """Button subclass for the options of the questions.""" + + def __init__(self, label: str): + self._time = perf_counter() + self.users_picked = {} + super().__init__(label=label, style=ButtonStyle.green) + + def answer(self, label: str) -> dict: + """Returns the dictionary of the users who picked the answer only if it was correct.""" + return self.users_picked if label == self.label else {} + + async def callback(self, interaction: Interaction) -> None: + """When a user interacts with the button, this will be called.""" + if interaction.user.id not in self.users_picked.keys(): + self.users_picked[interaction.user.id] = [self.label, 1, perf_counter() - self._time] + elif self.users_picked[interaction.user.id][1] < 3: + self.users_picked[interaction.user.id] = [ + self.label, self.users_picked[interaction.user.id][0] + 1, perf_counter() - self._time + ] + else: + await interaction.response.send_message( + embed=Embed( + title=choice(NEGATIVE_REPLIES), + description="You've already changed your answer more than once!", + color=Colours.soft_red + ), + ephemeral=True + ) + + +class QuestionView(View): + """View for the questions.""" + + def __init__(self): + self.current_question = {} + + def create_current_question(self) -> Embed: + """Helper function to create the embed for the current question.""" + question_embed = Embed( + title=f"Question {self.current_question['number']}", + description=self.current_question["description"], + color=Colours.python_yellow + ) + for label, answer in zip(("A", "B", "C", "D"), self.current_question["answers"]): + question_embed.add_field(name=label, value=answer, inline=False) + + self.buttons = [QuestionButton(label) for label in ("A", "B", "C", "D")] + for button in self.buttons: + self.add_item(button) + return question_embed + + def end_question(self) -> tuple[dict, Embed]: + """Returns the dictionaries from the corresponding buttons for those who got it correct.""" + labels = ("A", "B", "C", "D") + label = labels[self.current_question["correct"].index(self.current_question["answers"])] + return_dict = {} + for button in self.buttons: + return_dict.update(button.answer(label)) + self.remove_item(button) + + answer_embed = Embed( + title=f"The correct answer for Question {self.current_question['number']} was", + color=Colours.grass_green + ) + answer_embed.add_field( + name=label, + value=self.current_question["correct"].index(self.current_question["answers"]), + inline=False + ) + + return return_dict, answer_embed + + +class Questions: + """An interface to use from the TriviaNight cog for questions.""" + + def __init__(self, scoreboard: Scoreboard): + self.scoreboard = scoreboard + self.view = QuestionView() + self.questions = [] + self._ptr = -1 + + def set_questions(self, questions: list) -> None: + """Setting `self.questions` dynamically via a function to set it.""" + self.questions = questions + + def next_question(self) -> None: + """Advances to the next question.""" + self._ptr += 1 + if self._ptr < len(self.questions): + self.questions[self._ptr]["visited"] = True + self.view.current_question = self.questions[self._ptr] + + def current_question(self) -> tuple[Embed, QuestionView]: + """Returns an embed entailing the current question as an embed with a view.""" + return self.view.create_current_question(), self.view + + def end_question(self) -> None: + """Terminates answering of the question and displays the correct answer.""" + scores, answer_embed = self.view.end_question() + for user, score in scores.items(): + self.scoreboard[f"points: {user}"] = score[1] + self.scoreboard[f"speed: {user}"] = score[2] + + return answer_embed diff --git a/bot/exts/events/trivianight/_scoreboard.py b/bot/exts/events/trivianight/_scoreboard.py new file mode 100644 index 00000000..96ff5ced --- /dev/null +++ b/bot/exts/events/trivianight/_scoreboard.py @@ -0,0 +1,81 @@ +from typing import Union + +import discord.ui +from discord import ButtonStyle, Embed, Interaction +from discord.ui import Button, View + +from bot.bot import Bot +from bot.constants import Colours + + +class ScoreboardView(View): + """View for the scoreboard.""" + + def __init__(self, bot: Bot): + self.bot = bot + self.points = {} + self.speed = {} + + def create_main_leaderboard(self) -> Embed: + """Helper function that iterates through `self.points` to generate the main leaderboard embed.""" + main_embed = Embed( + title="Winners of the Trivia Night", + description="See the leaderboard for who got the most points during the Trivia Night!", + color=Colours.python_blue, + ) + for user, points in list(self.points.items())[:10]: + main_embed.add_field(name=self.bot.get_user(user), value=f"`{points}` pts", inline=False) + + return main_embed + + def _create_speed_embed(self) -> Embed: + """Helper function that iterates through `self.speed` to generate a leaderboard embed.""" + speed_embed = Embed( + title="Average Time Taken to Answer a Question", + description="See the leaderboard for how fast each user took to answer a question correctly!", + color=Colours.python_blue, + ) + for user, time_taken in list(self.speed.items())[:10]: + speed_embed.add_field( + name=self.bot.get_user(user), + value=f"`{(time_taken[1] / time_taken[0]):.3f}s` (on average)", + inline=False + ) + + return speed_embed + + @discord.ui.button(label="Scoreboard for Speed", style=ButtonStyle.green) + async def speed_leaderboard(self, button: Button, interaction: Interaction) -> None: + """Send an ephemeral message with the speed leaderboard embed.""" + await interaction.response.send_message(embed=self._create_speed_embed(), ephemeral=True) + + +class Scoreboard: + """Class for the scoreboard for the trivianight event.""" + + def __init__(self, bot: Bot): + self.view = ScoreboardView(bot) + + def __setitem__(self, key: str, value: int): + if key.startswith("points: "): + key = key.removeprefix("points: ") + if key not in self.view.points.keys(): + self.view.points[key] = value + else: + self.view.points[key] += self.view.points[key] + elif key.startswith("speed: "): + key = key.removeprefix("speed: ") + if key not in self.view.speed.keys(): + self.view.speed[key] = [1, value] + else: + self.view.speed[key] = [self.view.speed[key][0] + 1, self.view.speed[key][1] + value] + + def __getitem__(self, item: str): + if item.startswith("points: "): + return self.view.points[item.removeprefix("points: ")] + elif item.startswith("speed: "): + return self.view.speed[item.removeprefix("speed: ")] + + def display(self) -> Union[Embed, View]: + """Returns the embed of the main leaderboard along with the ScoreboardView.""" + return self.view.create_main_leaderboard(), self.view diff --git a/bot/exts/events/trivianight/questions.py b/bot/exts/events/trivianight/questions.py deleted file mode 100644 index 2c1dbc81..00000000 --- a/bot/exts/events/trivianight/questions.py +++ /dev/null @@ -1,115 +0,0 @@ -from random import choice -from time import perf_counter - -from discord import ButtonStyle, Embed, Interaction -from discord.ui import Button, View - -from bot.constants import Colours, NEGATIVE_REPLIES -from .scoreboard import Scoreboard - - -class QuestionButton(Button): - """Button subclass for the options of the questions.""" - - def __init__(self, label: str): - self._time = perf_counter() - self.users_picked = {} - super().__init__(label=label, style=ButtonStyle.green) - - def answer(self, label: str) -> dict: - """Returns the dictionary of the users who picked the answer only if it was correct.""" - return self.users_picked if label == self.label else {} - - async def callback(self, interaction: Interaction) -> None: - """When a user interacts with the button, this will be called.""" - if interaction.user.id not in self.users_picked.keys(): - self.users_picked[interaction.user.id] = [self.label, 1, perf_counter() - self._time] - elif self.users_picked[interaction.user.id][1] < 3: - self.users_picked[interaction.user.id] = [ - self.label, self.users_picked[interaction.user.id][0] + 1, perf_counter() - self._time - ] - else: - await interaction.response.send_message( - embed=Embed( - title=choice(NEGATIVE_REPLIES), - description="You've already changed your answer more than once!", - color=Colours.soft_red - ), - ephemeral=True - ) - - -class QuestionView(View): - """View for the questions.""" - - def __init__(self): - self.current_question = {} - - def create_current_question(self) -> Embed: - """Helper function to create the embed for the current question.""" - question_embed = Embed( - title=f"Question {self.current_question['number']}", - description=self.current_question["description"], - color=Colours.python_yellow - ) - for label, answer in zip(("A", "B", "C", "D"), self.current_question["answers"]): - question_embed.add_field(name=label, value=answer, inline=False) - - self.buttons = [QuestionButton(label) for label in ("A", "B", "C", "D")] - for button in self.buttons: - self.add_item(button) - return question_embed - - def end_question(self) -> tuple[dict, Embed]: - """Returns the dictionaries from the corresponding buttons for those who got it correct.""" - labels = ("A", "B", "C", "D") - label = labels[self.current_question["correct"].index(self.current_question["answers"])] - return_dict = {} - for button in self.buttons: - return_dict.update(button.answer(label)) - self.remove_item(button) - - answer_embed = Embed( - title=f"The correct answer for Question {self.current_question['number']} was", - color=Colours.grass_green - ) - answer_embed.add_field( - name=label, - value=self.current_question["correct"].index(self.current_question["answers"]), - inline=False - ) - - return return_dict, answer_embed - - -class Questions: - """An interface to use from the TriviaNight cog for questions.""" - - def __init__(self, scoreboard: Scoreboard): - self.scoreboard = scoreboard - self.questions = [] - self._ptr = -1 - - def set_questions(self, questions: list) -> None: - """Setting `self.questions` dynamically via a function to set it.""" - self.questions = questions - - def next_question(self) -> None: - """Advances to the next question.""" - self._ptr += 1 - if self._ptr < len(self.questions): - self.questions[self._ptr]["visited"] = True - self.view.current_question = self.questions[self._ptr] - - def current_question(self) -> tuple[Embed, QuestionView]: - """Returns an embed entailing the current question as an embed with a view.""" - return self.view.create_current_question(), self.view - - def end_question(self) -> None: - """Terminates answering of the question and displays the correct answer.""" - scores, answer_embed = self.view.end_question() - for user, score in scores.items(): - self.scoreboard[f"points: {user}"] = score[1] - self.scoreboard[f"speed: {user}"] = score[2] - - return answer_embed diff --git a/bot/exts/events/trivianight/scoreboard.py b/bot/exts/events/trivianight/scoreboard.py deleted file mode 100644 index 27a45e30..00000000 --- a/bot/exts/events/trivianight/scoreboard.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Union - -import discord.ui -from discord import ButtonStyle, Embed, Interaction -from discord.ui import Button, View - -from bot.bot import Bot -from bot.constants import Colours - - -class ScoreboardView(View): - """View for the scoreboard.""" - - def __init__(self, bot: Bot): - self.bot = bot - self.points = {} - self.speed = {} - - def create_main_leaderboard(self) -> Embed: - """Helper function that iterates through `self.points` to generate the main leaderboard embed.""" - main_embed = Embed( - title="Winners of the Trivia Night", - description="See the leaderboard for who got the most points during the Trivia Night!", - color=Colours.python_blue, - ) - for user, points in list(self.points.items())[:10]: - main_embed.add_field(name=self.bot.get_user(user), value=f"`{points}` pts", inline=False) - - return main_embed - - def _create_speed_embed(self) -> Embed: - """Helper function that iterates through `self.speed` to generate a leaderboard embed.""" - speed_embed = Embed( - title="Average Time Taken to Answer a Question", - description="See the leaderboard for how fast each user took to answer a question correctly!", - color=Colours.python_blue, - ) - for user, time_taken in list(self.speed.items())[:10]: - speed_embed.add_field( - name=self.bot.get_user(user), - value=f"`{(time_taken[1] / time_taken[0]):.3f}s` (on average)", - inline=False - ) - - return speed_embed - - @discord.ui.button(label="Scoreboard for Speed", style=ButtonStyle.green) - async def speed_leaderboard(self, button: Button, interaction: Interaction) -> None: - """Send an ephemeral message with the speed leaderboard embed.""" - await interaction.response.send_message(embed=self._create_speed_embed(), ephemeral=True) - - -class Scoreboard: - """Class for the scoreboard for the trivianight event.""" - - def __init__(self): - self.view = ScoreboardView() - - def __setitem__(self, key: str, value: int): - if key.startswith("points: "): - key = key.removeprefix("points: ") - if key not in self.view.points.keys(): - self.view.points[key] = value - else: - self.view.points[key] += self.view.points[key] - elif key.startswith("speed: "): - key = key.removeprefix("speed: ") - if key not in self.view.speed.keys(): - self.view.speed[key] = [1, value] - else: - self.view.speed[key] = [self.view.speed[key][0] + 1, self.view.speed[key][1] + value] - - def __getitem__(self, item: str): - if item.startswith("points: "): - return self.view.points[item.removeprefix("points: ")] - elif item.startswith("speed: "): - return self.view.speed[item.removeprefix("speed: ")] - - def display(self) -> Union[Embed, View]: - """Returns the embed of the main leaderboard along with the ScoreboardView.""" - return self.view.create_main_leaderboard(), self.view diff --git a/bot/exts/events/trivianight/trivianight.py b/bot/exts/events/trivianight/trivianight.py index 29a9e3d1..66b2ae43 100644 --- a/bot/exts/events/trivianight/trivianight.py +++ b/bot/exts/events/trivianight/trivianight.py @@ -1,6 +1,13 @@ +from json import loads +from random import choice + +from discord import Embed from discord.ext import commands from bot.bot import Bot +from bot.constants import Colours, POSITIVE_REPLIES +from ._questions import Questions +from ._scoreboard import Scoreboard class TriviaNight(commands.Cog): @@ -8,6 +15,26 @@ class TriviaNight(commands.Cog): def __init__(self, bot: Bot): self.bot = bot + self.scoreboard = Scoreboard(self.bot) + self.questions = Questions(self.scoreboard) + + @commands.group() + async def trivianight(self, ctx: commands.Context) -> None: + """No-op subcommand group for organizing different commands.""" + return + + @trivianight.command() + async def load(self, ctx: commands.Context) -> None: + """Load the JSON file provided into the questions.""" + json_text = (await ctx.message.attachments[0].read()).decode("utf8") + serialized_json = loads(json_text) + self.questions.set_questions(serialized_json) + success_embed = Embed( + title=choice(POSITIVE_REPLIES), + description="The JSON was loaded successfully!", + color=Colours.soft_green + ) + await ctx.send(embed=success_embed) def setup(bot: Bot) -> None: -- cgit v1.2.3