diff --git a/ark_nova_stats/bga_log_parser/fixtures/non_play_event.log.json b/ark_nova_stats/bga_log_parser/fixtures/non_play_event.log.json new file mode 100644 index 00000000..435606a0 --- /dev/null +++ b/ark_nova_stats/bga_log_parser/fixtures/non_play_event.log.json @@ -0,0 +1,9 @@ +{ + "uid": "669cd9fd255eb", + "type": "chooseActionCard", + "log": "${player_name} chooses action card ${action_card_name}${action_card_icon}${action_card_level} with strength ${strength_icon}${strength}", + "args": {"actionCard": {"id": 4, "strength": 5, "pId": 93481498, "extraDatas": null, "type": "Association", "status": 1, "level": 1}, "strength": 5, "player_name": "Darcelmaw", "player_id": 93481498, "i18n": ["action_card_name"], "action_card_name": "Association", "action_card_level": "I", "action_card_icon": "", "action_card_type": "Association", "preserve": ["action_card_type"], "strength_icon": ""}, + "lock_uuid": null, + "synchro": null, + "h": "939340" +} diff --git a/ark_nova_stats/bga_log_parser/fixtures/play_event.log.json b/ark_nova_stats/bga_log_parser/fixtures/play_event.log.json new file mode 100644 index 00000000..e2048735 --- /dev/null +++ b/ark_nova_stats/bga_log_parser/fixtures/play_event.log.json @@ -0,0 +1,12 @@ +{ + "uid": "669cd3629ee68", + "type": "buyAnimal", + "log": "${player_name} plays ${card_name} for ${amount_money} and places it in ${building_name}", + "args": {"card": {"id": "A445_CrestedPorcupine", "location": "inPlay", "state": 19, "pId": 94929538, "extraDatas": null}, "amount": 8, "amount_money": 8, "total": 5, "building": {"id": 8, "location": "board", "state": 1, "pId": 94929538, "type": "size-1", "x": 1, "y": 8, "rotation": 0}, "icons": {"Bird": 1, "Predator": 1, "Herbivore": 1, "Bear": 1, "Reptile": 1, "Pet": 0, "Primate": 0, "Africa": 0, "Europe": 1, "Asia": 1, "Americas": 1, "Australia": 0, "Partner-Zoo": 0, "AnimalsII": 0, "CardsII": 0, "Science": 1, "Fac": 0, "Rock": 2, "Water": 2, "SeaAnimal": 0}, "fromDisplay": false, "player_name": "Duci9", "player_id": 94929538, "i18n": ["building_name", "card_name"], "building_name": {"log": "a size-${n} enclosure", "args": {"n": 1}}, + "card_id": "A445_CrestedPorcupine", + "card_name": "Crested Porcupine", + "preserve": ["card_id"]}, + "lock_uuid": "one", + "synchro": "one", + "h": "08137c" +} diff --git a/ark_nova_stats/bga_log_parser/game_log.py b/ark_nova_stats/bga_log_parser/game_log.py index 4969a56a..e3b30c10 100644 --- a/ark_nova_stats/bga_log_parser/game_log.py +++ b/ark_nova_stats/bga_log_parser/game_log.py @@ -12,6 +12,20 @@ class GameLogEventData: synchro: Optional[int] = None h: Optional[str] = None + PLAY_LOGS = [ + "plays", + "supports a conservation project", + "and places it in", + "buys", + ] + + @property + def is_play_action(self) -> bool: + if "card_name" not in self.args: + return False + + return any(play_log in self.log for play_log in self.PLAY_LOGS) + @dataclass class GameLogEvent: @@ -19,13 +33,14 @@ class GameLogEvent: table_id: int packet_id: str packet_type: str - move_id: int time: int data: list[GameLogEventData] + move_id: Optional[int] = None def __post_init__(self): self.table_id = int(self.table_id) - self.move_id = int(self.move_id) + if self.move_id is not None: + self.move_id = int(self.move_id) self.time = int(self.time) self.data = [GameLogEventData(**x) for x in self.data] # type: ignore diff --git a/ark_nova_stats/bga_log_parser/game_log_test.py b/ark_nova_stats/bga_log_parser/game_log_test.py index d526da5b..93b974b6 100644 --- a/ark_nova_stats/bga_log_parser/game_log_test.py +++ b/ark_nova_stats/bga_log_parser/game_log_test.py @@ -4,7 +4,7 @@ import pytest from python.runfiles import Runfiles -from ark_nova_stats.bga_log_parser.game_log import GameLog +from ark_nova_stats.bga_log_parser.game_log import GameLog, GameLogEventData class TestGameLog: @@ -28,5 +28,29 @@ def test_parses_sample_game(self): assert x.winner is not None and "sorryimlikethis" == x.winner.name +class TestGameLogEventData: + def test_is_play_event_returns_true_for_play_action(self): + r = Runfiles.Create() + play_event_fixture = r.Rlocation( + "_main/ark_nova_stats/bga_log_parser/fixtures/play_event.log.json" + ) + with open(play_event_fixture, "r") as play_event_logfile: + play_log = json.loads(play_event_logfile.read().strip()) + + x = GameLogEventData(**play_log) + assert x.is_play_action + + def test_is_play_event_returns_false_for_other_actions(self): + r = Runfiles.Create() + non_play_event_fixture = r.Rlocation( + "_main/ark_nova_stats/bga_log_parser/fixtures/non_play_event.log.json" + ) + with open(non_play_event_fixture, "r") as non_play_event_logfile: + non_play_log = json.loads(non_play_event_logfile.read().strip()) + + x = GameLogEventData(**non_play_log) + assert not x.is_play_action + + if __name__ == "__main__": sys.exit(pytest.main([__file__] + sys.argv[1:])) diff --git a/ark_nova_stats/emu_cup/BUILD.bazel b/ark_nova_stats/emu_cup/BUILD.bazel new file mode 100644 index 00000000..f910a239 --- /dev/null +++ b/ark_nova_stats/emu_cup/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_python//python:defs.bzl", "py_binary") + +filegroup( + name = "game_data", + srcs = glob(["data/*.json"]), +) + +py_binary( + name = "analyze_games", + srcs = ["analyze_games.py"], + data = [":game_data"], + visibility = ["//:__subpackages__"], + deps = [ + "//ark_nova_stats/bga_log_parser:game_log", + "@rules_python//python/runfiles", + ], +) diff --git a/ark_nova_stats/emu_cup/analyze_games.py b/ark_nova_stats/emu_cup/analyze_games.py new file mode 100644 index 00000000..cb97cab0 --- /dev/null +++ b/ark_nova_stats/emu_cup/analyze_games.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import json +from collections import Counter +from pathlib import Path +from typing import Iterator + +from python.runfiles import Runfiles + +from ark_nova_stats.bga_log_parser.game_log import GameLog + + +def list_game_datafiles() -> Iterator[Path]: + r = Runfiles.Create() + known_game = Path( + r.Rlocation( + "_main/ark_nova_stats/emu_cup/data/539682665_sorryimlikethis_darcelmaw.json" + ) + ) + return known_game.parent.glob("*.json") + + +def main() -> int: + all_cards: Counter[str] = Counter() + event_logs: set[str] = set() + skipped_event_logs: set[str] = set() + + for p in list_game_datafiles(): + # print(p) + with open(p, "r") as f: + log = GameLog(**json.loads(f.read().strip())) + + game_cards = set() + for event in log.data.logs: + for event_data in event.data: + if not event_data.is_play_action: + continue + + game_cards.add(event_data.args["card_name"]) + + all_cards.update(game_cards) + + print("Most common cards:") + for card, count in all_cards.most_common(10): + print(f" - {card}: {count}") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ark_nova_stats/emu_cup/data/.gitignore b/ark_nova_stats/emu_cup/data/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/ark_nova_stats/emu_cup/data/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/ark_nova_stats/emu_cup/data/.gitkeep b/ark_nova_stats/emu_cup/data/.gitkeep new file mode 100644 index 00000000..e69de29b