Skip to content

Instantly share code, notes, and snippets.

@dermorz
Last active April 25, 2022 22:52
Show Gist options
  • Save dermorz/dc5082ac1252130ce4baf355cd735c39 to your computer and use it in GitHub Desktop.
Save dermorz/dc5082ac1252130ce4baf355cd735c39 to your computer and use it in GitHub Desktop.
Simple local Starcraft 2 replay analyser. Has some bugs but works for me. :)
#!/usr/bin/env python
"""Simple Starcraft 2 replay analysis
When playing I run...
$ watch ./parse_replay.py
...which always shows me the data of the most recent replay.
"""
import glob
import os
import pathlib
import sys
from zephyrus_sc2_parser import parse_replay
from zephyrus_sc2_parser.exceptions import MissingMmrError
# Hardcoded Battle.net identity
# Find it in your Starcraft Folder (e.g. $HOME/Starcraft II/Accounts/<some number>/<TOON_HANDLE>
TOON_HANDLE = "2-S2-1-1122334"
# Not sure if this is correct. I Eyeballed the value from a test replay.
ONE_BASE_MINERALS = 940
def get_player_id(players):
"""Determine our player id"""
for i, p in players.items():
if f"{p.region_id}-S2-{p.realm_id}-{p.profile_id}" == TOON_HANDLE:
return p.player_id
# Fallback if neither player is us (parsing replay from a friend)
choice = input(f"1 ({players[1].name}) or 2 ({players[2].name}): ")
if choice not in ("1", "2"):
print("Valid values: 1 or 2, dummy.")
sys.exit(1)
return int(choice)
def timestamp(frame):
"""Convert starcraft time unit into human readable timestamp"""
if frame < 0:
return "n/a"
seconds = round(frame / 22.4, 1)
return f"{int(seconds // 60)}:{int(seconds % 60):02}"
def analyze(replay):
"""HUGE hacky method to do all the stuff. Getting data and printing it."""
players, timeline, engagements, summary, metadata = replay
player_id = get_player_id(players)
for p in players.values():
print(
f"{p.name:<20}{p.race:<10}{summary['mmr'][p.player_id] or 'Game vs AI':>10}"
)
print()
print(
f"{'Duration:':<25}{replay.metadata['game_length']//60}:{replay.metadata['game_length']%60:0>2}"
)
print(f"{'Supply Block:':<20}{int(summary['supply_block'][player_id]):>9}s")
print(
f"{'Average idle larva:':<20}{summary['race'][player_id]['avg_idle_larva']:>10}"
)
inject_efficiency = summary["race"][player_id]["inject_efficiency"]
avg_inject_efficiency = sum(hatch[0] for hatch in inject_efficiency) / len(
inject_efficiency
)
print(f"{'Inject efficiency:':<20}{avg_inject_efficiency:>10.2%}")
print(f"{'SQ:':<20}{summary['sq'][player_id]:>10}")
print(f"{'Workers created:':<20}{summary['workers_produced'][player_id]:>10}")
print()
maxed_at = "n/a"
sixtysix_workers_at = "n/a"
one_base_saturation_at = "n/a"
two_base_saturation_at = "n/a"
three_base_saturation_at = "n/a"
supply_counts = {
4707: None, # 3:30
5376: None, # 4:00
6720: None, # 5:00
8072: None, # 6:00
}
for tick in timeline:
now = tick[player_id]
if maxed_at == "n/a" and now["supply"] >= 199:
maxed_at = timestamp(now["gameloop"])
if sixtysix_workers_at == "n/a" and now["workers_active"] >= 66:
sixtysix_workers_at = timestamp(now["gameloop"])
if (
one_base_saturation_at == "n/a"
and now["resource_collection_rate"]["minerals"] >= ONE_BASE_MINERALS
):
one_base_saturation_at = timestamp(now["gameloop"])
if (
two_base_saturation_at == "n/a"
and now["resource_collection_rate"]["minerals"] >= 2 * ONE_BASE_MINERALS
):
two_base_saturation_at = timestamp(now["gameloop"])
if (
three_base_saturation_at == "n/a"
and now["resource_collection_rate"]["minerals"] >= 3 * ONE_BASE_MINERALS
):
three_base_saturation_at = timestamp(now["gameloop"])
for k, v in supply_counts.items():
if now["gameloop"] <= k <= now["gameloop"] + 50 and v is None:
supply_counts[k] = {
"supply": f"{str(now['supply'])+'/'+str(now['supply_cap']):>7}",
"workers": now["workers_active"],
}
for k, v in supply_counts.items():
print(f"{timestamp(k)} " f"{v['supply']} " f"({v['workers']} Workers)")
print()
print(f"{'66 Workers at:':<33}{sixtysix_workers_at}")
print(f"{'Maxed at:':<33}{maxed_at}")
print(f"{'1 base saturation (minerals):':<33}{one_base_saturation_at}")
print(f"{'2 base saturation (minerals):':<33}{two_base_saturation_at}")
print(f"{'3 base saturation (minerals):':<33}{three_base_saturation_at}")
if __name__ == "__main__":
if len(sys.argv) < 2:
# Pick the latest replay file matching ~/replays/*.SC2Replay
# You might want to adjust this to your replay folder.
replays = glob.glob(os.path.join(pathlib.Path.home(), "replays", "*.SC2Replay"))
replay_file = max(replays, key=os.path.getctime)
else:
# or the replay file passed as argument.
replay_file = sys.argv[1]
try:
replay = parse_replay(replay_file, tick=22.4, network=False)
except MissingMmrError:
# Game vs AI
replay = parse_replay(replay_file, tick=22.4, local=True, network=False)
except Exception as e:
print("Could not load replay file:", e)
sys.exit(1)
analyze(replay)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment