diff options
author | 2019-08-27 17:32:50 +0530 | |
---|---|---|
committer | 2019-08-27 17:32:50 +0530 | |
commit | c558d88e74fa6e638dd38bc1232e2839ba72c8da (patch) | |
tree | 386920f795b7e7c56ae47f233d7b8a35bcf49273 /bot | |
parent | Merge pull request #265 from vivax3794/losing_addintions (diff) |
A fun trivia quiz that can be played on multiple channelsThe trivia quiz has the following features: quiz has the following features:
- There wont be any options to the questions asked, the users have to type the answer in the chat and the bot will pick up the right anwer
- If 3 questions have not been answered in a row, the quiz will be brought to a halt assuming inactivity.
- There are also hints in this game! A hint will be sent after 10s after the question has been sent.Each question has a maximum if 2 hints.
- The bot checks for the closest answer rather than the exact one(Incase of typoes in the users answer
Diffstat (limited to 'bot')
-rw-r--r-- | bot/resources/evergreen/trivia_quiz.json | 34 | ||||
-rw-r--r-- | bot/seasons/evergreen/TriviaQuiz.py | 263 |
2 files changed, 297 insertions, 0 deletions
diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json new file mode 100644 index 00000000..f884e67a --- /dev/null +++ b/bot/resources/evergreen/trivia_quiz.json @@ -0,0 +1,34 @@ +{ + "retro": [ + { + "id": 1, + "hints": ["It was not a mainline Mario Game, although the plumber was present.", "It was not a mainline Zelda Game, although Link was present."], + "question": "What was the best selling game on the Nintendo Gamecube?", + "answer": "Super Smash Bros" + }, + { + "id": 2, + "hints": ["It was released before the 90s.", "It was released after 1980."], + "question": "What year was Tetris released?", + "answer": "1984" + }, + { + "id": 3, + "hints": ["The occupation was in construction", "He appeared as this kind of worker in 1981's Donkey Kong"], + "question": "What was Mario's original occupation?", + "answer": "Carpenter" + }, + { + "id": 4, + "hints": ["It was revelead in the Nintendo Character Guide in 1993.", "His last name has to do with eating Mario's enemies."], + "question": "What is Yoshi's(from Mario Bros) full name?", + "answer": "Yoshisaur Munchakoopas" + }, + { + "id": 5, + "hints": ["The game was released in 1990.", "It was released on the SNES."], + "question": "What was the first game Yoshi appeared in?", + "answer": "Super Mario World" + } + ] +} 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!") |