Files
gitea-actionsandClaude Opus 5 d7c63cf98b stats: síň slávy a kariérní statistiky
- rekordy: nejvyšší tah, nejvyšší konečné skóre, nejrychlejší výhra
- kariéra: počet her, výher, úspěšnost, průměrné pořadí, průměr na tah
  a kolikrát koho postihlo vynulování
- do statistik jdou jen dohrané hry, nedohrané a opuštěné se ignorují
- celkové skóre počítá SQL stejným pravidlem jako core.Game.totals(),
  tedy součet tahů po posledním vynulování; testem ověřeno, že se obě
  cesty shodují
- vědomě chybí celkový součet bodů napříč hrami — cílové skóre je u každé
  hry jiné, metrika by odměňovala jen toho, kdo hrál nejvíc
- README se spuštěním a přehledem modulů

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 19:04:50 +02:00

125 lines
4.0 KiB
Python

"""Statistiky napříč hrami.
Dvě různé obrazovky, protože odpovídají na jiné otázky: síň slávy sbírá
rekordy, kariéra ukazuje, kdo je dlouhodobě dobrý.
Vědomě tu není celkový součet bodů napříč hrami — cílové skóre je u každé hry
jiné, hry nejsou srovnatelné a taková metrika by odměňovala jen toho, kdo hrál
nejvíc.
"""
import sqlite3
import storage
# Celkové skóre hráče ve hře: součet tahů po posledním vynulování.
# Stejné pravidlo jako v core.Game.totals(), jen vyjádřené v SQL.
TOTALS = """
WITH last_reset AS (
SELECT game_id, player_id, MAX(id) AS reset_id
FROM turns
WHERE reset = 1
GROUP BY game_id, player_id
),
totals AS (
SELECT gp.game_id,
gp.player_id,
COALESCE((
SELECT SUM(t.value)
FROM turns t
LEFT JOIN last_reset lr
ON lr.game_id = t.game_id AND lr.player_id = t.player_id
WHERE t.game_id = gp.game_id
AND t.player_id = gp.player_id
AND (lr.reset_id IS NULL OR t.id > lr.reset_id)
), 0) AS total
FROM game_players gp
)
"""
def records(conn: sqlite3.Connection) -> dict:
"""Síň slávy — tři rekordy, které jde překonat."""
best_turn = conn.execute("""
SELECT p.name, t.value, t.game_id, t.at
FROM turns t
JOIN players p ON p.id = t.player_id
ORDER BY t.value DESC, t.id ASC
LIMIT 1
""").fetchone()
best_score = conn.execute(TOTALS + """
SELECT p.name, totals.total, g.id AS game_id, g.finished_at
FROM totals
JOIN games g ON g.id = totals.game_id
JOIN players p ON p.id = totals.player_id
WHERE g.status = ?
ORDER BY totals.total DESC
LIMIT 1
""", (storage.STATUS_FINISHED,)).fetchone()
fastest = conn.execute("""
SELECT p.name, g.id AS game_id, MAX(t.round) AS rounds, g.finished_at
FROM games g
JOIN turns t ON t.game_id = g.id
JOIN players p ON p.id = g.winner_id
WHERE g.status = ?
GROUP BY g.id
ORDER BY rounds ASC, g.id ASC
LIMIT 1
""", (storage.STATUS_FINISHED,)).fetchone()
return {"best_turn": best_turn, "best_score": best_score, "fastest": fastest}
def careers(conn: sqlite3.Connection) -> list[dict]:
"""Kdo je dlouhodobě dobrý. Počítají se jen dohrané hry."""
placings = conn.execute(TOTALS + """
, ranked AS (
SELECT totals.game_id,
totals.player_id,
RANK() OVER (PARTITION BY totals.game_id
ORDER BY totals.total DESC) AS placing
FROM totals
JOIN games g ON g.id = totals.game_id
WHERE g.status = ?
)
SELECT r.player_id,
p.name,
COUNT(*) AS games,
SUM(CASE WHEN g.winner_id = r.player_id THEN 1 ELSE 0 END) AS wins,
AVG(r.placing) AS placing
FROM ranked r
JOIN players p ON p.id = r.player_id
JOIN games g ON g.id = r.game_id
GROUP BY r.player_id
""", (storage.STATUS_FINISHED,)).fetchall()
turn_stats = conn.execute("""
SELECT t.player_id,
AVG(t.value) AS per_turn,
SUM(t.reset) AS wipeouts
FROM turns t
JOIN games g ON g.id = t.game_id
WHERE g.status = ?
GROUP BY t.player_id
""", (storage.STATUS_FINISHED,)).fetchall()
by_player = {row["player_id"]: row for row in turn_stats}
table = []
for row in placings:
turns = by_player.get(row["player_id"])
table.append({
"name": row["name"],
"games": row["games"],
"wins": row["wins"],
"share": row["wins"] / row["games"] if row["games"] else 0,
"placing": row["placing"],
"per_turn": turns["per_turn"] if turns else 0,
"wipeouts": turns["wipeouts"] if turns else 0,
})
table.sort(key=lambda r: (-r["share"], r["placing"], -r["games"]))
return table