aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons/evergreen/trivia_quiz.py
blob: 84a16654a6d138e16089ed069d25ef5bbb8d4f05 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
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__)


annoyed_expressions = ["-_-", "-.-"]

wrong_ans_responses = [
    "No one gave the correct answer",
    "Losers",
    "You guys really need to learn"
]

game_rules = [
    "No cheating!",
    "Idk what rules to put in here haha."

]


class TriviaQuiz(commands.Cog):
    """A cog for all quiz commands."""

    def __init__(self, bot):
        self.bot = bot
        self.questions = self.load_questions()
        self.game_status = {}
        self.game_owners = {}
        self.question_limit = 3
        self.categories = {
            "general": "Test your general knwoledge",
            "retro": "Questions related to retro gaming."
        }

    @staticmethod
    def load_questions() -> dict:
        """Load the questions from  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")
    async def start(self, ctx, option: str, category: str = "retro") -> None:
        """
        Start/Stop a quiz!

        arguments:
        option:
        - start : to start a quiz in a channel
        - stop  : stop the quiz running in that channel.

        Questions for the quiz can be selected from the following categories:
        - general : Test your general knowledge. (default)
        - Retro : questions related to retro gaming.
        """
        category = category.lower()
        player_data = {}  # a dict to store players and their points.

        if ctx.channel.id not in self.game_status:
            self.game_status[ctx.channel.id] = None

        if option == "start":
            if self.game_status[ctx.channel.id] is True:
                return await ctx.send("Game already running.")
            else:
                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 :**\n"
                for rule in game_rules:
                    start_embed.description += f"- {rule}\n"
                start_embed.set_footer(text="2 hints per question sent after every 10s")
                await ctx.send(embed=start_embed)  # send an embed with the rules

        elif option == "stop":
            if self.game_status[ctx.channel.id] is False:
                return await ctx.send("No game running, nothing to stop here.")
            else:
                if (
                        ctx.author == self.game_owners[ctx.channel.id] or
                        Roles.moderator in [role.id for role in ctx.author.roles]
                ):
                    await self.declare_winner(ctx.channel, player_data)
                    self.game_status[ctx.channel.id] = False
                else:
                    await ctx.send(f"{ctx.author.mention}, you are not authorised to stop this game :ghost: !")
        else:
            self.game_status[ctx.channel.id] = False
            return await ctx.send("start or stop only")
        
        unanswerd = 0
        done_question = []
        hint_no = 0
        answer = None
        hints = None
        while self.game_status[ctx.channel.id] is True:
            if len(done_question) > self.question_limit and hint_no == 0:
                await ctx.send("The round ends here.")
                await self.declare_winner(ctx.channel, player_data)
                break
            if unanswerd > 3:
                await ctx.send("Game stopped due to inactivity.")
                await self.declare_winner(ctx.channel, player_data)
                break
            if category not in self.categories:
                embed = self.category_embed
                await ctx.send(embed=embed)
                break
            topic = self.questions[category]
            if hint_no == 0:
                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"]
                hints = question_dict["hints"]

                embed = discord.Embed(colour=discord.Colour.gold())
                embed.title = f"Question #{len(done_question)}"
                embed.description = q
                await ctx.send(embed=embed)

            def check(m):
                ratio = fuzz.ratio(answer.lower(), m.content)
                return ratio > 80 and m.channel == ctx.channel
            try:
                msg = await self.bot.wait_for('message', check=check, timeout=10)
            except Exception as e:
                if self.game_status[ctx.channel.id] is False:
                    break
                if isinstance(e, asyncio.TimeoutError):
                    if hint_no < 2:
                        await ctx.send(f"**Hint #{hint_no+1}\n**{hints[hint_no]}")
                        hint_no += 1
                    else:
                        response = random.choice(wrong_ans_responses)
                        expression = random.choice(annoyed_expressions)
                        await ctx.send(f"{response} {expression}, the correct answer is **{answer}**.")
                        hint_no = 0
                        unanswerd += 1
                        await self.send_score(ctx.channel, player_data)

            else:
                points = 100 - 25*hint_no
                if msg.author in player_data:
                    player_data[msg.author] += points
                else:
                    player_data[msg.author] = points
                hint_no = 0
                unanswerd = 0
                await ctx.send(f"{msg.author.mention} got the correct answer :tada: {points} points for ya.")
                await ctx.send(f"Correct answer is **{answer}**")
                await self.send_score(ctx.channel, player_data)

    @staticmethod
    async def send_score(channel, player_data):
        """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"
        await channel.send(embed=embed)

    @staticmethod
    async def declare_winner(channel, player_data):
        """A function declare the winner of the quiz."""

        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:
                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 = None
                for winner in winners:
                    winners_mention += f"{winner.mention} "

            else:
                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"You have won this quiz game with a grand total of {highest_points} points!!"
            )

    @property
    def category_embed(self) -> discord.Embed:
        """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


def setup(bot):
    """Loading the cog."""
    bot.add_cog(TriviaQuiz(bot))
    logger.debug("TriviaQuiz cog loaded!")