Compare commits

..

No commits in common. "5f9ae3e145a22235df8f71be169b134266a33cb0" and "fb91302032e1a995cd60f56c3dcff47ae8925997" have entirely different histories.

3 changed files with 138 additions and 48 deletions

View File

@ -18,9 +18,3 @@ usage: hhirlstats [-h] [-v] [-c] [-Q {gametime,moves,nicknames,playtime,usage}]
Recommended to cache replays (`-c`) to save issuing requests to Showdown on every query.
Run the program once with all your replays (or once for each replay), then start querying.
## Future work
- include timestamps in logs to correlate KOs with the mon that KOed
- calculate gametime based on active turns rather than moves used
- also solves the issue where paralyzed/confused turns are not counted

View File

@ -12,4 +12,3 @@ https://replay.pokemonshowdown.com/dl-gen9paldeadexposthomedraft-29698
https://replay.pokemonshowdown.com/dl-gen9paldeadexposthomedraft-30488
https://replay.pokemonshowdown.com/dl-gen9paldeadexposthomedraft-30858
https://replay.pokemonshowdown.com/dl-gen9paldeadexposthomedraft-31241
https://replay.pokemonshowdown.com/dl-gen9paldeadexposthomedraft-31809

179
main.py
View File

@ -46,21 +46,17 @@ def _init_db(conn: sqlite3.Connection):
UNIQUE(game, turn, player, user)
);
CREATE TABLE IF NOT EXISTS switches(
game, turn, player, name,
UNIQUE(game, turn, player, name)
game, turn, name,
UNIQUE(game, turn, name)
);
CREATE TABLE IF NOT EXISTS nicknames(
game, player, name, specie,
UNIQUE(game, player, specie)
UNIQUE(game,player, specie)
);
CREATE TABLE IF NOT EXISTS knockouts(
game, turn, player, name,
UNIQUE(game, turn, player)
);
CREATE TABLE IF NOT EXISTS games(
id, p1, p2, format, uploadtime,
UNIQUE(id)
)
"""
)
conn.execute(
"""
"""
)
@ -100,11 +96,11 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
player, name = resolve_mon(name)
conn.execute(
"""
INSERT INTO switches(game, turn, player, name)
VALUES (?, ?, ?, ?)
INSERT INTO switches(game, turn, name)
VALUES (?, ?, ?)
ON CONFLICT DO NOTHING
""",
(game, turn, player, name),
(game, turn, name),
)
conn.execute(
"""
@ -114,20 +110,116 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
""",
(game, player, name, specie.split(", ")[0]),
)
case ["faint", mon]:
player, mon = resolve_mon(mon)
conn.execute(
"""
INSERT INTO knockouts(game, turn, player, name)
VALUES(?, ?, ?, ?)
ON CONFLICT DO NOTHING
""",
(game, turn, player, mon),
)
case _:
debug(f"unhandled message {chunks[0]}")
QUERIES = ["gametime", "moves", "nicknames", "playtime", "usage"]
def query(type: str, conn: sqlite3.Connection):
match type:
case "gametime":
print("Longest games")
print("=============")
for row in conn.execute(
"""
SELECT game, MAX(turn) AS n
FROM moves
GROUP BY game
ORDER BY n DESC
LIMIT 5
"""
):
replay = fetch(row.game, cache=True)
print(f"{replay.p1} vs {replay.p2}: {row.n} turns")
case "moves":
print("Move usage overall")
print("==================")
for row in conn.execute(
"""
SELECT name, COUNT(*) AS n
FROM moves
GROUP BY name
ORDER BY n DESC, name
LIMIT 10
"""
):
print(f"{row.name}: {row.n}")
case "nicknames":
print("Nickname usage per player")
print("=========================")
for row_p in conn.execute("SELECT DISTINCT player FROM nicknames"):
print(row_p.player)
for row_s in conn.execute(
"SELECT DISTINCT specie FROM nicknames WHERE player = ?",
(row_p.player,),
):
print(f" {row_s.specie}: ", end="")
names = []
for row in conn.execute(
"""
SELECT player, specie, name, count(game) AS n
FROM nicknames
WHERE player = ? AND specie = ?
GROUP BY player, specie, name
ORDER BY player, specie, name
""",
(row_p.player, row_s.specie),
):
names.append(f"{row.name} (x{row.n})")
print(*names, sep=", ")
case "playtime":
print("Active playtime per Pokemon")
print("===========================")
for row in conn.execute(
"""
SELECT m.player, k.specie, COUNT(m.name) AS n
FROM moves m
LEFT JOIN nicknames k ON (m.game, m.player, m.user) = (k.game, k.player, k.name)
GROUP BY k.specie, m.player
ORDER BY n DESC, k.specie, m.player
LIMIT 10
"""
):
print(f"{row.specie} ({row.player}): {row.n} turns")
case "usage":
print("Pokemon usage per player")
print("========================")
games = {
r.player: r.n
for r in conn.execute(
"""
SELECT player, COUNT(m.game) AS n
FROM (SELECT DISTINCT player, game FROM moves) m
GROUP BY player
"""
)
}
for row_p in conn.execute("SELECT DISTINCT player FROM nicknames"):
print(row_p.player)
for row_s in conn.execute(
"""
SELECT specie, COUNT(game) AS n
FROM nicknames
WHERE player = ?
GROUP BY specie
ORDER BY n DESC, specie
""",
(row_p.player,),
):
print(
f" {row_s.specie}: {row_s.n}"
f" ({row_s.n / games[row_p.player] * 100:.2f}%)"
)
case _:
error(f"unknown query {type}")
@dataclass(frozen=True)
class Replay:
id: str
@ -174,7 +266,13 @@ def main():
"-v", "--verbose", action="store_true", help="add debugging info"
)
parser.add_argument("-c", "--cache", action="store_true", help="cache replays")
parser.add_argument("replay", nargs="+", help="replay ID or URL")
parser.add_argument(
"-Q",
"--query",
choices=QUERIES,
help="run query instead of download",
)
parser.add_argument("replay", nargs="*", help="replay ID or URL")
args = parser.parse_args(sys.argv[1:])
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
@ -183,24 +281,23 @@ def main():
db = sqlite3.connect("data.db")
_init_db(db)
for r in args.replay:
try:
replay = fetch(r, cache=args.cache)
except Exception as e:
error(f"bad replay {r}")
continue
if args.query:
query(args.query, db)
else:
if not args.replay:
parser.print_usage()
print(f"{APP}: error: either query or replay arguments are required")
sys.exit(1)
db.execute(
"""
INSERT INTO games(id, p1, p2, format, uploadtime)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING
""",
(replay.id, replay.p1, replay.p2, replay.format, replay.uploadtime),
)
for r in args.replay:
try:
replay = fetch(r, cache=args.cache)
except Exception as e:
error(f"bad replay {r}")
continue
parse_log(replay.id, replay.log, into=db)
db.commit()
parse_log(replay.id, replay.log, into=db)
db.commit()
finally:
db.close()