diff --git a/snakes/BetterMasterSnake.py b/snakes/BetterMasterSnake.py new file mode 100644 index 0000000..677ccf9 --- /dev/null +++ b/snakes/BetterMasterSnake.py @@ -0,0 +1,322 @@ +from snakes.TemplateSnake import TemplateSnake +from collections import deque + +class BetterMasterSnake(TemplateSnake): + def __init__(self): + super().__init__() + self.name = "CloseToBodySnake" + self.disabled_find_near_by_food = True + # Definiere die möglichen Bewegungsrichtungen + self.min_safe_area = 2 + self.directions = { + 'up': (0, -1), + 'down': (0, 1), + 'left': (-1, 0), + 'right': (1, 0) + } + + 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 avoid_my_body(self, my_body, possible_moves:dict) -> 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 possible_moves.items(): + if location in my_body: + remove.append(direction) + + for direction in remove: + del possible_moves[direction] + + return possible_moves + + def avoid_walls(self, board_width:int, board_height:int, possible_moves:dict): + remove = [] + for direction, location in list(possible_moves.items()): + x_out_range = (location["x"] < 0 or location["x"] == board_width) + y_out_range = (location["y"] < 0 or location["y"] == board_height) + if x_out_range or y_out_range: + remove.append(direction) + + for direction in remove: + del possible_moves[direction] + + return possible_moves + + def avoid_snakes(self, snakes:list, possible_moves:dict): + remove = [] + for snake in snakes: + for direction, location in possible_moves.items(): + if location in snake["body"]: + remove.append(direction) + + remove = set(remove) + for direction in remove: + del possible_moves[direction] + + return possible_moves + + def find_safe_positions(self): + safe_positions = self.get_possible_moves(self.my_head) + safe_positions = self.avoid_my_body(self.my_body, safe_positions) + safe_positions = self.avoid_walls(self.board_width, self.board_height, safe_positions) + safe_positions = self.avoid_snakes(self.snakes, safe_positions) + + return safe_positions + + def choose_move(self, game_data): + self.calculations = [] + self.board_width = game_data['board']['width'] + self.board_height = game_data['board']['height'] + self.snakes = game_data['board']['snakes'] + self.my_snake = game_data['you'] + self.my_head = self.my_snake['head'] + self.my_body = self.my_snake["body"] + self.food_positions = game_data['board']['food'] + move = None + + safe_positions = self.find_safe_positions() + # Finde die nächstgelegene Nahrungsquelle, wenn Nahrung vorhanden ist + try: + # 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], safe_positions) + 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(safe_positions) + self.add_calculations({"function": "move_close_to_body", "my_head": self.my_head, "move": move, "safe_positions": safe_positions}) + + # Überprfe, ob der Zug einen Ausweg lässt + move = self.ensure_escape_route(move, safe_positions) + self.add_calculations({"function": "ensure_escape_route", "my_head": self.my_head, "move": move, "safe_positions": safe_positions}) + except ValueError: + move = self.ensure_escape_route(self.find_direction(safe_positions), safe_positions) + self.add_calculations({"function": "ValueError - find_direction", "my_head": self.my_head, "move": move, "safe_positions": safe_positions}) + + self.add_to_history(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.snakes: + if snake['id'] != self.my_snake['id']: + 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.snakes: + if snake['id'] != self.my_snake['id']: + 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, safe_positions): + best_direction = None + min_distance = float('inf') + for direction, coords in 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, safe_positions): + # 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, (dx, dy) in self.directions.items(): + next_position = (self.my_head['x'] + dx, self.my_head['y'] + dy) + if next_position in 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, safe_positions): + try: + future_position = safe_positions[move] + except KeyError: + for move, pos in safe_positions.items(): + print(move, pos, (self.my_body[-1]['x'], self.my_body[-1]['y']), self.is_near_tail((pos["x"], pos["y"]), (self.my_body[-1]['x'], self.my_body[-1]['y']))) + if self.is_near_tail((pos["x"], pos["y"]), (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, safe_positions) + 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], safe_positions) + + self.add_calculations({"function": "ensure_escape_route", "move": move, "KeyError": "Snake Coild itself up"}) + 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": "ensure_escape_route", + "move": move, + "future_coords": future_coords, + "accessible_area_count": accessible_area_count, + }) + + print(accessible_area_count) + #if accessible_area_count < self.min_safe_area: + # # Finde den nächstgelegenen Zug zum Schwanz + # closest_move = min(safe_positions.keys(), key=lambda m: self.distance_to_tail(safe_positions[m], tail_position)) + # # Überprüfe, ob der nächstgelegene Zug eine größere zugängliche Fläche hat + # if self.flood_fill_count(safe_positions[closest_move], [(part['x'], part['y']) for part in self.my_body]) >= 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.directions.items(): + neighbor = (current[0] + pos[0], current[1] + pos[1]) + 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[0] - tail[0]) + abs(position[1] - tail[1]) <= 2 + + def distance_to_tail(self, position, tail): + return abs(position[0] - tail[0]) + abs(position[1] - 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, safe_positions): + # Beispielhafte Logik zur Auswahl einer Bewegungsrichtung + for direction, (dx, dy) in self.directions.items(): + next_position = (self.my_head['x'] + dx, self.my_head['y'] + dy) + # Konvertiere safe_positions in eine Liste von Tupeln für den Vergleich + safe_positions_tuples = [(pos['x'], pos['y']) for pos in 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)