Skip to content

Commit

Permalink
feat: separate views, add timeout prevention and fix bug in song titl…
Browse files Browse the repository at this point in the history
…e matching (#160)
  • Loading branch information
NiceAesth authored Jun 26, 2024
1 parent 795981e commit 4c19adb
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 16 deletions.
46 changes: 33 additions & 13 deletions src/classes/osudle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import asyncio
import time
from abc import ABC
from abc import abstractmethod
from io import BytesIO
Expand All @@ -13,6 +14,8 @@
from discord import File

from common import humanizer
from ui.menus.osu import OsudleContinueView
from ui.menus.osu import OsudleGuessView

if TYPE_CHECKING:
from typing import Any
Expand All @@ -23,13 +26,7 @@
from discord import Message


class OsudleSkipButton(discord.ui.Button["BaseOsudleGame"]):
def __init__(self, game: BaseOsudleGame) -> None:
super().__init__(style=discord.ButtonStyle.danger, label="Skip")
self.game = game

async def callback(self, interaction: discord.Interaction):
await self.game.skip(interaction)
CONTINUE_TIMEOUT_MINUTES = 10


class BaseOsudleGame(ABC):
Expand All @@ -40,20 +37,21 @@ class BaseOsudleGame(ABC):
"mode",
"current_guess_task",
"current_beatmapset",
"timeout",
"interaction_timeout",
"scores",
"latest_beatmapset_message",
"skip_votes",
)

def __init__(self) -> None:
self.interaction = None
self.last_interaction_time = time.monotonic()
self.running = False
self.stop_event = asyncio.Event()
self.mode = None
self.current_guess_task = None
self.current_beatmapset = None
self.timeout = 60
self.interaction_timeout = 60
self.scores = {}
self.latest_beatmapset_message = None
self.skip_votes = set()
Expand Down Expand Up @@ -109,14 +107,32 @@ async def wait_for_guess(self, beatmapset: Beatmapset) -> None:
message = await self.interaction.client.wait_for(
"message",
check=self.check_guess,
timeout=self.timeout,
timeout=self.interaction_timeout,
)

self.scores[message.author.id] = self.scores.get(message.author.id, 0) + 1
await message.reply(
f"Correct! {message.author.mention} guessed **{beatmapset.title}** by **{beatmapset.artist}**.",
)

async def wait_for_continue(self) -> None:
continue_view = OsudleContinueView(self)
message = await self.interaction.channel.send(
content="Due to Discord limitations of 15 minutes for interactions, you must confirm that you are still here.\nClick the button below to continue.",
view=continue_view,
)

timed_out = await continue_view.wait()
if timed_out:
await message.edit(
content="Stopping the game due to inactivity.",
view=None,
)
await self.stop_game()
raise asyncio.TimeoutError

await message.edit(content="Continuing the game...", view=None)

async def edit_latest_message(self, *args, **kwargs) -> None:
if self.latest_beatmapset_message:
return await self.latest_beatmapset_message.edit(*args, **kwargs)
Expand All @@ -126,6 +142,12 @@ async def do_next(self) -> None:
if self.current_guess_task:
raise RuntimeError("Task already running.")

if (
time.monotonic() - self.last_interaction_time
> CONTINUE_TIMEOUT_MINUTES * 60
):
await self.wait_for_continue()

beatmapset = await self.get_beatmapset()
await self.send_beatmapset_message(beatmapset)

Expand Down Expand Up @@ -208,11 +230,9 @@ async def stop_game(self) -> None:

async def send_beatmapset_message(self, beatmapset: Beatmapset) -> None:
content = await self.get_message_content(beatmapset)
guess_view = discord.ui.View()
guess_view.add_item(OsudleSkipButton(self))
self.latest_beatmapset_message = await self.send_response(
**content,
view=guess_view,
view=OsudleGuessView(self),
)

@abstractmethod
Expand Down
5 changes: 2 additions & 3 deletions src/common/humanizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ def song_title_match(guess: str, answer: str) -> bool:
]

alphanumeric = alphanumeric_rx.split(answer)[0]
print(alphanumeric)
minimum_words_required = 2 if len(answer.split(" ")) > 2 else 1
guess_length = max(len(alphanumeric.split(" ")), minimum_words_required)
minimum_words_required = 2 if len(alphanumeric.split(" ")) > 2 else 1
guess_length = max(len(guess.split(" ")), minimum_words_required)

for combo in banned_word_combos:
if fuzzy_string_match(guess, combo):
Expand Down
2 changes: 2 additions & 0 deletions src/ui/menus/osu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
from __future__ import annotations

from .osubeatmapview import OsuBeatmapView
from .osudleview import OsudleContinueView
from .osudleview import OsudleGuessView
from .osuscoresview import OsuScoresView
from .osuskinsview import OsuSkinsView
44 changes: 44 additions & 0 deletions src/ui/menus/osu/osudleview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
###
# Copyright (c) 2024 NiceAesth. All rights reserved.
###

from __future__ import annotations

import time
from typing import TYPE_CHECKING

import discord

if TYPE_CHECKING:
from classes.osudle import BaseOsudleGame


class OsudleGuessView(discord.ui.View):
def __init__(self, game: object) -> None:
super().__init__(timeout=game.interaction_timeout)
self.game: BaseOsudleGame = game

@discord.ui.button(label="Skip", style=discord.ButtonStyle.danger)
async def skip_button(
self,
interaction: discord.Interaction,
button: discord.ui.Button,
):
await self.game.skip(interaction)


class OsudleContinueView(discord.ui.View):
def __init__(self, game) -> None:
super().__init__(timeout=game.interaction_timeout)
self.game = game

@discord.ui.button(label="Continue", style=discord.ButtonStyle.primary)
async def continue_button(
self,
interaction: discord.Interaction,
button: discord.ui.Button,
):
await interaction.response.defer()
self.game.last_interaction_time = time.monotonic()
self.game.interaction = interaction
self.stop()

0 comments on commit 4c19adb

Please sign in to comment.