Skip to content

Commit

Permalink
Leaderboard (#19)
Browse files Browse the repository at this point in the history
* feat: wrote query to get leaderboard values

* fix: forgot to await fetch

* fix: query changed to use left join instead of join

* feat: added leaderboard command. can be used by anyone

* fix: used the wrong error

* feat: add function to increase the puzzle_solved column for a team after a solve

* fix: puzzle_solved was not increased after a solve

* feat: add query to get leaderboard values

* fix: update query updated for all teams instead of given team

* chore: removed print statement

* chore: removed old unused code

* chore: removed print statement

* fix: didn't change number of teams per embed back to 10

* chore: minor code cleanup

* fix: improve query

* fix: adjust leaderboard query to also sort by team name
  • Loading branch information
eluric authored Mar 13, 2024
1 parent b37de44 commit 8d8e840
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 4 deletions.
91 changes: 89 additions & 2 deletions cogs/puzzle.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import discord
from discord.ext import commands
from discord import app_commands
from discord.ui import View, view, Button, button

from src.queries.puzzle import get_puzzle
from src.queries.puzzle import get_puzzle, get_completed_puzzles, get_leaderboard
from src.queries.submission import (
create_submission,
find_submissions_by_discord_id_and_puzzle_id,
)
from src.queries.player import get_player
from src.queries.team import get_team, get_team_members
from src.queries.team import get_team, get_team_members, increase_puzzles_solved

from src.utils.decorators import in_team_channel
from src.context.puzzle import can_access_puzzle, get_accessible_puzzles
Expand All @@ -22,6 +23,42 @@
HUNT_START_TIME: datetime = config["HUNT_START_TIME"]


class PaginationView(View):
def __init__(self, pages):
super().__init__()

self.page_num = 0
self.pages = pages

@button(
label="<",
style=discord.ButtonStyle.blurple,
)
async def prev_button(self, interaction: discord.Interaction, button: Button):
await interaction.response.defer()

if self.page_num > 0:
self.page_num += -1

await interaction.followup.edit_message(
message_id=interaction.message.id, embed=self.pages[self.page_num]
)

@button(
label=">",
style=discord.ButtonStyle.blurple,
)
async def next_button(self, interaction: discord.Interaction, button: Button):
await interaction.response.defer()

if self.page_num < len(self.pages) - 1:
self.page_num += 1

await interaction.followup.edit_message(
message_id=interaction.message.id, embed=self.pages[self.page_num]
)


class Puzzle(commands.GroupCog):
def __init__(self, bot):
self.bot = bot
Expand Down Expand Up @@ -67,6 +104,8 @@ async def submit_answer(
if not submission_is_correct:
return await interaction.followup.send("The submitted answer is incorrect!")

await increase_puzzles_solved(player.team_name)

# check if they have solved all the metas
if puzzle_id == "UTS-M":
await interaction.followup.send(
Expand Down Expand Up @@ -151,6 +190,54 @@ async def hint(self, interaction: discord.Interaction):
"Your hint request has been submitted! Hang on tight - a hint giver will be with you shortly."
)

@app_commands.command(
name="leaderboard", description="Displays the current leaderboard for teams."
)
async def leaderboard(self, interaction: discord.Interaction):
await interaction.response.defer()

leaderboard_values = await get_leaderboard()
TEAMS_PER_EMBED = 10
num_vals = len(leaderboard_values)
if num_vals == 0:
await interaction.followup.send("There are no teams at this time.")
return

# getting the ceiling value. i didn't want to import math
num_embeds = (
(num_vals // TEAMS_PER_EMBED) + 1
if num_vals % TEAMS_PER_EMBED != 0
else num_vals // TEAMS_PER_EMBED
)

# each embed will display ten teams
leaderboard_embeds = [
discord.Embed(
title="Leaderboard",
description="Teams are sorted by the number of puzzles solved."
+ "Ties are broken by the latest correct submission time for each tied team.",
color=discord.Color.random(),
)
for _ in range(num_embeds)
]
leaderboard_text = [("", "") for _ in range(num_embeds)]
for i, val in enumerate(leaderboard_values):
team_name, puzzles_solved = val

team_str, puzzles_solved_str = leaderboard_text[i // TEAMS_PER_EMBED]
team_str += f"{i+1}. {team_name}\n"
puzzles_solved_str += f"{puzzles_solved}\n"

leaderboard_text[i // TEAMS_PER_EMBED] = (team_str, puzzles_solved_str)

for page_num, embed in enumerate(leaderboard_embeds):
embed.add_field(name="Team", value=leaderboard_text[page_num][0])
embed.add_field(name="Puzzles Solved", value=leaderboard_text[page_num][1])

await interaction.followup.send(
embed=leaderboard_embeds[0], view=PaginationView(leaderboard_embeds)
)


async def setup(bot: commands.Bot):
await bot.add_cog(Puzzle(bot))
2 changes: 1 addition & 1 deletion cogs/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ async def leave_team(self, interaction: discord.Interaction):
"You have left the team. Since there are no members left, the channels will be deleted.",
ephemeral=True,
)
except CommandInvokeError:
except discord.errors.NotFound:
return

return
Expand Down
25 changes: 24 additions & 1 deletion src/queries/puzzle.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List
import psycopg
from psycopg.rows import class_row
from psycopg.rows import class_row, dict_row

from src.config import config
from src.models.puzzle import Puzzle
Expand Down Expand Up @@ -128,3 +128,26 @@ async def delete_puzzle(puzzle_id: str):
await aconn.close()

return True


async def get_leaderboard() -> tuple[str, int]:
aconn = await psycopg.AsyncConnection.connect(DATABASE_URL)
acur = aconn.cursor()

await acur.execute(
"""
SELECT t.team_name, t.puzzle_solved
FROM public.teams AS t LEFT JOIN public.submissions AS s
ON (t.team_name = s.team_name)
AND s.submission_is_correct = TRUE
GROUP BY t.team_name
ORDER BY t.puzzle_solved DESC, MAX(s.submission_time) ASC, t.team_name ASC
"""
)

leaderboard = await acur.fetchall()

await acur.close()
await aconn.close()

return leaderboard
23 changes: 23 additions & 0 deletions src/queries/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,26 @@ async def remove_team(team_name: str):
await aconn.commit()
await acur.close()
await aconn.close()


async def increase_puzzles_solved(team_name: str):
aconn = await psycopg.AsyncConnection.connect(DATABASE_URL)
acur = aconn.cursor()

# this should never run since the team name should always be
# valid when calling this function
if not await get_team(team_name):
return False

await acur.execute(
"""
UPDATE public.teams
SET puzzle_solved = puzzle_solved + 1
WHERE team_name = %s
""",
(team_name,),
)

await aconn.commit()
await acur.close()
await aconn.close()

0 comments on commit 8d8e840

Please sign in to comment.