fix rusenie hier ked vsetkych odpoji
This commit is contained in:
+24
-5
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import hmac
|
import hmac
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -273,14 +274,32 @@ async def send_error(sid: str, message: str):
|
|||||||
await sio.emit("error", {"error": message}, to=sid)
|
await sio.emit("error", {"error": message}, to=sid)
|
||||||
|
|
||||||
|
|
||||||
|
# Ako dlho prezije nezacata hra, ked su vsetci hraci naraz offline. Na mobile
|
||||||
|
# sa socket bezne strati uz pri zamknuti obrazovky, takze okamzite zmazanie
|
||||||
|
# hry rusilo lobby, v ktorom hraci len cakali so zhasnutym telefonom.
|
||||||
|
LOBBY_ABANDON_GRACE_SECONDS = 10 * 60
|
||||||
|
|
||||||
|
|
||||||
|
async def _cleanup_abandoned_lobby(gid: str):
|
||||||
|
"""Po grace periode zmaz nezacatu hru, ak sa medzitym nikto nevratil.
|
||||||
|
Podmienka sa overuje az po uplynuti casu, takze pri navrate hraca je
|
||||||
|
task neskodny no-op (netreba nic rusit)."""
|
||||||
|
await asyncio.sleep(LOBBY_ABANDON_GRACE_SECONDS)
|
||||||
|
game = games.get(gid)
|
||||||
|
if game is not None and not game.started and not any(p.connected for p in game.players):
|
||||||
|
del games[gid]
|
||||||
|
await broadcast_lobby()
|
||||||
|
|
||||||
|
|
||||||
async def _mark_player_offline(game: "Game", player: "Player"):
|
async def _mark_player_offline(game: "Game", player: "Player"):
|
||||||
"""Mark player disconnected. An unstarted game with nobody left is cleaned
|
"""Mark player disconnected. An unstarted game with nobody left gets a
|
||||||
up; a started game is kept in memory so it stays in the lobby and can be
|
delayed cleanup (mobile sockets drop on screen lock, so an immediate
|
||||||
resumed (it's torn down only by end_game)."""
|
delete would kill lobbies where everyone is just waiting); a started game
|
||||||
|
is kept in memory so it stays in the lobby and can be resumed (it's torn
|
||||||
|
down only by end_game)."""
|
||||||
player.connected = False
|
player.connected = False
|
||||||
if not any(p.connected for p in game.players) and not game.started:
|
if not any(p.connected for p in game.players) and not game.started:
|
||||||
del games[game.gid]
|
asyncio.create_task(_cleanup_abandoned_lobby(game.gid))
|
||||||
else:
|
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"player_connection",
|
"player_connection",
|
||||||
{"order": player.order, "connected": False},
|
{"order": player.order, "connected": False},
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ export function setupSocketListeners() {
|
|||||||
|
|
||||||
// Surface connection failures instead of silently buffering emits.
|
// Surface connection failures instead of silently buffering emits.
|
||||||
socket.on('connect_error', (err: Error) => {
|
socket.on('connect_error', (err: Error) => {
|
||||||
|
// `active` = klient sa automaticky pokusi znova (typicky prebudeny mobil,
|
||||||
|
// kym sa siet zobudi) -- prechodne, netreba strasit toastom.
|
||||||
|
if (socket.active) return;
|
||||||
useGameStore.getState().setError(`Spojenie so serverom zlyhalo: ${err.message}`);
|
useGameStore.getState().setError(`Spojenie so serverom zlyhalo: ${err.message}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useGameStore } from '../store/gameStore';
|
import { useGameStore } from '../store/gameStore';
|
||||||
import { emit } from '../lib/socket';
|
import { emit } from '../lib/socket';
|
||||||
@@ -8,6 +9,8 @@ export default function Lobby() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const myPlayer = useGameStore((s) => s.myPlayer);
|
const myPlayer = useGameStore((s) => s.myPlayer);
|
||||||
const games = useGameStore((s) => s.games);
|
const games = useGameStore((s) => s.games);
|
||||||
|
const [showCopied, setShowCopied] = useState(false);
|
||||||
|
const copiedTimeout = useRef<ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
const game = games.find((g) => g.gid === gid);
|
const game = games.find((g) => g.gid === gid);
|
||||||
const players = game?.players ?? [];
|
const players = game?.players ?? [];
|
||||||
@@ -17,11 +20,25 @@ export default function Lobby() {
|
|||||||
const handleLeave = () => leaveGame(navigate);
|
const handleLeave = () => leaveGame(navigate);
|
||||||
|
|
||||||
const handleCopyCode = () => {
|
const handleCopyCode = () => {
|
||||||
if (gid) navigator.clipboard.writeText(`${window.location.origin}/lobby/${gid}`);
|
if (!gid) return;
|
||||||
|
navigator.clipboard.writeText(`${window.location.origin}/lobby/${gid}`);
|
||||||
|
setShowCopied(true);
|
||||||
|
clearTimeout(copiedTimeout.current);
|
||||||
|
copiedTimeout.current = setTimeout(() => setShowCopied(false), 1800);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => () => clearTimeout(copiedTimeout.current), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto p-4 pt-8 min-h-screen">
|
<div className="max-w-md mx-auto p-4 pt-8 min-h-screen">
|
||||||
|
<div
|
||||||
|
className={`fixed top-4 left-1/2 -translate-x-1/2 px-4 py-2 rounded-lg bg-header border border-gold/30 text-gold text-sm shadow-lg transition-opacity duration-300 z-50 ${
|
||||||
|
showCopied ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Odkaz na hru skopírovaný
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h1 className="font-serif text-2xl text-gold">{game?.name ?? 'Hra'}</h1>
|
<h1 className="font-serif text-2xl text-gold">{game?.name ?? 'Hra'}</h1>
|
||||||
<button onClick={handleLeave} className="text-sm text-green-dim hover:text-gold">
|
<button onClick={handleLeave} className="text-sm text-green-dim hover:text-gold">
|
||||||
@@ -38,7 +55,7 @@ export default function Lobby() {
|
|||||||
onClick={handleCopyCode}
|
onClick={handleCopyCode}
|
||||||
className="ml-3 px-3 py-1 rounded-lg text-sm border border-gold/30 text-gold hover:bg-gold hover:text-table transition-colors"
|
className="ml-3 px-3 py-1 rounded-lg text-sm border border-gold/30 text-gold hover:bg-gold hover:text-table transition-colors"
|
||||||
>
|
>
|
||||||
Kopírovať
|
Kopírovať URL
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user