From 5e63d0b35ecc2ca168c36cb6c572e594c5e63e90 Mon Sep 17 00:00:00 2001 From: Daniel Dolezal Date: Fri, 12 Apr 2024 18:28:42 +0200 Subject: [PATCH] make more turn often in circles up to 300 turns --- snakes/MasterSnake_FoodBySpace.py | 256 ++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 snakes/MasterSnake_FoodBySpace.py diff --git a/snakes/MasterSnake_FoodBySpace.py b/snakes/MasterSnake_FoodBySpace.py new file mode 100644 index 0000000..49df991 --- /dev/null +++ b/snakes/MasterSnake_FoodBySpace.py @@ -0,0 +1,256 @@ +from snakes.TemplateSnake import TemplateSnake + +class MasterSnake(TemplateSnake): + def __init__(self): + super().__init__() + self.name = "MasterSnake" + self.history_head = [] + + def avoid_snake_body(self, snakes, board_width, board_height): + # Konvertiere die Körperpositionen der Schlangen in ein Set von Tupeln für schnellen Zugriff + body_positions = set() + for snake in snakes: + for part in snake['body']: + body_positions.add((part['x'], part['y'])) + + # Implementiere die Logik, um Positionen zu finden, die nicht von Schlangenkörpern belegt sind + safe_positions = self.find_safe_positions(body_positions, board_width, board_height) + return safe_positions + + def find_safe_positions(self, body_positions, board_width, board_height): + # Finde sichere Positionen basierend auf den Körperpositionen und der Größe des Spielbretts + safe_positions = [] + for x in range(board_width): # Nutze die tatsächliche Breite des Spielbretts + for y in range(board_height): # Nutze die tatsächliche Höhe des Spielbretts + if (x, y) not in body_positions: + safe_positions.append({'x': x, 'y': y}) + return safe_positions + + def choose_move(self, game_data): + board_width = game_data['board']['width'] + board_height = game_data['board']['height'] + snakes = game_data['board']['snakes'] + my_snake = game_data['you'] + my_head = my_snake['head'] + + # Vermeide Schlangenkörper + safe_positions = self.avoid_snake_body(snakes, board_width, board_height) + + # Wähle Nahrung basierend auf verfügbarem Platz + try: + chosen_food = self.choose_food_based_on_space(game_data) + if chosen_food: + path_to_food = self.a_star_search(my_head, chosen_food, self.get_obstacles(game_data), board_width, board_height) + if path_to_food: + # Implementiere Logik, um in Richtung der Nahrungsquelle zu bewegen, falls sicher + move = self.move_towards_food(my_head, path_to_food[0], safe_positions) + self.add_to_history({"my_head": my_head, "path_to_food": path_to_food, "move": move}) + else: + # Einfache Logik, um eine Bewegungsrichtung zu wählen, wenn kein Pfad zur Nahrung vorhanden ist + move = self.find_direction(my_head, safe_positions) + self.add_to_history({"my_head": my_head, "move": move}) + else: + # Einfache Logik, um eine Bewegungsrichtung zu wählen, wenn keine geeignete Nahrung gefunden wird + move = self.find_direction(my_head, safe_positions) + self.add_to_history({"my_head": my_head, "move": move}) + except ValueError: + move = self.find_direction(my_head, safe_positions) + self.add_to_history({"my_head": my_head, "move": move}) + + # Überprüfe zukünftige Bewegungen, um Sackgassen zu vermeiden + move = self.avoid_dead_ends_and_circles(my_head, move, safe_positions, board_width, board_height, snakes) + self.add_to_history({"my_head": my_head, "move": move}) + self.add_to_history_head({"my_head": my_head, "move": move}) + + return move + + def move_towards_food(self, head, food, safe_positions): + directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)} + best_direction = None + min_distance = float('inf') + min_distance_to_body = float('inf') + body_positions = set((pos['x'], pos['y']) for pos in safe_positions[:-1]) # Exclude the head from body positions + + for direction, (dx, dy) in directions.items(): + next_position = {'x': head['x'] + dx, 'y': head['y'] + dy} + if next_position in safe_positions: + distance = abs(food[0] - next_position['x']) + abs(food[1] - next_position['y']) + distance_to_body = sum(abs(part[0] - next_position['x']) + abs(part[1] - next_position['y']) for part in body_positions) + if distance < min_distance or (distance == min_distance and distance_to_body < min_distance_to_body): + best_direction = direction + min_distance = distance + min_distance_to_body = distance_to_body + + return best_direction if best_direction else "up" # Default to moving up if no safe direction found + + def find_path_to_food(self, game_data): + my_head = game_data['you']['head'] + food_positions = game_data['board']['food'] + snakes = game_data['board']['snakes'] + board_width = game_data['board']['width'] + board_height = game_data['board']['height'] + + # Exclude own snake's body from obstacles + own_snake_body = game_data['you']['body'] + obstacles = set((part['x'], part['y']) for part in own_snake_body) + + for snake in snakes: + if snake['id'] != game_data['you']['id']: + for part in snake['body']: + obstacles.add((part['x'], part['y'])) + + # Choose the closest food source based on the heuristic + closest_food = min(food_positions, key=lambda food: abs(food['x'] - my_head['x']) + abs(food['y'] - my_head['y'])) + + # Use A* to search for a safe path + path = self.a_star_search(my_head, closest_food, obstacles, board_width, board_height) + return path + + def choose_food_based_on_space(self, game_data): + my_head = game_data['you']['head'] + food_positions = game_data['board']['food'] + snakes = game_data['board']['snakes'] + board_width = game_data['board']['width'] + board_height = game_data['board']['height'] + my_length = game_data['you']['length'] + + # Sortiere die Nahrungsquellen basierend auf ihrer Entfernung + sorted_food = sorted(food_positions, key=lambda food: abs(food['x'] - my_head['x']) + abs(food['y'] - my_head['y'])) + + for food in sorted_food: + path = self.a_star_search(my_head, food, self.get_obstacles(game_data), board_width, board_height) + if path and self.will_fit_in_space(path, my_length, board_width, board_height): + return food # Diese Nahrung ist erreichbar und es gibt genug Platz + + # Wenn keine geeignete Nahrung gefunden wird, gib ein Standard-Nahrungsobjekt zurück oder löse eine Ausnahme aus + if food_positions: + return food_positions[0] # Gib das erste Nahrungsobjekt zurück + else: + raise ValueError("Keine Nahrung gefunden") # Oder löse eine Ausnahme aus + + def will_fit_in_space(self, path, snake_length, board_width, board_height): + # Überprüfe, ob die Länge des Pfades größer oder gleich der Länge der Schlange ist + if len(path) >= snake_length: + return True + + # Überprüfe, ob es genügend Platz um den Endpunkt des Pfades gibt + end_of_path = path[-1] + space_count = self.count_space_around(end_of_path, board_width, board_height) + return space_count >= snake_length + + def count_space_around(self, position, board_width, board_height): + # Zähle die Anzahl der erreichbaren Positionen um einen Punkt herum + x, y = position + count = 0 + for dx in [-1, 0, 1]: + for dy in [-1, 0, 1]: + if (dx != 0 or dy != 0) and 0 <= x + dx < board_width and 0 <= y + dy < board_height: + count += 1 + return count + + def get_obstacles(self, game_data): + # Erstelle ein Set von Hindernissen für die A* Suche + obstacles = set() + for snake in game_data['board']['snakes']: + for part in snake['body']: + obstacles.add((part['x'], part['y'])) + return obstacles + + def a_star_search(self, start, goal, obstacles, board_width, board_height): + # Convert snake positions into a set of obstacles + # Helper functions + def is_position_safe(position): + x, y = position + return 0 <= x < board_width and 0 <= y < board_height and position not in obstacles + + def get_neighbors(position): + x, y = position + return [(nx, ny) for nx, ny in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)] if is_position_safe((nx, ny))] + + def heuristic(position, goal): + return abs(position[0] - goal[0]) + abs(position[1] - goal[1]) + + # Initialize start and goal positions + start = (start['x'], start['y']) + goal = (goal['x'], goal['y']) + + # Initialize the open and closed list + open_set = set([start]) + came_from = {} + g_score = {start: 0} + f_score = {start: heuristic(start, goal)} + + while open_set: + current = min(open_set, key=lambda pos: f_score.get(pos, float('inf'))) + if current == goal: + # Reconstruct the path + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.reverse() + return path # Return the path as a list of tuples + + open_set.remove(current) + for neighbor in get_neighbors(current): + tentative_g_score = g_score[current] + 1 # Distance between neighbors is always 1 + if tentative_g_score < g_score.get(neighbor, float('inf')): + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score[neighbor] = g_score[neighbor] + heuristic(neighbor, goal) + if neighbor not in open_set: + open_set.add(neighbor) + + return None # Kein Pfad gefunden + + def find_direction(self, head, safe_positions): + # Beispielhafte Logik zur Auswahl einer Bewegungsrichtung + directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)} + for direction, (dx, dy) in directions.items(): + next_position = {'x': head['x'] + dx, 'y': head['y'] + dy} + if next_position in safe_positions: + return direction + return "up" # Standardbewegung, falls keine sichere Position gefunden wird + + def is_in_history(self, future_head): + # Überprüfe, ob die zukünftige Kopfposition in den letzten N Bewegungen vorkommt + return any(future_head == move_data["my_head"] for move_data in self.history_head[-10:]) + + def avoid_dead_ends_and_circles(self, head, move, safe_positions, board_width, board_height, snakes): + directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)} + dx, dy = directions[move] + future_head = {'x': head['x'] + dx, 'y': head['y'] + dy} + + if not self.is_future_move_safe(future_head, safe_positions, board_width, board_height, snakes) or self.is_in_history(future_head): + for alternative_move in directions.keys(): + dx, dy = directions[alternative_move] + alternative_future_head = {'x': head['x'] + dx, 'y': head['y'] + dy} + if self.is_future_move_safe(alternative_future_head, safe_positions, board_width, board_height, snakes) and not self.is_in_history(alternative_future_head): + return alternative_move + return move + + def add_to_history_head(self, move_data): + # Füge die aktuelle Kopfposition zur Historie hinzu und behalte nur die letzten 10 Positionen + self.history_head.append(move_data) + self.history_head = self.history_head[-10:] + + def simulate_snake_movement(self, snakes): + future_body_positions = set() + for snake in snakes: + # Beachte, dass dies nur ein Beispiel ist und angepasst werden muss, um deine spezifische Spiellogik zu berücksichtigen + for part in snake['body'][:-1]: # Ignoriere den letzten Teil des Körpers, da er sich bewegt + future_body_positions.add((part['x'], part['y'])) + return future_body_positions + + def is_future_move_safe(self, future_head, safe_positions, board_width, board_height, snakes): + # Simuliere die Bewegung der Schlange und aktualisiere die Positionen des eigenen Körpers + future_body_positions = self.simulate_snake_movement(snakes) + # Konvertiere safe_positions in ein Set von Tupeln für den Flood Fill Algorithmus + safe_positions_set = set((pos['x'], pos['y']) for pos in safe_positions) + # Entferne die zukünftigen Körperpositionen aus den sicheren Positionen + safe_positions_set = safe_positions_set - future_body_positions + # Füge die zukünftige Kopfposition hinzu, um sie als Startpunkt zu verwenden + safe_positions_set.add((future_head['x'], future_head['y'])) + # Berechne die Anzahl der erreichbaren sicheren Positionen von der zukünftigen Kopfposition aus + # Entscheide, ob die Bewegung sicher ist, basierend auf der Anzahl der erreichbaren Positionen + return safe_positions_set # oder wähle einen anderen Schwellenwert