From 99f990c5f41057f55858b15393dd894b0eafa489 Mon Sep 17 00:00:00 2001 From: David Wu Date: Sat, 26 Aug 2023 09:47:08 -0400 Subject: [PATCH] Factor out non-Go-specific parts of elo.py --- python/elo.py | 293 ++++++++++++++++++++++++++++++++++++ python/summarize_sgfs.py | 318 ++++----------------------------------- 2 files changed, 324 insertions(+), 287 deletions(-) diff --git a/python/elo.py b/python/elo.py index bed96929a..6ad4a70ca 100644 --- a/python/elo.py +++ b/python/elo.py @@ -1,9 +1,13 @@ import copy import math import numpy as np +import os import scipy.stats import scipy.special +import itertools +from abc import ABC, abstractmethod from typing import List, Dict, Tuple, Set, Sequence +from dataclasses import dataclass Player = str PlayerIdx = int @@ -545,9 +549,284 @@ def line_search_ascend(strengths: np.array, cur_loglikelihood: float) -> Tuple[n ) return info +def has_only_factors_of_2_and_3(n: int) -> bool: + while n > 1: + if n % 2 == 0: + n //= 2 + elif n % 3 == 0: + n //= 3 + else: + return False + return True + +@dataclass +class GameRecord: + player1: str + player2: str + win: int = 0 + loss: int = 0 + draw: int = 0 + +class GameResultSummary: + + def __init__( + self, + elo_prior_games: float, + estimate_first_player_advantage: bool, + ): + self.results = {} # dict of { (player1_name, player2_name) : GameRecord } + + self._all_game_files = set() + self._elo_prior_games = elo_prior_games # number of games for bayesian prior around Elo 0 + self._estimate_first_player_advantage = estimate_first_player_advantage + self._elo_info = None + self._game_count = 0 + + def add_games_from_file_or_dir(self, input_file_or_dir: str, recursive=False): + """Add games found in input_file_or_dir into the results. Repeated paths to the same file across multiple calls will be ignored.""" + new_files = self._add_files(input_file_or_dir, recursive) + + def add_game_record(self, record: GameRecord): + """Add game record to results.""" + if (record.player1, record.player2) not in self.results: + self.results[(record.player1, record.player2)] = GameRecord(player1=record.player1,player2=record.player2) + self.results[(record.player1, record.player2)].win += record.win + self.results[(record.player1, record.player2)].loss += record.loss + self.results[(record.player1, record.player2)].draw += record.draw + self._game_count += record.win + record.loss + record.draw + + def clear(self): + """Clear all data added.""" + self.results = {} + self._all_game_files = set() + self._elo_info = None + + def print_game_results(self): + """Print tables of wins and win percentage.""" + pla_names = set(itertools.chain(*(name_pair for name_pair in self.results.keys()))) + self._print_result_matrix(pla_names) + + def print_elos(self): + """Print game results and maximum likelihood posterior Elos.""" + elo_info = self._compute_elos_if_needed() + real_players = [player for player in elo_info.players if player != P1_ADVANTAGE_NAME] + self._print_result_matrix(real_players) + print("Elos (+/- one approx standard error):") + print(elo_info) + + print("Pairwise approx % likelihood of superiority of row over column:") + los_matrix = [] + for player in real_players: + los_row = [] + for player2 in real_players: + los = elo_info.get_approx_likelihood_of_superiority(player,player2) + los_row.append(f"{los*100:.2f}") + los_matrix.append(los_row) + self._print_matrix(real_players,los_matrix) + + surprise_matrix = [] + for pla1 in real_players: + row = [] + for pla2 in real_players: + if (pla1 == pla2): + row.append("-") + continue + else: + pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else GameRecord(pla1,pla2) + pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else GameRecord(pla2,pla1) + win = pla1_pla2.win + pla2_pla1.loss + 0.5 * (pla1_pla2.draw + pla2_pla1.draw) + total = pla1_pla2.win + pla2_pla1.win + pla1_pla2.loss + pla2_pla1.loss + pla1_pla2.draw + pla2_pla1.draw + surprise = elo_info.get_log10_odds_surprise_max_likelihood(pla1, pla2, win, total) + if total <= 0: + row.append("-") + else: + row.append(f"{surprise:.1f}") + surprise_matrix.append(row) + print("Log10odds surprise matrix given the maximum-likelihood Elos:") + print("E.g. +3.0 means a 1:1000 unexpected good performance by row vs column.") + print("E.g. -4.0 means a 1:10000 unexpected bad performance by row vs column.") + print("Extreme numbers may indicate rock/paper/scissors, or Elo is not a good model for the data.") + self._print_matrix(real_players,surprise_matrix) + + print(f"Used a prior of {self._elo_prior_games} games worth that each player is near Elo 0.") + + def get_elos(self) -> EloInfo: + return self._compute_elos_if_needed() + + def get_game_results(self) -> Dict: + """Return a dictionary of game results as { (player1_name, player2_name) : GameRecord } + + You can retrieve results by player's name like: + results[(player1_name, player2_name)].win + results[(player1_name, player2_name)].loss + results[(player1_name, player2_name)].draw + """ + return self.results + + # Functions that can be implemented by subclasses ----------------------------------------------------- + # You can override these methods if you want add_games_from_file_or_dir to work. + # Otherwise, you can just add game records yourself via add_game_record. + @abstractmethod + def is_game_file(self, input_file: str) -> bool: + """Returns true if this file or directory is one that should have game results in it somewhere""" + raise NotImplementedError() + + @abstractmethod + def get_game_records(self, input_file: str) -> List[GameRecord]: + """Return all game records contained in this file""" + raise NotImplementedError() + + # Private functions ------------------------------------------------------------------------------------ + + def _compute_elos_if_needed(self): + if self._elo_info is None: + self._elo_info = self._estimate_elo() + return self._elo_info + + def _add_files(self, input_file_or_dir, recursive): + print(f"Searching and adding files in {input_file_or_dir}, please wait...") + + if not os.path.exists(input_file_or_dir): + raise Exception(f"There is no file or directory with name: {input_file_or_dir}") + + files = [] + if os.path.isdir(input_file_or_dir): + if recursive: + for (dirpath, dirnames, filenames) in os.walk(input_file_or_dir): + files += [os.path.join(dirpath, file) for file in filenames if self.is_game_file(os.path.join(dirpath, file))] + else: + files = [os.path.join(input_file_or_dir, file) for file in os.listdir(input_file_or_dir) if self.is_game_file(os.path.join(dirpath, file))] + else: + if self.is_game_file(input_file_or_dir): + files.append(input_file_or_dir) + + # Remove duplicates + new_game_files = set(files) + new_game_files = new_game_files.difference(self._all_game_files) + self._all_game_files = self._all_game_files.union(new_game_files) + self._add_new_games_to_result_dict(new_game_files, input_file_or_dir) + + print(f"Added {len(new_game_files)} new game files from {input_file_or_dir}") + + def _add_new_games_to_result_dict(self, new_game_files, source): + idx = 0 + for game_file in new_game_files: + records = self.get_game_records(game_file) + idx += 1 + if idx % 10 == 0 and has_only_factors_of_2_and_3(idx // 10): + print(f"Added {idx}/{len(new_game_files)} game files for {source}") + + for record in records: + self.add_game_record(record) + + def _estimate_elo(self) -> EloInfo: + """Estimate and print elo values. This function must be called after adding all games""" + pla_names = set(itertools.chain(*(name_pair for name_pair in self.results.keys()))) + data = [] + for pla_first in pla_names: + for pla_second in pla_names: + if (pla_first == pla_second): + continue + else: + if (pla_first, pla_second) not in self.results: + continue + record = self.results[(pla_first, pla_second)] + total = record.win + record.loss + record.draw + assert total >= 0 + if total == 0: + continue + + win = record.win + 0.5 * record.draw + winrate = win / total + data.extend(likelihood_of_games( + pla_first, + pla_second, + total, + winrate, + include_first_player_advantage=self._estimate_first_player_advantage + )) + + for pla in pla_names: + data.extend(make_single_player_prior(pla, self._elo_prior_games,0)) + data.extend(make_center_elos_prior(list(pla_names),0)) # Add this in case user put elo_prior_games = 0 + if self._estimate_first_player_advantage: + data.extend(make_single_player_prior(P1_ADVANTAGE_NAME, (1.0 + self._elo_prior_games) * 2.0, 0)) + + info = compute_elos(data, verbose=True) + return info + + def _print_matrix(self,pla_names,results_matrix): + per_elt_space = 2 + for sublist in results_matrix: + for elt in sublist: + per_elt_space = max(per_elt_space, len(str(elt))) + per_elt_space += 2 + + per_name_space = 1 if len(pla_names) == 0 else max(len(name) for name in pla_names) + per_name_space += 1 + if per_name_space > per_elt_space: + per_elt_space += 1 + + row_format = f"{{:>{per_name_space}}}" + f"{{:>{per_elt_space}}}" * len(results_matrix) + print(row_format.format("", *[name[:per_elt_space-2] for name in pla_names])) + for name, row in zip(pla_names, results_matrix): + print(row_format.format(name, *row)) + + def _print_result_matrix(self, pla_names): + print(f"Total games: {self._game_count}") + print("Games by player:") + for pla1 in pla_names: + total = 0 + for pla2 in pla_names: + if (pla1 == pla2): + continue + else: + pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else GameRecord(pla1,pla2) + pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else GameRecord(pla2,pla1) + total += pla1_pla2.win + pla2_pla1.win + pla1_pla2.loss + pla2_pla1.loss + pla1_pla2.draw + pla2_pla1.draw + print(f"{pla1}: {total:.1f}") + + print("Wins by row player against column player:") + result_matrix = [] + for pla1 in pla_names: + row = [] + for pla2 in pla_names: + if (pla1 == pla2): + row.append("-") + continue + else: + pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else GameRecord(pla1,pla2) + pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else GameRecord(pla2,pla1) + win = pla1_pla2.win + pla2_pla1.loss + 0.5 * (pla1_pla2.draw + pla2_pla1.draw) + total = pla1_pla2.win + pla2_pla1.win + pla1_pla2.loss + pla2_pla1.loss + pla1_pla2.draw + pla2_pla1.draw + row.append(f"{win:.1f}/{total:.1f}") + result_matrix.append(row) + self._print_matrix(pla_names,result_matrix) + + print("Win% by row player against column player:") + result_matrix = [] + for pla1 in pla_names: + row = [] + for pla2 in pla_names: + if (pla1 == pla2): + row.append("-") + continue + else: + pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else GameRecord(pla1,pla2) + pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else GameRecord(pla2,pla1) + win = pla1_pla2.win + pla2_pla1.loss + 0.5 * (pla1_pla2.draw + pla2_pla1.draw) + total = pla1_pla2.win + pla2_pla1.win + pla1_pla2.loss + pla2_pla1.loss + pla1_pla2.draw + pla2_pla1.draw + if total <= 0: + row.append("-") + else: + row.append(f"{win/total*100.0:.1f}%") + result_matrix.append(row) + + self._print_matrix(pla_names,result_matrix) # Testing code if __name__ == "__main__": + # Example 1 data = [] data.extend(likelihood_of_games("Alice","Bob", 18, 12/18, False)) data.extend(likelihood_of_games("Bob","Carol", 18, 12/18, False)) @@ -560,3 +839,17 @@ def line_search_ascend(strengths: np.array, cur_loglikelihood: float) -> Tuple[n for player2 in info.players: print(info.get_approx_likelihood_of_superiority(player,player2),end=" ") print() + + # Example 2 + summary = GameResultSummary( + elo_prior_games=1.0, + estimate_first_player_advantage=False, + ) + summary.add_game_record(GameRecord("Alice","Bob",win=12,loss=6,draw=0)) + summary.add_game_record(GameRecord("Bob","Carol",win=12,loss=6,draw=0)) + summary.add_game_record(GameRecord("Carol","Dan",win=36,loss=18,draw=0)) + summary.add_game_record(GameRecord("Dan","Eve",win=48,loss=24,draw=0)) + summary.print_elos() + + + diff --git a/python/summarize_sgfs.py b/python/summarize_sgfs.py index 3a222667a..73c516124 100644 --- a/python/summarize_sgfs.py +++ b/python/summarize_sgfs.py @@ -1,5 +1,6 @@ import argparse import elo +from elo import GameRecord import itertools import math import os @@ -8,196 +9,44 @@ from sgfmill import sgf from typing import List, Dict, Tuple, Set, Sequence -@dataclass -class Record: - win: int = 0 - lost: int = 0 - draw: int = 0 - -class GameResultSummary: - """ - Summrize Go games results in sgf file format under a list of directories (optionally recursively in subdirs). - Also supports katago "sgfs" file, which is simply a bunch of sgf files (with no newlines) concatenated one per line. - - Example: - Call it from terminal: - :$python summarize_sgfs.py [input_directory1 input directory2] - - call it by other function: - import summarize_sgfs - elo_prior_games = 4 - estimate_first_player_advantage = False - game_result_summary = summarize_sgfs.GameResultSummary(elo_prior_games, estimate_first_player_advantage) - game_result_summary.add_games(input_file_or_dir) - game_result_summary.print_game_results() - game_result_summary.print_elos() - """ +class GoGameResultSummary(elo.GameResultSummary): def __init__( self, elo_prior_games: float, estimate_first_player_advantage: bool, ): - self.results = {} # dict of { (black_player_name, white_player_name) : Record } - - self._all_sgfs_files = set() - self._all_sgf_files = set() + super().__init__(elo_prior_games, estimate_first_player_advantage) self._should_warn_handicap_komi = False - self._elo_prior_games = elo_prior_games # number of games for bayesian prior around Elo 0 - self._estimate_first_player_advantage = estimate_first_player_advantage - self._elo_info = None - self._game_count = 0 - - def add_games(self, input_file_or_dir: str, recursive=False): - """Add sgfs found in input_file_or_dir into the results. Repeated paths to the same file will be ignored.""" - new_files = self._add_files(input_file_or_dir, recursive) - - def clear(self): - """Clear all data added.""" - self.results = {} - self._all_sgfs_files = set() - self._all_sgf_files = set() - self._should_warn_handicap_komi = False - self._elo_info = None - - def print_game_results(self): - """Print tables of wins and win percentage.""" - pla_names = set(itertools.chain(*(name_pair for name_pair in self.results.keys()))) - self._print_result_matrix(pla_names) + # @override def print_elos(self): - """Print game results and maximum likelihood posterior Elos.""" - elo_info = self._compute_elos_if_needed() - real_players = [player for player in elo_info.players if player != elo.P1_ADVANTAGE_NAME] - self._print_result_matrix(real_players) - print("Elos (+/- one approx standard error):") - print(elo_info) - - print("Pairwise approx % likelihood of superiority of row over column:") - los_matrix = [] - for player in real_players: - los_row = [] - for player2 in real_players: - los = elo_info.get_approx_likelihood_of_superiority(player,player2) - los_row.append(f"{los*100:.2f}") - los_matrix.append(los_row) - self._print_matrix(real_players,los_matrix) - - surprise_matrix = [] - for pla1 in real_players: - row = [] - for pla2 in real_players: - if (pla1 == pla2): - row.append("-") - continue - else: - pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else Record() - pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else Record() - win = pla1_pla2.win + pla2_pla1.lost + 0.5 * (pla1_pla2.draw + pla2_pla1.draw) - total = pla1_pla2.win + pla2_pla1.win + pla1_pla2.lost + pla2_pla1.lost + pla1_pla2.draw + pla2_pla1.draw - surprise = elo_info.get_log10_odds_surprise_max_likelihood(pla1, pla2, win, total) - if total <= 0: - row.append("-") - else: - row.append(f"{surprise:.1f}") - surprise_matrix.append(row) - print("Log10odds surprise matrix given the maximum-likelihood Elos:") - print("E.g. +3.0 means a 1:1000 unexpected good performance by row vs column.") - print("E.g. -4.0 means a 1:10000 unexpected bad performance by row vs column.") - print("Extreme numbers may indicate rock/paper/scissors, or Elo is not a good model for the data.") - self._print_matrix(real_players,surprise_matrix) - - print(f"Used a prior of {self._elo_prior_games} games worth that each player is near Elo 0.") + super().print_elos() if self._should_warn_handicap_komi: print("WARNING: There are handicap games or games with komi < 5.5 or komi > 7.5, these games may not be fair?") - def get_elos(self) -> elo.EloInfo: - return self._compute_elos_if_needed() - - def get_game_results(self) -> Dict: - """Return a dictionary of game results as { (black_player_name, white_player_name) : Record } - - You can retrieve results by player's name like: - results[(black_player_name, white_player_name)].win - results[(black_player_name, white_player_name)].lost - results[(black_player_name, white_player_name)].draw - """ - return self.results - - # Private functions ------------------------------------------------------------------------------------ - - def _compute_elos_if_needed(self): - if self._elo_info is None: - self._elo_info = self._estimate_elo() - return self._elo_info - - def _add_files(self, input_file_or_dir, recursive): - print(f"Searching and adding files in {input_file_or_dir}, please wait...") - - if not os.path.exists(input_file_or_dir): - raise Exception(f"There is no file or directory with name: {input_file_or_dir}") - - files = [] - if input_file_or_dir.lower().endswith((".sgf", ".sgfs")): - files.append(input_file_or_dir) - elif recursive: - for (dirpath, dirnames, filenames) in os.walk(input_file_or_dir): - files += [os.path.join(dirpath, file) for file in filenames] + # @override + def is_game_file(self, input_file: str) -> bool: + lower = input_file.lower() + return input_file.endswith(".sgf") or input_file.endswith(".sgfs") + + # @override + def get_game_records(self, input_file: str) -> List[GameRecord]: + if input_file.lower().endswith(".sgfs"): + with open(input_file, "rb") as f: + sgfs_strings = f.readlines() + + records = [] + for sgf in sgfs_strings: + records.append(self.sgf_string_to_game_record(sgf, input_file)) + return records else: - files = [os.path.join(input_file_or_dir, file) for file in os.listdir(input_file_or_dir)] - - new_sgfs_files = set([file for file in files if file.split(".")[-1].lower() == "sgfs"]) - new_sgf_files = set([file for file in files if file.split(".")[-1].lower() == "sgf"]) + with open(sgf_file_name, "rb") as f: + sgf = f.read() - # Remove duplicates - new_sgfs_files = new_sgfs_files.difference(self._all_sgfs_files) - new_sgf_files = new_sgf_files.difference(self._all_sgf_files) + return [self.sgf_string_to_game_record(sgf, input_file)] - self._all_sgfs_files = self._all_sgfs_files.union(new_sgfs_files) - self._all_sgf_files = self._all_sgf_files.union(new_sgf_files) - - self._add_new_games_to_result_dict(new_sgf_files, new_sgfs_files, input_file_or_dir) - - print(f"Added {len(new_sgfs_files)} new sgfs files and {len(new_sgf_files)} new sgf files from {input_file_or_dir}") - - def _add_new_games_to_result_dict(self, new_sgf_files, new_sgfs_files, source): - idx = 0 - for sgfs in new_sgfs_files: - self._add_one_sgfs_file_to_result(sgfs) - idx += 1 - if (idx % 10 == 0): - print(f"Added {idx}/{len(new_sgfs_files)} sgfs files for {source}") - - idx = 0 - for sgf in new_sgf_files: - self._add_one_sgf_file_to_result(sgf) - idx += 1 - if (idx % 10 == 0): - print(f"Added {idx}/{len(new_sgf_files)} sgf files for {source}") - - def _add_one_sgfs_file_to_result(self, sgfs_file_name): - """Add a single sgfs file. Each line of an sgfs file should be the contents of a valid sgf file with no newlines.""" - if not os.path.exists(sgfs_file_name): - raise Exception(f"There is no SGFs file named: {sgfs_file_name}") - - with open(sgfs_file_name, "rb") as f: - sgfs_strings = f.readlines() - - for sgf in sgfs_strings: - self._add_a_single_sgf_string(sgf, sgfs_file_name) - - def _add_one_sgf_file_to_result(self, sgf_file_name): - """Add a single sgf file.""" - if not os.path.exists(sgf_file_name): - raise Exception(f"There is no SGF file named: {sgf_file_name}") - - with open(sgf_file_name, "rb") as f: - sgf = f.read() - - self._add_a_single_sgf_string(sgf, sgf_file_name) - - def _add_a_single_sgf_string(self, sgf_string, debug_source = None): - """add a single game in a sgf string save the results in self.results.""" + def sgf_string_to_game_record(self, sgf_string, debug_source = None) -> GameRecord: try: game = sgf.Sgf_game.from_bytes(sgf_string) winner = game.get_winner() @@ -209,120 +58,15 @@ def _add_a_single_sgf_string(self, sgf_string, debug_source = None): if (game.get_handicap() is not None) or game.get_komi() < 5.5 or game.get_komi() > 7.5: self._should_warn_handicap_komi = True - if (pla_black, pla_white) not in self.results: - self.results[(pla_black, pla_white)] = Record() - + game_record = GameRecord(player1=pla_black,player2=pla_white) if (winner == 'b'): - self.results[(pla_black, pla_white)].win += 1 + game_record.win += 1 elif (winner == 'w'): - self.results[(pla_black, pla_white)].lost += 1 + game_record.loss += 1 else: - self.results[(pla_black, pla_white)].draw += 1 - self._game_count += 1 - - def _estimate_elo(self) -> elo.EloInfo: - """Estimate and print elo values. This function must be called after add all the sgfs/sgf files""" - pla_names = set(itertools.chain(*(name_pair for name_pair in self.results.keys()))) - data = [] - for pla_black in pla_names: - for pla_white in pla_names: - if (pla_black == pla_white): - continue - else: - if (pla_black, pla_white) not in self.results: - continue - record = self.results[(pla_black, pla_white)] - total = record.win + record.lost + record.draw - assert total >= 0 - if total == 0: - continue - - win = record.win + 0.5 * record.draw - winrate = win / total - data.extend(elo.likelihood_of_games( - pla_black, - pla_white, - total, - winrate, - include_first_player_advantage=self._estimate_first_player_advantage - )) - - for pla in pla_names: - data.extend(elo.make_single_player_prior(pla, self._elo_prior_games,0)) - data.extend(elo.make_center_elos_prior(list(pla_names),0)) # Add this in case user put elo_prior_games = 0 - if self._estimate_first_player_advantage: - data.extend(elo.make_single_player_prior(elo.P1_ADVANTAGE_NAME, (1.0 + self._elo_prior_games) * 2.0, 0)) - - info = elo.compute_elos(data, verbose=True) - return info - - def _print_matrix(self,pla_names,results_matrix): - per_elt_space = 2 - for sublist in results_matrix: - for elt in sublist: - per_elt_space = max(per_elt_space, len(str(elt))) - per_elt_space += 2 - - per_name_space = 1 if len(pla_names) == 0 else max(len(name) for name in pla_names) - per_name_space += 1 - if per_name_space > per_elt_space: - per_elt_space += 1 - - row_format = f"{{:>{per_name_space}}}" + f"{{:>{per_elt_space}}}" * len(results_matrix) - print(row_format.format("", *[name[:per_elt_space-2] for name in pla_names])) - for name, row in zip(pla_names, results_matrix): - print(row_format.format(name, *row)) - - def _print_result_matrix(self, pla_names): - print(f"Total games: {self._game_count}") - print("Games by player:") - for pla1 in pla_names: - total = 0 - for pla2 in pla_names: - if (pla1 == pla2): - continue - else: - pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else Record() - pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else Record() - total += pla1_pla2.win + pla2_pla1.win + pla1_pla2.lost + pla2_pla1.lost + pla1_pla2.draw + pla2_pla1.draw - print(f"{pla1}: {total:.1f}") - - print("Wins by row player against column player:") - result_matrix = [] - for pla1 in pla_names: - row = [] - for pla2 in pla_names: - if (pla1 == pla2): - row.append("-") - continue - else: - pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else Record() - pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else Record() - win = pla1_pla2.win + pla2_pla1.lost + 0.5 * (pla1_pla2.draw + pla2_pla1.draw) - total = pla1_pla2.win + pla2_pla1.win + pla1_pla2.lost + pla2_pla1.lost + pla1_pla2.draw + pla2_pla1.draw - row.append(f"{win:.1f}/{total:.1f}") - result_matrix.append(row) - self._print_matrix(pla_names,result_matrix) + game_record.draw += 1 + return game_record - print("Win% by row player against column player:") - result_matrix = [] - for pla1 in pla_names: - row = [] - for pla2 in pla_names: - if (pla1 == pla2): - row.append("-") - continue - else: - pla1_pla2 = self.results[(pla1, pla2)] if (pla1, pla2) in self.results else Record() - pla2_pla1 = self.results[(pla2, pla1)] if (pla2, pla1) in self.results else Record() - win = pla1_pla2.win + pla2_pla1.lost + 0.5 * (pla1_pla2.draw + pla2_pla1.draw) - total = pla1_pla2.win + pla2_pla1.win + pla1_pla2.lost + pla2_pla1.lost + pla1_pla2.draw + pla2_pla1.draw - if total <= 0: - row.append("-") - else: - row.append(f"{win/total*100.0:.1f}%") - result_matrix.append(row) - self._print_matrix(pla_names,result_matrix) if __name__ == "__main__": @@ -362,11 +106,11 @@ def _print_result_matrix(self, pla_names): elo_prior_games = args["elo_prior_games"] estimate_first_player_advantage = args["estimate_first_player_advantage"] - game_result_summary = GameResultSummary( + game_result_summary = GoGameResultSummary( elo_prior_games=elo_prior_games, estimate_first_player_advantage=estimate_first_player_advantage, ) for input_file_or_dir in input_files_or_dirs: - game_result_summary.add_games(input_file_or_dir, recursive=recursive) + game_result_summary.add_games_from_file_or_dir(input_file_or_dir, recursive=recursive) game_result_summary.print_elos()