import numpy as np
from mctsnc import MCTSNC
__version__ = "1.0.0"
__author__ = "Przemysław Klęsk"
__email__ = "pklesk@zut.edu.pl"
[docs]
class GameRunner:
"""
Class responsible for carrying out a single game (game class specified via parameter) within a match between two AIs or human vs AI.
Parameters:
game_class (class):
class object allowing to instantiate the initial state of some two-person game (e.g., Connect4, Gomoku, chess, etc.) and to carry out a game on it.
black_ai (object):
reference to AI instance responsible for the black player (instance of class ``MCTS`` or ``MCTSNC``) or ``None`` for human.
white_ai (object):
reference to AI instance responsible for the white player (instance of class ``MCTS`` or ``MCTSNC``) or ``None`` for human.
game_index (int):
index of game with a match (for informative purposes).
n_games (int):
total of games in a match (for informative purposes).
experiment_info_old (dict):
dictionary allowing to reproduce a former experiment (allows to force the limit of steps rather than time on an AI instance) or ``None`` for a new experiment, , defaults to ``None``.
Attributes:
OUTCOME_MESSAGES (list):
list of strings with messages describing outcomes of games.
"""
OUTCOME_MESSAGES = ["WHITE WINS", "DRAW", "BLACK WINS"]
[docs]
def __init__(self, game_class, black_ai, white_ai, game_index, n_games, experiment_info_old=None):
"""
Constructor ``GameRunner`` instances.
Args:
game_class (class):
class object allowing to instantiate the initial state of some two-person game (e.g., Connect4, Gomoku, chess, etc.) and to carry out a game on it.
black_ai (object):
reference to AI instance responsible for the black player (instance of class ``MCTS`` or ``MCTSNC``) or ``None`` for human.
white_ai (object):
reference to AI instance responsible for the white player (instance of class ``MCTS`` or ``MCTSNC``) or ``None`` for human.
game_index (int):
index of game with a match (for informative purposes).
n_games (int):
total of games in a match (for informative purposes).
experiment_info_old (dict):
dictionary allowing to reproduce a former experiment (allows to force the limit of steps rather than time on an AI instance) or ``None`` for a new experiment, , defaults to ``None``.
"""
self.game_class = game_class
self.black_ai = black_ai
self.white_ai = white_ai
self.game_index = game_index
self.n_games = n_games
self.experiment_info_old = experiment_info_old
[docs]
def run(self):
"""Carries out a game."""
game = self.game_class()
print(game)
outcome = 0
game_info = {"black": str(self.black_ai), "white": str(self.white_ai), "initial_state": str(game), "moves_rounds": {}, "outcome": None, "outcome_message": None}
move_count = 0
while True:
print(f"\nMOVES ROUND: {move_count + 1} [game: {self.game_index}/{self.n_games}]")
forced_search_steps_limit = np.inf
moves_round_info = {}
if not self.black_ai:
move_valid = False
escaped = False
while not (move_valid or escaped):
try:
move_name = input("BLACK PLAYER, PICK YOUR MOVE: ")
move_index = self.game_class.action_name_to_index(move_name)
game_moved = game.take_action(move_index)
if game_moved is not None:
game = game_moved
move_valid = True
except:
print("INVALID MOVE. GAME STOPPED.")
escaped = True
break
if escaped:
break
else:
if self.experiment_info_old is not None:
forced_search_steps_limit = self.experiment_info_old["games_infos"][str(self.game_index)]["moves_rounds"][str(move_count + 1)]["black_performance_info"]["steps"]
if isinstance(self.black_ai, MCTSNC):
move_index = self.black_ai.run(game.get_board(), game.get_extra_info(), game.get_turn(), forced_search_steps_limit)
else:
move_index = self.black_ai.run(game, forced_search_steps_limit)
move_name = self.game_class.action_index_to_name(move_index)
print(f"MOVE PLAYED: {move_name}")
game = game.take_action(move_index)
moves_round_info["black_best_action_info"] = self.black_ai.actions_info["best"]
moves_round_info["black_performance_info"] = self.black_ai.performance_info
print(str(game), flush=True)
outcome = game.compute_outcome()
if outcome is not None:
outcome_message = GameRunner.OUTCOME_MESSAGES[outcome + 1]
print(f"GAME OUTCOME: {outcome_message}")
game_info["moves_rounds"][str(move_count + 1)] = moves_round_info
game_info["outcome"] = outcome
game_info["outcome_message"] = outcome_message
break
if not self.white_ai:
move_valid = False
escaped = False
while not (move_valid or escaped):
try:
move_name = input("WHITE PLAYER, PICK YOUR MOVE: ")
move_index = self.game_class.action_name_to_index(move_name)
game_moved = game.take_action(move_index)
if game_moved is not None:
game = game_moved
move_valid = True
except:
print("INVALID MOVE. GAME STOPPED.")
escaped = True
break
if escaped:
break
else:
if self.experiment_info_old is not None:
forced_search_steps_limit = self.experiment_info_old["games_infos"][str(self.game_index)]["moves_rounds"][str(move_count + 1)]["white_performance_info"]["steps"]
if isinstance(self.white_ai, MCTSNC):
move_index = self.white_ai.run(game.get_board(), game.get_extra_info(), game.get_turn(), forced_search_steps_limit)
else:
move_index = self.white_ai.run(game, forced_search_steps_limit)
move_name = self.game_class.action_index_to_name(move_index)
print(f"MOVE PLAYED: {move_name}")
game = game.take_action(move_index)
moves_round_info["white_best_action_info"] = self.white_ai.actions_info["best"]
moves_round_info["white_performance_info"] = self.white_ai.performance_info
print(str(game), flush=True)
game_info["moves_rounds"][str(move_count + 1)] = moves_round_info
outcome = game.compute_outcome()
if outcome is not None:
outcome_message = GameRunner.OUTCOME_MESSAGES[outcome + 1]
print(f"GAME OUTCOME: {outcome_message}")
game_info["moves_rounds"][str(move_count + 1)] = moves_round_info
game_info["outcome"] = outcome
game_info["outcome_message"] = outcome_message
break
move_count += 1
return outcome, game_info