aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons/evergreen/trivia_quiz.py
blob: 798523e650389b5ea9c087b15158740b2b7d6727 (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
226
227
228
229
230
231
232
233
234
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_RESPONSE = [
    "No one gave the correct answer",
    "Better luck next time"
]


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

    def __init__(self, bot: commands.Bot) -> None:
        self.bot = bot
        self.questions = self.load_questions()
        self.game_status = {}
        self.game_owners = {}
        self.question_limit = 4
        self.player_dict = {}
        self.categories = {
            "general": "Test your general knowledge"
            # "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", aliases=["trivia"])
    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.

        Questions for the quiz can be selected from the following categories:
        - general : Test your general knowledge. (default)
        (we wil be adding more later)
        """
        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)

        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: !")

        if category not in self.categories:
            embed = self.category_embed
            await ctx.send(embed=embed)
            return
        topic = self.questions[category]

        unanswered = 0
        done_question = []
        hint_no = 0
        answer = None
        while self.game_status[ctx.channel.id]:
            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])
                break
            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"]

                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: 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:
                if self.game_status[ctx.channel.id] is False:
                    break
                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!")

                else:
                    response = random.choice(WRONG_ANS_RESPONSE)
                    expression = random.choice(ANNOYED_EXPRESSIONS)
                    await ctx.send(f"{response} {expression}")
                    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])
                    await asyncio.sleep(2)

            else:
                points = 100 - 25*hint_no
                if msg.author in self.player_dict[ctx.channel.id]:
                    self.player_dict[ctx.channel.id][msg.author] += points
                else:
                    self.player_dict[ctx.channel.id][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 self.send_answer(ctx.channel, question_dict)
                await self.send_score(ctx.channel, self.player_dict[ctx.channel.id])
                await asyncio.sleep(2)

    @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"
        await channel.send(embed=embed)

    @staticmethod
    async def declare_winner(channel: discord.TextChannel, player_data: dict) -> None:
        """Announce the winner of the quiz in the game channel."""
        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:
                word = "You guys"
                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:
                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!!"
            )

    @property
    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.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

    @staticmethod
    async def send_answer(channel: discord.TextChannel, 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 != "":
            embed.description += f"**Information**\n{info}\n\n"
        embed.description += "Lets move to the next question.\nRemaining questions: "
        await channel.send(embed=embed)


def setup(bot: commands.Bot) -> None:
    """Loading the cog."""
    bot.add_cog(TriviaQuiz(bot))
    logger.debug("TriviaQuiz cog loaded")