aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Objectivitix <[email protected]>2021-05-07 10:23:46 -0400
committerGravatar GitHub <[email protected]>2021-05-07 10:23:46 -0400
commitfc800513354b4a1ef3338b2a0fb8481d5b664e56 (patch)
tree9bbb00c7adb2e10bad73691d70eb46cd350dffed
parentMerge pull request #1 from Objectivitix/Objectivitix-patch-1 (diff)
parentfix NameError, add some forgotten imports (diff)
Merge pull request #2 from Objectivitix/Objectivitix-patch-1
Implement the math and science questions, added a dynamic_id checker
-rw-r--r--bot/exts/evergreen/trivia_quiz.py244
1 files changed, 184 insertions, 60 deletions
diff --git a/bot/exts/evergreen/trivia_quiz.py b/bot/exts/evergreen/trivia_quiz.py
index fe692c2a..2158ac9c 100644
--- a/bot/exts/evergreen/trivia_quiz.py
+++ b/bot/exts/evergreen/trivia_quiz.py
@@ -9,11 +9,12 @@ from discord.ext import commands
from fuzzywuzzy import fuzz
from bot.constants import Roles
+from bot.constants import NEGATIVE_REPLIES
+from typing import Optional, List, Tuple
logger = logging.getLogger(__name__)
-
WRONG_ANS_RESPONSE = [
"No one answered correctly!",
"Better luck next time"
@@ -25,34 +26,58 @@ class TriviaQuiz(commands.Cog):
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.questions = self.load_questions()
+ self.question_limit = 7
+
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."
+ "general": "Test your general knowledge.",
+ "retro": "Questions related to retro gaming.",
+ "math": "General questions about mathematics ranging from grade 8 to grade 12.",
+ "science": "Put your understanding of science to the test!",
}
+ @commands.command()
+ async def test(self, ctx: commands.Context) -> None:
+ embed = discord.Embed(
+ title="yeet",
+ description="yeet",
+ )
+
+ print("yeet")
+ embed.set_image(url="")
+
+ await ctx.send(embed=embed)
+
@staticmethod
def load_questions() -> dict:
"""Load the questions from the JSON file."""
p = Path("bot", "resources", "evergreen", "trivia_quiz.json")
+
with p.open(encoding="utf8") 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:
+ async def quiz_game(self, ctx: commands.Context, category: Optional[str], questions: Optional[int]) -> None:
"""
Start a quiz!
Questions for the quiz can be selected from the following categories:
- - general : Test your general knowledge. (default)
+ - general: Test your general knowledge. (default)
+ - retro: Questions related to retro gaming.
+ - math: General questions about mathematics ranging from grade 8 to grade 12.
+ - science: Put your understanding of science to the test!
+
(More to come!)
"""
+
if ctx.channel.id not in self.game_status:
self.game_status[ctx.channel.id] = False
@@ -61,11 +86,13 @@ class TriviaQuiz(commands.Cog):
# Stop game if running.
if self.game_status[ctx.channel.id] is True:
- return await ctx.send(
- f"Game is already running..."
+ await ctx.send(
+ "Game is already running..."
f"do `{self.bot.command_prefix}quiz stop`"
)
+ return
+
# Send embed showing available categories if inputted category is invalid.
if category is None:
category = random.choice(list(self.categories))
@@ -76,6 +103,30 @@ class TriviaQuiz(commands.Cog):
await ctx.send(embed=embed)
return
+ topic = self.questions[category]
+ topic_length = len(topic)
+
+ if questions:
+ if questions > topic_length:
+ await ctx.send(
+ embed=self.make_error_embed(
+ f"This category only has {topic_length} questions. "
+ "Please input a lower value!"
+ )
+ )
+ return
+
+ elif questions < 1:
+ await ctx.send(
+ embed=self.make_error_embed(
+ "Please do at least one question."
+ )
+ )
+ return
+
+ else:
+ self.question_limit = questions - 1
+
# Start game if not running.
if self.game_status[ctx.channel.id] is False:
self.game_owners[ctx.channel.id] = ctx.author
@@ -85,16 +136,17 @@ class TriviaQuiz(commands.Cog):
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
+ answers = 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])
+ 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]
@@ -111,23 +163,39 @@ class TriviaQuiz(commands.Cog):
done_question.append(question_dict["id"])
break
- q = question_dict["question"]
- answer = question_dict["answer"]
+ if "dynamic_id" not in question_dict:
+ q = question_dict["question"]
+ answers = question_dict["answer"].split(", ")
+ else:
+ q, answers = DYNAMIC_QUESTIONS_FORMAT_FUNCS[
+ question_dict["dynamic_id"]
+ ](
+ question_dict["question"],
+ question_dict["answer"],
+ )
+
+ answers = [answers]
+
+ embed = discord.Embed(
+ colour=discord.Colour.gold(),
+ title=f"Question #{len(done_question)}",
+ description=q,
+ )
+
+ if "img_url" in question_dict:
+ embed.set_image(url=question_dict["img_url"])
- 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:
- return (
- m.channel == ctx.channel
- and fuzz.ratio(answer.lower(), m.content.lower()) > 85
+ return m.channel == ctx.channel and any(
+ fuzz.ratio(answer.lower(), m.content.lower()) > VARIATION_TOLERANCE
+ for answer in answers
)
try:
- msg = await self.bot.wait_for('message', check=check, timeout=10)
+ 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:
@@ -136,9 +204,14 @@ class TriviaQuiz(commands.Cog):
# 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]}")
+
+ await ctx.send(
+ f"**Hint #{hint_no}\n**"
+ f"{hints[hint_no - 1]}"
+ )
else:
await ctx.send(f"{30 - hint_no * 10}s left!")
@@ -151,19 +224,22 @@ class TriviaQuiz(commands.Cog):
response = random.choice(WRONG_ANS_RESPONSE)
await ctx.send(response)
- await self.send_answer(ctx.channel, question_dict)
+ await self.send_answer(ctx.channel, answers, 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 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
+ 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:
@@ -177,24 +253,49 @@ class TriviaQuiz(commands.Cog):
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 ctx.send(
+ f"{msg.author.mention} got the correct answer :tada: {points} points!"
+ )
+
+ await self.send_answer(ctx.channel, answers, 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:
+ def make_start_embed(self, 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 = discord.Embed(
+ colour=discord.Colour.red(),
+ title="Quiz game starting!",
+ description=(
+ f"Each game consists of {self.question_limit + 1} questions.\n"
+ "**Rules: **No cheating and have fun!\n"
+ f"**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"
+ text=(
+ "Points for each question reduces by 25 after 10s or after a hint."
+ "Total time is 30s per question"
+ )
)
+
return start_embed
+ @staticmethod
+ def make_error_embed(desc: str) -> discord.Embed:
+ """Generate an error embed with the given description."""
+ error_embed = discord.Embed(
+ colour=discord.Colour.red(),
+ title=random.choice(NEGATIVE_REPLIES),
+ description=desc,
+ )
+
+ return error_embed
+
@quiz_game.command(name="stop")
async def stop_quiz(self, ctx: commands.Context) -> None:
"""
@@ -204,18 +305,23 @@ class TriviaQuiz(commands.Cog):
"""
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)
+ 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])
+ 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:!")
+ await ctx.send(
+ f"{ctx.author.mention}, you are not authorised to stop this game :ghost:!"
+ )
else:
await ctx.send("No quiz running.")
@@ -231,13 +337,15 @@ class TriviaQuiz(commands.Cog):
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 = ""
+ embed = discord.Embed(
+ colour=discord.Colour.blue(),
+ title="Score Board",
+ 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"
+ embed.description += f"{item[0]}: {item[1]}\n"
await channel.send(embed=embed)
@@ -250,7 +358,6 @@ class TriviaQuiz(commands.Cog):
# 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()
@@ -261,36 +368,53 @@ class TriviaQuiz(commands.Cog):
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!"
+ f"You 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 = ""
+ embed = discord.Embed(
+ colour=discord.Colour.blue(),
+ title="The available question categories are:",
+ description="",
+ )
+
+ embed.set_footer(
+ text="If a category is not chosen, a random one will be selected."
+ )
for cat, description in self.categories.items():
- embed.description += f"**- {cat.capitalize()}**\n{description.capitalize()}\n"
+ embed.description += (
+ f"**- {cat.capitalize()}**\n"
+ f"{description.capitalize()}\n"
+ )
return embed
@staticmethod
- async def send_answer(channel: discord.TextChannel, question_dict: dict) -> None:
+ async def send_answer(
+ channel: discord.TextChannel,
+ answers: List[str],
+ 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" in question_dict:
+ info = question_dict["info"]
+ else:
+ info = ""
+
+ embed = discord.Embed(
+ color=discord.Colour.red(),
+ title=f"The correct answer is/are **{', '.join(answers)}**\n",
+ description="",
+ )
if info != "":
embed.description += f"**Information**\n{info}\n\n"