Match history and win rate

Every finished match goes into a small sqlite database: mode, deck, both
classes, result, turn count and who started. The result tag arrives in the
last turn of the match, so the record is written then, not when the game
object is closed (that only happens once the next match starts). The unique
key is the session directory plus the match start time, so importing old
session logs into the same database can be repeated without duplicates.

The new window (from the menu) shows the totals, the win rate by deck and by
opponent class, and the recent matches. Classes are drawn as hero portraits
cut from the card render, with the class colour as a placeholder until the
image is downloaded. Every table sorts by any column: the sort key is hidden
data, not the visible text, so dates, turn counts and results sort correctly,
and numeric columns start descending.

tools/import_history.py backfills the database from the session logs already
on disk, tests/test_history.py covers the date handling, the idempotency and
the win rate maths.
This commit is contained in:
yusufipk
2026-07-28 00:04:24 +07:00
parent 4e67b34977
commit fa45c19d3b
11 changed files with 1442 additions and 12 deletions
+43
View File
@@ -37,6 +37,41 @@ STRINGS: dict[str, dict[str, str]] = {
"menu_draw_chance": {"tr": "Çekme yüzdesi", "en": "Draw chance"},
"menu_language": {"tr": "Dil", "en": "Language"},
"menu_reload_decks": {"tr": "Desteleri yeniden yükle", "en": "Reload decks"},
"menu_history": {"tr": "Maç geçmişi", "en": "Match history"},
"history_title": {"tr": "Maç geçmişi", "en": "Match history"},
"history_empty": {
"tr": "Henüz kayıtlı maç yok. Uygulama açıkken oynadığın maçlar buraya düşer.",
"en": "No matches recorded yet. Games you play with the app open land here.",
},
"history_totals": {
"tr": "{total} maç {wins}G {losses}M",
"en": "{total} matches {wins}W {losses}L",
},
"history_by_deck": {"tr": "DESTEYE GÖRE", "en": "BY DECK"},
"history_by_opponent": {"tr": "RAKİP SINIFINA GÖRE", "en": "BY OPPONENT CLASS"},
"history_recent": {"tr": "SON MAÇLAR", "en": "RECENT MATCHES"},
"history_all_modes": {"tr": "Tüm modlar", "en": "All modes"},
"history_no_deck": {"tr": "Deste yok", "en": "No deck"},
"column_date": {"tr": "Tarih", "en": "Date"},
"column_mode": {"tr": "Mod", "en": "Mode"},
"column_deck": {"tr": "Deste", "en": "Deck"},
"column_opponent": {"tr": "Rakip", "en": "Opponent"},
"column_result": {"tr": "Sonuç", "en": "Result"},
"column_turns": {"tr": "Tur", "en": "Turns"},
"column_record": {"tr": "G-M", "en": "W-L"},
"column_winrate": {"tr": "Galibiyet", "en": "Win rate"},
"percent": {"tr": "%{value}", "en": "{value}%"},
# strftime kalıbı. Ay adı yok: adlar sistem yerelinden geliyor, İngilizce
# arayüzde Türkçe ay kısaltması çıkıyordu.
"date_format": {"tr": "%d.%m %H:%M", "en": "%m-%d %H:%M"},
"coin_first": {"tr": "Önce başladın", "en": "You went first"},
"coin_second": {"tr": "Sonra başladın", "en": "You went second"},
"mode_RANKED": {"tr": "Dereceli", "en": "Ranked"},
"mode_CASUAL": {"tr": "Serbest", "en": "Casual"},
"mode_ARENA": {"tr": "Arena", "en": "Arena"},
"mode_VS_AI": {"tr": "Yapay zeka", "en": "vs AI"},
"mode_TAVERNBRAWL": {"tr": "Meyhane kapışması", "en": "Tavern Brawl"},
"mode_BATTLEGROUNDS": {"tr": "Battlegrounds", "en": "Battlegrounds"},
"menu_hide": {"tr": "Gizle (tepsiye)", "en": "Hide to tray"},
"menu_quit": {"tr": "Çıkış", "en": "Quit"},
"tray_toggle": {"tr": "Göster / Gizle", "en": "Show / Hide"},
@@ -62,6 +97,9 @@ STRINGS: dict[str, dict[str, str]] = {
},
}
# Maç sonucunun metin anahtarı. İşaretin kendisi theme.RESULT_MARKS içinde.
RESULT_KEYS = {"WON": "result_won", "LOST": "result_lost", "TIED": "result_tied"}
_current = "en"
@@ -82,6 +120,11 @@ def current() -> str:
return _current
def has(key: str) -> bool:
"""Anahtar sözlükte var mı? Çevirisi olmayan değerler ham gösterilsin diye."""
return key in STRINGS
def t(key: str, **kwargs) -> str:
text = STRINGS.get(key, {}).get(_current, key)
return text.format(**kwargs) if kwargs else text