def find_chain(pawn, black_pawns, available_camps, camps_found=0, chain=set()): ''' Find a chain of black pawns connecting two or more camps groups, starting from the given coordinates ''' neighbors = TablutBoard.unique_full_k_neighbors(pawn, k=1) if not available_camps.isdisjoint(neighbors): chain.add(pawn) camps = available_camps.intersection(neighbors) camp = utils.get_rand(camps) camps.update(TablutBoard.unique_full_k_neighbors(camp, k=1), TablutBoard.unique_full_k_neighbors(camp, k=2)) available_camps.difference_update(camps) camps_found += 1 good_neighbors = black_pawns.intersection(neighbors) new_camps_found = camps_found for neighbor in good_neighbors: if neighbor in black_pawns: black_pawns.remove(neighbor) chain, new_camps_found, _ = find_chain(neighbor, black_pawns, available_camps, new_camps_found, chain) if new_camps_found > camps_found: chain.add(pawn) return chain, new_camps_found, black_pawns
def get_pawns(state, king=False): ''' Return white and black pawns, with or without considering the king ''' white_pawns = (TablutBoard.player_pawns(state.pawns, TPlayerType.WHITE) if king else state.pawns[TPawnType.WHITE]) return white_pawns, TablutBoard.player_pawns(state.pawns, TPlayerType.BLACK)
def count_dead(moves, potential_killers, potential_victims): count = 0 for _, to_move in moves: killables = TablutBoard.orthogonal_k_neighbors(to_move, k=1) killers = TablutBoard.orthogonal_k_neighbors(to_move, k=2) for victim, killer in zip(killables, killers): if victim in potential_victims and killer in potential_killers: count += 1 return count
def _compute_utility(self, pawns, player): ''' Compute utility value for the given player, with the given pawns ''' return (0 if not self._goal_state(pawns) else 1 if ( (player == TablutPlayerType.WHITE and TablutBoard.king_position(pawns) in TablutBoard.WHITE_GOALS) or (player == TablutPlayerType.BLACK and TablutBoard.king_position(pawns) is None)) else -1)
def compute_reachable_goals(state): ''' Compute the goals that the king can reach ''' king = TablutBoard.king_position(state.pawns) reachable_goals = set() for goal in TablutBoard.WHITE_GOALS: if TablutBoard.dfs(state.pawns, king, goal): reachable_goals.add(goal) return reachable_goals
def blocked_chain_pawns(state, chain): ''' Return the number of white pawns, black pawns and corners blocked from a chain of black pawns ''' white_pawns, black_pawns = get_pawns(state, king=True) near_corners = find_near_corners(chain) whites_found = 0 blacks_found = 0 king_found = False for corner in near_corners: corner_whites = 0 corner_blacks = 0 neighbor = corner neighbors = TablutBoard.unique_orthogonal_k_neighbors(neighbor, k=1) visited_neighbors = set() while len(neighbors) > 0: visited_neighbors.add(neighbor) if neighbor not in chain: if neighbor in white_pawns: if neighbor == TablutBoard.king_position(state.pawns): king_found = True corner_whites += 1 elif neighbor in black_pawns: corner_blacks += 1 current_neighbors = TablutBoard.unique_orthogonal_k_neighbors( neighbor, k=1) neighbors.update( current_neighbors.difference(visited_neighbors, TablutBoard.CAMPS)) neighbor = neighbors.pop() if king_found: return corner_whites, corner_blacks, 0 whites_found += corner_whites blacks_found += corner_blacks if len(near_corners) > 1: camps = set() for a_corner in near_corners: for b_corner in near_corners: if a_corner != b_corner: camp = a_corner.middle_position(b_corner) if camp is not None: camps.add(camp) camps.update( TablutBoard.unique_orthogonal_k_neighbors(camp, k=1)) for camp in camps: if camp in black_pawns: blacks_found += 1 return whites_found, blacks_found, len(near_corners)
def black_survival(state): ''' Return a move that blocks white player winning situation ''' king_moves = TablutBoard.legal_moves( state.pawns, TablutBoard.king_position(state.pawns)) for white_position in TablutBoard.WHITE_GOALS: for king_to_move in king_moves: if white_position == king_to_move: for black_from_move, black_to_move in state.moves: if king_to_move == black_to_move: return (black_from_move, black_to_move) return None
def compute_goals_distances(state): ''' Compute king distances to goals ''' king = TablutBoard.king_position(state.pawns) goals_distances = {} for goal in TablutBoard.WHITE_GOALS: goals_distances[goal] = TablutBoard.simulate_distance( state.pawns, king, goal, max_moves=MAX_KING_MOVES_GOALS, unwanted_positions=TablutBoard.WHITE_GOALS) return goals_distances
def pawns_in_corners(state): ''' Return a value representing the number of player and enemy pawns in the extreme corners, in range [-1, 1] ''' value = 0.0 player_pawns = TablutBoard.player_pawns(state.pawns, gutils.other_player(state.to_move)) enemy_pawns = TablutBoard.player_pawns(state.pawns, state.to_move) for corner in CORNERS: if corner in player_pawns: value -= 1 / 20 elif corner in enemy_pawns: value += 1 / 30 return value
def will_king_be_dead(self, state): ''' Check if the king will be dead in the next possible states ''' return any([ TablutBoard.king_position(new_state.pawns) is None for new_state in self.next_states(state) ])
def king_killers(state): ''' Return a value representing the number of black pawns, camps and castle around the king, in range [-1, 1] ''' _, black_moves = get_moves(state) free_positions = [] killer_positions = [] killers, free_positions, killer_positions = ( TablutBoard.potential_king_killers(state.pawns)) possible_killers_count = _reachable_positions(killer_positions, black_moves) occupable_free_positions = _reachable_positions(free_positions, black_moves) white_neighbors = (4 - (killers + len(free_positions) + len(killer_positions))) value = 0.0 values = [killers, occupable_free_positions, possible_killers_count] weights = [0, 1 / 32, 0] if (TablutBoard.is_king_in_castle(state.pawns) or TablutBoard.is_king_near_castle(state.pawns)): if killers >= 3: value = 0.7 if possible_killers_count != 0: value = 0.9 elif killers == 2: weights = [1 / 6, 1 / 10, 1 / 10] elif killers == 1: weights = [1 / 8, 1 / 25, 1 / 10] else: if killers >= 1 and possible_killers_count != 0: value = 0.9 elif killers >= 3: value = 0.7 elif killers == 2: weights = [1 / 4, 1 / 10, 0] elif killers == 1: weights = [1 / 3, 1 / 10, 0] if value == 0.0: value = -white_neighbors * (3 / 40) for val, weight in zip(values, weights): value += (val * weight) return heuristic_pov(state, TPlayerType.BLACK, value)
def compute_reachable_corners(pawn, pawns): ''' Compute pawn distances to goals ''' reachable_corners = {corner: False for corner in CORNERS} for corner in reachable_corners: if corner not in pawns[TPawnType.BLACK]: pawns[TPawnType.WHITE].discard(corner) reachable_corners[corner] = TablutBoard.dfs(pawns, pawn, corner) return reachable_corners
def player_moves(cls, pawns, player_type): ''' Return a list of tuples of coordinates representing every possibile new position for each pawn of the given player ''' moves = [] pawn_types = gutils.from_player_to_pawn_types(player_type) for pawn_type in pawn_types: for pawn in pawns[pawn_type]: moves.extend(TablutBoard.moves(pawns, pawn)) return moves
def white_barriers(state): ''' Return a value representing the number of corners reachable by the king and not by the black pawns, in range [-1, 1] ''' value = 0.0 king = TablutBoard.king_position(state.pawns) tmp_pawns = gutils.clone_pawns(state.pawns) tmp_pawns[TPawnType.WHITE].difference_update(CORNERS) reachable_corners = compute_reachable_corners(king, state.pawns) for corner, king_reach in reachable_corners.items(): barrier = True if king_reach: for black_pawn in tmp_pawns[TPawnType.BLACK]: black_reach = TablutBoard.dfs(tmp_pawns, black_pawn, corner) if black_reach: barrier = False break if barrier: value = 0.99 break return heuristic_pov(state, TPlayerType.WHITE, value)
def order_moves(state, initial_moves=None, depth=None): if initial_moves is None: initial_moves = list(state.moves) beam = len(initial_moves) if depth is None or depth % 2 != 0: beam = int(beam / 2) best_move = initial_moves[0] best_moves = initial_moves[1:beam] near_king_moves, best_moves = TablutBoard.near_king_moves( state.pawns, initial_moves, best_moves) if best_move in near_king_moves: near_king_moves.remove(best_move) return [best_move] + near_king_moves + best_moves
def useful_chain(state, chain): ''' Return if the given chain is useful in the sense that the king could reach the examined corner before the chain was present, and now it is blocked ''' king = TablutBoard.king_position(state.pawns) tmp_pawns = gutils.clone_pawns(state.pawns) tmp_pawns[TPawnType.BLACK].difference_update(chain) reachable_corners = compute_reachable_corners(king, state.pawns) tmp_reachable_corners = compute_reachable_corners(king, tmp_pawns) for corner in CORNERS: if not reachable_corners[corner] and tmp_reachable_corners[corner]: return True return False
def result(self, state, move, compute_moves=True): ''' Return the next state with the given move and compute the new state moves, if specified ''' pawns = TablutBoard.move(state.pawns, state.to_move, move) to_move = gutils.other_player(state.to_move) res = TablutGameState(to_move=to_move, utility=self._compute_utility( pawns, state.to_move), is_terminal=True, pawns=pawns, moves=[], old_state=state) if not self.terminal_test(res): res.is_terminal = False if compute_moves: res.moves = self.player_moves(pawns, to_move) return res
def blocked_goals(state): ''' Return a value representing the number of blocked white goals for each corner, in range [-1, 1] ''' value = 0.0 white_pawns, black_pawns = get_pawns(state, king=False) free_goals = compute_reachable_goals(state) for pos in TablutBoard.OUTER_CORNERS: if pos in black_pawns: value -= (1 / 9) free_goals.difference_update( TablutBoard.unique_orthogonal_k_neighbors(pos)) elif pos in white_pawns: value -= (1 / 16) for goal in free_goals: if goal in black_pawns: value -= (1 / 17) elif goal in white_pawns: value -= (1 / 20) return heuristic_pov(state, TPlayerType.WHITE, value)
def _goal_state(self, pawns): ''' A state is a goal state if either the white or the black player wins ''' return (TablutBoard.king_position(pawns) is None or TablutBoard.king_position(pawns) in TablutBoard.WHITE_GOALS)
def will_king_be_dead_by_move(self, state, move): ''' Check if the king will be dead by applying the given move ''' new_state = self.result(state, move) return TablutBoard.king_position(new_state.pawns) is None