Skip to content

Commit 69497e2

Browse files
authored
Merge pull request CSS360-2026-Winter#167 from rdum4/lobby-timeout
Lobby timeout
2 parents da07c8f + 3fe21ad commit 69497e2

2 files changed

Lines changed: 95 additions & 3 deletions

File tree

‎controllers/uno_cog.py‎

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def __init__(self, bot: commands.Bot):
4040
# Initialize renderer
4141
self._renderer = Renderer(self.lobby_service, self.game_service)
4242

43+
# Solo timer
44+
self._solo_lobby_timers: dict[int, asyncio.Task] = {}
45+
4346
@app_commands.command(name="create", description="Create a lobby in this channel.")
4447
async def create(self, interaction: discord.Interaction) -> None:
4548
"""
@@ -69,6 +72,9 @@ async def create(self, interaction: discord.Interaction) -> None:
6972
# Silently fail if we can't ping.
7073
pass
7174

75+
lobby.channel_id = cid
76+
asyncio.create_task(self.start_solo_lobby_timer(lobby))
77+
7278
@app_commands.command(
7379
name="play",
7480
description="Play a card from your hand on your turn (index starts at 0).",
@@ -184,7 +190,7 @@ async def kick(self, interaction: discord.Interaction, player: discord.Member):
184190
title="Player Not Found",
185191
)
186192

187-
self._kick_player(lobby, player.id)
193+
await self._kick_player(lobby, player.id, channel_id=cid)
188194

189195
except GameError as e:
190196
embed = self._renderer.lobby_views.error_embed(
@@ -206,9 +212,8 @@ async def _kick_player(
206212
Helper to remove a player from the game.
207213
afk=True changes messages to AFK-specific ones
208214
"""
209-
cid = channel_id
215+
cid = channel_id if channel_id is not None else lobby.channel_id
210216
game = lobby.game
211-
channel = self.bot.get_channel(cid)
212217

213218
try:
214219
game.kick_player(player_id)
@@ -338,6 +343,88 @@ def start_afk_timer(self, channel_id: int, lobby) -> None:
338343
self.run_afk_timer(channel_id, game.current_player(), game.turn_count())
339344
)
340345

346+
async def start_solo_lobby_timer(self, lobby):
347+
"""
348+
Starts a solo lobby timer for a lobby with only one player.
349+
Sends a separate countdown embed. Replaces it with a "Lobby expired" embed
350+
when timer ends and deletes the main lobby embed.
351+
"""
352+
channel = self.bot.get_channel(lobby.channel_id)
353+
if channel is None or not getattr(lobby, "main_message", None):
354+
return
355+
356+
timer_embed = discord.Embed(
357+
title="⏳ Solo Lobby Timer",
358+
description="Lobby expires in **120** seconds if nobody joins.",
359+
color=discord.Color.orange(),
360+
)
361+
362+
try:
363+
timer_msg = await channel.send(embed=timer_embed)
364+
except discord.HTTPException:
365+
return
366+
367+
total_sec = 120
368+
interval = 1
369+
370+
try:
371+
while total_sec > 0:
372+
if len(lobby.game.players()) > 1:
373+
await timer_msg.delete()
374+
return
375+
376+
minutes, seconds = divmod(total_sec, 60)
377+
timer_embed.description = (
378+
f"Lobby expires in **{minutes}:{seconds:02d}** if nobody joins."
379+
)
380+
381+
try:
382+
await timer_msg.edit(embed=timer_embed)
383+
except discord.HTTPException:
384+
pass
385+
386+
await asyncio.sleep(interval)
387+
total_sec -= interval
388+
389+
self.lobby_service.disband_lobby(lobby.channel_id, lobby.user)
390+
391+
timer_embed.title = "🕒 Lobby Expired"
392+
timer_embed.description = "The solo lobby has expired due to inactivity."
393+
try:
394+
await timer_msg.edit(embed=timer_embed)
395+
except discord.HTTPException:
396+
pass
397+
398+
try:
399+
main_msg = await channel.fetch_message(lobby.main_message)
400+
await main_msg.delete()
401+
except (discord.NotFound, discord.HTTPException):
402+
pass
403+
404+
except asyncio.CancelledError:
405+
try:
406+
await timer_msg.delete()
407+
except discord.HTTPException:
408+
pass
409+
410+
except (discord.HTTPException, discord.Forbidden, GameError) as e:
411+
print(f"Solo lobby timer error: {e}")
412+
413+
def restart_solo_lobby_timer(self, lobby):
414+
"""
415+
Starts the solo timer if there's 1 player, cancels it otherwise.
416+
"""
417+
cid = lobby.channel_id
418+
419+
task = self._solo_lobby_timers.get(cid)
420+
if task and not task.done():
421+
task.cancel()
422+
423+
if len(lobby.game.players()) == 1:
424+
self._solo_lobby_timers[cid] = asyncio.create_task(
425+
self.start_solo_lobby_timer(lobby)
426+
)
427+
341428

342429
async def setup(bot: commands.Bot) -> None:
343430
"""

‎ui/lobby_ui.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ async def leave(
7878
return
7979

8080
lobby = self.lobby_service.get_lobby(cid)
81+
82+
cog = interaction.client.get_cog("UnoCog")
83+
if cog:
84+
cog.restart_solo_lobby_timer(lobby)
85+
8186
await self._renderer.update_from_interaction(interaction, lobby)
8287

8388
@discord.ui.button(label="🚀 Start Game", style=discord.ButtonStyle.success)

0 commit comments

Comments
 (0)