aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons
diff options
context:
space:
mode:
Diffstat (limited to 'bot/seasons')
-rw-r--r--bot/seasons/evergreen/trivia_quiz.py184
1 files changed, 118 insertions, 66 deletions
diff --git a/bot/seasons/evergreen/trivia_quiz.py b/bot/seasons/evergreen/trivia_quiz.py
index 798523e6..bf304644 100644
--- a/bot/seasons/evergreen/trivia_quiz.py
+++ b/bot/seasons/evergreen/trivia_quiz.py
@@ -6,6 +6,8 @@ from pathlib import Path
import discord
from discord.ext import commands
+from discord.ext.commands import cooldown
+from discord.ext.commands.cooldowns import BucketType
from fuzzywuzzy import fuzz
from bot.constants import Roles
@@ -14,10 +16,8 @@ from bot.constants import Roles
logger = logging.getLogger(__name__)
-ANNOYED_EXPRESSIONS = ["-_-", "-.-"]
-
WRONG_ANS_RESPONSE = [
- "No one gave the correct answer",
+ "No answered correctly!",
"Better luck next time"
]
@@ -28,10 +28,11 @@ class TriviaQuiz(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot
self.questions = self.load_questions()
- self.game_status = {}
- self.game_owners = {}
+ 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_dict = {}
+ 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."
@@ -39,77 +40,72 @@ class TriviaQuiz(commands.Cog):
@staticmethod
def load_questions() -> dict:
- """Load the questions from json file."""
+ """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.command(name="quiz", aliases=["trivia"])
+ @cooldown(1, 20, BucketType.channel)
+ @commands.group(name="quiz", aliases=["trivia"], invoke_without_command=True)
async def quiz_game(self, ctx: commands.Context, category: str = "general") -> None:
"""
Start/Stop a quiz!
- arguments:
- option:
- - start : to start a quiz in a channel
- - stop : stop the quiz running in that channel.
+ If the quiz game is running, then the owner or a mod can stop it by using this command
+ without providing any arguments or vice versa.
Questions for the quiz can be selected from the following categories:
- general : Test your general knowledge. (default)
- (we wil be adding more later)
+ (More to come!)
"""
- category = category.lower()
-
if ctx.channel.id not in self.game_status:
self.game_status[ctx.channel.id] = False
- self.player_dict[ctx.channel.id] = {}
- if not self.game_status[ctx.channel.id]:
- self.game_owners[ctx.channel.id] = ctx.author
- self.game_status[ctx.channel.id] = True
- 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.set_footer(
- text="Points for a question reduces by 25 after 10s or after a hint. Total time is 30s per question"
- )
- await ctx.send(embed=start_embed) # send an embed with the rules
- await asyncio.sleep(1)
+ if ctx.channel.id not in self.game_player_scores:
+ self.game_player_scores[ctx.channel.id] = {}
- else:
- if (
- ctx.author == self.game_owners[ctx.channel.id]
- or Roles.moderator in [role.id for role in ctx.author.roles]
- ):
- await ctx.send("Quiz is no longer running.")
- await self.declare_winner(ctx.channel, self.player_dict[ctx.channel.id])
- self.game_status[ctx.channel.id] = False
- del self.game_owners[ctx.channel.id]
- else:
- await ctx.send(f"{ctx.author.mention}, you are not authorised to stop this game :ghost: !")
+ # Stop game if running.
+ if self.game_status[ctx.channel.id] is True:
+ await self.stop_quiz(ctx.author, ctx.channel)
+ return
+ category = category.lower()
+ # Send embed showing available categories if inputted category is invalid.
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()
+
+ await ctx.send(embed=start_embed) # send an embed with the rules
+ await asyncio.sleep(1)
+
topic = self.questions[category]
- unanswered = 0
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 ends here.")
- await self.declare_winner(ctx.channel, self.player_dict[ctx.channel.id])
- break
- if unanswered > 3:
- await ctx.send("Game stopped due to inactivity.")
- await self.declare_winner(ctx.channel, self.player_dict[ctx.channel.id])
+ 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:
@@ -121,56 +117,112 @@ class TriviaQuiz(commands.Cog):
embed = discord.Embed(colour=discord.Colour.gold())
embed.title = f"Question #{len(done_question)}"
embed.description = q
- await ctx.send(embed=embed)
+ 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"Cmon guys, {30-hint_no*10}s left!")
+ 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)
- expression = random.choice(ANNOYED_EXPRESSIONS)
- await ctx.send(f"{response} {expression}")
+ await ctx.send(response)
await self.send_answer(ctx.channel, question_dict)
await asyncio.sleep(1)
- hint_no = 0
- unanswered += 1
- await self.send_score(ctx.channel, self.player_dict[ctx.channel.id])
+
+ 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.player_dict[ctx.channel.id]:
- self.player_dict[ctx.channel.id][msg.author] += points
+ 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_dict[ctx.channel.id][msg.author] = points
+ self.player_scores[msg.author] = points
+
hint_no = 0
- unanswered = 0
- await ctx.send(f"{msg.author.mention} got the correct answer :tada: {points} points for ya.")
+
+ 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.player_dict[ctx.channel.id])
+ await self.send_score(ctx.channel, self.game_player_scores[ctx.channel.id])
await asyncio.sleep(2)
@staticmethod
+ def make_start_embed() -> 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.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
+
+ async def stop_quiz(self, author: discord.Member, channel: discord.TextChannel) -> None:
+ """Stop the quiz."""
+ # Check if the author is the game starter or a moderator.
+ if (
+ author == self.game_owners[channel.id]
+ or any(Roles.moderator == role.id for role in author.roles)
+ ):
+ await channel.send("Quiz stopped.")
+ await self.declare_winner(channel, self.game_player_scores[channel.id])
+ self.game_status[channel.id] = False
+ del self.game_owners[channel.id]
+ self.game_player_scores[channel.id] = {}
+ else:
+ await channel.send(f"{author.mention}, you are not authorised to stop this game :ghost:!")
+
+ @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."""
embed = discord.Embed(colour=discord.Colour.blue())
embed.title = "Score Board"
embed.description = ""
- for k, v in player_data.items():
- embed.description += f"{k} : {v}\n"
+ if len(player_data) == 0:
+ await channel.send("No one has made it onto the leaderboard yet.")
+ return
+ 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
@@ -192,15 +244,15 @@ class TriviaQuiz(commands.Cog):
winners_mention = None
for winner in winners:
winners_mention += f"{winner.mention} "
-
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"Congratz {winners_mention} :tada: "
- f"{word} have won this quiz game with a grand total of {highest_points} points!!"
+ f"Congratulations {winners_mention} :tada: "
+ f"{word} have won this quiz game with a grand total of {highest_points} points!"
)
@property
@@ -224,11 +276,11 @@ class TriviaQuiz(commands.Cog):
embed.description = ""
if info != "":
embed.description += f"**Information**\n{info}\n\n"
- embed.description += "Lets move to the next question.\nRemaining questions: "
+ embed.description += "Let's move to the next question.\nRemaining questions: "
await channel.send(embed=embed)
def setup(bot: commands.Bot) -> None:
- """Loading the cog."""
+ """Load the cog."""
bot.add_cog(TriviaQuiz(bot))
logger.debug("TriviaQuiz cog loaded")