mass refactor

- resolve nicknames as we go
- update column names
- better variables
This commit is contained in:
xeals 2023-04-04 21:03:41 +10:00
parent 262a051356
commit 8a348eca84
Signed by: xeals
GPG Key ID: A498C7AF27EC6B5C

211
main.py
View File

@ -79,23 +79,24 @@ def _init_db(conn: sqlite3.Connection):
conn.executescript( conn.executescript(
""" """
CREATE TABLE IF NOT EXISTS moves( CREATE TABLE IF NOT EXISTS moves(
game, turn, player, name, user, target, game, turn, player, pokemon, move, target,
UNIQUE(game, turn, player, user) UNIQUE(game, turn, player, pokemon)
); );
CREATE TABLE IF NOT EXISTS switches( CREATE TABLE IF NOT EXISTS switches(
game, turn, player, name, game, turn, player, pokemon,
UNIQUE(game, turn, player, name) UNIQUE(game, turn, player, pokemon)
); );
CREATE TABLE IF NOT EXISTS nicknames( CREATE TABLE IF NOT EXISTS nicknames(
game, player, name, specie, game, player, pokemon, specie,
UNIQUE(game, player, specie) UNIQUE(game, player, specie)
); );
CREATE TABLE IF NOT EXISTS knockouts( CREATE TABLE IF NOT EXISTS knockouts(
game, turn, player, name, game, turn, player, pokemon,
UNIQUE(game, turn, player) UNIQUE(game, turn, player)
); );
CREATE TABLE IF NOT EXISTS indirect_knockouts( CREATE TABLE IF NOT EXISTS indirect_knockouts(
game, turn, player, name, source, source_user, source_player, game, turn, player, pokemon,
reason, source, source_player,
UNIQUE(game, turn, player) UNIQUE(game, turn, player)
); );
CREATE TABLE IF NOT EXISTS games( CREATE TABLE IF NOT EXISTS games(
@ -104,9 +105,9 @@ def _init_db(conn: sqlite3.Connection):
); );
-- No good way to ensure idempotence for damage; just re-build it. -- No good way to ensure idempotence for damage; just re-build it.
DROP TABLE IF EXISTS damage; DROP TABLE IF EXISTS damage;
CREATE TABLE damage(game, player, name, value); CREATE TABLE damage(game, player, pokemon, value);
DROP TABLE IF EXISTS indirect_damage; DROP TABLE IF EXISTS indirect_damage;
CREATE TABLE indirect_damage(game, player, name, value); CREATE TABLE indirect_damage(game, player, pokemon, value);
""" """
) )
@ -129,10 +130,36 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
# ("p1a: Meteo", "brn") => "p2a: Edward" # ("p1a: Meteo", "brn") => "p2a: Edward"
last_status_set: dict[tuple[str, str], str] = {} last_status_set: dict[tuple[str, str], str] = {}
def resolve_mon(user: str) -> tuple[str, str]: def split_pokemon(user: str) -> tuple[str, str]:
"""Splits a Pokemon identifier of the form `pXa: Pokemon` into the
player's name (as marked by the player log) and "Pokemon".
Note that all Pokemon are referred to by their nicknames, and will
require resolving to obtain the Pokemon specie."""
[player, name] = user.split(": ") [player, name] = user.split(": ")
return players[player.strip("ab")], name return players[player.strip("ab")], name
def specie_from_parts(player: str, nickname: str) -> str:
"""Resolves the species of a nicknamed Pokemon."""
return (
conn.execute(
"""
SELECT specie
FROM nicknames
WHERE (game, player, pokemon) = (?, ?, ?)
LIMIT 1
""",
(game, team(player), nickname),
)
.fetchall()[0]
.specie
)
def specie(pokemon: str) -> str:
"""Resolves the species of a Pokemon given its Showdown identifier (used
in split_pokemon)."""
return specie_from_parts(*split_pokemon(pokemon))
for line in log.split("\n"): for line in log.split("\n"):
chunks = line.split("|")[1:] chunks = line.split("|")[1:]
if not chunks: if not chunks:
@ -149,50 +176,85 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
case ["move", user, move, target]: case ["move", user, move, target]:
last_move = (user, target) last_move = (user, target)
player, user = resolve_mon(user) player, _ = split_pokemon(user)
_, target = resolve_mon(target)
conn.execute( conn.execute(
""" """
INSERT INTO moves(game, turn, player, name, user, target) INSERT INTO moves(game, turn, player, pokemon, move, target)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""", """,
(game, turn, team(player), move, user, target), (
game,
turn,
team(player),
specie(user),
move,
specie(target),
),
) )
case ["drag", name, specie, status, *rest]: case ["drag", name, specie_, status, *rest]:
hp[name] = int(status.split("/")[0]) hp[name] = int(status.split("/")[0])
case ["switch", name, specie, status, *rest]: # Also includes gender and formes.
hp[name] = int(status.split("/")[0]) trimmed_specie = specie_.split(", ")[0]
player, name = resolve_mon(name) player, nickname = split_pokemon(name)
conn.execute( conn.execute(
""" """
INSERT INTO switches(game, turn, player, name) INSERT INTO nicknames(game, player, pokemon, specie)
VALUES(?, ?, ?, ?)
ON CONFLICT DO NOTHING
""",
(game, team(player), nickname, trimmed_specie),
)
case ["replace", name, specie_]:
# Also includes gender and formes.
trimmed_specie = specie_.split(", ")[0]
player, nickname = split_pokemon(name)
conn.execute(
"""
INSERT INTO nicknames(game, player, pokemon, specie)
VALUES(?, ?, ?, ?)
ON CONFLICT DO NOTHING
""",
(game, team(player), nickname, trimmed_specie),
)
case ["switch", name, specie_, status, *rest]:
hp[name] = int(status.split("/")[0])
# Also includes gender and formes.
trimmed_specie = specie_.split(", ")[0]
player, nickname = split_pokemon(name)
conn.execute(
"""
INSERT INTO switches(game, turn, player, pokemon)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""", """,
(game, turn, team(player), name), (game, turn, team(player), trimmed_specie),
) )
conn.execute( conn.execute(
""" """
INSERT INTO nicknames(game, player, name, specie) INSERT INTO nicknames(game, player, pokemon, specie)
VALUES(?, ?, ?, ?) VALUES(?, ?, ?, ?)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""", """,
(game, team(player), name, specie.split(", ")[0]), (game, team(player), nickname, trimmed_specie),
) )
case ["faint", mon]: case ["faint", pokemon]:
player, mon = resolve_mon(mon)
conn.execute( conn.execute(
""" """
INSERT INTO knockouts(game, turn, player, name) INSERT INTO knockouts(game, turn, player, pokemon)
VALUES(?, ?, ?, ?) VALUES(?, ?, ?, ?)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""", """,
(game, turn, team(player), mon), (game, turn, team(player), specie(pokemon)),
) )
case ["win", player]: case ["win", player]:
@ -209,6 +271,7 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
if not last_move: if not last_move:
LOG.warning(f"missing previous move for {line}") LOG.warning(f"missing previous move for {line}")
continue continue
LOG.debug(f"{line} <- {last_move}") LOG.debug(f"{line} <- {last_move}")
last_env_set[(side[0:1], env.replace("move: ", ""))] = last_move[0] last_env_set[(side[0:1], env.replace("move: ", ""))] = last_move[0]
@ -216,88 +279,104 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
if not last_move or last_move[1] != mon: if not last_move or last_move[1] != mon:
LOG.warning(f"missing previous move for {line}") LOG.warning(f"missing previous move for {line}")
continue continue
LOG.debug(f"{line} <- {last_move}") LOG.debug(f"{line} <- {last_move}")
last_status_set[(mon, cond)] = last_move[0] last_status_set[(mon, cond)] = last_move[0]
case ["-damage", mon, status]: case ["-damage", pokemon, status]:
# mon takes direct (non-hazard/condition) damage # mon takes direct (non-hazard/condition) damage
# status can be a percentage 70/100 with or without condition, # status can be a percentage 70/100 with or without condition,
# or "0 fnt" # or "0 fnt"
new_hp = int(re.split("[/ ]", status)[0]) new_hp = int(re.split("[/ ]", status)[0])
LOG.debug(f"{mon} dropped to {new_hp} from {hp[mon]}") LOG.debug(f"{pokemon} dropped to {new_hp} from {hp[pokemon]}")
LOG.debug(f"source: {last_move}") LOG.debug(f"source: {last_move}")
# resolve to damage source # resolve to damage source
if last_move[1] != mon: if last_move[1] != pokemon:
LOG.warn( LOG.warning(
f"{mon} took direct damage but last move was not targeted at them" f"{pokemon} took direct damage but last move was not"
" targeted at them"
) )
continue continue
user = last_move[0] damage_source = last_move[0]
source_player, source_mon = resolve_mon(user) source_player, source_nickname = split_pokemon(damage_source)
conn.execute( conn.execute(
""" """
INSERT INTO damage(game, player, name, value) INSERT INTO damage(game, player, pokemon, value)
VALUES(?, ?, ?, ?) VALUES(?, ?, ?, ?)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""", """,
(game, team(source_player), source_mon, hp[mon] - new_hp), (
game,
team(source_player),
specie(damage_source),
hp[pokemon] - new_hp,
),
) )
hp[mon] = new_hp hp[pokemon] = new_hp
case ["-damage", mon, status, from_]: case ["-damage", pokemon, status, from_]:
# mon takes indirect damage # mon takes indirect damage
# status can be a percentage 70/100 with or without condition, # status can be a percentage 70/100 with or without condition,
# or "0 fnt" # or "0 fnt"
# mon has fainted from an indirect damage source
#
new_hp = int(re.split("[/ ]", status)[0]) new_hp = int(re.split("[/ ]", status)[0])
LOG.debug(f"{mon} dropped to {new_hp} from {from_}") LOG.debug(f"{pokemon} dropped to {new_hp} from {from_}")
LOG.debug(f"tracing source for {line}") LOG.debug(f"tracing reason for {line}")
source = from_.replace("[from] ", "") reason = from_.replace("[from] ", "")
source_user = None
test_hazard = last_env_set.get((mon[0:1], source)) source = None
source_is_pokemon = True
test_hazard = last_env_set.get((pokemon[0:1], reason))
if test_hazard: if test_hazard:
source_user = test_hazard source = test_hazard
LOG.debug(f"identified hazard source {source_user}") LOG.debug(f"identified hazard source {source}")
test_status = last_status_set.get((mon, source)) test_status = last_status_set.get((pokemon, reason))
if test_status: if test_status:
source_user = test_status source = test_status
LOG.debug(f"identified move source {source_user}") LOG.debug(f"identified move source {source}")
if source == "Recoil" or source.startswith("item: "): if reason == "Recoil" or reason.startswith("item: "):
LOG.debug(f"identified special source {source}") LOG.debug(f"identified special source {reason}")
source = source.replace("item: ", "") reason = reason.replace("item: ", "")
source_user = "self" source = "self"
source_is_pokemon = False
if not source_user: if not source:
LOG.error(f"missing source for {line}") LOG.error(f"missing reason for {line}")
continue continue
player, pkmn = resolve_mon(mon) player, nickname = split_pokemon(pokemon)
if source_user.startswith("p1") or source_user.startswith("p2"): if source.startswith("p1") or source.startswith("p2"):
source_player, source_mon = resolve_mon(source_user) source_player, _ = split_pokemon(source)
else: else:
source_player = None source_player = None
source_is_pokemon = False
if source_player: if source_player:
conn.execute( conn.execute(
""" """
INSERT INTO indirect_damage(game, player, name, value) INSERT INTO indirect_damage(game, player, pokemon, value)
VALUES(?, ?, ?, ?) VALUES(?, ?, ?, ?)
""", """,
(game, team(source_player), source_mon, hp[mon] - new_hp), (
game,
team(source_player),
specie(source),
hp[pokemon] - new_hp,
),
) )
if status == "0 fnt": if status == "0 fnt":
conn.execute( conn.execute(
""" """
INSERT INTO indirect_knockouts(game, turn, player, name, source, source_user, source_player) INSERT INTO indirect_knockouts(
game, turn, player, pokemon,
reason, source, source_player)
VALUES(?, ?, ?, ?, ?, ?, ?) VALUES(?, ?, ?, ?, ?, ?, ?)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""", """,
@ -305,15 +384,15 @@ def parse_log(game: str, log: str, into: sqlite3.Connection):
game, game,
turn, turn,
team(player), team(player),
pkmn, specie(pokemon),
source, reason,
source_mon, specie(source) if source_is_pokemon else source,
team(source_player), team(source_player),
), ),
) )
case ["-heal", mon, status, *rest]: case ["-heal", pokemon, status, *rest]:
hp[mon] = int(status.split("/")[0]) hp[pokemon] = int(status.split("/")[0])
case _: case _:
# LOG.debug(f"unhandled message {chunks[0]}") # LOG.debug(f"unhandled message {chunks[0]}")