from snakes.TemplateSnake import TemplateSnake from collections import deque class BetterMasterSnake(TemplateSnake): def __init__(self): super().__init__() self.name = "BetterMasterSnake" # Definiere die möglichen Bewegungsrichtungen self.min_safe_area = 2 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.board = game_data['board'] self.find_safe_positions() if self.eat_the_snake_overwrite: return self.overwrite_eat_the_other_snake(game_data["turn"]) if self.game_type == "constrictor": move = self.selected_move_constrictor() else: move = self.selected_move_standard() self.add_to_history({"turn": game_data["turn"], "data": self.calculations}) return move if move else "up" def overwrite_eat_the_other_snake(self, turn:int): self.add_calculations({"function": "eat_the_snake_overwrite", "my_head": self.my_head, "move": self.kill_the_snake, "safe_positions": self.safe_positions}) self.add_to_history({"turn": turn, "data": self.calculations}) return self.kill_the_snake #TODO: How to Fill the Gameboard best? def selected_move_constrictor(self): move = self.move_close_to_body() self.add_calculations({"function": "move_close_to_body", "my_head": self.my_head, "move": move}) 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}) return move def selected_move_standard(self, move=None): # Finde den besten Weg zur Nahrung 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 or self.would_eating_the_food_kill_the_snake(move): move = self.move_close_to_body(move_close_to_tail=True) 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}) return move 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) return path def find_path_to_tail(self): # Exclude other 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'])) 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) 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, move_close_to_tail=False): # Heuristik, um Positionen nahe dem eigenen Körper zu bevorzugen body_positions = set((part['x'], part['y']) for part in self.my_body) tail_position = (self.my_body[-1]['x'], self.my_body[-1]['y']) best_move = None max_distance = -1 # Initialize maximum distance 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) # Berechne die Distanz zum eigenen Schwanz distance_to_tail = abs(next_position[0] - tail_position[0]) + abs(next_position[1] - tail_position[1]) # Wähle die maximale Distanz (Körper oder Schwanz) if move_close_to_tail: distance = min(next_position, distance_to_tail) else: distance = max(next_position, distance_to_body) # Update max_distance if a larger distance is found if distance > max_distance: max_distance = distance best_move = direction return best_move if best_move else "up" # Standardbewegung, falls keine bessere gefunden wird #TODO: Neat to Implement Function to check if eating the food would kill the snake? def would_eating_the_food_kill_the_snake(self, move:str): return False def ensure_escape_route(self, move:str): 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 # TODO: Fix - Snake Neat to find the best way - Close to the Tail and maybe fill most free cells as posible return move def is_near_tail(self, position, tail): return abs(position["x"] - tail[0]) + abs(position["y"] - tail[1]) <= 2 def a_star_search(self, start, goal, obstacles): # Helper functions def is_position_safe(position): return 0 <= position['x'] < self.board_width and 0 <= position['y'] < self.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