Skip to content

Commit

Permalink
Updating hints (#20)
Browse files Browse the repository at this point in the history
* fix: update README

* feat: update schema to include number of hints used for each team

* feat: add team_info command that shows current team status

* feat: add query to increase number of hints used by a team

* feat: removed need to type team name in add_member command

* saving changes

* feat: write query to get all finished teams

* saving progress

* feat: added a hint check to see if team has used all their available hints

* feat: added helper functions for hint checking

* feat: added hints_used as a new attribute in teams

* fix: query did not use quotation marks around string

* feat: abstracted the retrieval of the total hint count

* feat: added hint status info to team_info

* feat: made hint messages in hint requests mandatory

* fix: made some context functions not asynchronous

* fix: async-await issues

* feat: added check for unlimited hints

* fix: cursor expected Team object but query only returned team names

* fix: forgot to change testing value

* feat: add a more ceremonious victory message for finishers

* feat: hints are now given every 2 hours instead of every hour
  • Loading branch information
eluric authored Mar 15, 2024
1 parent ce13118 commit 881832e
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ To start the local database, simply do:
This will start a local database that runs on port 5432 and store its data in a local pg_data directory.
To connect to the docker database, do:

docker exec -it postgres16 psql -U postgres
docker exec -it postgres16 psql -U postgres -d puzzlehunt_bot

For future maintainer, since the infra we're curerntly using is Fly.io:
To run the schema on remote fly machine, open a tunnel with `fly proxy`, then use `psql` with -f and -h flags (-h flags forces psql to use TCP/IP instead of UNIX sockets)
25 changes: 7 additions & 18 deletions cogs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,14 @@ async def remove_member(
)
@commands.has_role(EXEC_ID)
async def add_member(
self,
interaction: discord.Interaction,
member: discord.Member,
team_name: str,
self, interaction: discord.Interaction, member: discord.Member
):
await interaction.response.defer(ephemeral=True)

category = interaction.channel.category
vc = category.voice_channels[0]
team_name = vc.name

guild = interaction.guild

player = await get_player(member.id)
Expand All @@ -239,7 +240,8 @@ async def add_member(
team = await get_team(team_name)
if not team:
await interaction.followup.send(
f"{team_name} does not exist. Did you type the name correctly?",
f"This channel does not belong to a team. "
+ "Did you type the command in the correct channel?",
ephemeral=True,
)
return
Expand All @@ -259,19 +261,6 @@ async def add_member(
f"{member.display_name} is now part of the {team_name}!", ephemeral=True
)

@app_commands.command(
name="get_team_name",
description="Gets the team name of the current channel you're in.",
)
@commands.has_role(EXEC_ID)
async def get_team_name(self, interaction: discord.Interaction):
await interaction.response.defer(ephemeral=True)

category = interaction.channel.category
vc = category.voice_channels[0]

await interaction.followup.send(vc.name)


async def setup(bot: commands.Bot):
await bot.add_cog(Admin(bot))
61 changes: 54 additions & 7 deletions cogs/puzzle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
from discord import app_commands
from discord.ui import View, view, Button, button

from src.queries.puzzle import get_puzzle, get_completed_puzzles, get_leaderboard
from src.queries.puzzle import get_puzzle, 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, increase_puzzles_solved
from src.queries.team import (
get_team,
get_team_members,
increase_puzzles_solved,
increase_hints_used,
)

from src.utils.decorators import in_team_channel
from src.context.puzzle import can_access_puzzle, get_accessible_puzzles
from src.context.team import check_if_max_hints, get_next_hint_time


EXEC_ID = config["EXEC_ID"]

Expand Down Expand Up @@ -134,8 +141,15 @@ async def submit_answer(

elif puzzle_id == "METAMETA":
team = await get_team(player.team_name)

victory_embed = discord.Embed(
colour=discord.Color.gold(),
title=f"Team <#{team.text_channel_id}> has finished the hunt!",
description=f"Congratulate them over in the <#{config['VICTOR_TEXT_CHANNEL_ID']}>",
)

await interaction.client.get_channel(config["ADMIN_CHANNEL_ID"]).send(
f"Team <#{team.text_channel_id}> has finished all the puzzles!"
embed=victory_embed
)

# give team the victor role
Expand Down Expand Up @@ -185,14 +199,47 @@ async def list_puzzles(self, interaction: discord.Interaction):
embed.add_field(name="Puzzles", value="\n".join(puzzle_name_links), inline=True)
await interaction.followup.send(embed=embed)

@app_commands.command(name="hint", description="Request a hint for the puzzle!")
@app_commands.command(
name="hint",
description="Request a hint for the puzzle! Please be detailed about what you're stuck on.",
)
@in_team_channel
async def hint(self, interaction: discord.Interaction):
async def hint(
self, interaction: discord.Interaction, puzzle_name: str, hint_msg: str
):
await interaction.response.defer()
team = await get_player(interaction.user.id)

if datetime.now(tz=ZoneInfo("Australia/Sydney")) < config["HUNT_START_TIME"]:
await interaction.followup.send("The hunt has not started yet :pensive:")
return

player = await get_player(interaction.user.id)

max_hints = await check_if_max_hints(player.team_name)
if max_hints:
next_hint_time = get_next_hint_time()
await interaction.followup.send(
"You have used up all your available hints! "
+ f"Next hint at {next_hint_time}. A new hint is available every hour."
)

return

await increase_hints_used(player.team_name)

hint_embed = discord.Embed(
colour=discord.Color.dark_teal(),
title=f"Hint Request From {interaction.channel.mention}",
description=f"**Puzzle Name:** {puzzle_name}",
)

if hint_msg:
hint_embed.add_field(name="Details", value=hint_msg)

await interaction.client.get_channel(config["ADMIN_CHANNEL_ID"]).send(
f"Hint request submitted from team {team.team_name}! {interaction.channel.mention}"
embed=hint_embed
)

await interaction.followup.send(
"Your hint request has been submitted! Hang on tight - a hint giver will be with you shortly."
)
Expand Down
47 changes: 46 additions & 1 deletion cogs/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
import src.queries.player as player_query
from src.config import config

from src.context.team import remove_member_from_team
from src.context.puzzle import get_accessible_puzzles
from src.context.team import (
remove_member_from_team,
get_max_hints,
get_next_hint_time,
get_finished_teams,
)

from src.utils.decorators import in_team_channel

MAX_TEAM_SIZE = 6
EXEC_ID = config["EXEC_ID"]
Expand Down Expand Up @@ -234,6 +242,43 @@ async def reject_callback(interaction: discord.Interaction):
+ "Tag an exec and they can add the member to your team for you!"
)

@app_commands.command(name="info", description="Check your team status.")
@in_team_channel
async def team_info(self, interaction: discord.Interaction):
await interaction.response.defer()

player = await player_query.get_player(interaction.user.id)
team = await team_query.get_team(player.team_name)

info_embed = discord.Embed(
colour=discord.Color.random(),
title=f"{team.team_name} Info",
)

accessible_puzzles = await get_accessible_puzzles(team.team_name)
info_embed.add_field(
name="Puzzle Status",
value=f"You have solved {team.puzzle_solved} puzzles "
+ f"out of {len(accessible_puzzles)} available!",
)

finished_teams = await get_finished_teams()
if len(finished_teams) >= 3:
info_embed.add_field(
name="Hint Status",
value=f"Hints are now unlimited! You have used {team.hints_used} hints.",
)
else:
max_hints = get_max_hints()
next_hint_time = get_next_hint_time()
info_embed.add_field(
name="Hint Status",
value=f"You have used {team.hints_used} out of {max_hints} available. "
+ f"Next hint at {next_hint_time}.",
)

await interaction.followup.send(embed=info_embed)


async def setup(bot: commands.Bot):
await bot.add_cog(Team(bot))
51 changes: 51 additions & 0 deletions src/context/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@
from typing import List

from src.queries.player import get_player, remove_player
from src.queries.puzzle import get_finished_teams
from src.queries.team import get_team, get_team_members, remove_team

from src.models.player import Player

from src.config import config

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

SECONDS_BETWEEN_HINTS = 7200 # how many seconds between each new hint
SECONDS_IN_AN_HOUR = 3600


async def get_team_channels(guild: discord.Guild, team_name: str):
team = await get_team(team_name)
Expand Down Expand Up @@ -70,3 +79,45 @@ async def remove_member_from_team(guild: discord.Guild, member: discord.Member):
await remove_team(team_name)

return "deleted"


def get_max_hints():
time_difference = (
datetime.now(tz=ZoneInfo("Australia/Sydney")) - config["HUNT_START_TIME"]
)
max_hints = time_difference.seconds // SECONDS_BETWEEN_HINTS

return max_hints


async def check_if_max_hints(team_name: str):
team = await get_team(team_name)

# check if top 3 has been taken
# unlimited hints if so and we just return False
finished_teams = await get_finished_teams()
if len(finished_teams) >= 3:
return False

# check how many hints would be available
# if no hints were used
# hints are given out 1 per hour
# so total number of hints that would be available
# is the same as the number of hours that have passed
total_hints = get_max_hints()
if team.hints_used < total_hints:
return False

return True


def get_next_hint_time() -> str:
time_difference = (
datetime.now(tz=ZoneInfo("Australia/Sydney")) - config["HUNT_START_TIME"]
)
hours_passed = time_difference.seconds // SECONDS_BETWEEN_HINTS

next_hint_time = config["HUNT_START_TIME"] + timedelta(
hours=hours_passed + (SECONDS_BETWEEN_HINTS // SECONDS_IN_AN_HOUR)
)
return next_hint_time.strftime("%H:%M")
1 change: 1 addition & 0 deletions src/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ CREATE TABLE public.teams (
text_channel_id text NOT NULL,
team_role_id text NOT NULL,
puzzle_solved integer NOT NULL default 0
hints_used integer NOT NULL default 0
);

CREATE TABLE public.players (
Expand Down
1 change: 1 addition & 0 deletions src/models/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ class Team(BaseModel):
text_channel_id: int
team_role_id: int
puzzle_solved: int
hints_used: int
22 changes: 22 additions & 0 deletions src/queries/puzzle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from src.config import config
from src.models.puzzle import Puzzle
from src.models.team import Team

DATABASE_URL = config["DATABASE_URL"]

Expand Down Expand Up @@ -151,3 +152,24 @@ async def get_leaderboard() -> tuple[str, int]:
await aconn.close()

return leaderboard


async def get_finished_teams():
aconn = await psycopg.AsyncConnection.connect(DATABASE_URL)
acur = aconn.cursor()

await acur.execute(
"""
SELECT t.team_name
FROM public.teams AS t JOIN public.submissions AS s
ON (t.team_name = s.team_name)
WHERE s.puzzle_id = 'METAMETA' AND s.submission_is_correct = TRUE
"""
)

finished_teams = await acur.fetchall()

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

return finished_teams
21 changes: 21 additions & 0 deletions src/queries/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,24 @@ async def increase_puzzles_solved(team_name: str):
await aconn.commit()
await acur.close()
await aconn.close()


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

if not await get_team(team_name):
return False

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

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

0 comments on commit 881832e

Please sign in to comment.