def test_connected_four_oracle(empty_board: np.ndarray): from connectn.game import check_end_state, other_player, apply_player_action from connectn.game import PlayerAction, NO_PLAYER from connectn.game import connected_four_convolve, connected_four, connected_four_generators for game_n in range(1000): board = empty_board.copy() curr_player = PLAYER1 end_state = check_end_state(board, curr_player) move_n = 0 while end_state == GameStatus.STILL_PLAYING: move_n += 1 curr_player = other_player(curr_player) moves = np.array([ col for col in np.arange(PlayerAction(board.shape[1])) if board[PlayerAction(-1), col] == NO_PLAYER ]) move = np.random.choice(moves, 1)[0] apply_player_action(board, move, curr_player) end_state = check_end_state(board, curr_player) conn_4_a = connected_four_convolve(board, curr_player) conn_4_b = connected_four(board, curr_player, move, True) conn_4_b2 = connected_four(board, curr_player, move, False) conn_4_c = connected_four(board, curr_player, None, True) conn_4_c2 = connected_four(board, curr_player, None, False) conn_4_u = connected_four_generators(board, curr_player, move) assert conn_4_a == conn_4_b assert conn_4_b == conn_4_b2 assert conn_4_b2 == conn_4_c assert conn_4_c == conn_4_c2 assert conn_4_c2 == conn_4_u
def test_check_end_state_cols(empty_board): from connectn.game import check_end_state, other_player for i in range(CONNECT_N - 1): for col in range(empty_board.shape[1]): for row in range(empty_board.shape[0] - CONNECT_N + i + 1): for player in (PLAYER1, PLAYER2): b0 = empty_board.copy() b0[row:row + CONNECT_N - i, col] = player b0[:row, col] = other_player(player) if i == 0: assert check_end_state(b0, player) == GameStatus.IS_WIN else: assert check_end_state( b0, player) == GameStatus.STILL_PLAYING
def test_check_end_state_diagonal(empty_board: np.ndarray): from connectn.game import check_end_state for i in range(CONNECT_N): n_diagonal = np.diag(np.ones(CONNECT_N - i, dtype=empty_board.dtype)) for n_conn in (n_diagonal, n_diagonal[:, ::-1]): for row in range(empty_board.shape[0] - CONNECT_N + i + 1): for col in range(empty_board.shape[1] - CONNECT_N + i + 1): for player in (PLAYER1, PLAYER2): b0 = empty_board.copy() b0[row:row + CONNECT_N - i, col:col + CONNECT_N - i] = (player * n_conn) if i == 0: assert check_end_state(b0, player) == GameStatus.IS_WIN else: assert check_end_state( b0, player) == GameStatus.STILL_PLAYING
def test_check_end_state_straight(empty_board: np.ndarray): from connectn.game import check_end_state from itertools import product dim = len(empty_board.shape) for player in (PLAYER1, PLAYER2): for n in range(CONNECT_N): for d in range(dim): for i in range(empty_board.shape[d] - CONNECT_N + n + 1): remaining_indices = list( range(empty_board.shape[k]) for k in range(dim) if k != d) for jk in product(*remaining_indices): b0 = empty_board.copy() indices = jk[:d] + (slice( i, i + CONNECT_N - n), ) + jk[d:] b0[indices] = player if n == 0: assert check_end_state(b0, player) == GameStatus.IS_WIN else: assert check_end_state( b0, player) == GameStatus.STILL_PLAYING
def run_single_game(agent_1: str, agent_2: str, game_seed: Optional[int] = None) -> GameResult: """ Likely has to be replaced by separate function runnable via the GridEngine """ from connectn import IS_DEBUGGING from queue import Empty as EmptyQueue from connectn.game import initialize_game_state, other_player from connectn.game import valid_player_action, apply_player_action from connectn.game import check_end_state, pretty_print_board from connectn.game import PLAYER1, PLAYER2, GameStatus logger = logging.getLogger(__name__) logger.debug(f"Entered run_single_game for {agent_1} vs {agent_2}") rs = np.random.RandomState(game_seed) agent_modules = import_agents({}) agent_names = (agent_1, agent_2) def get_name(_player: BoardPiece) -> str: return agent_names[_player - 1] states = {agent_name: None for agent_name in agent_names} winner = player = NO_PLAYER agent_name = agent_1 results = {PLAYER1: AgentResult(agent_1), PLAYER2: AgentResult(agent_2)} gr = GameResult(results[PLAYER1], results[PLAYER2]) gen_move = {} for player, agent_name in zip((PLAYER1, PLAYER2), agent_names): try: gen_move[agent_name]: cu.GenMove = getattr( agent_modules[agent_name], "generate_move") except AttributeError: results[player].stderr.append( "\nYou did not define generate_move at the package level") gr.winner = other_player(player) results[player].outcome = "FAIL" results[gr.winner].outcome = "WIN" return gr except KeyError as e: # If this occurs and it isn't for agent_fail, then something has gone terribly wrong. # Presumably one of the agents is not defined in users.py logger.exception("Something has gone terribly wrong") raise e game_state = initialize_game_state() for player, agent_name in zip((PLAYER1, PLAYER2), agent_names): try: init = getattr(agent_modules[agent_name], "initialize") init(game_state.copy(), player) except (Exception, AttributeError): pass loser_result = "LOSS" try: logger.info(f"Playing game between {agent_1} and {agent_2}") moves_q = mp.Manager().Queue() end_state = GameStatus.STILL_PLAYING playing = True action = PlayerAction(0) while playing: for player, agent_name in zip((PLAYER1, PLAYER2), agent_names): move_seed = rs.randint(2**32) results[player].seeds.append(move_seed) gma = GenMoveArgs(move_seed, game_state.copy(), player, states[agent_name]) moves_q.put(gma) if IS_DEBUGGING: generate_move_process(gen_move[agent_name], moves_q) else: ap = mp.Process( target=generate_move_process, args=(gen_move[agent_name], moves_q), ) t0 = time.time() ap.start() ap.join(cu.MOVE_TIME_MAX) move_time = time.time() - t0 if ap.is_alive(): ap.terminate() loser_result = "TIMEOUT" msg = f"Agent {agent_name} timed out after {cu.MOVE_TIME_MAX} seconds ({move_time:.1f}s)." raise AgentFailed(msg) try: ret: Union[GenMoveSuccess, GenMoveFailure] = moves_q.get(block=True, timeout=60.0) except EmptyQueue: logger.exception( "Timed out waiting to get move result from queue") raise results[player].stdout.append(ret.stdout) results[player].stderr.append(ret.stderr) if isinstance(ret, GenMoveFailure): loser_result = "EXCEPTION" error_msg = ret.error_msg msg = f"Agent {agent_name} threw an exception:\n {error_msg}" raise AgentFailed(msg) assert isinstance(ret, GenMoveSuccess) action = ret.action state_size = cu.get_size(ret.state) results[player].move_times.append(ret.move_time) results[player].state_size.append(state_size) if state_size > cu.STATE_MEMORY_MAX: loser_result = "MAX_STATE_MEM" msg = f"Agent {agent_name} used {cu.mib(state_size):.2f} MiB > {cu.mib(cu.STATE_MEMORY_MAX)} MiB" raise AgentFailed(msg) if not np.issubdtype(type(action), np.integer): loser_result = "NONINT_ACTION" msg = f"Agent {agent_name} returned an invalid type of action {type(action)}" raise AgentFailed(msg) action = PlayerAction(action) results[player].moves.append(action) if not valid_player_action(game_state, action): loser_result = "INVALID_ACTION" msg = f"Agent {agent_name} returned an invalid action {action}" raise AgentFailed(msg) apply_player_action(game_state, action, player) end_state = check_end_state(game_state, player) playing = end_state == GameStatus.STILL_PLAYING states[agent_name] = ret.state if not playing: break if end_state == GameStatus.IS_WIN: winner = player logger.info( f"Game finished, {get_name(player)} beat {get_name(other_player(player))} by playing column {action}." ) elif end_state == GameStatus.IS_DRAW: winner = NO_PLAYER logger.info("Game finished, no winner") else: logger.info( "Something went wrong, game-play stopped before the end state." ) except AgentFailed as err: logger.info(pretty_print_board(game_state)) logger.info(f"Agent failed: {agent_name}") logger.info(err) winner = other_player(player) results[player].stderr.append(str(err)) # fig = plt.figure() # fig.suptitle('Odds of win') # for i, (agent, saved_state) in enumerate(states.items()): # ax = fig.add_subplot(2, 1, i+1) # for odds in saved_state.odds.values(): # ax.plot(odds, ('r', 'b')[i]) # ax.set_title(agent) # ax.set_ylim(0, 1) # # fig = plt.figure() # fig.suptitle('Odds of draw') # for i, (agent, saved_state) in enumerate(states.items()): # ax = fig.add_subplot(2, 1, i+1) # for odds in saved_state.draw.values(): # ax.plot(odds, ('r', 'b')[i]) # ax.set_title(agent) # ax.set_ylim(0, 1) # # fig = plt.figure() # for i, (agent, saved_state) in enumerate(states.items()): # ax = fig.add_subplot(2, 1, i+1) # ax.plot(saved_state.nodes, label='Nodes') # ax.plot(saved_state.visits, label='Visits') # ax.set_title(agent) # ax.legend() # # fig = plt.figure() # fig.suptitle('Time') # for i, (agent, saved_state) in enumerate(states.items()): # ax = fig.add_subplot(2, 1, i+1) # ax.plot(saved_state.time, label=f'{np.mean(saved_state.time):.2f}') # ax.set_title(agent) # ax.legend() # # plt.show() # for i, (agent, saved_state) in enumerate(states.items()): # print( # f'TIME {agent} mu:{np.mean(saved_state.time):.2f}, # med:{np.median(saved_state.time):.2f}, max:{np.max(saved_state.time):.2f}' # ) gr.winner = winner if winner == NO_PLAYER: results[PLAYER1].outcome = results[PLAYER2].outcome = "DRAW" else: results[PLAYER1 if winner == PLAYER1 else PLAYER2].outcome = "WIN" results[PLAYER2 if winner == PLAYER1 else PLAYER1].outcome = loser_result logger.debug(f"Finished run_single_game for {agent_1} vs {agent_2}") return gr