Spaces:
Runtime error
Runtime error
Store results
Browse files- backend/main.py +23 -6
- backend/stats.py +6 -2
backend/main.py
CHANGED
|
@@ -49,7 +49,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
| 49 |
from fastapi.staticfiles import StaticFiles
|
| 50 |
from fastapi.responses import FileResponse
|
| 51 |
|
| 52 |
-
from game.bot import BOT_PLAYER_ID, BotPlayer
|
| 53 |
from game.commands import ActionType
|
| 54 |
from game.engine import GameEngine
|
| 55 |
from game.map import MAP_LANDMARKS
|
|
@@ -75,16 +75,33 @@ log = logging.getLogger(__name__)
|
|
| 75 |
|
| 76 |
def _record_game_over(room, winner_id: str, winner_name: str) -> None:
|
| 77 |
"""Persist stats for tutorial completion or PvP win."""
|
| 78 |
-
# Never record
|
| 79 |
-
if winner_id
|
| 80 |
return
|
| 81 |
import time as _time
|
| 82 |
duration = int(_time.time() - room.started_at) if room and room.started_at else None
|
| 83 |
engine = engines.get(room.room_id) if room else None
|
| 84 |
is_tutorial = bool(engine and engine.state.is_tutorial) if engine else False
|
| 85 |
event_type = "tutorial_complete" if is_tutorial else "game_won"
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
if not is_tutorial and engine:
|
| 89 |
for pid, pstate in engine.state.players.items():
|
| 90 |
if pid != winner_id and pid != BOT_PLAYER_ID:
|
|
@@ -92,7 +109,7 @@ def _record_game_over(room, winner_id: str, winner_name: str) -> None:
|
|
| 92 |
break
|
| 93 |
# If opponent was the bot, label it
|
| 94 |
if opponent_name is None and any(pid == BOT_PLAYER_ID for pid in engine.state.players):
|
| 95 |
-
opponent_name =
|
| 96 |
try:
|
| 97 |
_stats.record(winner_name, event_type, duration, opponent_name)
|
| 98 |
log.info("Stat recorded: %s %s vs %s %ss", winner_name, event_type, opponent_name, duration)
|
|
|
|
| 49 |
from fastapi.staticfiles import StaticFiles
|
| 50 |
from fastapi.responses import FileResponse
|
| 51 |
|
| 52 |
+
from game.bot import BOT_PLAYER_ID, BOT_PLAYER_NAME, BotPlayer
|
| 53 |
from game.commands import ActionType
|
| 54 |
from game.engine import GameEngine
|
| 55 |
from game.map import MAP_LANDMARKS
|
|
|
|
| 75 |
|
| 76 |
def _record_game_over(room, winner_id: str, winner_name: str) -> None:
|
| 77 |
"""Persist stats for tutorial completion or PvP win."""
|
| 78 |
+
# Never record tutorial dummy wins
|
| 79 |
+
if winner_id == TUTORIAL_DUMMY_ID:
|
| 80 |
return
|
| 81 |
import time as _time
|
| 82 |
duration = int(_time.time() - room.started_at) if room and room.started_at else None
|
| 83 |
engine = engines.get(room.room_id) if room else None
|
| 84 |
is_tutorial = bool(engine and engine.state.is_tutorial) if engine else False
|
| 85 |
event_type = "tutorial_complete" if is_tutorial else "game_won"
|
| 86 |
+
|
| 87 |
+
# When the bot wins, record the match with the human opponent so it appears
|
| 88 |
+
# in match history — bot wins are excluded from the leaderboard in stats.py
|
| 89 |
+
if winner_id == BOT_PLAYER_ID:
|
| 90 |
+
opponent_name: str | None = None
|
| 91 |
+
if engine:
|
| 92 |
+
for pid, pstate in engine.state.players.items():
|
| 93 |
+
if pid != BOT_PLAYER_ID:
|
| 94 |
+
opponent_name = pstate.player_name
|
| 95 |
+
break
|
| 96 |
+
try:
|
| 97 |
+
_stats.record(BOT_PLAYER_NAME, event_type, duration, opponent_name)
|
| 98 |
+
log.info("Stat recorded: %s %s vs %s %ss", BOT_PLAYER_NAME, event_type, opponent_name, duration)
|
| 99 |
+
except Exception:
|
| 100 |
+
log.exception("Failed to record stat")
|
| 101 |
+
return
|
| 102 |
+
|
| 103 |
+
# Resolve opponent name for PvP/bot matches
|
| 104 |
+
opponent_name = None
|
| 105 |
if not is_tutorial and engine:
|
| 106 |
for pid, pstate in engine.state.players.items():
|
| 107 |
if pid != winner_id and pid != BOT_PLAYER_ID:
|
|
|
|
| 109 |
break
|
| 110 |
# If opponent was the bot, label it
|
| 111 |
if opponent_name is None and any(pid == BOT_PLAYER_ID for pid in engine.state.players):
|
| 112 |
+
opponent_name = BOT_PLAYER_NAME
|
| 113 |
try:
|
| 114 |
_stats.record(winner_name, event_type, duration, opponent_name)
|
| 115 |
log.info("Stat recorded: %s %s vs %s %ss", winner_name, event_type, opponent_name, duration)
|
backend/stats.py
CHANGED
|
@@ -87,8 +87,11 @@ def get_tutorial_leaderboard(limit: int = 50) -> list[dict]:
|
|
| 87 |
return [dict(r) for r in rows]
|
| 88 |
|
| 89 |
|
|
|
|
|
|
|
|
|
|
| 90 |
def get_wins_leaderboard(limit: int = 50) -> list[dict]:
|
| 91 |
-
"""Most 1v1 wins, ordered by win count then best time."""
|
| 92 |
with _connect() as conn:
|
| 93 |
rows = conn.execute(
|
| 94 |
"""
|
|
@@ -98,11 +101,12 @@ def get_wins_leaderboard(limit: int = 50) -> list[dict]:
|
|
| 98 |
MAX(recorded_at) AS last_at
|
| 99 |
FROM player_stats
|
| 100 |
WHERE event_type = 'game_won'
|
|
|
|
| 101 |
GROUP BY player_name
|
| 102 |
ORDER BY wins DESC, best_s ASC NULLS LAST
|
| 103 |
LIMIT ?
|
| 104 |
""",
|
| 105 |
-
(
|
| 106 |
).fetchall()
|
| 107 |
return [dict(r) for r in rows]
|
| 108 |
|
|
|
|
| 87 |
return [dict(r) for r in rows]
|
| 88 |
|
| 89 |
|
| 90 |
+
_BOT_PLAYER_NAME = "Bot IA"
|
| 91 |
+
|
| 92 |
+
|
| 93 |
def get_wins_leaderboard(limit: int = 50) -> list[dict]:
|
| 94 |
+
"""Most 1v1 wins, ordered by win count then best time. Bots are excluded."""
|
| 95 |
with _connect() as conn:
|
| 96 |
rows = conn.execute(
|
| 97 |
"""
|
|
|
|
| 101 |
MAX(recorded_at) AS last_at
|
| 102 |
FROM player_stats
|
| 103 |
WHERE event_type = 'game_won'
|
| 104 |
+
AND player_name != ?
|
| 105 |
GROUP BY player_name
|
| 106 |
ORDER BY wins DESC, best_s ASC NULLS LAST
|
| 107 |
LIMIT ?
|
| 108 |
""",
|
| 109 |
+
(_BOT_PLAYER_NAME, limit),
|
| 110 |
).fetchall()
|
| 111 |
return [dict(r) for r in rows]
|
| 112 |
|