aboutsummaryrefslogtreecommitdiffstats
path: root/bot/seasons/evergreen/TriviaQuiz.py
blob: 38f1992245c14f4b9a591a56c153dbacb7df8f1c (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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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!")