def two_enemy_endgame(self, game_state, simulations, search_depth): enemy_stacks = len(game_state[self.other]) for piece in game_state[self.player]: temp_game = game.Game(game_state) temp_game.boom((piece[1], piece[2]), self.player) if not temp_game.get_game_state()[self.player]: strategy = self.monte_carlo(game_state, simulations, search_depth) return strategy if not temp_game.get_game_state()[self.other]: return None, (piece[1], piece[2]), "Boom", None # One stack if enemy_stacks == 1: enemy = game_state[self.other][0] enemy_xy = enemy[1], enemy[2] 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] enemy_corner_xy = self.get_nearest_corner(ally_xy, enemy_xy) return self.go_there(2, ally, enemy_corner_xy) # Two seperate stacks else: return self.one_enemy_endgame(game_state, simulations, search_depth, two_enemy=True)
def one_enemy_endgame(self, game_state, simulations, search_depth, two_enemy=False): # If enemy can draw or we can win for piece in game_state[self.player]: home_b = features.count_pieces(game_state[self.player]) temp_game = game.Game(game_state) temp_game.boom((piece[1], piece[2]), self.player) home_a = features.count_pieces(temp_game.get_game_state()[self.player]) if not temp_game.get_game_state()[self.player] or (two_enemy and home_b-home_a >= 2): strategy = self.monte_carlo(game_state, simulations, search_depth) return strategy if not temp_game.get_game_state()[self.other]: return None, (piece[1], piece[2]), "Boom", None enemy = game_state[self.other][0] enemy_xy = enemy[1], enemy[2] ally = self.closest_npiece(game_state, 1, self.player, enemy_xy) ally_xy = ally[1], ally[2] # Close enough to boom if abs(enemy_xy[0] - ally_xy[0]) <= 1 and abs(enemy_xy[1] - ally_xy[1]) <= 1: return None, ally_xy, "Boom", None if two_enemy: enemy = game_state[self.other][1] enemy_xy = enemy[1], enemy[2] ally = self.closest_npiece(game_state, 1, self.player, enemy_xy) ally_xy = ally[1], ally[2] # Close enough to boom if abs(enemy_xy[0] - ally_xy[0]) <= 1 and abs(enemy_xy[1] - ally_xy[1]) <= 1: return None, ally_xy, "Boom", None return self.go_there(1, ally, enemy_xy)
def rollout_policy(self, node): game_state = node.data player = node.player temp_game = game.Game(game_state) # Assume opponent is random if player != self.player: available_moves = tokens.available_moves(player) pieces = game_state[player] strategy = None has_valid_move = False while not has_valid_move: move = random.choice(available_moves) piece = random.choice(pieces) xy = piece[1], piece[2] if move == "Boom": has_valid_move = True temp_game.boom(xy, player) strategy = [(None, xy, move, None), temp_game.get_game_state()] else: distance = random.randint(1, piece[0]) amount = random.randint(1, piece[0]) if temp_game.is_valid_move(xy, move, distance, player): has_valid_move = True temp_game.move_token(amount, xy, move, distance, player) strategy = [(amount, xy, move, distance), temp_game.get_game_state()] temp_node = self.Node(self, strategy, game.other_player(player), None) # Stick to the plan else: available = self.available_states(game_state, player) strategy = random.choice(available) temp_node = self.Node(self, strategy, game.other_player(player), None) return temp_node
def available_states(self, game_state, player): available = [] all_available = [] other = game.other_player(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.get_game_state()]) # If current number of home pieces <= current number of away pieces if features.count_pieces(game_state[player]) < features.count_pieces(game_state[other]): home_diff = features.count_pieces(game_state[player]) - features.count_pieces(temp_game_state[player]) away_diff = features.count_pieces(game_state[other]) - features.count_pieces(temp_game_state[other]) # Not worth it to trade for less if home_diff >= away_diff: continue # If suicide for nothing if features.count_pieces(game_state[other]) == features.count_pieces(temp_game_state[other]): # Don't 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) # We don't like a v pattern (inefficient move) if self.creates_v(temp_game, xy2): 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): from math import sqrt game_state = root.data n_sim = float("-inf") can_boom = False best_boom_diff = float("-inf") best_strategy = None best_boom = None home_c = features.count_pieces(game_state[self.player]) away_c = features.count_pieces(game_state[self.other]) potential_threat = self.has_potential_threat(self.away_recently_moved, self.player) if potential_threat: temp_game = game.Game(game_state) temp_game.boom(self.away_recently_moved, self.other) temp_game_state = temp_game.get_game_state() home_t = features.count_pieces(temp_game_state[self.player]) away_t = features.count_pieces(temp_game_state[self.other]) potential_diff = home_t - away_t run_aways = [] if (home_c > away_c and potential_diff > 0) or (home_c <= away_c and potential_diff >= 0): 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_stacks(next_state): continue if self.has_potential_threat(xy, self.other): home_b = features.count_pieces(next_state[self.player]) away_b = features.count_pieces(next_state[self.other]) temp_game = game.Game(next_state) temp_game.boom(xy, self.player) temp_game_state = temp_game.get_game_state() home_a = features.count_pieces(temp_game_state[self.player]) away_a = features.count_pieces(temp_game_state[self.other]) # IS dead if home_a == 0: continue # Running into a trap if (home_b - home_a) > (away_b - away_a): continue if potential_threat: # Losses temp_game = game.Game(next_state) temp_game.boom(self.away_recently_moved, self.other) temp_game_state = temp_game.get_game_state() home_l = features.count_pieces(temp_game_state[self.player]) away_l = features.count_pieces(temp_game_state[self.other]) loss = home_l - away_l # Gains temp_game1 = game.Game(next_state) temp_game1.boom(xy, self.player) temp_game_state = temp_game1.get_game_state() home_g1 = features.count_pieces(temp_game_state[self.player]) away_g1 = features.count_pieces(temp_game_state[self.other]) gain1 = home_g1 - away_g1 gain2 = float("-inf") # Leave one behind and boom there if not temp_game1.board.is_cell_empty(strategy[1]): temp_game2 = game.Game(next_state) temp_game2.boom(strategy[1], self.player) temp_game_state = temp_game2.get_game_state() home_g2 = features.count_pieces(temp_game_state[self.player]) away_g2 = features.count_pieces(temp_game_state[self.other]) gain2 = home_g2 - away_g2 gain = max(gain1, gain2) if gain2 > gain1: temp_game = temp_game2 else: temp_game = temp_game1 # Same Cluster boomed or Moving to trade is not worth it if gain <= loss: continue else: trade_game = temp_game trade_game.boom(self.away_recently_moved, self.other) trade_game_state = trade_game.get_game_state() home_o = features.count_pieces(trade_game_state[self.player]) away_o = features.count_pieces(trade_game_state[self.other]) outcome = home_o - away_o run_aways.append((outcome, strategy)) # Minimise Losses if loss > potential_diff: run_aways.append((loss, strategy)) # If can trade for more if strategy[2] == "Boom": home_a = features.count_pieces(next_state[self.player]) away_a = features.count_pieces(next_state[self.other]) diff = home_a - away_a if home_a == 0: continue if (home_c > away_c and diff > 0) or (home_c <= away_c and diff >= 0): if diff > best_boom_diff: best_boom_diff = diff best_boom = strategy can_boom = True else: continue if child.n > n_sim: n_sim = child.n best_strategy = strategy if potential_threat: best_move_diff = float("-inf") # Run away if len(run_aways) != 0: run_aways = sorted(run_aways, reverse=True) best_move = run_aways[0][1] best_move_diff = run_aways[0][0] # Trade first if best_boom_diff > potential_diff and can_boom: if best_move_diff > best_boom_diff: return best_move return best_boom if can_boom: return best_boom # If nothing is good if best_strategy is None: print("lose") # We lose anyways moves = self.available_states(game_state, self.player) return random.choice(moves)[0] return best_strategy