diff options
| author | 2020-03-28 15:13:26 +0100 | |
|---|---|---|
| committer | 2020-03-28 15:13:26 +0100 | |
| commit | 294915013680c9ad205d6c9fa0c7fa2b79cc1919 (patch) | |
| tree | 117612b7f890e35010fe53575879edc1c41362e0 /bot/seasons/evergreen/trivia_quiz.py | |
| parent | Deseasonify: info log on help cog load (diff) | |
Deseasonify: rename `seasons` pkg to `exts`
It is believed that this is now a more logical name for the package,
as extensions no longer bind to seasons.
Internally, packages are still grouped into seasonal sub-packages.
There are quite a few, and it makes sense to group them by a common
theme that inspired their functionality.
Diffstat (limited to 'bot/seasons/evergreen/trivia_quiz.py')
| -rw-r--r-- | bot/seasons/evergreen/trivia_quiz.py | 303 |
1 files changed, 0 insertions, 303 deletions
diff --git a/bot/seasons/evergreen/trivia_quiz.py b/bot/seasons/evergreen/trivia_quiz.py deleted file mode 100644 index 99b64497..00000000 --- a/bot/seasons/evergreen/trivia_quiz.py +++ /dev/null @@ -1,303 +0,0 @@ -import asyncio -import json -import logging -import random -from pathlib import Path - -import discord -from discord.ext import commands -from fuzzywuzzy import fuzz - -from bot.constants import Roles - - -logger = logging.getLogger(__name__) - - -WRONG_ANS_RESPONSE = [ - "No one answered correctly!", - "Better luck next time" -] - - -class TriviaQuiz(commands.Cog): - """A cog for all quiz commands.""" - - def __init__(self, bot: commands.Bot) -> None: - self.bot = bot - self.questions = self.load_questions() - self.game_status = {} # A variable to store the game status: either running or not running. - self.game_owners = {} # A variable to store the person's ID who started the quiz game in a channel. - self.question_limit = 4 - self.player_scores = {} # A variable to store all player's scores for a bot session. - self.game_player_scores = {} # A variable to store temporary game player's scores. - self.categories = { - "general": "Test your general knowledge" - # "retro": "Questions related to retro gaming." - } - - @staticmethod - def load_questions() -> dict: - """Load the questions from the JSON file.""" - p = Path("bot", "resources", "evergreen", "trivia_quiz.json") - with p.open() as json_data: - questions = json.load(json_data) - return questions - - @commands.group(name="quiz", aliases=["trivia"], invoke_without_command=True) - async def quiz_game(self, ctx: commands.Context, category: str = None) -> None: - """ - Start a quiz! - - Questions for the quiz can be selected from the following categories: - - general : Test your general knowledge. (default) - (More to come!) - """ - if ctx.channel.id not in self.game_status: - self.game_status[ctx.channel.id] = False - - if ctx.channel.id not in self.game_player_scores: - self.game_player_scores[ctx.channel.id] = {} - - # Stop game if running. - if self.game_status[ctx.channel.id] is True: - return await ctx.send( - f"Game is already running..." - f"do `{self.bot.command_prefix}quiz stop`" - ) - - # Send embed showing available categories if inputted category is invalid. - if category is None: - category = random.choice(list(self.categories)) - - category = category.lower() - if category not in self.categories: - embed = self.category_embed() - await ctx.send(embed=embed) - return - - # Start game if not running. - if self.game_status[ctx.channel.id] is False: - self.game_owners[ctx.channel.id] = ctx.author - self.game_status[ctx.channel.id] = True - start_embed = self.make_start_embed(category) - - await ctx.send(embed=start_embed) # send an embed with the rules - await asyncio.sleep(1) - - topic = self.questions[category] - - done_question = [] - hint_no = 0 - answer = None - while self.game_status[ctx.channel.id]: - # Exit quiz if number of questions for a round are already sent. - if len(done_question) > self.question_limit and hint_no == 0: - await ctx.send("The round has ended.") - await self.declare_winner(ctx.channel, self.game_player_scores[ctx.channel.id]) - - self.game_status[ctx.channel.id] = False - del self.game_owners[ctx.channel.id] - self.game_player_scores[ctx.channel.id] = {} - - break - - # If no hint has been sent or any time alert. Basically if hint_no = 0 means it is a new question. - if hint_no == 0: - # Select a random question which has not been used yet. - while True: - question_dict = random.choice(topic) - if question_dict["id"] not in done_question: - done_question.append(question_dict["id"]) - break - - q = question_dict["question"] - answer = question_dict["answer"] - - embed = discord.Embed(colour=discord.Colour.gold()) - embed.title = f"Question #{len(done_question)}" - embed.description = q - await ctx.send(embed=embed) # Send question embed. - - # A function to check whether user input is the correct answer(close to the right answer) - def check(m: discord.Message) -> bool: - ratio = fuzz.ratio(answer.lower(), m.content.lower()) - return ratio > 85 and m.channel == ctx.channel - - try: - msg = await self.bot.wait_for('message', check=check, timeout=10) - except asyncio.TimeoutError: - # In case of TimeoutError and the game has been stopped, then do nothing. - if self.game_status[ctx.channel.id] is False: - break - - # if number of hints sent or time alerts sent is less than 2, then send one. - if hint_no < 2: - hint_no += 1 - if "hints" in question_dict: - hints = question_dict["hints"] - await ctx.send(f"**Hint #{hint_no+1}\n**{hints[hint_no]}") - else: - await ctx.send(f"{30 - hint_no * 10}s left!") - - # Once hint or time alerts has been sent 2 times, the hint_no value will be 3 - # If hint_no > 2, then it means that all hints/time alerts have been sent. - # Also means that the answer is not yet given and the bot sends the answer and the next question. - else: - if self.game_status[ctx.channel.id] is False: - break - - response = random.choice(WRONG_ANS_RESPONSE) - await ctx.send(response) - await self.send_answer(ctx.channel, question_dict) - await asyncio.sleep(1) - - hint_no = 0 # init hint_no = 0 so that 2 hints/time alerts can be sent for the new question. - - await self.send_score(ctx.channel, self.game_player_scores[ctx.channel.id]) - await asyncio.sleep(2) - else: - if self.game_status[ctx.channel.id] is False: - break - - # Reduce points by 25 for every hint/time alert that has been sent. - points = 100 - 25*hint_no - if msg.author in self.game_player_scores[ctx.channel.id]: - self.game_player_scores[ctx.channel.id][msg.author] += points - else: - self.game_player_scores[ctx.channel.id][msg.author] = points - - # Also updating the overall scoreboard. - if msg.author in self.player_scores: - self.player_scores[msg.author] += points - else: - self.player_scores[msg.author] = points - - hint_no = 0 - - await ctx.send(f"{msg.author.mention} got the correct answer :tada: {points} points!") - await self.send_answer(ctx.channel, question_dict) - await self.send_score(ctx.channel, self.game_player_scores[ctx.channel.id]) - await asyncio.sleep(2) - - @staticmethod - def make_start_embed(category: str) -> discord.Embed: - """Generate a starting/introduction embed for the quiz.""" - start_embed = discord.Embed(colour=discord.Colour.red()) - start_embed.title = "Quiz game Starting!!" - start_embed.description = "Each game consists of 5 questions.\n" - start_embed.description += "**Rules :**\nNo cheating and have fun!" - start_embed.description += f"\n **Category** : {category}" - start_embed.set_footer( - text="Points for each question reduces by 25 after 10s or after a hint. Total time is 30s per question" - ) - return start_embed - - @quiz_game.command(name="stop") - async def stop_quiz(self, ctx: commands.Context) -> None: - """ - Stop a quiz game if its running in the channel. - - Note: Only mods or the owner of the quiz can stop it. - """ - if self.game_status[ctx.channel.id] is True: - # Check if the author is the game starter or a moderator. - if ( - ctx.author == self.game_owners[ctx.channel.id] - or any(Roles.moderator == role.id for role in ctx.author.roles) - ): - await ctx.send("Quiz stopped.") - await self.declare_winner(ctx.channel, self.game_player_scores[ctx.channel.id]) - - self.game_status[ctx.channel.id] = False - del self.game_owners[ctx.channel.id] - self.game_player_scores[ctx.channel.id] = {} - else: - await ctx.send(f"{ctx.author.mention}, you are not authorised to stop this game :ghost:!") - else: - await ctx.send("No quiz running.") - - @quiz_game.command(name="leaderboard") - async def leaderboard(self, ctx: commands.Context) -> None: - """View everyone's score for this bot session.""" - await self.send_score(ctx.channel, self.player_scores) - - @staticmethod - async def send_score(channel: discord.TextChannel, player_data: dict) -> None: - """A function which sends the score.""" - if len(player_data) == 0: - await channel.send("No one has made it onto the leaderboard yet.") - return - - embed = discord.Embed(colour=discord.Colour.blue()) - embed.title = "Score Board" - embed.description = "" - - sorted_dict = sorted(player_data.items(), key=lambda a: a[1], reverse=True) - for item in sorted_dict: - embed.description += f"{item[0]} : {item[1]}\n" - - await channel.send(embed=embed) - - @staticmethod - async def declare_winner(channel: discord.TextChannel, player_data: dict) -> None: - """Announce the winner of the quiz in the game channel.""" - if player_data: - highest_points = max(list(player_data.values())) - no_of_winners = list(player_data.values()).count(highest_points) - - # Check if more than 1 player has highest points. - if no_of_winners > 1: - word = "You guys" - winners = [] - points_copy = list(player_data.values()).copy() - - for _ in range(no_of_winners): - index = points_copy.index(highest_points) - winners.append(list(player_data.keys())[index]) - points_copy[index] = 0 - - winners_mention = " ".join(winner.mention for winner in winners) - else: - word = "You" - author_index = list(player_data.values()).index(highest_points) - winner = list(player_data.keys())[author_index] - winners_mention = winner.mention - - await channel.send( - f"Congratulations {winners_mention} :tada: " - f"{word} have won this quiz game with a grand total of {highest_points} points!" - ) - - def category_embed(self) -> discord.Embed: - """Build an embed showing all available trivia categories.""" - embed = discord.Embed(colour=discord.Colour.blue()) - embed.title = "The available question categories are:" - embed.set_footer(text="If a category is not chosen, a random one will be selected.") - embed.description = "" - - for cat, description in self.categories.items(): - embed.description += f"**- {cat.capitalize()}**\n{description.capitalize()}\n" - - return embed - - @staticmethod - async def send_answer(channel: discord.TextChannel, question_dict: dict) -> None: - """Send the correct answer of a question to the game channel.""" - answer = question_dict["answer"] - info = question_dict["info"] - embed = discord.Embed(color=discord.Colour.red()) - embed.title = f"The correct answer is **{answer}**\n" - embed.description = "" - - if info != "": - embed.description += f"**Information**\n{info}\n\n" - - embed.description += "Let's move to the next question.\nRemaining questions: " - await channel.send(embed=embed) - - -def setup(bot: commands.Bot) -> None: - """Load the cog.""" - bot.add_cog(TriviaQuiz(bot)) - logger.debug("TriviaQuiz cog loaded") |