diff options
Diffstat (limited to 'bot/seasons')
| -rw-r--r-- | bot/seasons/evergreen/trivia_quiz.py | 184 | 
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")  |