forked from Hanabi/hanabi-league
Add code to analyze all games for bdrs
Also update stats code to accumulate these into the user stats
This commit is contained in:
parent
852f2ea329
commit
ddd751f0f3
3 changed files with 88 additions and 16 deletions
|
@ -320,7 +320,7 @@ DROP TABLE IF EXISTS game_statistics CASCADE;
|
|||
CREATE TABLE game_statistics (
|
||||
game_id INTEGER PRIMARY KEY REFERENCES games (id),
|
||||
/** I'd say all of the following can just be null in case we have not evaluated them yet. */
|
||||
bottom_deck_risk SMALLINT,
|
||||
num_bottom_deck_risks SMALLINT,
|
||||
num_crits_lost SMALLINT
|
||||
);
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from typing import List
|
||||
from typing import List, Tuple
|
||||
|
||||
import psycopg2.extras
|
||||
from database import conn_manager
|
||||
|
||||
import hanabi.hanab_game
|
||||
from log_setup import logger
|
||||
|
||||
|
||||
def store_actions(game_id: int, actions: List[hanabi.hanab_game.Action]):
|
||||
|
@ -53,6 +54,10 @@ def load_actions(game_id: int) -> List[hanabi.hanab_game.Action]:
|
|||
actions.append(
|
||||
hanabi.hanab_game.Action(hanabi.hanab_game.ActionType(action_type), target, value)
|
||||
)
|
||||
if len(actions) == 0:
|
||||
err_msg = "Failed to load actions for game id {} from DB: No actions stored.".format(game_id)
|
||||
logger.error(err_msg)
|
||||
raise ValueError(err_msg)
|
||||
return actions
|
||||
|
||||
|
||||
|
@ -69,4 +74,34 @@ def load_deck(seed: str) -> List[hanabi.hanab_game.DeckCard]:
|
|||
deck.append(
|
||||
hanabi.hanab_game.DeckCard(suit_index, rank, card_index)
|
||||
)
|
||||
if len(deck) == 0:
|
||||
err_msg = "Failed to load deck for seed {} from DB: No cards stored.".format(seed)
|
||||
logger.error(err_msg)
|
||||
raise ValueError(err_msg)
|
||||
return deck
|
||||
|
||||
|
||||
def load_game(game_id: int) -> Tuple[hanabi.hanab_game.HanabiInstance, List[hanabi.hanab_game.Action]]:
|
||||
cur = conn_manager.get_new_cursor()
|
||||
cur.execute(
|
||||
"SELECT games.num_players, games.seed, variants.clue_starved "
|
||||
"FROM games "
|
||||
"INNER JOIN variants"
|
||||
" ON games.variant_id = variants.id "
|
||||
"WHERE games.id = %s",
|
||||
(game_id,)
|
||||
)
|
||||
res = cur.fetchone()
|
||||
if res is None:
|
||||
err_msg = "Failed to retrieve game details of game {}.".format(game_id)
|
||||
logger.error(err_msg)
|
||||
raise ValueError(err_msg)
|
||||
|
||||
# Unpack results now
|
||||
(num_players, seed, clue_starved) = res
|
||||
|
||||
actions = load_actions(game_id)
|
||||
deck = load_deck(seed)
|
||||
|
||||
instance = hanabi.hanab_game.HanabiInstance(deck, num_players, clue_starved=clue_starved)
|
||||
return instance, actions
|
||||
|
|
65
src/stats.py
65
src/stats.py
|
@ -1,9 +1,11 @@
|
|||
import enum
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Set
|
||||
|
||||
from hanabi import hanab_game
|
||||
import utils
|
||||
from database import conn_manager
|
||||
import games_db_interface
|
||||
from log_setup import logger
|
||||
|
||||
|
||||
class GameOutcome(enum.Enum):
|
||||
|
@ -17,22 +19,29 @@ class GameOutcome(enum.Enum):
|
|||
|
||||
|
||||
class GameAnalysisResult:
|
||||
def __init__(self, outcomes: List[GameOutcome], bdrs: List[Tuple[hanab_game.DeckCard, int]]):
|
||||
def __init__(self,
|
||||
outcomes: Set[GameOutcome],
|
||||
bdrs: List[Tuple[hanab_game.DeckCard, int]],
|
||||
lost_crits: List[hanab_game.DeckCard]
|
||||
):
|
||||
self.outcome = GameOutcome
|
||||
self.bdrs = bdrs
|
||||
self.lost_crits = lost_crits
|
||||
|
||||
|
||||
def analyze_game(instance: hanab_game.HanabiInstance, actions: List[hanab_game.Action]) -> GameAnalysisResult:
|
||||
def analyze_replay(instance: hanab_game.HanabiInstance, actions: List[hanab_game.Action]) -> GameAnalysisResult:
|
||||
# List of bdrs
|
||||
bdrs = []
|
||||
# This is the default value if we find no other reason why the game was lost (or won)
|
||||
outcomes = []
|
||||
outcomes = set()
|
||||
lost_crits = []
|
||||
game = hanab_game.GameState(instance)
|
||||
|
||||
def handle_lost_card(card, game, play: bool):
|
||||
if not game.is_trash(card):
|
||||
if game.is_critical(card):
|
||||
outcomes.append(GameOutcome.bomb_crit if play else GameOutcome.discard_crit)
|
||||
outcomes.add(GameOutcome.bomb_crit if play else GameOutcome.discard_crit)
|
||||
lost_crits.append(card)
|
||||
elif card.rank != 1:
|
||||
if card in game.deck[game.progress:]:
|
||||
bdrs.append((card, game.draw_pile_size))
|
||||
|
@ -50,17 +59,45 @@ def analyze_game(instance: hanab_game.HanabiInstance, actions: List[hanab_game.A
|
|||
bombed_card = instance.deck[action.target]
|
||||
handle_lost_card(bombed_card, game, True)
|
||||
game.make_action(action)
|
||||
if game.pace < 0 and GameOutcome.out_of_pace not in outcomes:
|
||||
outcomes.append(GameOutcome.out_of_pace)
|
||||
if game.pace < 0:
|
||||
outcomes.add(GameOutcome.out_of_pace)
|
||||
|
||||
if game.strikes == 3:
|
||||
outcomes.append(GameOutcome.strikeout)
|
||||
outcomes.add(GameOutcome.strikeout)
|
||||
elif actions[-1].type in [hanab_game.ActionType.EndGame, hanab_game.ActionType.VoteTerminate]:
|
||||
outcomes.append(GameOutcome.vote_to_kill)
|
||||
outcomes.add(GameOutcome.vote_to_kill)
|
||||
if game.score == 5 * instance.num_suits:
|
||||
outcomes.append(GameOutcome.win)
|
||||
outcomes.add(GameOutcome.win)
|
||||
|
||||
return GameAnalysisResult(outcomes, bdrs)
|
||||
return GameAnalysisResult(outcomes, bdrs, lost_crits)
|
||||
|
||||
|
||||
def analyze_game_and_store_stats(game_id: int):
|
||||
instance, actions = games_db_interface.load_game(game_id)
|
||||
analysis = analyze_replay(instance, actions)
|
||||
|
||||
cur = conn_manager.get_new_cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO game_statistics (game_id, num_bottom_deck_risks, num_crits_lost) "
|
||||
"VALUES (%s, %s, %s) "
|
||||
"ON CONFLICT (game_id) DO UPDATE "
|
||||
"SET (num_crits_lost, num_bottom_deck_risks) = (EXCLUDED.num_crits_lost, EXCLUDED.num_bottom_deck_risks)",
|
||||
(game_id, len(analysis.bdrs), len(analysis.lost_crits))
|
||||
)
|
||||
conn_manager.get_connection().commit()
|
||||
|
||||
|
||||
def analyze_all_games():
|
||||
cur = conn_manager.get_new_cursor()
|
||||
cur.execute(
|
||||
"SELECT id FROM games "
|
||||
"LEFT OUTER JOIN game_statistics "
|
||||
" ON games.id = game_statistics.game_id "
|
||||
"WHERE game_statistics.game_id IS NULL "
|
||||
"ORDER BY games.id"
|
||||
)
|
||||
for (game_id, ) in cur.fetchall():
|
||||
analyze_game_and_store_stats(game_id)
|
||||
|
||||
|
||||
def update_user_statistics():
|
||||
|
@ -106,7 +143,7 @@ def update_user_statistics():
|
|||
" SUM(games.num_turns),"
|
||||
" COUNT(*),"
|
||||
" COUNT(*) FILTER ( WHERE variants.num_suits * 5 = games.score ),"
|
||||
" SUM (game_statistics.bottom_deck_risk)"
|
||||
" SUM (game_statistics.num_bottom_deck_risks)"
|
||||
"FROM users"
|
||||
" INNER JOIN game_participants "
|
||||
" ON game_participants.user_id = users.id "
|
||||
|
@ -120,9 +157,9 @@ def update_user_statistics():
|
|||
" ) "
|
||||
"ON CONFLICT (user_id, variant_type) DO UPDATE "
|
||||
"SET"
|
||||
" (total_game_moves, games_played, games_won)"
|
||||
" (total_game_moves, games_played, games_won, total_bdr)"
|
||||
" ="
|
||||
" (EXCLUDED.total_game_moves, EXCLUDED.games_played, EXCLUDED.games_won)",
|
||||
" (EXCLUDED.total_game_moves, EXCLUDED.games_played, EXCLUDED.games_won, EXCLUDED.total_bdr)",
|
||||
(utils.get_rating_type(True), utils.get_rating_type(False))
|
||||
)
|
||||
cur.execute(
|
||||
|
|
Loading…
Reference in a new issue