aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons/evergreen/trivia_quiz.py
diff options
context:
space:
mode:
authorGravatar kwzrd <[email protected]>2020-03-28 15:13:26 +0100
committerGravatar kwzrd <[email protected]>2020-03-28 15:13:26 +0100
commit294915013680c9ad205d6c9fa0c7fa2b79cc1919 (patch)
tree117612b7f890e35010fe53575879edc1c41362e0 /bot/seasons/evergreen/trivia_quiz.py
parentDeseasonify: 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.py303
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")