diff options
author | 2021-09-07 16:41:27 +0530 | |
---|---|---|
committer | 2021-09-07 04:11:27 -0700 | |
commit | edb80b90186b7ebfcce8b3dae55fedb62a77b912 (patch) | |
tree | 05072d2057bc592f71ce9e2be64ff634e61fc1d0 | |
parent | CI: check license compatibility (#855) (diff) |
Wikiguess Game (#618)
* (trivia-quiz): Add Wikipedia Guess Game
This commit also moves all the 'dynamic' question generator to a separate class.
Closes: #446
* (trivia quiz): Use tuples for command aliases
* (trivia quiz): Edit congratulations message
* (trivia quiz): Use default dict for storing player scores
* (trivia quiz): 'done_question' to 'done_questions'
* Add space after 'Congratulations' word
* Use classmethods for dynamically generating questions
* Don't add wiki category if max error fetches hit
If the task hit max error fetches, which is 3 currently, it would remove wikipedia from listed categories and not add it to loaded questions.
If it doesn't hit max fetches, then it adds them.
* Don't hardcode the number of questions in RULES
* Add information field only if it exists
* Add "cs" and "python" categories to the `.quiz` command
* add 30 questions each under the categories "cs" and "python"
add the two categories into the code and modify the starting phase
Co-authored-by: Xithrius <[email protected]>
Co-authored-by: ToxicKidz <[email protected]>
* refactor: Use yesterday's most read to make trivia questions
Since random wikipedia article guess questions weren't really "knowledgeable", no one could really guess it or gain any "good" knowledge from them, so after asking wookie (this commits mentions his review comments above also), I decided to use these.
* refactor: Logic to remove pronounciations from question
Co-authored-by: wookie184 <[email protected]>
* fix: Set to correct question limit
If the number of questions are less then the default limit which can
happen in the case of wikipedia guess game as it is dependent on the
most read articiles on wikipedia, it would create a infinite loop
sending us into infinite amount of errors, so let's prevent that, thanks
wookie
* chore: Add comment for d5f8205 change
* refactor: Remove double mention of dataclass in quizentry
* chore: Use r"" over noqa
We can use r"", a raw string, here to make it clear that \* and \s
aren't supposed to be handled as escape sequences and just use the "raw
string".
* fix: Correct off by one bug
Originally, before this commit, we checked the number of questions left
by comparing `len(done_questions) > self.question_limit`, so question
limit had to be 1 since if it wasn't we would compare 7 > 7, which would
be false and then it would send another question.
To correct this bug, we now use == comparision on the two, so if the
number of done questions is same as the question limit it means that the
round is over. I have changed the relevant parts of the code to reflect
this change i.e. where-ever we did +-1 due to the off by one bug.
* refactor: Noramlize the title to remove all punctuations
Since the title can sometimes contain punctuations making it very
difficult to get the matching answer to the question, we originally
removed all such questions. This took the question count down :( and
wasn't an effective way. Therefore now we keep them but as normalized,
yay!
I have also updated the code documentation to make the process much
clearer to anyone reading the "normalizing" code section of the wiki
questions generator.
* refactor: Keep answers as a list & not ",".join()
Wasn't fitting in character limit so shorterned it ^^ lol. Okay, getting
to the point, this mentions fix error's comment of making quiz entry
except the answers as a list and not as a string which could a comma
joined list. The same structure was in the json resource, where multiple
answers where joined with commas. This didn't allow you to use commas in
answers.
So I went ahead and did a bit more than requested to change the json
structure and make `answers` a list. Also now all questions are in the
form of the quiz entry to keep it same through out the code and var
tolerance has become a valid param in QuizEntry, this is done because it
was differing between questions, if not needed this would make the
process to add `var_tol` as a argument to the json easier. And that's
it!
Co-authored-by: Objectivitix <[email protected]>
Co-authored-by: Xithrius <[email protected]>
Co-authored-by: ToxicKidz <[email protected]>
Co-authored-by: wookie184 <[email protected]>
-rw-r--r-- | bot/exts/fun/trivia_quiz.py | 422 | ||||
-rw-r--r-- | bot/resources/fun/trivia_quiz.json | 94 |
2 files changed, 299 insertions, 217 deletions
diff --git a/bot/exts/fun/trivia_quiz.py b/bot/exts/fun/trivia_quiz.py index cf9e6cd3..236586b0 100644 --- a/bot/exts/fun/trivia_quiz.py +++ b/bot/exts/fun/trivia_quiz.py @@ -3,12 +3,16 @@ import json import logging import operator import random +import re +import string +from collections import defaultdict from dataclasses import dataclass +from datetime import datetime, timedelta from pathlib import Path from typing import Callable, Optional import discord -from discord.ext import commands +from discord.ext import commands, tasks from rapidfuzz import fuzz from bot.bot import Bot @@ -16,177 +20,194 @@ from bot.constants import Colours, NEGATIVE_REPLIES, Roles logger = logging.getLogger(__name__) -DEFAULT_QUESTION_LIMIT = 6 +DEFAULT_QUESTION_LIMIT = 7 STANDARD_VARIATION_TOLERANCE = 88 DYNAMICALLY_GEN_VARIATION_TOLERANCE = 97 +MAX_ERROR_FETCH_TRIES = 3 + WRONG_ANS_RESPONSE = [ "No one answered correctly!", "Better luck next time...", ] -N_PREFIX_STARTS_AT = 5 -N_PREFIXES = [ - "penta", "hexa", "hepta", "octa", "nona", - "deca", "hendeca", "dodeca", "trideca", "tetradeca", -] +RULES = ( + "No cheating and have fun!", + "Points for each question reduces by 25 after 10s or after a hint. Total time is 30s per question" +) -PLANETS = [ - ("1st", "Mercury"), - ("2nd", "Venus"), - ("3rd", "Earth"), - ("4th", "Mars"), - ("5th", "Jupiter"), - ("6th", "Saturn"), - ("7th", "Uranus"), - ("8th", "Neptune"), -] - -TAXONOMIC_HIERARCHY = [ - "species", "genus", "family", "order", - "class", "phylum", "kingdom", "domain", -] - -UNITS_TO_BASE_UNITS = { - "hertz": ("(unit of frequency)", "s^-1"), - "newton": ("(unit of force)", "m*kg*s^-2"), - "pascal": ("(unit of pressure & stress)", "m^-1*kg*s^-2"), - "joule": ("(unit of energy & quantity of heat)", "m^2*kg*s^-2"), - "watt": ("(unit of power)", "m^2*kg*s^-3"), - "coulomb": ("(unit of electric charge & quantity of electricity)", "s*A"), - "volt": ("(unit of voltage & electromotive force)", "m^2*kg*s^-3*A^-1"), - "farad": ("(unit of capacitance)", "m^-2*kg^-1*s^4*A^2"), - "ohm": ("(unit of electric resistance)", "m^2*kg*s^-3*A^-2"), - "weber": ("(unit of magnetic flux)", "m^2*kg*s^-2*A^-1"), - "tesla": ("(unit of magnetic flux density)", "kg*s^-2*A^-1"), -} +WIKI_FEED_API_URL = "https://en.wikipedia.org/api/rest_v1/feed/featured/{date}" +TRIVIA_QUIZ_ICON = ( + "https://raw.githubusercontent.com/python-discord/branding/main/icons/trivia_quiz/trivia-quiz-dist.png" +) @dataclass(frozen=True) class QuizEntry: - """Dataclass for a quiz entry (a question and a string containing answers separated by commas).""" + """Stores quiz entry (a question and a list of answers).""" question: str - answer: str - - -def linear_system(q_format: str, a_format: str) -> QuizEntry: - """Generate a system of linear equations with two unknowns.""" - x, y = random.randint(2, 5), random.randint(2, 5) - answer = a_format.format(x, y) - - coeffs = random.sample(range(1, 6), 4) - - question = q_format.format( - coeffs[0], - coeffs[1], - coeffs[0] * x + coeffs[1] * y, - coeffs[2], - coeffs[3], - coeffs[2] * x + coeffs[3] * y, - ) - - return QuizEntry(question, answer) - - -def mod_arith(q_format: str, a_format: str) -> QuizEntry: - """Generate a basic modular arithmetic question.""" - quotient, m, b = random.randint(30, 40), random.randint(10, 20), random.randint(200, 350) - ans = random.randint(0, 9) # max remainder is 9, since the minimum modulus is 10 - a = quotient * m + ans - b - - question = q_format.format(a, b, m) - answer = a_format.format(ans) + answers: list[str] + var_tol: int + + +class DynamicQuestionGen: + """Class that contains functions to generate math/science questions for TriviaQuiz Cog.""" + + N_PREFIX_STARTS_AT = 5 + N_PREFIXES = [ + "penta", "hexa", "hepta", "octa", "nona", + "deca", "hendeca", "dodeca", "trideca", "tetradeca", + ] + + PLANETS = [ + ("1st", "Mercury"), + ("2nd", "Venus"), + ("3rd", "Earth"), + ("4th", "Mars"), + ("5th", "Jupiter"), + ("6th", "Saturn"), + ("7th", "Uranus"), + ("8th", "Neptune"), + ] + + TAXONOMIC_HIERARCHY = [ + "species", "genus", "family", "order", + "class", "phylum", "kingdom", "domain", + ] + + UNITS_TO_BASE_UNITS = { + "hertz": ("(unit of frequency)", "s^-1"), + "newton": ("(unit of force)", "m*kg*s^-2"), + "pascal": ("(unit of pressure & stress)", "m^-1*kg*s^-2"), + "joule": ("(unit of energy & quantity of heat)", "m^2*kg*s^-2"), + "watt": ("(unit of power)", "m^2*kg*s^-3"), + "coulomb": ("(unit of electric charge & quantity of electricity)", "s*A"), + "volt": ("(unit of voltage & electromotive force)", "m^2*kg*s^-3*A^-1"), + "farad": ("(unit of capacitance)", "m^-2*kg^-1*s^4*A^2"), + "ohm": ("(unit of electric resistance)", "m^2*kg*s^-3*A^-2"), + "weber": ("(unit of magnetic flux)", "m^2*kg*s^-2*A^-1"), + "tesla": ("(unit of magnetic flux density)", "kg*s^-2*A^-1"), + } + + @classmethod + def linear_system(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a system of linear equations with two unknowns.""" + x, y = random.randint(2, 5), random.randint(2, 5) + answer = a_format.format(x, y) + + coeffs = random.sample(range(1, 6), 4) + + question = q_format.format( + coeffs[0], + coeffs[1], + coeffs[0] * x + coeffs[1] * y, + coeffs[2], + coeffs[3], + coeffs[2] * x + coeffs[3] * y, + ) - return QuizEntry(question, answer) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) + @classmethod + def mod_arith(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a basic modular arithmetic question.""" + quotient, m, b = random.randint(30, 40), random.randint(10, 20), random.randint(200, 350) + ans = random.randint(0, 9) # max remainder is 9, since the minimum modulus is 10 + a = quotient * m + ans - b -def ngonal_prism(q_format: str, a_format: str) -> QuizEntry: - """Generate a question regarding vertices on n-gonal prisms.""" - n = random.randint(0, len(N_PREFIXES) - 1) + question = q_format.format(a, b, m) + answer = a_format.format(ans) - question = q_format.format(N_PREFIXES[n]) - answer = a_format.format((n + N_PREFIX_STARTS_AT) * 2) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) - return QuizEntry(question, answer) + @classmethod + def ngonal_prism(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a question regarding vertices on n-gonal prisms.""" + n = random.randint(0, len(cls.N_PREFIXES) - 1) + question = q_format.format(cls.N_PREFIXES[n]) + answer = a_format.format((n + cls.N_PREFIX_STARTS_AT) * 2) -def imag_sqrt(q_format: str, a_format: str) -> QuizEntry: - """Generate a negative square root question.""" - ans_coeff = random.randint(3, 10) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) - question = q_format.format(ans_coeff ** 2) - answer = a_format.format(ans_coeff) + @classmethod + def imag_sqrt(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a negative square root question.""" + ans_coeff = random.randint(3, 10) - return QuizEntry(question, answer) + question = q_format.format(ans_coeff ** 2) + answer = a_format.format(ans_coeff) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) -def binary_calc(q_format: str, a_format: str) -> QuizEntry: - """Generate a binary calculation question.""" - a = random.randint(15, 20) - b = random.randint(10, a) - oper = random.choice( - ( - ("+", operator.add), - ("-", operator.sub), - ("*", operator.mul), + @classmethod + def binary_calc(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a binary calculation question.""" + a = random.randint(15, 20) + b = random.randint(10, a) + oper = random.choice( + ( + ("+", operator.add), + ("-", operator.sub), + ("*", operator.mul), + ) ) - ) - # if the operator is multiplication, lower the values of the two operands to make it easier - if oper[0] == "*": - a -= 5 - b -= 5 + # if the operator is multiplication, lower the values of the two operands to make it easier + if oper[0] == "*": + a -= 5 + b -= 5 - question = q_format.format(a, oper[0], b) - answer = a_format.format(oper[1](a, b)) + question = q_format.format(a, oper[0], b) + answer = a_format.format(oper[1](a, b)) - return QuizEntry(question, answer) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) + @classmethod + def solar_system(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a question on the planets of the Solar System.""" + planet = random.choice(cls.PLANETS) -def solar_system(q_format: str, a_format: str) -> QuizEntry: - """Generate a question on the planets of the Solar System.""" - planet = random.choice(PLANETS) + question = q_format.format(planet[0]) + answer = a_format.format(planet[1]) - question = q_format.format(planet[0]) - answer = a_format.format(planet[1]) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) - return QuizEntry(question, answer) + @classmethod + def taxonomic_rank(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a question on taxonomic classification.""" + level = random.randint(0, len(cls.TAXONOMIC_HIERARCHY) - 2) + question = q_format.format(cls.TAXONOMIC_HIERARCHY[level]) + answer = a_format.format(cls.TAXONOMIC_HIERARCHY[level + 1]) -def taxonomic_rank(q_format: str, a_format: str) -> QuizEntry: - """Generate a question on taxonomic classification.""" - level = random.randint(0, len(TAXONOMIC_HIERARCHY) - 2) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) - question = q_format.format(TAXONOMIC_HIERARCHY[level]) - answer = a_format.format(TAXONOMIC_HIERARCHY[level + 1]) + @classmethod + def base_units_convert(cls, q_format: str, a_format: str) -> QuizEntry: + """Generate a SI base units conversion question.""" + unit = random.choice(list(cls.UNITS_TO_BASE_UNITS)) - return QuizEntry(question, answer) - - -def base_units_convert(q_format: str, a_format: str) -> QuizEntry: - """Generate a SI base units conversion question.""" - unit = random.choice(list(UNITS_TO_BASE_UNITS)) - - question = q_format.format( - unit + " " + UNITS_TO_BASE_UNITS[unit][0] - ) - answer = a_format.format( - UNITS_TO_BASE_UNITS[unit][1] - ) + question = q_format.format( + unit + " " + cls.UNITS_TO_BASE_UNITS[unit][0] + ) + answer = a_format.format( + cls.UNITS_TO_BASE_UNITS[unit][1] + ) - return QuizEntry(question, answer) + return QuizEntry(question, [answer], DYNAMICALLY_GEN_VARIATION_TOLERANCE) DYNAMIC_QUESTIONS_FORMAT_FUNCS = { - 201: linear_system, - 202: mod_arith, - 203: ngonal_prism, - 204: imag_sqrt, - 205: binary_calc, - 301: solar_system, - 302: taxonomic_rank, - 303: base_units_convert, + 201: DynamicQuestionGen.linear_system, + 202: DynamicQuestionGen.mod_arith, + 203: DynamicQuestionGen.ngonal_prism, + 204: DynamicQuestionGen.imag_sqrt, + 205: DynamicQuestionGen.binary_calc, + 301: DynamicQuestionGen.solar_system, + 302: DynamicQuestionGen.taxonomic_rank, + 303: DynamicQuestionGen.base_units_convert, } @@ -202,7 +223,7 @@ class TriviaQuiz(commands.Cog): self.questions = self.load_questions() self.question_limit = 0 - self.player_scores = {} # A variable to store all player's scores for a bot session. + self.player_scores = defaultdict(int) # A variable to store all player's scores for a bot session. self.game_player_scores = {} # A variable to store temporary game player's scores. self.categories = { @@ -212,8 +233,72 @@ class TriviaQuiz(commands.Cog): "science": "Put your understanding of science to the test!", "cs": "A large variety of computer science questions.", "python": "Trivia on our amazing language, Python!", + "wikipedia": "Guess the title of random wikipedia passages.", } + self.get_wiki_questions.start() + + def cog_unload(self) -> None: + """Cancel `get_wiki_questions` task when Cog will unload.""" + self.get_wiki_questions.cancel() + + @tasks.loop(hours=24.0) + async def get_wiki_questions(self) -> None: + """Get yesterday's most read articles from wikipedia and format them like trivia questions.""" + error_fetches = 0 + wiki_questions = [] + # trivia_quiz.json follows a pattern, every new category starts with the next century. + start_id = 501 + yesterday = datetime.strftime(datetime.now() - timedelta(1), '%Y/%m/%d') + + while error_fetches < MAX_ERROR_FETCH_TRIES: + async with self.bot.http_session.get(url=WIKI_FEED_API_URL.format(date=yesterday)) as r: + if r.status != 200: + error_fetches += 1 + continue + raw_json = await r.json() + articles_raw = raw_json["mostread"]["articles"] + + for article in articles_raw: + question = article.get("extract") + if not question: + continue + + # Normalize the wikipedia article title to remove all punctuations from it + for word in re.split(r"[\s-]", title := article["normalizedtitle"]): + cleaned_title = re.sub( + rf'\b{word.strip(string.punctuation)}\b', word, title, flags=re.IGNORECASE + ) + + # Since the extract contains the article name sometimes this would replace all the matching words + # in that article with *** of that length. + # NOTE: This removes the "answer" for 99% of the cases, but sometimes the wikipedia article is + # very different from the words in the extract, for example the title would be the nickname of a + # person (Bob Ross) whereas in the extract it would the full name (Robert Norman Ross) so it comes + # out as (Robert Norman ****) and (Robert Norman Ross) won't be a right answer :( + for word in re.split(r"[\s-]", cleaned_title): + word = word.strip(string.punctuation) + secret_word = r"\*" * len(word) + question = re.sub(rf'\b{word}\b', f"**{secret_word}**", question, flags=re.IGNORECASE) + + formatted_article_question = { + "id": start_id, + "question": f"Guess the title of the Wikipedia article.\n\n{question}", + "answer": cleaned_title, + "info": article["extract"] + } + start_id += 1 + wiki_questions.append(formatted_article_question) + + # If everything has gone smoothly until now, we can break out of the while loop + break + + if error_fetches < MAX_ERROR_FETCH_TRIES: + self.questions["wikipedia"] = wiki_questions.copy() + else: + del self.categories["wikipedia"] + logger.warning(f"Not loading wikipedia guess questions, hit max error fetches: {MAX_ERROR_FETCH_TRIES}.") + @staticmethod def load_questions() -> dict: """Load the questions from the JSON file.""" @@ -221,7 +306,7 @@ class TriviaQuiz(commands.Cog): return json.loads(p.read_text(encoding="utf-8")) - @commands.group(name="quiz", aliases=["trivia"], invoke_without_command=True) + @commands.group(name="quiz", aliases=("trivia", "triviaquiz"), invoke_without_command=True) async def quiz_game(self, ctx: commands.Context, category: Optional[str], questions: Optional[int]) -> None: """ Start a quiz! @@ -233,6 +318,7 @@ class TriviaQuiz(commands.Cog): - science: Put your understanding of science to the test! - cs: A large variety of computer science questions. - python: Trivia on our amazing language, Python! + - wikipedia: Guess the title of random wikipedia passages. (More to come!) """ @@ -264,7 +350,7 @@ class TriviaQuiz(commands.Cog): topic_length = len(topic) if questions is None: - self.question_limit = DEFAULT_QUESTION_LIMIT + self.question_limit = min(DEFAULT_QUESTION_LIMIT, topic_length) else: if questions > topic_length: await ctx.send( @@ -279,13 +365,13 @@ class TriviaQuiz(commands.Cog): await ctx.send( embed=self.make_error_embed( "You must choose to complete at least one question. " - f"(or enter nothing for the default value of {DEFAULT_QUESTION_LIMIT + 1} questions)" + f"(or enter nothing for the default value of {DEFAULT_QUESTION_LIMIT} questions)" ) ) return else: - self.question_limit = questions - 1 + self.question_limit = questions # Start game if not running. if not self.game_status[ctx.channel.id]: @@ -296,13 +382,13 @@ class TriviaQuiz(commands.Cog): await ctx.send(embed=start_embed) # send an embed with the rules await asyncio.sleep(5) - done_question = [] + done_questions = [] hint_no = 0 - answers = None + quiz_entry = None while self.game_status[ctx.channel.id]: # Exit quiz if number of questions for a round are already sent. - if len(done_question) > self.question_limit and hint_no == 0: + if len(done_questions) == self.question_limit and hint_no == 0: await ctx.send("The round has ended.") await self.declare_winner(ctx.channel, self.game_player_scores[ctx.channel.id]) @@ -317,32 +403,27 @@ class TriviaQuiz(commands.Cog): # Select a random question which has not been used yet. while True: question_dict = random.choice(topic) - if question_dict["id"] not in done_question: - done_question.append(question_dict["id"]) + if question_dict["id"] not in done_questions: + done_questions.append(question_dict["id"]) break if "dynamic_id" not in question_dict: - question = question_dict["question"] - answers = question_dict["answer"].split(", ") - - var_tol = STANDARD_VARIATION_TOLERANCE + quiz_entry = QuizEntry( + question_dict["question"], + quiz_answers if isinstance(quiz_answers := question_dict["answer"], list) else [quiz_answers], + STANDARD_VARIATION_TOLERANCE + ) else: format_func = DYNAMIC_QUESTIONS_FORMAT_FUNCS[question_dict["dynamic_id"]] - quiz_entry = format_func( question_dict["question"], question_dict["answer"], ) - question, answers = quiz_entry.question, quiz_entry.answer - answers = [answers] - - var_tol = DYNAMICALLY_GEN_VARIATION_TOLERANCE - embed = discord.Embed( colour=Colours.gold, - title=f"Question #{len(done_question)}", - description=question, + title=f"Question #{len(done_questions)}", + description=quiz_entry.question, ) if img_url := question_dict.get("img_url"): @@ -354,13 +435,13 @@ class TriviaQuiz(commands.Cog): def contains_correct_answer(m: discord.Message) -> bool: return m.channel == ctx.channel and any( fuzz.ratio(answer.lower(), m.content.lower()) > variation_tolerance - for answer in answers + for answer in quiz_entry.answers ) return contains_correct_answer try: - msg = await self.bot.wait_for("message", check=check_func(var_tol), timeout=10) + msg = await self.bot.wait_for("message", check=check_func(quiz_entry.var_tol), timeout=10) except asyncio.TimeoutError: # In case of TimeoutError and the game has been stopped, then do nothing. if not self.game_status[ctx.channel.id]: @@ -388,10 +469,10 @@ class TriviaQuiz(commands.Cog): await self.send_answer( ctx.channel, - answers, + quiz_entry.answers, False, question_dict, - self.question_limit - len(done_question) + 1, + self.question_limit - len(done_questions), ) await asyncio.sleep(1) @@ -421,10 +502,10 @@ class TriviaQuiz(commands.Cog): await self.send_answer( ctx.channel, - answers, + quiz_entry.answers, True, question_dict, - self.question_limit - len(done_question) + 1, + self.question_limit - len(done_questions), ) await self.send_score(ctx.channel, self.game_player_scores[ctx.channel.id]) @@ -432,19 +513,18 @@ class TriviaQuiz(commands.Cog): def make_start_embed(self, category: str) -> discord.Embed: """Generate a starting/introduction embed for the quiz.""" + rules = "\n".join([f"{index}: {rule}" for index, rule in enumerate(RULES, start=1)]) + start_embed = discord.Embed( - colour=Colours.blue, - title="A quiz game is starting!", + title="Quiz game Starting!!", description=( - f"This game consists of {self.question_limit + 1} questions.\n\n" - "**Rules: **\n" - "1. Only enclose your answer in backticks when the question tells you to.\n" - "2. If the question specifies an answer format, follow it or else it won't be accepted.\n" - "3. You have 30s per question. Points for each question reduces by 25 after 10s or after a hint.\n" - "4. No cheating and have fun!\n\n" - f"**Category**: {category}" + f"Each game consists of {self.question_limit} questions.\n" + f"**Rules :**\n{rules}" + f"\n **Category** : {category}" ), + colour=Colours.blue ) + start_embed.set_thumbnail(url=TRIVIA_QUIZ_ICON) return start_embed @@ -503,6 +583,7 @@ class TriviaQuiz(commands.Cog): title="Score Board", description="", ) + embed.set_thumbnail(url=TRIVIA_QUIZ_ICON) sorted_dict = sorted(player_data.items(), key=operator.itemgetter(1), reverse=True) for item in sorted_dict: @@ -534,8 +615,8 @@ class TriviaQuiz(commands.Cog): winners_mention = winner.mention await channel.send( - f"Congratulations {winners_mention} :tada: " - f"You have won this quiz game with a grand total of {highest_points} points!" + f"{winners_mention} Congratulations " + f"on winning this quiz game with a grand total of {highest_points} points :tada:" ) def category_embed(self) -> discord.Embed: @@ -578,7 +659,8 @@ class TriviaQuiz(commands.Cog): description="", ) - if info is not None: + # Don't check for info is not None, as we want to filter out empty strings. + if info: embed.description += f"**Information**\n{info}\n\n" embed.description += ( diff --git a/bot/resources/fun/trivia_quiz.json b/bot/resources/fun/trivia_quiz.json index 8008838c..0b3e6802 100644 --- a/bot/resources/fun/trivia_quiz.json +++ b/bot/resources/fun/trivia_quiz.json @@ -52,7 +52,7 @@ "They generally have handdrawn nature images on them." ], "question": "What did Nintendo make before video games and toys?", - "answer": "Hanafuda, Hanafuda cards" + "answer": ["Hanafuda", "Hanafuda cards"] }, { "id": 7, @@ -292,22 +292,22 @@ { "id": 201, "question": "What is the highest power of a biquadratic polynomial?", - "answer": "4, four" + "answer": ["4", "four"] }, { "id": 202, "question": "What is the formula for surface area of a sphere?", - "answer": "4pir^2, 4πr^2" + "answer": ["4pir^2", "4πr^2"] }, { "id": 203, "question": "Which theorem states that hypotenuse^2 = base^2 + height^2?", - "answer": "Pythagorean's, Pythagorean's theorem" + "answer": ["Pythagorean's", "Pythagorean's theorem"] }, { "id": 204, "question": "Which trigonometric function is defined as hypotenuse/opposite?", - "answer": "cosecant, cosec, csc" + "answer": ["cosecant", "cosec", "csc"] }, { "id": 205, @@ -317,7 +317,7 @@ { "id": 206, "question": "How many quadrants are there in a cartesian plane?", - "answer": "4, four" + "answer": ["4", "four"] }, { "id": 207, @@ -328,7 +328,7 @@ "id": 208, "question": "What's the following formula that finds the area of a triangle called?", "img_url": "https://wikimedia.org/api/rest_v1/media/math/render/png/d22b8566e8187542966e8d166e72e93746a1a6fc", - "answer": "Heron's formula, Heron" + "answer": ["Heron's formula", "Heron"] }, { "id": 209, @@ -372,12 +372,12 @@ { "id": 216, "question": "In set builder notation, what does {p/q | q ≠ 0, p & q ∈ Z} represent?", - "answer": "Rationals, Rational Numbers" + "answer": ["Rationals", "Rational Numbers"] }, { "id": 217, "question": "What is the natural log of -1 (use i for imaginary number)?", - "answer": "pi*i, pii, πi" + "answer": ["pi*i", "pii", "πi"] }, { "id": 218, @@ -397,7 +397,7 @@ { "id": 221, "question": "Prime numbers only have __ factors.", - "answer": "2, two" + "answer": ["2", "two"] }, { "id": 222, @@ -408,7 +408,7 @@ "id": 223, "question": "In statistics, what does this formula represent?", "img_url": "https://www.statisticshowto.com/wp-content/uploads/2013/11/sample-standard-deviation.jpg", - "answer": "sample standard deviation, standard deviation of a sample" + "answer": ["sample standard deviation", "standard deviation of a sample"] }, { "id": 224, @@ -418,7 +418,7 @@ { "id": 225, "question": "A matrix multiplied by its inverse matrix equals...", - "answer": "the identity matrix, identity matrix" + "answer": ["the identity matrix", "identity matrix"] }, { "id": 226, @@ -429,19 +429,19 @@ { "id": 227, "question": "What is the only number in the entire number system which can be spelled with the same number of letters as itself?", - "answer": "4, four" + "answer": ["4", "four"] }, { "id": 228, "question": "1/100th of a second is also termed as what?", - "answer": "a jiffy, jiffy, centisecond" + "answer": ["a jiffy", "jiffy", "centisecond"] }, { "id": 229, "question": "What is this triangle called?", "img_url": "https://cdn.askpython.com/wp-content/uploads/2020/07/Pascals-triangle.png", - "answer": "Pascal's triangle, Pascal" + "answer": ["Pascal's triangle", "Pascal"] }, { "id": 230, @@ -468,17 +468,17 @@ { "id": 304, "question": "What do you call an organism composed of only one cell?", - "answer": "unicellular, single-celled" + "answer": ["unicellular", "single-celled"] }, { "id": 305, "question": "The Heisenberg's Uncertainty Principle states that the position and \\_\\_\\_\\_\\_\\_\\_\\_ of a quantum object can't be both exactly measured at the same time.", - "answer": "velocity, momentum" + "answer": ["velocity", "momentum"] }, { "id": 306, "question": "A \\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_\\_ reaction is the one wherein an atom or a set of atoms is/are replaced by another atom or a set of atoms", - "answer": "displacement, exchange" + "answer": ["displacement", "exchange"] }, { "id": 307, @@ -512,7 +512,7 @@ { "id": 312, "question": "What is the intermolecular force caused by temporary and induced dipoles?", - "answer": "LDF, London dispersion, London dispersion force" + "answer": ["LDF", "London dispersion", "London dispersion force"] }, { "id": 313, @@ -522,7 +522,7 @@ { "id": 314, "question": "About how many neurons are in the human brain?\n(A. 1 billion, B. 10 billion, C. 100 billion, D. 300 billion)", - "answer": "C, 100 billion, 100 bil" + "answer": ["C", "100 billion", "100 bil"] }, { "id": 315, @@ -537,12 +537,12 @@ { "id": 317, "question": "Which vascular tissue transports water and minerals from the roots to the rest of a plant?", - "answer": "the xylem, xylem" + "answer": ["the xylem", "xylem"] }, { "id": 318, "question": "Who discovered the theories of relativity?", - "answer": "Albert Einstein, Einstein" + "answer": ["Albert Einstein", "Einstein"] }, { "id": 319, @@ -557,7 +557,7 @@ { "id": 321, "question": "What range of frequency are the average human ears capable of hearing?\n(A. 10Hz-10kHz, B. 20Hz-20kHz, C. 20Hz-2000Hz, D. 10kHz-20kHz)", - "answer": "B, 20Hz-20kHz" + "answer": ["B", "20Hz-20kHz"] }, { "id": 322, @@ -572,7 +572,7 @@ { "id": 324, "question": "The type of rock that is formed by the accumulation or deposition of mineral or organic particles at the Earth's surface, followed by cementation, is called...", - "answer": "sedimentary, sedimentary rock" + "answer": ["sedimentary", "sedimentary rock"] }, { "id": 325, @@ -582,7 +582,7 @@ { "id": 326, "question": "What type of image is formed by a convex mirror?", - "answer": "virtual image, virtual" + "answer": ["virtual image", "virtual"] }, { "id": 327, @@ -592,17 +592,17 @@ { "id": 328, "question": "Which law states that the global entropy in a closed system can only increase?", - "answer": "second law, second law of thermodynamics" + "answer": ["second law", "second law of thermodynamics"] }, { "id": 329, "question": "Which particle is emitted during the beta decay of a radioactive element?", - "answer": "an electron, the electron, electron" + "answer": ["an electron", "the electron", "electron"] }, { "id": 330, "question": "When DNA is unzipped, two strands are formed. What are they called (separate both answers by the word \"and\")?", - "answer": "leading and lagging, leading strand and lagging strand" + "answer": ["leading and lagging", "leading strand and lagging strand"] } ], "cs": [ @@ -619,7 +619,7 @@ { "id": 403, "question": "What does SASS stand for?", - "answer": "Syntactically Awesome Stylesheets, Syntactically Awesome Style Sheets" + "answer": ["Syntactically Awesome Stylesheets", "Syntactically Awesome Style Sheets"] }, { "id": 404, @@ -629,7 +629,7 @@ { "id": 405, "question": "What is computing capable of performing exaFLOPS called?", - "answer": "exascale computing, exascale" + "answer": ["exascale computing", "exascale"] }, { "id": 406, @@ -654,7 +654,7 @@ { "id": 410, "question": "A hypothetical point in time at which technological growth becomes uncontrollable and irreversible, resulting in unforeseeable changes to human civilization is termed as...?", - "answer": "technological singularity, singularity" + "answer": ["technological singularity", "singularity"] }, { "id": 411, @@ -664,12 +664,12 @@ { "id": 412, "question": "How many bits are in a TCP checksum header?", - "answer": "16, sixteen" + "answer": ["16", "sixteen"] }, { "id": 413, "question": "What is the most popular protocol (as of 2021) that handles communication between email servers?", - "answer": "SMTP, Simple Mail Transfer Protocol" + "answer": ["SMTP", "Simple Mail Transfer Protocol"] }, { "id": 414, @@ -679,12 +679,12 @@ { "id": 415, "question": "Which DNS record contains mail servers of a given domain?", - "answer": "MX, mail exchange" + "answer": ["MX", "mail exchange"] }, { "id": 416, "question": "Which newline sequence does HTTP use?", - "answer": "carriage return line feed, CRLF, \\r\\n" + "answer": ["carriage return line feed", "CRLF", "\\r\\n"] }, { "id": 417, @@ -694,7 +694,7 @@ { "id": 418, "question": "Name a universal logic gate.", - "answer": "NAND, NOR" + "answer": ["NAND", "NOR"] }, { "id": 419, @@ -729,7 +729,7 @@ { "id": 425, "question": "What does the \"a\" represent in a HSLA color value?", - "answer": "transparency, translucency, alpha value, alpha channel, alpha" + "answer": ["transparency", "translucency", "alpha value", "alpha channel", "alpha"] }, { "id": 426, @@ -749,7 +749,7 @@ { "id": 429, "question": "Which of these sorting algorithms is not stable?\n(Counting sort, quick sort, insertion sort, tim sort, bubble sort)", - "answer": "quick, quick sort" + "answer": ["quick", "quick sort"] }, { "id": 430, @@ -761,7 +761,7 @@ { "id": 501, "question": "Is everything an instance of the `object` class (y/n)?", - "answer": "y, yes" + "answer": ["y", "yes"] }, { "id": 502, @@ -781,7 +781,7 @@ { "id": 505, "question": "Can you pickle a running `list_iterator` (y/n)?", - "answer": "y, yes" + "answer": ["y", "yes"] }, { "id": 506, @@ -821,12 +821,12 @@ { "id": 513, "question": "Where does the name Python come from?", - "answer": "Monty Python, Monty Python's Flying Circus" + "answer": ["Monty Python", "Monty Python's Flying Circus"] }, { "id": 514, "question": "How is infinity represented in Python?", - "answer": "float(\"infinity\"), float('infinity'), float(\"inf\"), float('inf')" + "answer": ["float(\"infinity\")", "float('infinity')", "float(\"inf\")", "float('inf')"] }, { "id": 515, @@ -846,12 +846,12 @@ { "id": 518, "question": "What decorator is used to allow a protocol to be checked at runtime?", - "answer": "runtime_checkable, typing.runtime_checkable" + "answer": ["runtime_checkable", "typing.runtime_checkable"] }, { "id": 519, "question": "Does `numbers.Rational` include the builtin object float (y/n)", - "answer": "n, no" + "answer": ["n", "no"] }, { "id": 520, @@ -866,7 +866,7 @@ { "id": 522, "question": "What is the garbage collection strategy used by cpython to collect everything but reference cycles?", - "answer": "reference counting, refcounting" + "answer": ["reference counting", "refcounting"] }, { "id": 523, @@ -891,12 +891,12 @@ { "id": 527, "question": "Is the `__aiter__` method async (y/n)?", - "answer": "n, no" + "answer": ["n", "no"] }, { "id": 528, "question": "How does one call a class who defines the behavior of their instance classes?", - "answer": "a metaclass, metaclass" + "answer": ["a metaclass", "metaclass"] }, { "id": 529, |