Add match, turn and history views with a dark theme
- Top bar switching between Deck, Match, Turn and History views - Match view: life, player counters, commanders, emblems, zone counts, turn timer and timeouts, land drops used/allowed, play/draw and Bo3 draw counts, with the deck view below - Turn view: per-player and total stats per turn with history, plus whole-game totals and land drops - History: every finished game (colours, winner, who was on the play); click to reopen it in the other views; imports Player-prev.log - Customize menu to show/hide items and an Arrange dialog to reorder deck sections and turn stats, remembered across sessions - Static dark theme with mint accent, rounded corners, larger text - Commanders tracked separately from emblems, with location and tax - Quieter, non-sticky warning for cards lost to MTGA log gaps - Correct Scryfall images for tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7dc2ce0da8
commit
5b3c8c8e63
22 files changed
+2599
-193
No files matched your search
@@ -0,0 +1,67 @@
|
||||
from mtga_assistant.history import HistoryRecorder, HistoryStore, decode, encode, import_log, replay_game
|
||||
from mtga_assistant.logparse import LogParser
|
||||
|
||||
from test_gamestate import FIXTURE, ME
|
||||
|
||||
|
||||
def parse(path):
|
||||
p = LogParser()
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return [ev for line in f for ev in p.feed(line)]
|
||||
|
||||
|
||||
def test_events_round_trip():
|
||||
for ev in parse(FIXTURE):
|
||||
enc = encode(ev)
|
||||
if enc is None:
|
||||
continue
|
||||
back = decode(enc)
|
||||
assert type(back) is type(ev)
|
||||
|
||||
|
||||
def test_import_records_each_game_once(tmp_path):
|
||||
store = HistoryStore(tmp_path / "h.db")
|
||||
assert import_log(FIXTURE, store, None) == 1
|
||||
assert import_log(FIXTURE, store, None) == 0 # already recorded
|
||||
games = store.games()
|
||||
assert [g.game_number for g in games] == [2, 1] # newest first
|
||||
g1 = games[1]
|
||||
assert g1.opponent == "Opponent Player"
|
||||
assert g1.result == "Won" and games[0].result == "Won"
|
||||
assert g1.best_of == 3
|
||||
assert g1.on_play == "me"
|
||||
assert g1.deck_name == "Cruel Tutelage"
|
||||
assert g1.replayable
|
||||
|
||||
|
||||
def test_replay_stops_at_the_requested_game(tmp_path):
|
||||
store = HistoryStore(tmp_path / "h.db")
|
||||
import_log(FIXTURE, store, None)
|
||||
events = store.events(store.games()[0].match_id)
|
||||
t1 = replay_game(events, 1, None)
|
||||
assert t1.game.game_number == 1
|
||||
assert t1.game.on_play == ME
|
||||
assert t1.game_results == ["Won"]
|
||||
t2 = replay_game(events, 2, None)
|
||||
assert t2.game.game_number == 2
|
||||
assert t2.game_results == ["Won", "Won"]
|
||||
assert t2.games_on_draw() == {2: 1}
|
||||
|
||||
|
||||
def test_events_pruned_beyond_cap_but_summaries_kept(tmp_path):
|
||||
store = HistoryStore(tmp_path / "h.db", max_events_mb=0)
|
||||
import_log(FIXTURE, store, None)
|
||||
games = store.games()
|
||||
assert len(games) == 2 and not any(g.replayable for g in games)
|
||||
assert store.events(games[0].match_id) == []
|
||||
|
||||
|
||||
def test_recorder_ignores_unfinished_match(tmp_path):
|
||||
from mtga_assistant.gamestate import MatchTracker
|
||||
store = HistoryStore(tmp_path / "h.db")
|
||||
rec, t = HistoryRecorder(store), MatchTracker()
|
||||
events = parse(FIXTURE)
|
||||
for ev in events[:-3]: # stop before the match completes
|
||||
t.handle(ev)
|
||||
rec.observe(ev, t)
|
||||
assert store.games() == []
|
||||
Reference in new issue
Block a user