def pieces_threatened(game_state, player): other = game.other_player(player) home_b = count_pieces(game_state[player]) pieces = 0 for enemy in game_state[other]: xy = enemy[1], enemy[2] for move in tokens.available_moves(other): if move == "Boom": continue for dist in range(enemy[0]): dist = enemy[0] - dist xy2 = game.dir_to_xy(xy, move, dist) temp_game = game.Game(game_state) if tokens.out_of_board( xy2) or not temp_game.board.is_cell_empty(xy2): continue temp_game.move_token(1, xy, move, dist, other) temp_game.boom(xy2, other) home_a = count_pieces(temp_game.get_game_state()[player]) pieces += home_b - home_a return pieces
def action(self): """ This method is called at the beginning of each of your turns to request a choice of action from your program. Based on the current state of the game, your player should select and return an allowed action to play on this turn. The action must be represented based on the spec's instructions for representing actions. """ # TODO: Decide what action to take, and return it self.past_states.append(self.game_state[self.colour]) self.home_tokens = sum([x[0] for x in self.game_state[self.colour]]) self.away_tokens = sum( [x[0] for x in self.game_state[game.other_player(self.colour)]]) simulations = 14 * len(self.game_state[self.colour]) search_depth = 3 action = None if self.opening_book.check_early_game(): action = self.opening_book.next_move() if action: return action if self.away_tokens == 1 and self.home_tokens >= 1: strategy = self.agent.one_enemy_endgame(self.game_state, simulations, search_depth, 1) elif self.away_tokens == 2 and self.home_tokens >= 2: strategy = self.agent.two_enemy_endgame(self.game_state, simulations, search_depth, 1) elif self.away_tokens <= self.trading_prop and self.away_tokens < self.home_tokens: strategy = self.agent.trade_tokens(self.game_state, simulations, search_depth, 1) else: strategy = self.agent.monte_carlo(self.game_state, simulations, search_depth) n, xy, move, distance = strategy if move == "Boom": return "BOOM", xy else: x_a, y_a = xy x_b, y_b = game.dir_to_xy(xy, move, distance) return "MOVE", n, (x_a, y_a), (x_b, y_b)
def two_enemy_endgame(self, game_state, simulations, search_depth, trade_threshold): enemy_stacks = len(game_state[self.other]) # Check if enemy can kill us or draw for piece in game_state[self.other]: temp_game = game.Game(game_state) temp_game.boom((piece[1], piece[2]), self.other) home_a = features.count_pieces( temp_game.get_game_state()[self.player]) away_a = features.count_pieces( temp_game.get_game_state()[self.other]) if not temp_game.get_game_state()[ self.player] or home_a - away_a < trade_threshold: strategy = self.monte_carlo(game_state, simulations, search_depth) return strategy # One stack if enemy_stacks == 1: enemy = game_state[self.other][0] enemy_xy = enemy[1], enemy[2] for piece in game_state[self.player]: temp_game = game.Game(game_state) temp_game.boom((piece[1], piece[2]), self.player) if temp_game.get_game_state( )[self.player] and not temp_game.get_game_state()[self.other]: return None, (piece[1], piece[2]), "Boom", None ally = self.closest_npiece(game_state, 2, self.player, enemy_xy) if ally is None: return self.make_stack(game_state) ally_xy = ally[1], ally[2] width = enemy_xy[0] - ally_xy[0] height = enemy_xy[1] - ally_xy[1] if width == 0 and abs(height) <= ally[0]: return self.go_there( 2, ally, (enemy_xy[0], enemy_xy[1] - np.sign(height))) if height == 0 and abs(width) <= ally[0]: return self.go_there( 2, ally, (enemy_xy[0] - np.sign(width), enemy_xy[1])) enemy_corner_xy = self.get_nearest_corner(ally_xy, enemy_xy) move = self.go_there(2, ally, enemy_corner_xy) if tokens.is_valid_move(self.game.board, move[1], game.dir_to_xy(move[1], move[2], move[3])): return move else: return self.monte_carlo(game_state, simulations, search_depth) # Two seperate stacks else: return self.trade_tokens(game_state, simulations, search_depth, trade_threshold)
def available_states(self, game_state, player): available = [] all_available = [] home_b, away_b = features.count_all(game_state, player) for piece in game_state[player]: xy = piece[1], piece[2] available_moves = tokens.available_moves(player) for move in available_moves: if move == "Boom": temp_game = game.Game(game_state) if not temp_game.has_surrounding(xy): continue temp_game.boom(xy, player) temp_game_state = temp_game.get_game_state() all_available.append([(None, xy, move, None), temp_game_state]) home_a, away_a = features.count_all( temp_game_state, player) # If suicide for nothing if away_a == away_b or self.is_bad_boom( game_state, temp_game_state, player): continue available.append([(None, xy, move, None), temp_game.get_game_state()]) else: if piece[0] == 1: amounts = [1] elif piece[0] == 2: amounts = [1, 2] else: amount = min(piece[0], 8) # Move whole stack or leave one or move one amounts = [1, amount, amount - 1] for n in amounts: for distance in range(piece[0]): distance = piece[0] - distance temp_game = game.Game(game_state) if temp_game.is_valid_move(xy, move, distance, player): temp_game.move_token(n, xy, move, distance, player) xy2 = game.dir_to_xy(xy, move, distance) # Don't break stack if not moving to attack if piece[ 0] != 1 and n == 1 and not self.has_potential_threat( xy2, self.other): continue if not self.has_potential_threat( xy, self.other): # Don't break stack if not running away and leaving if piece[0] > 2 and n == piece[0] - 1: continue # We don't like a v pattern (inefficient move) if self.creates_v(temp_game, xy2): continue if move in [ "Up", "Down" ] and (self.creates_v( temp_game, (xy[0] + 1, xy[1])) or self.creates_v( temp_game, (xy[0] - 1, xy[1]))): continue if move in [ "Left", "Right" ] and (self.creates_v( temp_game, (xy[0], xy[1] + 1)) or self.creates_v( temp_game, (xy[0], xy[1] - 1))): continue available.append([(n, xy, move, distance), temp_game.get_game_state()]) if player != self.player or len(available) == 0: return all_available return available
def best_child(self, root, all_simulated=True): from math import sqrt game_state = root.data uct_sim = float("-inf") can_boom = False best_boom_diff = float("-inf") best_strategy = None best_boom = None potential_threat = False run_aways = [] offensive = [] home_c = features.count_pieces(game_state[self.player]) away_c = features.count_pieces(game_state[self.other]) diff_c = self.value_diff(game_state, self.player) potential_threat1 = self.has_potential_threat( self.away_recently_moved_to, self.player) potential_threat2 = self.has_potential_threat( self.away_recently_moved_from, self.player) # If there is a threat if potential_threat1 and potential_threat2: potential_threat = True temp_game1 = game.Game(game_state) temp_game1.boom(self.away_recently_moved_to, self.other) temp_game_state1 = temp_game1.get_game_state() potential_diff1 = self.value_diff(temp_game_state1, self.player) temp_game2 = game.Game(game_state) temp_game2.boom(self.away_recently_moved_from, self.other) temp_game_state2 = temp_game2.get_game_state() potential_diff2 = self.value_diff(temp_game_state2, self.player) potential_diff = min(potential_diff1, potential_diff2) if potential_diff1 <= potential_diff2: away_recently_moved = self.away_recently_moved_to # If is bad boom for the other player if potential_diff1 >= diff_c or self.is_bad_boom( game_state, temp_game_state1, self.other): potential_threat = False else: away_recently_moved = self.away_recently_moved_from # If is bad boom for the other player if potential_diff2 >= diff_c or self.is_bad_boom( game_state, temp_game_state2, self.other): potential_threat = False else: if potential_threat1: potential_threat = True away_recently_moved = self.away_recently_moved_to temp_game = game.Game(game_state) temp_game.boom(away_recently_moved, self.other) temp_game_state = temp_game.get_game_state() potential_diff = self.value_diff(temp_game_state, self.player) if potential_diff >= diff_c or self.is_bad_boom( game_state, temp_game_state, self.other): potential_threat = False if potential_threat2: potential_threat = True away_recently_moved = self.away_recently_moved_from temp_game = game.Game(game_state) temp_game.boom(away_recently_moved, self.other) temp_game_state = temp_game.get_game_state() potential_diff = self.value_diff(temp_game_state, self.player) if potential_diff >= diff_c or self.is_bad_boom( game_state, temp_game_state, self.other): potential_threat = False for child in root.seen: strategy = child.move next_state = child.data if next_state[self.player] in self.past_states: continue # If won if not next_state[self.other] and next_state[self.player]: return strategy if strategy[2] != "Boom": xy = game.dir_to_xy(xy=strategy[1], direction=strategy[2], distance=strategy[3]) damage = sqrt(features.pieces_per_boom(next_state, self.other)) if damage == features.count_pieces(next_state[self.player]): continue # If no threat and can go on the offensive if not potential_threat and self.has_potential_threat( xy, self.other): temp_game = game.Game(next_state) temp_game.boom(xy, self.player) temp_game_state = temp_game.get_game_state() if self.is_bad_boom(next_state, temp_game_state, self.player): continue offensive.append( (self.value_diff(temp_game_state, self.player), child.uct, strategy)) # React to threat if potential_threat: # Losses from moving temp_game = game.Game(next_state) temp_game.boom(away_recently_moved, self.other) temp_game_state = temp_game.get_game_state() loss = self.value_diff(temp_game_state, self.player) if self.has_potential_threat(xy, self.other): # Potential gains from moving temp_game = game.Game(next_state) temp_game.boom(xy, self.player) temp_game_state = temp_game.get_game_state() gain = self.value_diff(temp_game_state, self.player) # Same Cluster boomed or Moving to trade is not worth it if gain > loss: trade_game = temp_game trade_game.boom(away_recently_moved, self.other) trade_game_state = trade_game.get_game_state() outcome = self.value_diff(trade_game_state, self.player) # Minimise Losses by trading run_aways.append((outcome, child.uct, strategy)) else: # Minimise Losses by Running if loss > potential_diff: run_aways.append((loss, child.uct, strategy)) else: continue # If can trade for more if strategy[2] == "Boom": temp_game = game.Game(next_state) # React to threat if potential_threat and not temp_game.board.is_cell_empty( away_recently_moved): temp_game.boom(away_recently_moved, self.other) temp_game_state = temp_game.get_game_state() else: temp_game_state = temp_game.get_game_state() if self.is_bad_boom(game_state, temp_game_state, self.player): continue diff = self.value_diff(temp_game_state, self.player) if diff > best_boom_diff and \ ((home_c < away_c and diff > diff_c) or (home_c >= away_c and diff >= diff_c)): best_boom_diff = diff best_boom = strategy can_boom = True # Chose best child if child.uct > uct_sim: uct_sim = child.uct best_strategy = strategy if potential_threat: best_move = None best_move_diff = float("-inf") # Run away if len(run_aways) != 0: run_aways = sorted(run_aways, reverse=True) best_move = run_aways[0][2] best_move_diff = run_aways[0][0] if best_boom_diff >= best_move_diff: # Trade first if best_boom_diff > potential_diff and can_boom: return best_boom else: # Run away or Run away to Trade if best_move is not None and best_move_diff > potential_diff: return best_move if can_boom: return best_boom if len(offensive) != 0: offensive = sorted(offensive, reverse=True) return offensive[0][2] if not all_simulated: return None # If nothing is good if best_strategy is None: return self.random_valid_move(game_state, self.player)[0] return best_strategy