def test_feature4(self): # |==============| # | | # | | # | | # | | # | O | # | X | # |==============| # |0 1 2 3 4 5 6 | b_1_col2 = Board() b_1_col2.drop_piece(2, PLAYER_1) b_1_col2.drop_piece(2, PLAYER_2) self.assertEqual(heuristic_1.feature4(b_1_col2, PLAYER_1), float(120)) # |==============| # | | # | | # | | # | | # | O X | # | X O | # |==============| # |0 1 2 3 4 5 6 | b_1_col2_1_col5 = Board() b_1_col2_1_col5.drop_piece(2, PLAYER_1) b_1_col2_1_col5.drop_piece(5, PLAYER_2) b_1_col2_1_col5.drop_piece(5, PLAYER_1) b_1_col2_1_col5.drop_piece(2, PLAYER_2) self.assertEqual(heuristic_1.feature4(b_1_col2_1_col5, PLAYER_1), float(190))
def generate_move_minimax( board: Board, player: Player, saved_state: Optional[SavedState] ) -> Tuple[PlayerAction, Optional[SavedState]]: """ Generates a move for player on the current board. Optionally consumes and updates a saved state. :param board: The board state :param player: The player to generate a move for :param saved_state: (optional) Saved state :return: Returns the calculated move and the new saved state """ # first, check if a winning move is possible. This can fix a problem where MiniMax # would not finish the game since all moves will have for move in board.actions(): nb = board.drop_piece_copy(move) if nb.check_winner(player): return move, saved_state # retrieve our MiniMaxCache from saved state if possible, otherwise create the cache object cache = saved_state if type( saved_state) == MiniMaxCache2 else MiniMaxCache2() # calculate the MiniMax result minimax_result = mini_max(board, alpha=-np.inf, beta=np.inf, depth=3, max_player=player, cache=cache) print( f"generate_move_minimax() returned move {minimax_result.best_move}, h={minimax_result.value}" ) return minimax_result.best_move, cache
def look_ahead_next_move(self, game_state, move): piece = Piece(game_state.player_in_action, move.point) new_board = Board(game_state.board.board_size, game_state.board.grid) new_board.place_piece(piece) return GameState( new_board, self.get_player_after_move(game_state.player_in_action), move)
class TestGame(unittest.TestCase): def setUp(self): self.board = Board() self.first_player = Player(State.white, self.board) self.second_player = Player(State.black, self.board) self.game = Game(self.board, self.first_player, self.second_player) def test_initialize(self): self.assertEqual(self.game.first_player.colour, State.white) self.assertEqual(self.game.second_player.colour, State.black) self.assertEqual(self.game.current_player_colour, State.black) def test_get_current_player(self): self.assertEqual(self.game.get_current_player(), self.second_player) def test_get_other_player(self): self.assertEqual(self.game.get_other_player(), self.first_player) def test_is_game_won_should_return_false(self): self.assertFalse(self.game.is_game_won()) def test_is_game_won_should_return_true(self): for x in range(8): for y in range(8): self.board.make_white(x, y) self.assertTrue(self.game.is_game_won()) def test_get_winner_should_return_none(self): self.assertEqual(self.game.get_winner(), None) def test_change_current_player(self): self.game.change_current_player() self.assertEqual(self.game.get_current_player(), self.first_player)
class Game: def __init__(self, settings): self.board = Board(**settings['board_settings']) self.game_tick = 0 self.actions = [] self.players = [] self.ants = [] for i, player_settings in enumerate(settings['players_settings']): # Initialize players player = Player(player_id=i, **player_settings) self.players.append(player) # Initialize ants tile = self.board.random_empty_tile() self.ants.append(Ant(player=player, tile=tile, type="queen")) # Initialize food amt = settings['food_amount'] num_sources = settings['food_sources'] sprawl = settings['food_sprawl'] tiles = self.board.flat_tiles() shuffle(tiles) for t in tiles[0:num_sources]: self.gen_food(t, amt, sprawl) def gen_food(self, tile, amt, itr): if itr is not 0: if type(tile.item) is Food: tile.item.quantity += amt elif tile.item is None: tile.add_item(Food(tile=tile, quantity=amt)) nt = choice(tile.adjacent_tiles()) self.gen_food(nt, amt, itr - 1) def grow_food(self): for t in self.board.flat_tiles(): if t.has_item and not t.has_food(): next food_tiles = [t for t in t.adjacent_tiles() if t.has_food()] if t.has_food(): food_tiles += [t] num_food = len(food_tiles) sum_food = sum([t.item.quantity for t in food_tiles]) prob = math.pow(num_food + 1, 3) prob = prob * (1 + (sum_food / 100)) if random() < (prob / 100000): t.add_food(10) def advance_game(self): shuffle(self.ants) for ant in self.ants: action = ant.player.get_action_for_ant(ant, self.board) # Perform the action. If it succeeds, append it to actions if action.perform(self): # This will get big pretty fast if we don't flush it self.actions.append(action) self.grow_food() self.game_tick += 1
def generate_move(net: Connect4Network, current_board: Board, args: AlphaZeroArgs) -> Column: """ Generates move with low randomness of given Board. Used for actual playing @param net: Neural Network Object @param current_board: current board state @param args: AlphaZeroArgs @return: Column index """ if current_board.check_winner() or current_board.is_final(): raise AssertionError("Game is already over.") # Low temperature t = 0.1 # Run a UCT_search for current board root = UCT_search(current_board, args.num_reads_mcts, net) policy = get_policy(root, t) print(f"Policy: {policy}") # Determines move from policy move = np.random.choice(np.array([0, 1, 2, 3, 4, 5, 6]), p=policy) print(f"Playing col: {move}") return move
def generate_move( board: Board, player: Player, saved_state: Optional[SavedState] ) -> Tuple[PlayerAction, Optional[SavedState]]: """ Genrates move using trained model and MCTS @param board: connect4board @param saved_state: @return: PlayerAction """ from agents.agent_alphazero.alpha_zero_args import AlphaZeroArgs global nn # translate the board b = board.copy() b.current_board[b.current_board == 0] = PLAYER_NONE alpha_zero_args = AlphaZeroArgs() if nn is None: nn = load_connect4network(alpha_zero_args, alpha_zero_args.playing_iteration) print(f"I'm AlphaZero playing as Player {b.player}") return PlayerAction(generate_move_mc(nn, b, alpha_zero_args)), saved_state
def _simulate_random_game_for_state(self, game_state): random_agent_0 = RandomAgent(Connect5Game.ASSIGNED_PLAYER_ID_1, "RandomAgent0") random_agent_1 = RandomAgent(Connect5Game.ASSIGNED_PLAYER_ID_2, "RandomAgent1") bots = {} bots[random_agent_0.id] = random_agent_0 bots[random_agent_1.id] = random_agent_1 # current board status board = Board(game_state.board.board_size, game_state.board.grid) init_game_state = GameState(board, game_state.player_in_action, None) game = Connect5Game(init_game_state, [random_agent_0.id, random_agent_1.id], 0) # game.working_game_state.board.print_board() while not game.is_over(): move = bots[game.working_game_state.player_in_action].select_move( game) game.apply_move(move) winner = game.final_winner # game.working_game_state.board.print_board() if winner is None: return 0.0 else: return 1.0 if winner == game_state.player_in_action else -1.0
def evaluate(): mcts_agent = MCTSAgent(Connect5Game.ASSIGNED_PLAYER_ID_1, "MCTSAgent", 7000, 5.0) human_player = HumanPlayer(Connect5Game.ASSIGNED_PLAYER_ID_2, "Human") board = Board(11) players = {} players[mcts_agent.id] = mcts_agent players[human_player.id] = human_player init_game_state = GameState(board, 0, None) game = Connect5Game(init_game_state, [mcts_agent.id, human_player.id], 0) while not game.is_over(): move = players[game.working_game_state.player_in_action].select_move( game) if game.working_game_state.player_in_action == Connect5Game.ASSIGNED_PLAYER_ID_2: mcts_agent.mcts_tree.go_down(game, move) game.apply_move(move) print("Last move is {}".format(move.point)) if game.working_game_state.player_in_action == Connect5Game.ASSIGNED_PLAYER_ID_1: game.working_game_state.board.print_board() game.working_game_state.board.print_board() # self._logger.info(game.final_winner.name) print(players[game.final_winner].name)
def _check_policy_once(self): device = self._devices[0] mcts_agent = MCTSAgent(Connect5Game.ASSIGNED_PLAYER_ID_1, "MCTSAgent", self._basic_mcts_rounds_per_move, self._basic_mcts_c_puct) az_agent = AlphaZeroAgent(Connect5Game.ASSIGNED_PLAYER_ID_2, "AlphaZeroAgent", self._encoder, self._model, self._az_mcts_rounds_per_move, self._c_puct, self._az_mcts_temperature, device=device) board = Board(self._board_size) players = {} players[mcts_agent.id] = mcts_agent players[az_agent.id] = az_agent start_game_state = GameState(board, mcts_agent.id, None) # MCTS agent always plays first game = Connect5Game(start_game_state, [mcts_agent.id, az_agent.id], self._number_of_planes, is_self_play=False) while not game.is_over(): move = players[game.working_game_state.player_in_action].select_move( game) if players[0].id == game.working_game_state.player_in_action: players[1].mcts_tree.go_down(game, move) else: players[0].mcts_tree.go_down(game, move) game.apply_move(move) winner = game.final_winner score = 0 if winner == az_agent.id: score = 1 print('This score is {}'.format(score)) return score
def calc_hash(self, board: Board, max_player: Player): """ Calculates the hash value of a given board state combined with the given maximizing player. Mirrored boards with the same maximizing player will have the same hash value. :param board: The board state :param max_player: The maximizing player :return: Returns the calculated hash """ return min(hash((board, max_player)), hash((board.mirrored_copy(), max_player)))
def __init__(self, tty_file, id): log("%s.%s tty_file=%s id=%s", self.__class__, self.__init__.__name__, tty_file, id) self.id = id self.btp_address = BTP_ADDRESS + '-' + str(self.id) self.tty_file = tty_file self.socat_process = None self._btp_socket = None self._btp_worker = None self.rtt = None self.log_filename = "iut-mynewt-{}.log".format(id) self.log_file = open(self.log_filename, "w") self.board = Board(id, "nrf52", self.log_file) self._stack = Stack() self._event_handler = BTPEventHandler(self)
def agent_vs_agent(generate_move_1: GenMove, generate_move_2: GenMove, player_1: str = "Player 1", player_2: str = "Player 2", args_1: tuple = (), args_2: tuple = (), init_1: Callable = lambda board, player: None, init_2: Callable = lambda board, player: None): from agents.common import PLAYER1, PLAYER2, GameState from agents.common import initialize_game_state, pretty_print_board, apply_player_action, check_end_state players = (PLAYER1, PLAYER2) for play_first in (1, -1): for init, player in zip((init_1, init_2)[::play_first], players): init(initialize_game_state(), player) saved_state = {PLAYER1: None, PLAYER2: None} board = initialize_game_state() gen_moves = (generate_move_1, generate_move_2)[::play_first] player_names = (player_1, player_2)[::play_first] gen_args = (args_1, args_2)[::play_first] playing = True while playing: for player, player_name, gen_move, args in zip( players, player_names, gen_moves, gen_args, ): t0 = time.time() print(pretty_print_board(board)) c_board = Board(board.copy(), player) action, saved_state[player] = gen_move(c_board, player, saved_state[player], *args) print(f"Move time: {time.time() - t0:.3f}s") apply_player_action(board, action, player) end_state = check_end_state(board, player) if end_state != GameState.STILL_PLAYING: print(pretty_print_board(board)) if end_state == GameState.IS_DRAW: print("Game ended in draw") else: print( f'{player_name} won playing {"X" if player == PLAYER1 else "O"}' ) time.sleep(4) playing = False
def _collect_data_once_in_parallel(encoder, model, az_mcts_round_per_moves, board_size, number_of_planes, c_puct, az_mcts_temperature, device, pipe): agent_1 = AlphaZeroAgent(Connect5Game.ASSIGNED_PLAYER_ID_1, "AlphaZeroAgent1", encoder, model, az_mcts_round_per_moves, c_puct, az_mcts_temperature, device=device) agent_2 = AlphaZeroAgent(Connect5Game.ASSIGNED_PLAYER_ID_2, "AlphaZeroAgent2", encoder, model, az_mcts_round_per_moves, c_puct, az_mcts_temperature, device=device) agent_2.mcts_tree = agent_1.mcts_tree board = Board(board_size) players = {agent_1.id: agent_1, agent_2.id: agent_2} start_game_state = GameState(board, random.choice([agent_1.id, agent_2.id]), None) game = Connect5Game(start_game_state, [agent_1.id, agent_2.id], number_of_planes, is_self_play=True) while not game.is_over(): move = players[ game.working_game_state.player_in_action].select_move(game) game.apply_move(move) # game.working_game_state.board.print_board() # game.working_game_state.board.print_board() winner = game.final_winner if winner is not None: if winner == players[0].id: players[0].experience_collector.complete_episode(reward=1) players[1].experience_collector.complete_episode(reward=-1) if winner == players[1].id: players[1].experience_collector.complete_episode(reward=1) players[0].experience_collector.complete_episode(reward=-1) expericence_buffer = ExpericenceBuffer() expericence_buffer.combine_experience( [agent_1.experience_collector, agent_2.experience_collector]) pipe.send(expericence_buffer) pipe.close()
def feature1(board: Board, player: Player) -> float: """ Feature 1 checks for four connected stones (win). :param board: The board to check against :param player: The player to check for :return: The heuristic for feature 1 """ if board.check_winner(player): return np.inf else: return float(0)
def select_move(bot_name): bot_agent = AlphaZeroAgent(Connect5Game.ASSIGNED_PLAYER_ID_2, "Agent_New", encoder, model_new, az_mcts_round_per_moves, c_puct, az_mcts_temperature, device=device) human_agent = HumanPlayer(Connect5Game.ASSIGNED_PLAYER_ID_1, "HumanPlayerX") board = Board(board_size) players = {} players[bot_agent.id] = bot_agent players[human_agent.id] = human_agent start_game_state = GameState(board, human_agent.id, None) game = Connect5Game(start_game_state, [bot_agent.id, human_agent.id], number_of_planes, False) historic_jboard_positions = [ point_from_coords([move][0]) for move in (request.json)['moves'] ] historic_moves = [ Move(Point(historic_jboard_position.row, historic_jboard_position.col)) for historic_jboard_position in historic_jboard_positions ] over = False bot_move_str = '' for move in historic_moves: game.apply_move(move) if game.is_over(): over = True else: bot_move = bot_agent.select_move(game) game.apply_move(bot_move) game.working_game_state.board.print_board() jboard_postion = Point(bot_move.point.row, bot_move.point.col) bot_move_str = coords_from_point(jboard_postion) over = True if game.is_over() else False return jsonify({ 'bot_move': bot_move_str, 'over': over, 'diagnostics': None })
def _collect_data_once(self): agent_1 = AlphaZeroAgent(Connect5Game.ASSIGNED_PLAYER_ID_1, "AlphaZeroAgent1", self._encoder, self._model, self._az_mcts_rounds_per_move, self._c_puct, self._az_mcts_temperature, device=self._devices[0]) agent_2 = AlphaZeroAgent(Connect5Game.ASSIGNED_PLAYER_ID_2, "AlphaZeroAgent2", self._encoder, self._model, self._az_mcts_rounds_per_move, self._c_puct, self._az_mcts_temperature, device=self._devices[0]) # Two agents use the same tree to save the memory and improve the efficency agent_2.mcts_tree = agent_1.mcts_tree board = Board(self._board_size) players = {agent_1.id: agent_1, agent_2.id: agent_2} start_game_state = GameState(board, random.choice([agent_1.id, agent_2.id]), None) game = Connect5Game(start_game_state, [agent_1.id, agent_2.id], self._number_of_planes, is_self_play=True) while not game.is_over(): move = players[ game.working_game_state.player_in_action].select_move(game) game.apply_move(move) # game.working_game_state.board.print_board() # game.working_game_state.board.print_board() winner = game.final_winner if winner is not None: if winner == players[0].id: players[0].experience_collector.complete_episode(reward=1) players[1].experience_collector.complete_episode(reward=-1) if winner == players[1].id: players[1].experience_collector.complete_episode(reward=1) players[0].experience_collector.complete_episode(reward=-1) self._experience_buffer.combine_experience( [agent_1.experience_collector, agent_2.experience_collector])
def __init__(self, settings): self.board = Board(**settings['board_settings']) self.game_tick = 0 self.actions = [] self.players = [] self.ants = [] for i, player_settings in enumerate(settings['players_settings']): # Initialize players player = Player(player_id=i, **player_settings) self.players.append(player) # Initialize ants tile = self.board.random_empty_tile() self.ants.append(Ant(player=player, tile=tile, type="queen")) # Initialize food amt = settings['food_amount'] num_sources = settings['food_sources'] sprawl = settings['food_sprawl'] tiles = self.board.flat_tiles() shuffle(tiles) for t in tiles[0:num_sources]: self.gen_food(t, amt, sprawl)
def generate_move_random( board: Board, player: Player, saved_state: Optional[SavedState] ) -> Tuple[PlayerAction, Optional[SavedState]]: """ Generates a random move for a given board state. :param board: The board to generate the move for :param player: The player to generate a move for :param saved_state: The saved state (not used here) :return: The randomly generated move and the passed saved state """ print(f"I'm the random agent. Playing as {board.player}") available_columns = board.actions() action = PlayerAction(rnd.choice(available_columns)) print(f"Choosing to play {action}!") return action, saved_state
def test_board_is_game_over_false(self,size,initial_grid): board1 = Board(size); board1.grid = initial_grid self.assertFalse(board1.is_game_over())
def point_from_coords(cls, text): col_name = text[0] row = int(text[1]) return Point(row, Board.get_column_indicator_index(col_name)+1)
def play_round(self, num_reads: int) -> Tuple[Optional[str], List[np.ndarray]]: """ Evaluate the trained network by playing matches between the current and the previous NN @param num_reads: see args """ print("Starting game round...") # randomly choose starting player if np.random.uniform(0, 1) <= 0.5: white = self.current black = self.best w = "current" b = "best" else: white = self.best black = self.current w = "best" b = "current" # initializing current_board = Board() game_won = False dataset = [] value = 0 temperature = 0.1 # exploration vs exploitation factor (smaller -> more exploitation) while not game_won and current_board.is_playable(): dataset.append(copy.deepcopy(current_board.encode())) # get Policy if current_board.player == PLAYER_1: root = UCT_search(current_board, num_reads, white) policy = get_policy(root, temperature) print("Policy: ", policy, "white = %s" % (str(w))) elif current_board.player == PLAYER_2: root = UCT_search(current_board, num_reads, black) policy = get_policy(root, temperature) print("Policy: ", policy, "black = %s" % (str(b))) else: raise AssertionError("Invalid player.") # Chose a Column with given policy col_choice = np.random.choice(np.array([0, 1, 2, 3, 4, 5, 6]), p=policy) current_board.drop_piece(col_choice) # move piece print(current_board) if current_board.check_winner(): # someone wins if current_board.player == PLAYER_1: # black wins value = -1 elif current_board.player == PLAYER_2: # white wins value = 1 game_won = True # Append new board to the dataset encoded in one-hot-encoding manner dataset.append(current_board.encode()) if value == -1: dataset.append(f"{b} as black wins") return b, dataset elif value == 1: dataset.append(f"{w} as white wins") return w, dataset else: dataset.append("Nobody wins") return None, dataset
def test_board_grid_update_move_not_allowed(self,size,initial_grid, move): board1 = Board(size); board1.grid = initial_grid self.assertFalse(board1.update_move(move,"X"))
def test_board_is_winner_true(self,size,initial_grid): board1 = Board(size); board1.grid = initial_grid self.assertTrue(board1.is_winner(RECORD_THE_WINNER))
def test_feature1(self): # |==============| # | | # | | # | | # | | # |O O O | # |X X X X | # |==============| # |0 1 2 3 4 5 6 | b = Board() b.drop_piece(0, PLAYER_1) b.drop_piece(0, PLAYER_2) b.drop_piece(1, PLAYER_1) b.drop_piece(1, PLAYER_2) b.drop_piece(2, PLAYER_1) b.drop_piece(2, PLAYER_2) b.drop_piece(3, PLAYER_1) self.assertTrue(b.check_winner(PLAYER_1)) self.assertFalse(b.check_winner(PLAYER_2)) self.assertEqual(heuristic_1.feature1(b, PLAYER_1), np.inf)
def test_feature2(self): # |==============| # | | # | | # | | # | | # | O O | # | X X X O | # |==============| # |0 1 2 3 4 5 6 | b_3_in_row_1_neighbour = Board() b_3_in_row_1_neighbour.drop_piece(1, PLAYER_1) b_3_in_row_1_neighbour.drop_piece(1, PLAYER_2) b_3_in_row_1_neighbour.drop_piece(2, PLAYER_1) b_3_in_row_1_neighbour.drop_piece(2, PLAYER_2) b_3_in_row_1_neighbour.drop_piece(3, PLAYER_1) b_3_in_row_1_neighbour.drop_piece(4, PLAYER_2) self.assertEqual( heuristic_1.feature2(b_3_in_row_1_neighbour, PLAYER_1), float(900000)) # |==============| # | | # | | # | | # | | # |O O | # |X X X O | # |==============| # |0 1 2 3 4 5 6 | b_3_in_row_1_gap = Board() b_3_in_row_1_gap.drop_piece(0, PLAYER_1) b_3_in_row_1_gap.drop_piece(0, PLAYER_2) b_3_in_row_1_gap.drop_piece(2, PLAYER_1) b_3_in_row_1_gap.drop_piece(2, PLAYER_2) b_3_in_row_1_gap.drop_piece(3, PLAYER_1) b_3_in_row_1_gap.drop_piece(4, PLAYER_2) self.assertEqual(heuristic_1.feature2(b_3_in_row_1_gap, PLAYER_1), float(900000))
def coords_from_point(point): coords = '%s%d' % (Board.get_column_indicator(point.col - 1), point.row - 1) return coords
def test_board_grid_update_move_match_expected(self,size,move,expected_grid): board1 = Board(size); board1.update_move(move,"X") self.assertEqual(board1.grid,expected_grid)
def setUp(self): self.board = Board() self.first_player = Player(State.white, self.board) self.second_player = Player(State.black, self.board) self.game = Game(self.board, self.first_player, self.second_player)
def mini_max(board: Board, alpha: float, beta: float, depth: int, max_player: Player, cache: MiniMaxCache2) \ -> MiniMaxResult: """ Execute the MiniMax algorithm with alpha-beta-pruning on a board. :param board: The board :param alpha: Lower boundary :param beta: Upper boundary :param depth: Maximum depth :param max_player: The maximizing player ID :param cache: The MiniMax cache to use :return: Returns the best possible result after traversing the tree with the given depth. """ # first, let's see if we already cached this value cached_value = cache.get(board, max_player) if cached_value is not None: return cached_value # If we hit max depth or the board is in a final state, calculate and return the heuristic. # We cannot decide about the best move here, so leave it empty. if depth == 0 or board.get_state() != GameState.ONGOING: return MiniMaxResult(None, calculate_heuristic(board, max_player).value) # collect a list of available moves from the board and order them according to moveOrder # which orders moves based on their distance to the middle column available_moves = board.actions() available_moves = [move for move in moveOrder if move in available_moves] # check if we are maximizing right now is_maximizing = (board.player == max_player) # store min and max value, initialize with boundaries max_value, min_value = alpha, beta # initialize the best move randomly best_move: Column = random.choice(available_moves) # enumerate all available moves. Recurse for each while respecting is_maximizing and adjusting # min_value and max_value accordingly. for move in available_moves: # create a board with the new state child_board = board.drop_piece_copy(move) if is_maximizing: min_result = mini_max(child_board, max_value, beta, depth - 1, max_player, cache) if min_result == np.inf: # we cannot perform better than +inf. return MiniMaxResult(move, np.inf) elif min_result.value > max_value: max_value = min_result.value best_move = move if max_value >= beta: # beta pruning break else: max_result = mini_max(child_board, alpha, min_value, depth - 1, max_player, cache) if max_result.value == -np.inf: # we cannot perform better than -inf. return MiniMaxResult(move, -np.inf) elif max_result.value < min_value: min_value = max_result.value best_move = move if min_value <= alpha: # alpha pruning break # return the best move and according heuristic return MiniMaxResult(best_move, max_value if is_maximizing else min_value)
def point_from_coords(coords): col_name = coords[0] row = int(coords[1:]) point = Point(row + 1, Board.get_column_indicator_index(col_name) + 1) return point
def test_feature3(self): # |==============| # | | # | | # | | # | | # | O O | # | X X | # |==============| # |0 1 2 3 4 5 6 | b_2_in_row_both_nbs_free = Board() b_2_in_row_both_nbs_free.drop_piece(1, PLAYER_1) b_2_in_row_both_nbs_free.drop_piece(1, PLAYER_2) b_2_in_row_both_nbs_free.drop_piece(2, PLAYER_1) b_2_in_row_both_nbs_free.drop_piece(2, PLAYER_2) self.assertEqual( heuristic_1.feature3(b_2_in_row_both_nbs_free, PLAYER_1), float(50000)) # |==============| # | | # | | # | | # | | # | O | # |O X X | # |==============| # |0 1 2 3 4 5 6 | b_2_in_row_4_free = Board() b_2_in_row_4_free.drop_piece(1, PLAYER_1) b_2_in_row_4_free.drop_piece(1, PLAYER_2) b_2_in_row_4_free.drop_piece(2, PLAYER_1) b_2_in_row_4_free.drop_piece(0, PLAYER_2) self.assertEqual(heuristic_1.feature3(b_2_in_row_4_free, PLAYER_1), float(30000))