diff options
Diffstat (limited to 'bot/seasons/evergreen/TriviaQuiz.py')
| -rw-r--r-- | bot/seasons/evergreen/TriviaQuiz.py | 263 | 
1 files changed, 263 insertions, 0 deletions
| diff --git a/bot/seasons/evergreen/TriviaQuiz.py b/bot/seasons/evergreen/TriviaQuiz.py new file mode 100644 index 00000000..38f19922 --- /dev/null +++ b/bot/seasons/evergreen/TriviaQuiz.py @@ -0,0 +1,263 @@ +import asyncio +import logging +import random +from dataclasses import dataclass +from json import load +from pathlib import Path + +import discord +from discord.ext import commands +from fuzzywuzzy import fuzz + +from bot.constants import Roles + + +logger = logging.getLogger(__name__) + + +annoyed_expressions = ["-_-", "-.-"] + +wrong_ans_responses = [ +    "No one gave the correct answer", +    "Losers", +    "You guys really need to learn" +] + + +@dataclass +class GameData: +    """A dataclass for game data.""" + +    owner: int +    players = [] +    points = [] +    done_questions = [] +    category: str +    question: dict = None +    hints = 0 +    unanswered_questions = 0 + + +class TriviaQuiz(commands.Cog): +    """A cog for all quiz commands.""" + +    def __init__(self, bot): +        self.bot = bot +        self.questions = self.load_questions() +        self.games = {}  # channel as key and value as instinct of dataclass GameData +        self.categories = { +            "retro": "Questions related to retro gaming." +        } +        self.inactivity_limit = 3  # Number of questions unanswered in a row after which quiz stops. + +    @staticmethod +    def load_questions(): +        """Load the questions from  json file.""" +        p = Path("bot", "resources", "evergreen", "trivia_quiz.json ") +        with p.open() as json_data: +            questions = load(json_data) +            return questions + +    @commands.group(name="tquiz", invoke_without_command=False) +    async def tquiz(self, ctx): +        """Trivia Quiz game for fun!""" +        await ctx.send_help("tquiz") + +    @tquiz.command(name="start") +    async def start(self, ctx, category=None): +        """Start a quiz! + +        Questions for the quiz can be selected from the following categories: +        - Retro : questions related to retro gaming. +        """ +        await ctx.send("Quiz triggered! Gonna start in a couple of seconds...") +        await asyncio.sleep(1) + +        # Checking if there is already a game running in that channel. +        if ctx.channel.id in list(self.games.keys()): +            return await ctx.send("Game already running in this channel!") + +        if category is None: +            category = random.choice(list(self.categories.keys())) + +        else: +            category = category.lower() +            if category not in self.categories.keys(): +                embed = self.category_embed() +                return await ctx.send(f"Category {category} does not exist!", embed=embed) + +        self.games[ctx.channel.id] = GameData( +            owner=ctx.author.id, +            category=category +        ) + +        await self.send_question(ctx.channel) + +    def category_embed(self): +        """A function which returns an embed showing all avilable categories""" +        embed = discord.Embed(colour=discord.Colour.blue()) +        embed.title = "The available question categories are:" +        embed.description = "" +        for cat, description in self.categories.items(): +            embed.description += f"**- {cat.capitalize()}**\n{description.capitalize()}\n" +        embed.set_footer(text="If not category is chosen, then a random one will be selected.") +        return embed + +    async def send_question(self, channel): +        """This function is to be called whenever a question needs to be sent.""" +        await asyncio.sleep(2) +        game = self.games[channel.id] +        if game.unanswered_questions == self.inactivity_limit: +            del self.games[channel.id] +            return await channel.send("Game stopped due to inactivity.") + +        category = game.category +        category_dict = self.questions[category] +        question_dict = random.choice(category_dict) + +        same_question = True + +        # Making sure a question is not repeated. +        while same_question is True: +            question_dict = random.choice(category_dict) +            if question_dict["id"] not in game.done_questions: +                same_question = False +            else: +                pass + +        # Initial points for a question. +        question_dict["points"] = 100 +        game.question = question_dict +        game.hints = 0 + +        embed = discord.Embed(colour=discord.Colour.dark_gold()) + +        question = question_dict["question"] +        question_id = question_dict["id"] + +        game.done_questions.append(question_id) +        question_number = len(game.done_questions) + +        embed.title = f"#{question_number} Question" +        embed.description = question +        embed.set_footer(text="A hint will be provided after every 10s if no one gives the right answer.Max of 2 hints") + +        await channel.send(embed=embed) +        await self.send_hint(channel, question_dict) + +    @commands.Cog.listener() +    async def on_message(self, message): +        """A function triggered when a message is sent.""" +        channel = message.channel +        if channel.id not in list(self.games.keys()): +            return +        if message.author.bot: +            return + +        game = self.games[message.channel.id] +        question_data = game.question +        answer = question_data["answer"].lower() +        user_answer = message.content.lower() +        ratio = fuzz.ratio(answer, user_answer) +        if ratio > 84: +            points = question_data["points"] - game.hints*25 +            if message.author in game.players: +                author_index = game.players.index(message.author) +                game.points[author_index] = game.points[author_index] + points +            else: +                game.players.append(message.author) +                game.points.append(points) + +            await channel.send(f"{message.author.mention} got it right! Good job :tada:" +                               f"You got {points} points.") +            await self.score_embed(channel) +            await self.send_question(channel) +        elif ratio in range(75, 84): +            await channel.send(f"Your close to the answer {message.author.mention}") + +    async def send_hint(self, channel, question_dict): +        """Function to be called whenever a hint has to be sent.""" +        await asyncio.sleep(10) +        try: +            game = self.games[channel.id] +        except KeyError: +            return + +        # Checking if the question is the same after 10 seconds. +        if question_dict["id"] == game.question["id"]: + +            # If the max number of hints is already reached, then send the answer. +            if 2 - game.hints == 0: +                return await self.send_answer(channel) + +            hint_list = question_dict["hints"] +            hint_index = game.hints +            hint = hint_list[hint_index] +            game.hints += 1 +            message = f"**Hint {game.hints}**: {hint}\n*Number of hints remaining: {2-game.hints}*" +            await channel.send(message) +            await self.send_hint(channel, question_dict) +        else: +            pass + +    async def send_answer(self, channel): +        """A function to send the answer in the channel if no user have given the correct answer even after 2 hints.""" +        game = self.games[channel.id] +        answer = game.question["answer"] +        response = random.choice(wrong_ans_responses) +        expression = random.choice(annoyed_expressions) +        await channel.send(f"{response} {expression}, the correct answer is **{answer}**.") +        self.games[channel.id].unanswered_questions += 1 +        await self.score_embed(channel) +        await self.send_question(channel) + +    @tquiz.command(name="score") +    async def send_score(self, ctx): +        """Show scoreboard of the game running in this channel.""" +        await self.score_embed(ctx.channel) + +    async def score_embed(self, channel): +        """Show score of each player in the quiz.""" +        if channel.id not in list(self.games.keys()): +            return await channel.send("There are no games running in this channel!") +        game = self.games[channel.id] +        players = game.players +        if len(players) == 0: +            return +        points = game.points +        embed = discord.Embed(color=discord.Colour.dark_gold()) +        embed.title = "Scoreboard" +        embed.description = "" +        for player, score in zip(players, points): +            embed.description = f"{player} - {score}\n" +        await channel.send(embed=embed) + +    @tquiz.command(name="stop") +    async def stop_quiz(self, ctx): +        """Stop the quiz.""" +        if ctx.channel.id not in list(self.games.keys()): +            return await ctx.send("No game running, nothing to stop here -.-") +        game = self.games[ctx.channel.id] +        owner = game.owner +        mods = Roles.moderator +        if ctx.author.id == owner or mods in [role.id for role in ctx.author.roles]: +            await ctx.send("Game is not running anymore!") +            await self.score_embed(ctx.channel) +            if game.players: +                highest_points = max(game.points) +                author_index = game.points.index(highest_points) +                winner = game.players[author_index] +                await ctx.send( +                    f"Congratz {winner.mention} :tada: " +                    f"You have won this quiz game with a grand total of {highest_points} points!!" +                ) +            await asyncio.sleep(2) +            del self.games[ctx.channel.id] +        else: +            await ctx.send("You are not authorised to close this game!") + + +def setup(bot): +    """Loading the cog.""" +    bot.add_cog(TriviaQuiz(bot)) +    logger.debug("TriviaQuiz cog loaded!") | 
