from snakes.TemplateSnake import TemplateSnake from collections import deque class BetterMasterSnake(TemplateSnake): def __init__(self): super().__init__() self.name = "BetterMasterSnake" self.disabled_find_near_by_food = True # Definiere die möglichen Bewegungsrichtungen self.min_safe_area = 2 def get_possible_moves(self, my_head): return { "up": { "x": my_head["x"], "y": my_head["y"] + 1 }, "down": { "x": my_head["x"], "y": my_head["y"] - 1 }, "left": { "x": my_head["x"] - 1, "y": my_head["y"] }, "right": { "x": my_head["x"] + 1, "y": my_head["y"] } } def get_snake_body_without_snake_tail(self, snake:list[dict]): if len(set((pos["x"], pos["y"]) for pos in snake)) < 3: return snake snake.pop() return snake def avoid_my_body(self) -> list: """ my_body: List of dictionaries of x/y coordinates for every segment of a Battlesnake. e.g. [ {"x": 0, "y": 0}, {"x": 1, "y": 0}, {"x": 2, "y": 0} ] possible_moves: List of strings. Moves to pick from. e.g. ["up", "down", "left", "right"] return: The list of remaining possible_moves, with the 'neck' direction removed """ remove = [] for direction, location in self.safe_positions.items(): if location in self.my_body: remove.append(direction) for direction in remove: del self.safe_positions[direction] self.add_calculations({"function": "avoid_my_body", "my_body": self.my_body, "safe_positions": self.safe_positions}) def avoid_walls(self): remove = [] for direction, location in list(self.safe_positions.items()): x_out_range = (location["x"] < 0 or location["x"] == self.board_width) y_out_range = (location["y"] < 0 or location["y"] == self.board_height) if x_out_range or y_out_range: remove.append(direction) for direction in remove: del self.safe_positions[direction] self.add_calculations({"function": "avoid_walls", "board_width": self.board_width, "board_height": self.board_height, "safe_positions": self.safe_positions}) def avoid_snakes(self): remove = [] for snake in self.other_snakes: for direction, location in self.safe_positions.items(): if self.game_type == "constrictor": if location in snake["body"]: remove.append(direction) else: if location in self.get_snake_body_without_snake_tail(snake["body"]): remove.append(direction) remove = set(remove) for direction in remove: del self.safe_positions[direction] self.add_calculations({"function": "avoid_snakes", "other_snakes": self.other_snakes, "safe_positions": self.safe_positions}) def avoid_get_eaten_by_other_snakes(self): remove = [] for snake in self.other_snakes: for direction, location in self.safe_positions.items(): if len(self.safe_positions) > 1: if snake["length"] < self.my_snake["length"] and location in [{"x": v["x"], "y": v["y"]} for k, v in self.get_possible_moves(snake["head"]).items()]: self.eat_the_snake_overwrite = True return direction elif location in [{"x": v["x"], "y": v["y"]} for k, v in self.get_possible_moves(snake["head"]).items()]: remove.append(direction) remove = set(remove) for direction in remove: del self.safe_positions[direction] self.add_calculations({"function": "avoid_get_eaten_by_other_snakes", "other_snakes": self.other_snakes, "safe_positions": self.safe_positions}) def find_safe_positions(self): self.safe_positions = self.get_possible_moves(self.my_head) self.add_calculations({"function": "get_possible_moves", "safe_positions": self.safe_positions}) self.avoid_my_body() self.avoid_walls() self.avoid_snakes() self.avoid_get_eaten_by_other_snakes() def choose_move(self, game_data): move = None self.calculations = [] self.eat_the_snake_overwrite = False self.board_width = game_data['board']['width'] self.board_height = game_data['board']['height'] self.my_snake = game_data['you'] self.other_snakes = [ x for x in game_data['board']['snakes'] if x["id"] != self.my_snake["id"] ] self.my_head = self.my_snake['head'] self.my_body = self.my_snake["body"] self.food_positions = game_data['board']['food'] self.game_type = game_data['game']["ruleset"]["name"] self.find_safe_positions() if self.eat_the_snake_overwrite: self.add_calculations({"function": "eat_the_snake_overwrite", "my_head": self.my_head, "move": move}) return self.safe_positions if self.game_type == "constrictor": move = self.ensure_escape_route(self.find_direction()) self.add_calculations({"function": "find_direction", "my_head": self.my_head, "move": move}) else: # Finde den besten Weg zur Nahrung if self.is_food_nearby() or self.disabled_find_near_by_food: path_to_food = self.find_path_to_food() if path_to_food: move = self.move_towards(path_to_food[0]) self.add_calculations({"function": "move_towards", "my_head": self.my_head, "path_to_food": path_to_food, "move": move}) if not move: move = self.move_close_to_body() self.add_calculations({"function": "move_close_to_body", "my_head": self.my_head, "move": move}) # Überprfe, ob der Zug einen Ausweg lässt move = self.ensure_escape_route(move) self.add_calculations({"function": "ensure_escape_route", "my_head": self.my_head, "move": move, "safe_positions": self.safe_positions}) self.add_to_history({"turn": game_data["turn"], "data": self.calculations}) return move if move else "up" def is_food_nearby(self): for food in self.food_positions: if abs(self.my_head['x'] - food['x']) <= 1 and abs(self.my_head['y'] - food['y']) <= 1: self.add_calculations({"function": "is_food_nearby", "my_head": self.my_head, "food": food, "return": True}) return True self.add_calculations({"function": "is_food_nearby", "my_head": self.my_head, "food_positions": self.food_positions, "return": False}) return False def find_path_to_food(self): # Exclude own snake's body from obstacles obstacles = set((part['x'], part['y']) for part in self.my_body) for snake in self.other_snakes: for part in snake['body']: obstacles.add((part['x'], part['y'])) # Choose the closest food source based on the heuristic closest_food = min(self.food_positions, key=lambda food: abs(food['x'] - self.my_head['x']) + abs(food['y'] - self.my_head['y'])) # Use A* to search for a safe path path = self.a_star_search(self.my_head, closest_food, obstacles, self.board_width, self.board_height) return path def find_path_to_tail(self): # Exclude other snake's body from obstacles obstacles = set() for snake in self.other_snakes: for part in snake['body']: obstacles.add((part['x'], part['y'])) my_snake_tail = {"x": self.my_body[-1]['x'], "y": self.my_body[-1]['y']} # Use A* to search for a safe path path = self.a_star_search(self.my_head, my_snake_tail, obstacles, self.board_width, self.board_height) return path def move_towards(self, target): best_direction = None min_distance = float('inf') for direction, coords in self.safe_positions.items(): distance = abs(target['x'] - coords['x']) + abs(target['y'] - coords['y']) if distance < min_distance: min_distance = distance best_direction = direction return best_direction if best_direction else "up" def move_close_to_body(self): # Heuristik, um Positionen nahe dem eigenen Körper zu bevorzugen body_positions = set((part['x'], part['y']) for part in self.my_body) best_move = None best_score = float('inf') for direction, pos in self.safe_positions.items(): next_position = (pos['x'], pos['y']) if next_position in self.safe_positions: # Berechne die Distanz zum eigenen Körper distance_to_body = min(abs(next_position[0] - part[0]) + abs(next_position[1] - part[1]) for part in body_positions) if distance_to_body < best_score: best_score = distance_to_body best_move = direction return best_move if best_move else "up" # Standardbewegung, falls keine bessere gefunden wird def ensure_escape_route(self, move): try: future_position = self.safe_positions[move] except KeyError: for move, pos in self.safe_positions.items(): if self.is_near_tail(pos, (self.my_body[-1]['x'], self.my_body[-1]['y'])): self.add_calculations({"function": "ensure_escape_route", "move": move, "is_near_tail": True}) move = self.move_towards(pos) return move else: path_to_tail = self.find_path_to_tail() if path_to_tail: self.add_calculations({"function": "move_towards", "my_head": self.my_head, "path_to_tail": path_to_tail, "move": move}) move = self.move_towards(path_to_tail[0]) self.add_calculations({"function": "ensure_escape_route", "move": move, "KeyError": "Snake Coild itself up"}) return move return move future_coords = (future_position['x'], future_position['y']) tail_position = (self.my_body[-1]['x'], self.my_body[-1]['y']) accessible_area_count = self.flood_fill_count(future_coords, [(part['x'], part['y']) for part in self.my_body]) self.add_calculations({ "function": "flood_fill_count", "move": move, "future_coords": future_coords, "tail_position": tail_position, "accessible_area_count": accessible_area_count, }) # TODO: Fix - Snake Neat to find the best way - Close to the Tail and maybe fill most free cells as posible #if accessible_area_count < self.min_safe_area: # # Finde den nächstgelegenen Zug zum Schwanz # print(self.safe_positions.keys()) # closest_move = min(self.safe_positions.keys(), key=lambda m: self.distance_to_tail(self.safe_positions[m], tail_position)) # accessible_area_count = self.flood_fill_count((self.safe_positions[closest_move]["x"], self.safe_positions[closest_move]["y"]), [(part['x'], part['y']) for part in self.my_body]) # print(closest_move, accessible_area_count, accessible_area_count >= self.min_safe_area) # # Überprüfe, ob der nächstgelegene Zug eine größere zugängliche Fläche hat # if accessible_area_count >= self.min_safe_area: # return closest_move return move def flood_fill_count(self, start, body): visited = set() queue = deque([start]) body_set = set(body) while queue: current = queue.popleft() if current not in visited: visited.add(current) for direction, pos in self.safe_positions.items(): neighbor = (pos["x"], pos["y"]) if (neighbor not in visited and neighbor not in body_set and 0 <= neighbor[0] < self.board_width and 0 <= neighbor[1] < self.board_height): queue.append(neighbor) return len(visited) def is_near_tail(self, position, tail): return abs(position["x"] - tail[0]) + abs(position["y"] - tail[1]) <= 2 def distance_to_tail(self, position, tail): return abs(position["x"] - tail[0]) + abs(position["y"] - tail[1]) def a_star_search(self, start, goal, obstacles, board_width, board_height): # Helper functions def is_position_safe(position): return 0 <= position['x'] < board_width and 0 <= position['y'] < board_height and (position['x'], position['y']) not in obstacles def get_neighbors(position): neighbors = [] for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: # links, rechts, oben, unten neighbor = {'x': position['x'] + dx, 'y': position['y'] + dy} if is_position_safe(neighbor): neighbors.append(neighbor) return neighbors def heuristic(position, goal): # Verwenden Sie eine Heuristik, die immer positiv ist, selbst wenn das Ziel in der Nähe ist return max(abs(position['x'] - goal['x']), abs(position['y'] - goal['y'])) # Überprüfen, ob das Ziel direkt neben dem Startpunkt liegt if start == goal or (abs(start['x'] - goal['x']) <= 1 and abs(start['y'] - goal['y']) <= 1): # Wenn das Ziel neben dem Startpunkt liegt, ist der Pfad das Ziel selbst return [goal] # Initialize the open and closed list open_set = set([(start['x'], start['y'])]) came_from = {} g_score = {(start['x'], start['y']): 0} f_score = {(start['x'], start['y']): heuristic(start, goal)} while open_set: current = min(open_set, key=lambda pos: f_score.get(pos, float('inf'))) current_dict = {'x': current[0], 'y': current[1]} if current_dict == goal: # Reconstruct the path path = [] while current in came_from: current = came_from[current] path.append({'x': current[0], 'y': current[1]}) path.reverse() if path and path[0] == start: path.pop(0) # Entferne das erste Element, wenn es dem Start entspricht return path # Return the path as a list of dicts open_set.remove(current) for neighbor in get_neighbors(current_dict): neighbor_tuple = (neighbor['x'], neighbor['y']) tentative_g_score = g_score[current] + 1 # Distance between neighbors is always 1 if tentative_g_score < g_score.get(neighbor_tuple, float('inf')): came_from[neighbor_tuple] = current g_score[neighbor_tuple] = tentative_g_score f_score[neighbor_tuple] = g_score[neighbor_tuple] + heuristic(neighbor, goal) if neighbor_tuple not in open_set: open_set.add(neighbor_tuple) return None # Kein Pfad gefunden def find_direction(self): # Beispielhafte Logik zur Auswahl einer Bewegungsrichtung for direction, pos in self.safe_positions.items(): next_position = (pos['x'], pos['y']) # Konvertiere safe_positions in eine Liste von Tupeln für den Vergleich safe_positions_tuples = [(pos['x'], pos['y']) for pos in self.safe_positions.values()] if next_position in safe_positions_tuples: return direction return "up" # Standardbewegung, falls keine sichere Position gefunden wird def add_calculations(self, calculations:dict): self.calculations.append(calculations)