aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Shom770 <[email protected]>2021-12-19 20:27:34 -0500
committerGravatar Shom770 <[email protected]>2022-02-09 18:13:37 -0500
commit8c7baf05a82cf53813ed3eabc197abf7a0d98a63 (patch)
treed60f5f27275c3cc9db29c8f966c0110586ef20f0
parentFix bugs after testing and add TODO comments (diff)
refactor
-rw-r--r--bot/exts/events/trivianight/_game.py31
-rw-r--r--bot/exts/events/trivianight/_questions.py36
-rw-r--r--bot/exts/events/trivianight/_scoreboard.py36
-rw-r--r--bot/exts/events/trivianight/trivianight.py27
4 files changed, 90 insertions, 40 deletions
diff --git a/bot/exts/events/trivianight/_game.py b/bot/exts/events/trivianight/_game.py
index 7f2e48dc..db303c58 100644
--- a/bot/exts/events/trivianight/_game.py
+++ b/bot/exts/events/trivianight/_game.py
@@ -3,7 +3,6 @@ from random import randrange
from string import ascii_uppercase
from typing import Iterable, Optional, TypedDict
-
DEFAULT_QUESTION_POINTS = 10
DEFAULT_QUESTION_TIME = 10
@@ -56,7 +55,8 @@ class Question:
@property
def answers(self) -> list[tuple[str, str]]:
- """The possible answers for this answer.
+ """
+ The possible answers for this answer.
This is a property that returns a list of letter, answer pairs.
"""
@@ -119,7 +119,11 @@ class TriviaNightGame:
def __init__(self, data: list[QuestionData]) -> None:
self._questions = [Question(q) for q in data]
+ # A copy of the questions to keep for `.trivianight list`
+ self._all_questions = list(self._questions)
self.current_question: Optional[Question] = None
+ self._points = {}
+ self._speed = {}
def __iter__(self) -> Iterable[Question]:
return iter(self._questions)
@@ -135,7 +139,7 @@ class TriviaNightGame:
if number is not None:
try:
- question = [q for q in self._questions if q.number == number][0]
+ question = [q for q in self._all_questions if q.number == number][0]
except IndexError:
raise ValueError(f"Question number {number} does not exist.")
else:
@@ -156,3 +160,24 @@ class TriviaNightGame:
self.current_question.stop()
self.current_question = None
+
+ def list_questions(self) -> None:
+ """
+ List all the questions.
+
+ This method should be called when `.trivianight list` is called to display the following information:
+ - Question number
+ - Question description
+ - Visited/not visited
+ """
+ formatted_string = ""
+
+ spaces = max(len(q.description) for q in self._all_questions)
+
+ for question in self._all_questions:
+ visited, not_visited = ":checkmark:", ":x:"
+ formatted_string += f"`Q{question.number}: {question.description}" \
+ f"{' ' * (spaces - len(question.description))}|`" \
+ f" {visited if question not in self._all_questions else not_visited}\n"
+
+ return formatted_string
diff --git a/bot/exts/events/trivianight/_questions.py b/bot/exts/events/trivianight/_questions.py
index 9a2cb7d2..7fb6dedf 100644
--- a/bot/exts/events/trivianight/_questions.py
+++ b/bot/exts/events/trivianight/_questions.py
@@ -1,7 +1,5 @@
-from random import choice, randrange
+from random import choice
from string import ascii_uppercase
-from time import perf_counter
-from typing import Optional, TypedDict, Union
import discord
from discord import Embed, Interaction
@@ -10,7 +8,7 @@ from discord.ui import Button, View
from bot.constants import Colours, NEGATIVE_REPLIES
from . import UserScore
-from ._game import Question, AlreadyUpdated, QuestionClosed
+from ._game import AlreadyUpdated, Question, QuestionClosed
from ._scoreboard import Scoreboard
@@ -110,7 +108,7 @@ class QuestionView(View):
return question_embed
- def end_question(self) -> Embed:
+ def end_question(self, scoreboard: Scoreboard) -> Embed:
"""
Ends the question and displays the statistics on who got the question correct, awards points, etc.
@@ -120,7 +118,6 @@ class QuestionView(View):
guesses = self.question.stop()
labels = ascii_uppercase[:len(self.question.answers)]
- correct = [label for (label, description) in self.question.answers if description == self.question.correct]
answer_embed = Embed(
title=f"The correct answer for Question {self.question.number} was..",
@@ -135,9 +132,13 @@ class QuestionView(View):
for answer_choice in labels
}
- for idx, (answer, percent) in enumerate(answers_chosen.items()):
+ answers_chosen = dict(
+ sorted(list(answers_chosen.items()), key=lambda item: item[1], reverse=True)
+ )
+
+ for answer, percent in answers_chosen.items():
# Setting the color of answer_embed to the % of people that got it correct via the mapping
- if idx == 0:
+ if dict(self.question.answers)[answer[0]] == self.question.correct:
# Maps the % of people who got it right to a color, from a range of red to green
percentage_to_color = [0xFC94A1, 0xFFCCCB, 0xCDFFCC, 0xB0F5AB, 0xB0F5AB]
answer_embed.color = percentage_to_color[round(percent * 100) // 25]
@@ -149,4 +150,23 @@ class QuestionView(View):
inline=False
)
+ # Assign points to users
+ for user_id, answer in guesses.items():
+ if dict(self.question.answers)[answer[0]] == self.question.correct:
+ scoreboard.assign_points(
+ UserScore(int(user_id)),
+ points=(1 - (answer[-1] / self.question.time) / 2) * self.question.max_points,
+ speed=answer[-1]
+ )
+ elif answer[-1] <= 2:
+ scoreboard.assign_points(
+ UserScore(int(user_id)),
+ points=-(1 - (answer[-1] / self.question.time) / 2) * self.question.max_points
+ )
+ else:
+ scoreboard.assign_points(
+ UserScore(int(user_id)),
+ points=0
+ )
+
return answer_embed
diff --git a/bot/exts/events/trivianight/_scoreboard.py b/bot/exts/events/trivianight/_scoreboard.py
index 40f93475..babd1bd6 100644
--- a/bot/exts/events/trivianight/_scoreboard.py
+++ b/bot/exts/events/trivianight/_scoreboard.py
@@ -16,8 +16,6 @@ class ScoreboardView(View):
def __init__(self, bot: Bot):
super().__init__()
self.bot = bot
- self.points = {}
- self.speed = {}
async def create_main_leaderboard(self) -> Embed:
"""
@@ -142,19 +140,31 @@ class ScoreboardView(View):
class Scoreboard:
"""Class for the scoreboard for the Trivia Night event."""
- def __setitem__(self, key: UserScore, value: dict):
- if value.get("points") and key.user_id not in self.view.points.keys():
- self.view.points[key.user_id] = value["points"]
- elif value.get("points"):
- self.view.points[key.user_id] += self.view.points[key.user_id]
-
- if value.get("speed") and key.user_id not in self.view.speed.keys():
- self.view.speed[key.user_id] = [1, value["speed"]]
- elif value.get("speed"):
- self.view.speed[key.user_id] = [
- self.view.speed[key.user_id][0] + 1, self.view.speed[key.user_id][1] + value["speed"]
+ def __init__(self, bot: Bot):
+ self.view = ScoreboardView(bot)
+ self._points = {}
+ self._speed = {}
+
+ def assign_points(self, user: UserScore, *, points: int = None, speed: float = None) -> None:
+ """
+ Assign points or deduct points to/from a certain user.
+
+ This method should be called once the question has finished and all answers have been registered.
+ """
+ if points is not None and user.user_id not in self._points.keys():
+ self._points[user.user_id] = points
+ elif points is not None:
+ self._points[user.user_id] += self._points[user.user_id]
+
+ if speed is not None and user.user_id not in self._speed.keys():
+ self._speed[user.user_id] = [1, speed]
+ elif speed is not None:
+ self._speed[user.user_id] = [
+ self._speed[user.user_id][0] + 1, self._speed[user.user_id][1] + speed
]
async def display(self) -> tuple[Embed, View]:
"""Returns the embed of the main leaderboard along with the ScoreboardView."""
+ self.view.points = self._points
+ self.view.speed = self._speed
return await self.view.create_main_leaderboard(), self.view
diff --git a/bot/exts/events/trivianight/trivianight.py b/bot/exts/events/trivianight/trivianight.py
index 1465a03d..f158ec0c 100644
--- a/bot/exts/events/trivianight/trivianight.py
+++ b/bot/exts/events/trivianight/trivianight.py
@@ -4,7 +4,6 @@ from random import choice
from typing import Optional
from discord import Embed
-from discord.colour import Color
from discord.ext import commands
from bot.bot import Bot
@@ -12,7 +11,7 @@ from bot.constants import Colours, NEGATIVE_REPLIES, POSITIVE_REPLIES, Roles
from ._game import TriviaNightGame
from ._questions import QuestionView
-from ._scoreboard import Scoreboard, ScoreboardView
+from ._scoreboard import Scoreboard
# The ID you see below is the Events Lead role ID
TRIVIA_NIGHT_ROLES = (Roles.admin, 78361735739998228)
@@ -24,6 +23,8 @@ class TriviaNightCog(commands.Cog):
def __init__(self, bot: Bot):
self.bot = bot
self.game: Optional[TriviaNightGame] = None
+ self.scoreboard: Optional[Scoreboard] = None
+ self.question_closed: asyncio.Event = None
@commands.group(aliases=["tn"], invoke_without_command=True)
async def trivianight(self, ctx: commands.Context) -> None:
@@ -91,12 +92,16 @@ class TriviaNightCog(commands.Cog):
raise commands.BadArgument("Invalid JSON")
self.game = TriviaNightGame(serialized_json)
+ self.question_closed = asyncio.Event()
success_embed = Embed(
title=choice(POSITIVE_REPLIES),
description="The JSON was loaded successfully!",
color=Colours.soft_green
)
+
+ self.scoreboard = Scoreboard(self.bot)
+
await ctx.send(embed=success_embed)
@trivianight.command(aliases=('next',))
@@ -140,6 +145,7 @@ class TriviaNightCog(commands.Cog):
duration = next_question.time * percentage
await asyncio.sleep(duration)
+
if int(duration) > 1:
# It is quite ugly to display decimals, the delay for requests to reach Discord
# cause sub-second accuracy to be quite pointless.
@@ -150,7 +156,7 @@ class TriviaNightCog(commands.Cog):
await asyncio.sleep(duration)
break
- await ctx.send(embed=question_view.end_question())
+ await ctx.send(embed=question_view.end_question(self.scoreboard))
await message.edit(embed=question_embed, view=None)
self.game.end_question()
@@ -172,14 +178,7 @@ class TriviaNightCog(commands.Cog):
))
return
- # TODO: Because of how the game currently works, only the questions left will be able to
- # be gotten. Iterate through self.game:
- #
- # for question in self.game:
- # # This is an instance of Question from _game.py
- # print(question.description)
-
- question_list = self.questions.list_questions()
+ question_list = self.game.list_questions()
if isinstance(question_list, Embed):
await ctx.send(embed=question_list)
return
@@ -211,10 +210,7 @@ class TriviaNightCog(commands.Cog):
await ctx.send(embed=error_embed)
return
- # TODO: We need to tell the 'trivianight next' command that the game has ended, if it is still
- # running that means it is currently counting down waiting to end the question. Use an asyncio.Event and
- # asyncio.wait(self.lock.wait(), timeout=duration) as opposed to asyncio.sleep(duration).
- self.game.end_question()
+ self.ongoing_question = False
@trivianight.command()
@commands.has_any_role(*TRIVIA_NIGHT_ROLES)
@@ -245,7 +241,6 @@ class TriviaNightCog(commands.Cog):
await ctx.send(embed=error_embed)
return
- # TODO: Refactor the scoreboard after the game simplification.
scoreboard_embed, scoreboard_view = await self.scoreboard.display()
await ctx.send(embed=scoreboard_embed, view=scoreboard_view)
self.game = None