Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
10551a9
Add: Global leaderboard core infrastructure (#627)
ammaralzeno Feb 23, 2026
3c3a410
Integrate games to update leaderboard and normalize points for each g…
xrrayy Feb 27, 2026
727d1e5
Fix: missing space issues in messages
ammaralzeno Feb 28, 2026
72f7ac1
Add: remaining games to update global leaderboard on win
ammaralzeno Feb 28, 2026
d884d43
Fix: a more fair point system
ammaralzeno Feb 28, 2026
36fb837
Fix: correct github content link for duck coin
ammaralzeno Feb 28, 2026
5eaf692
Fix: trailing-whitespace
ammaralzeno Feb 28, 2026
d0e99d2
Fix: ruff check errors
ammaralzeno Feb 28, 2026
1042d08
Fix: add_points to return actual earned points
ammaralzeno Mar 1, 2026
493339a
Fix: pre-commit & ruff check errors
ammaralzeno Mar 1, 2026
11326e8
Fix: handle ties & optimize get_user_rank
ammaralzeno Mar 1, 2026
8114a47
Update bot/exts/fun/snakes/_snakes_cog.py
ammaralzeno Mar 2, 2026
f490396
Update bot/exts/fun/connect_four.py
ammaralzeno Mar 2, 2026
77b3d00
Update bot/utils/leaderboard.py
ammaralzeno Mar 2, 2026
30afb24
Add: operator import
ammaralzeno Mar 2, 2026
ac2254c
Add: module-level game name constant
ammaralzeno Mar 2, 2026
a632876
Fix: change 'winners' from list to tuple
norawennerstrom Mar 2, 2026
101965a
refactor: duck game scoring
remmare Mar 2, 2026
8a4ac27
Fix: call 'increment' directly without checking that cache contains u…
norawennerstrom Mar 2, 2026
da6c010
refactor: remove redundant award_points check in snakes
remmare Mar 2, 2026
554d380
Refactor: "me"-command
Mar 2, 2026
d2e1747
Refactor: easter_riddle winner variable cleanup
Mar 2, 2026
ead4028
Fix: replace "global pts" with "pts" in end_game()
Mar 2, 2026
606951b
Fix:replace "global pts" with "pts" in quiz_game()
Mar 2, 2026
e07b181
Fix: move points_cache from cog to global variable in bot.utils.leade…
norawennerstrom Mar 2, 2026
1ab15ef
Merge remote-tracking branch 'origin/dev-anna' into feat/global-leade…
ammaralzeno Mar 3, 2026
4a5ac29
Merge pull request #2 from ammaralzeno/dev-nora
ammaralzeno Mar 3, 2026
33df0e5
Merge branch 'dev-amanda' into feat/global-leaderboard
ammaralzeno Mar 3, 2026
474ef43
Fix: 6 as default value in minesweeper
ammaralzeno Mar 3, 2026
ab88deb
Merge branch 'main' into feat/global-leaderboard
ammaralzeno Mar 4, 2026
6d9c58e
Update bot/exts/fun/connect_four.py
ammaralzeno Mar 12, 2026
dd585fa
Update bot/utils/leaderboard.py
ammaralzeno Mar 12, 2026
68a9b7b
Add: cog_unload to reset POINTS_CACHE
ammaralzeno Mar 12, 2026
91ee6af
Remove: unnecessary get_user_points() call
ammaralzeno Mar 12, 2026
8673c43
Fix: AttributeError if winner is None
ammaralzeno Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion bot/exts/fun/anagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

from bot.bot import Bot
from bot.constants import Colours
from bot.utils.leaderboard import add_points

ANAGRAM_WIN_POINTS = 10
ANAGRAM_GAME_NAME = "anagram"

log = get_logger(__name__)

Expand All @@ -32,11 +36,13 @@ def __init__(self, scrambled: str, correct: list[str]) -> None:
self.correct = set(correct)

self.winners = set()
self.winner_ids = set()

async def message_creation(self, message: discord.Message) -> None:
"""Check if the message is a correct answer and remove it from the list of answers."""
if message.content.lower() in self.correct:
self.winners.add(message.author.mention)
self.winner_ids.add(message.author.id)
self.correct.remove(message.content.lower())


Expand Down Expand Up @@ -77,7 +83,15 @@ async def anagram_command(self, ctx: commands.Context) -> None:

if game.winners:
win_list = ", ".join(game.winners)
content = f"Well done {win_list} for getting it right!"
points_earned = set()
for winner_id in game.winner_ids:
_, earned = await add_points(self.bot, winner_id, ANAGRAM_WIN_POINTS, ANAGRAM_GAME_NAME)
points_earned.add(earned)
if len(points_earned) == 1:
pts = points_earned.pop()
content = f"Well done {win_list} for getting it right! (+{pts} pts)"
else:
content = f"Well done {win_list} for getting it right!"
else:
content = "Nobody got it right."

Expand Down
27 changes: 21 additions & 6 deletions bot/exts/fun/battleship.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

from bot.bot import Bot
from bot.constants import Colours, Emojis
from bot.utils.leaderboard import add_points

BATTLESHIP_WIN_POINTS = 30
BATTLESHIP_GAME_NAME = "battleship"


log = get_logger(__name__)

Expand Down Expand Up @@ -150,11 +155,13 @@ async def game_over(
loser: discord.Member
) -> None:
"""Removes games from list of current games and announces to public chat."""
await self.public_channel.send(f"Game Over! {winner.mention} won against {loser.mention}")
_, earned = await add_points(self.bot, winner.id, BATTLESHIP_WIN_POINTS, BATTLESHIP_GAME_NAME)
header = f"Game Over! {winner.mention} won against {loser.mention} (+{earned} pts)"

for player in (self.p1, self.p2):
for i, player in enumerate((self.p1, self.p2)):
grid = self.format_grid(player, SHIP_EMOJIS)
await self.public_channel.send(f"{player.user}'s Board:\n{grid}")
content = f"{header}\n{player.user}'s Board:\n{grid}" if i == 0 else f"{player.user}'s Board:\n{grid}"
await self.public_channel.send(content)

@staticmethod
def check_sink(grid: Grid, boat: str) -> bool:
Expand Down Expand Up @@ -245,16 +252,22 @@ async def take_turn(self) -> Square | None:
except TimeoutError:
await self.turn.user.send("You took too long. Game over!")
await self.next.user.send(f"{self.turn.user} took too long. Game over!")
_, earned = await add_points(self.bot, self.next.user.id, BATTLESHIP_WIN_POINTS, BATTLESHIP_GAME_NAME)
await self.public_channel.send(
f"Game over! {self.turn.user.mention} timed out so {self.next.user.mention} wins!"
f"Game over! {self.turn.user.mention} timed out so {self.next.user.mention} wins! "
f"(+{earned} pts)"
)
self.gameover = True
break
else:
if self.surrender:
await self.next.user.send(f"{self.turn.user} surrendered. Game over!")
_, earned = await add_points(
self.bot, self.next.user.id, BATTLESHIP_WIN_POINTS, BATTLESHIP_GAME_NAME
)
await self.public_channel.send(
f"Game over! {self.turn.user.mention} surrendered to {self.next.user.mention}!"
f"Game over! {self.turn.user.mention} surrendered to {self.next.user.mention}! "
f"(+{earned} pts)"
)
self.gameover = True
break
Expand All @@ -274,11 +287,13 @@ async def hit(self, square: Square, alert_messages: list[discord.Message]) -> No
await self.turn.user.send(f"You've sunk their {square.boat} ship!", delete_after=3.0)
alert_messages.append(await self.next.user.send(f"Oh no! Your {square.boat} ship sunk!"))
if self.check_gameover(self.next.grid):
await self.turn.user.send("You win!")
_, earned = await add_points(self.bot, self.turn.user.id, BATTLESHIP_WIN_POINTS, BATTLESHIP_GAME_NAME)
await self.turn.user.send(f"You win! (+{earned} pts)")
await self.next.user.send("You lose!")
self.gameover = True
await self.game_over(winner=self.turn.user, loser=self.next.user)


async def start_game(self) -> None:
"""Begins the game."""
await self.p1.user.send(f"You're playing battleship with {self.p2.user}.")
Expand Down
12 changes: 10 additions & 2 deletions bot/exts/fun/coinflip.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

from bot.bot import Bot
from bot.constants import Emojis
from bot.utils.leaderboard import add_points

COINFLIP_WIN_POINTS = 2
COINFLIP_GAME_NAME = "coinflip"


class CoinSide(commands.Converter):
Expand All @@ -27,6 +31,9 @@ async def convert(self, ctx: commands.Context, side: str) -> str:
class CoinFlip(commands.Cog):
"""Cog for the CoinFlip command."""

def __init__(self, bot: Bot):
self.bot = bot

@commands.command(name="coinflip", aliases=("flip", "coin", "cf"))
async def coinflip_command(self, ctx: commands.Context, side: CoinSide = None) -> None:
"""
Expand All @@ -42,12 +49,13 @@ async def coinflip_command(self, ctx: commands.Context, side: CoinSide = None) -
return

if side == flipped_side:
message += f"You guessed correctly! {Emojis.lemon_hyperpleased}"
_, earned = await add_points(self.bot, ctx.author.id, COINFLIP_WIN_POINTS, COINFLIP_GAME_NAME)
message += f"You guessed correctly! {Emojis.lemon_hyperpleased} (+{earned} pts)"
else:
message += f"You guessed incorrectly. {Emojis.lemon_pensive}"
await ctx.send(message)


async def setup(bot: Bot) -> None:
"""Loads the coinflip cog."""
await bot.add_cog(CoinFlip())
await bot.add_cog(CoinFlip(bot))
35 changes: 32 additions & 3 deletions bot/exts/fun/connect_four.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

from bot.bot import Bot
from bot.constants import Emojis
from bot.utils.leaderboard import add_points

CONNECT_FOUR_WIN_POINTS = 15
CONNECT_FOUR_GAME_NAME = "connect_four"

NUMBERS = list(Emojis.number_emojis.values())
CROSS_EMOJI = Emojis.incident_unactioned
Expand Down Expand Up @@ -75,11 +79,27 @@ async def game_over(
) -> None:
"""Announces to public chat."""
if action == "win":
await self.channel.send(f"Game Over! {player1.mention} won against {player2.mention}")
if isinstance(player1, Member):
_, earned = await add_points(self.bot, player1.id, CONNECT_FOUR_WIN_POINTS, CONNECT_FOUR_GAME_NAME)
await self.channel.send(
f"Game Over! {player1.mention} won against {player2.mention} (+{earned} pts)"
)
else:
await self.channel.send(
f"Game Over! {player1.mention} won against {player2.mention}"
)
elif action == "draw":
await self.channel.send(f"Game Over! {player1.mention} {player2.mention} It's A Draw :tada:")
elif action == "quit":
await self.channel.send(f"{self.player1.mention} surrendered. Game over!")
if isinstance(player2, Member):
_, earned = await add_points(self.bot, player2.id, CONNECT_FOUR_WIN_POINTS, CONNECT_FOUR_GAME_NAME)
await self.channel.send(
f"{player1.mention} surrendered. {player2.mention} wins! Game over! (+{earned} pts)"
)
else:
await self.channel.send(
f"{player1.mention} surrendered. {player2.mention} wins! Game over!"
)
await self.print_grid()

async def start_game(self) -> None:
Expand Down Expand Up @@ -131,7 +151,16 @@ async def player_turn(self) -> Coordinate:
try:
reaction, user = await self.bot.wait_for("reaction_add", check=self.predicate, timeout=30.0)
except TimeoutError:
await self.channel.send(f"{self.player_active.mention}, you took too long. Game over!")
message = (
f"{self.player_active.mention}, you took too long. Game over! "
f"{self.player_inactive.mention} wins!"
)
if isinstance(self.player_inactive, Member):
_, earned = await add_points(
self.bot, self.player_inactive.id, CONNECT_FOUR_WIN_POINTS, CONNECT_FOUR_GAME_NAME
)
message += f" (+{earned} pts)"
await self.channel.send(message)
return None
else:
await message.delete()
Expand Down
27 changes: 26 additions & 1 deletion bot/exts/fun/duck_game.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
from bot.bot import Bot
from bot.constants import MODERATION_ROLES
from bot.utils.decorators import with_role
from bot.utils.leaderboard import add_points

DUCK_GAME_FIRST_PLACE_POINTS = 30
DUCK_GAME_SECOND_PLACE_POINTS = 20
DUCK_GAME_THIRD_PLACE_POINTS = 10
DUCK_GAME_POINT_AWARDS = (
DUCK_GAME_FIRST_PLACE_POINTS,
DUCK_GAME_SECOND_PLACE_POINTS,
DUCK_GAME_THIRD_PLACE_POINTS,
)
DUCK_GAME_NAME = "duck_game"

DECK = list(product(*[(0, 1, 2)]*4))

Expand Down Expand Up @@ -297,8 +308,22 @@ async def end_game(self, channel: discord.TextChannel, game: DuckGame, end_messa
key=lambda item: item[1],
reverse=True,
)

# Award leaderboard points to top finishers (number of places from DUCK_GAME_POINT_AWARDS)
earned_points = {}
for rank, (member, score) in enumerate(scores[:len(DUCK_GAME_POINT_AWARDS)]):
if score > 0:
_, earned = await add_points(
self.bot, member.id, DUCK_GAME_POINT_AWARDS[rank], DUCK_GAME_NAME
)
earned_points[member.id] = earned

scoreboard = "Final scores:\n\n"
scoreboard += "\n".join(f"{member.display_name}: {score}" for member, score in scores)
for rank, (member, score) in enumerate(scores):
if rank < len(DUCK_GAME_POINT_AWARDS) and score > 0 and member.id in earned_points:
scoreboard += f"{member.display_name}: {score} (+{earned_points[member.id]} pts)\n"
else:
scoreboard += f"{member.display_name}: {score}\n"
scoreboard_embed.description = scoreboard
await channel.send(embed=scoreboard_embed)

Expand Down
7 changes: 6 additions & 1 deletion bot/exts/fun/hangman.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

from bot.bot import Bot
from bot.constants import Colours, NEGATIVE_REPLIES
from bot.utils.leaderboard import add_points

HANGMAN_WIN_POINTS = 15
HANGMAN_GAME_NAME = "hangman"

# Defining all words in the list of words as a global variable
ALL_WORDS = Path("bot/resources/fun/hangman_words.txt").read_text().splitlines()
Expand Down Expand Up @@ -167,9 +171,10 @@ def check(msg: Message) -> bool:

# The loop exited meaning that the user has guessed the word
await original_message.edit(embed=self.create_embed(tries, user_guess))
_, earned = await add_points(self.bot, ctx.author.id, HANGMAN_WIN_POINTS, HANGMAN_GAME_NAME)
win_embed = Embed(
title="You won!",
description=f"The word was `{word}`.",
description=f"The word was `{word}`. (+{earned} pts)",
color=Colours.grass_green
)
await ctx.send(embed=win_embed)
Expand Down
Loading
Loading