Skip to content

Global Leaderboard#1730

Open
ammaralzeno wants to merge 35 commits intopython-discord:mainfrom
ammaralzeno:feat/global-leaderboard
Open

Global Leaderboard#1730
ammaralzeno wants to merge 35 commits intopython-discord:mainfrom
ammaralzeno:feat/global-leaderboard

Conversation

@ammaralzeno
Copy link

Relevant Issues

Closes #627

Description

This PR implements the Global Leaderboard feature (Issue #627). It creates a unified scoring system across all fun and holiday games in the bot.

The persistent global scores are stored in Leaderboard.points_cache (a RedisCache with namespace "leaderboard:points"), which maintains user totals indefinitely across all games. The daily point tracking uses raw Redis keys with the pattern leaderboard:daily:{user_id}:{game_name}, each with a TTL set to expire at UTC midnight via seconds_until_midnight_utc(). When add_points() is called, it first checks the daily key to enforce the 100-point cap per game, then atomically updates the persistent global cache.

Core Implementation

  • New Cog: Added leaderboard.py which handles:
    • Displaying the global leaderboard (.leaderboard, .lb).
    • Displaying individual user stats (.leaderboard me, .leaderboard user @Member).
    • Displaying daily stats (.leaderboard today).
    • Implements LinePaginator (from pagination.py).
    • Admin utilities (!leaderboard clear).
  • Backend: Added leaderboard.py containing the logic for:
    • Adding points via Redis.
    • Enforcing a daily cap (100 points per game per day as discussed earlier)
    • Handling automatic daily resets using Redis TTLs (no background tasks required).
  • Game Integration: Integrated scoring into 14 different games.

Visuals

Followed preferences from old closed PR, such as using the duck coin & using medals for top 3.

Leaderboard Leaderboard for individual user Leaderboard clear

Game Integrations (Added add_points calls)

  • Coinflip & RPS: 2 pts
  • Snake Quiz: 10 pts
  • Tic-Tac-Toe: 10 pts
  • Anagram, Easter Riddle, Egghead Quiz: 10 pts
  • Trivia Quiz: 10-12 pts
  • Hangman: 15 pts
  • Connect Four: 15 pts
  • Battleship: 30 pts
  • Minesweeper: 15-20 pts
  • Duck Game: 30 (1st), 20 (2nd), 10 (3rd)
  • Snakes & Ladders: 15 pts

All integrated games now display a (+X pts) message when winning to provide user feedback.

Did you:

ammaralzeno and others added 8 commits February 23, 2026 13:51
- Implements core utility functions for leaderboard
- Uses Redis cache for persistency
- Global leaderboard, daily global leaderboard, a specific user's points & admin clear command
- Implements the coin flip game for testing the leaderboard
- Uses the duck coin image shown in the issue
with coinflip = 2 as baseline
Copy link

@rf20008 rf20008 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've made some good progress so far. However, I have given you some feedback.

Now if points are clamped due to daily limit, the real value is shown instead of the constants. Updates all 14 games to use this new value.
@ammaralzeno ammaralzeno requested a review from rf20008 March 1, 2026 14:04
@jchristgit jchristgit requested review from jchristgit and removed request for rf20008 March 1, 2026 19:34
Copy link
Member

@jchristgit jchristgit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all, looks really solid! I have a few improvement suggestions, but in general the design is sound, the implementation is sound, and the Python Discord Hive Mind is satisfied with your offering.

@ammaralzeno ammaralzeno requested a review from jchristgit March 3, 2026 13:29
Copy link
Member

@jchristgit jchristgit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Some minor finishing touches, then we can :shipit:!

Copy link

@rf20008 rf20008 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think you've made significant progress on this PR. Still, I think these minor changes could make it even better.

"You are not authorized to perform this action.",
ephemeral=True,
)
return False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the exception raised by returning False (as Discord.py will internally raise a CheckFailure) be handled properly here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is handled properly. The CheckFailure that discord.py raises internally doesn't cause issues because we already responded to the interaction, so there is no unhandled interaction error. The unauthorized user sees the message and the button callback just doesn't execute.

Comment on lines +41 to +43
if points <= 0:
total = await get_user_points(bot, user_id)
return (total, 0)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you intentionally preventing adding negative points?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well none of the games give any penalties, etc. It's all only positive points on win. And also we do have a remove_points function, this can be used instead for corrections/penalties in the future.

else:
for child in view.children:
child.disabled = True
await msg.edit(view=view)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this send another confirmatory message, or is disabling the view (your original design choice) confirmation enough of the leaderboard's clearing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The confirmation message is being sent at interaction.response.send_message("Leaderboard has been cleared.") so we are doing both for confirmation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Global Leaderboard

5 participants