diff --git a/config.py b/config.py index 1c873ec..ea6c0e3 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,6 @@ from snakes.DummSnake import DummSnake from snakes.LogicSnake import LogicSnake from snakes.AStarSnake import AStarSnake +from snakes.MasterSnake import MasterSnake -SNAKE = AStarSnake() +SNAKE = MasterSnake() diff --git a/snakes/MasterSnake.py b/snakes/MasterSnake.py index 9d67b69..1a01690 100644 --- a/snakes/MasterSnake.py +++ b/snakes/MasterSnake.py @@ -36,13 +36,15 @@ class MasterSnake(TemplateSnake): safe_positions = self.avoid_snake_body(snakes, board_width, board_height) # Finde die nächstgelegene Nahrungsquelle, wenn Nahrung vorhanden ist - path_to_food = self.find_path_to_food(game_data) - 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) - print(move) - else: - # Einfache Logik, um eine Bewegungsrichtung zu wählen, wenn keine Nahrung vorhanden ist + try: + path_to_food = self.find_path_to_food(game_data) + 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) + else: + # Einfache Logik, um eine Bewegungsrichtung zu wählen, wenn keine Nahrung vorhanden ist + move = self.find_direction(my_head, safe_positions) + except ValueError: move = self.find_direction(my_head, safe_positions) # Überprüfe zukünftige Bewegungen, um Sackgassen zu vermeiden @@ -51,18 +53,23 @@ class MasterSnake(TemplateSnake): return move def move_towards_food(self, head, food, safe_positions): - # Beispielhafte Logik, um in Richtung der Nahrungsquelle zu bewegen 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']) - if distance < min_distance: + 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 - return best_direction if best_direction else "up" # Standardbewegung, falls keine sichere Richtung gefunden wird + 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'] @@ -70,22 +77,26 @@ class MasterSnake(TemplateSnake): snakes = game_data['board']['snakes'] board_width = game_data['board']['width'] board_height = game_data['board']['height'] - - # Wähle die nächste Nahrungsquelle basierend auf der Heuristik + + # 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'])) - - # Verwende A* zur Suche nach einem sicheren Pfad - path = self.a_star_search(my_head, closest_food, snakes, board_width, board_height) + + # 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 a_star_search(self, start, goal, snakes, board_width, board_height): - # Konvertiere Schlangenpositionen in ein Set von Hindernissen - obstacles = set() - for snake in snakes: - for part in snake['body']: - obstacles.add((part['x'], part['y'])) - - # Hilfsfunktionen + 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 @@ -97,11 +108,11 @@ class MasterSnake(TemplateSnake): def heuristic(position, goal): return abs(position[0] - goal[0]) + abs(position[1] - goal[1]) - # Initialisiere Start- und Zielpositionen + # Initialize start and goal positions start = (start['x'], start['y']) goal = (goal['x'], goal['y']) - # Initialisiere die offene und geschlossene Liste + # Initialize the open and closed list open_set = set([start]) came_from = {} g_score = {start: 0} @@ -110,17 +121,17 @@ class MasterSnake(TemplateSnake): while open_set: current = min(open_set, key=lambda pos: f_score.get(pos, float('inf'))) if current == goal: - # Rekonstruiere den Pfad + # Reconstruct the path path = [] while current in came_from: path.append(current) current = came_from[current] path.reverse() - return path # Rückgabe des Pfades als Liste von Tupeln + 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 # Distanz zwischen Nachbarn ist immer 1 + 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 diff --git a/tests/test_MasterSnake.py b/tests/test_MasterSnake.py new file mode 100644 index 0000000..3347a57 --- /dev/null +++ b/tests/test_MasterSnake.py @@ -0,0 +1,108 @@ +import unittest +from snakes.MasterSnake import MasterSnake + +class TestMasterSnake(unittest.TestCase): + def setUp(self): + self.snake = MasterSnake() + self.game_data = { + "board": { + "height": 11, + "width": 11, + "snakes": [ + { + "id": "snake-1", + "body": [ + {"x": 0, "y": 0}, + {"x": 1, "y": 0}, + {"x": 2, "y": 0} + ], + "head": {"x": 2, "y": 1}, + }, + { + "id": "snake-2", + "body": [ + {"x": 5, "y": 4}, + {"x": 5, "y": 3}, + {"x": 6, "y": 3}, + {"x": 6, "y": 2} + ], + "head": {"x": 6, "y": 3}, + } + ], + "food": [ + { + "x": 2, + "y": 0 + }, + { + "x": 10, + "y": 8 + }, + { + "x": 5, + "y": 5 + } + ], + }, + "you": { + "id": "snake-1", + "body": [ + {"x": 0, "y": 0}, + {"x": 1, "y": 0}, + {"x": 2, "y": 0} + ], + "head": {"x": 0, "y": 0}, + } + } + + def test_avoid_snake_body(self): + snakes = self.game_data['board']['snakes'] + board_width = self.game_data['board']['width'] + board_height = self.game_data['board']['height'] + safe_positions = self.snake.avoid_snake_body(snakes, board_width, board_height) + self.assertIsInstance(safe_positions, list) + self.assertTrue(all(isinstance(pos, dict) for pos in safe_positions)) + self.assertTrue(all('x' in pos and 'y' in pos for pos in safe_positions)) + + def test_find_safe_positions(self): + body_positions = {(0, 0), (1, 0), (2, 0), (5, 4), (5, 3), (6, 3), (6, 2)} + safe_positions = self.snake.find_safe_positions(body_positions, 11, 11) + self.assertNotIn({'x': 0, 'y': 0}, safe_positions) + self.assertIn({'x': 10, 'y': 10}, safe_positions) + + def test_choose_move(self): + move = self.snake.choose_move(self.game_data) + self.assertIn(move, ["up", "down", "left", "right"]) + + def test_move_towards_food(self): + head = {'x': 0, 'y': 0} + food = (5, 5) + safe_positions = [{'x': 1, 'y': 0}, {'x': 0, 'y': 1}, {'x': 1, 'y': 1}] + direction = self.snake.move_towards_food(head, food, safe_positions) + self.assertIn(direction, ["up", "down", "left", "right"]) + + def test_find_path_to_food(self): + path = self.snake.find_path_to_food(self.game_data) + print(path) + self.assertIsInstance(path, list) + self.assertTrue(all(isinstance(pos, dict) for pos in path)) + self.assertTrue(all('x' in pos and 'y' in pos for pos in path)) + + def test_find_direction(self): + head = {'x': 0, 'y': 0} + safe_positions = [{'x': 1, 'y': 0}, {'x': 0, 'y': 1}, {'x': 1, 'y': 1}] + direction = self.snake.find_direction(head, safe_positions) + self.assertIn(direction, ["up", "down", "left", "right"]) + + def test_avoid_dead_ends(self): + head = {'x': 0, 'y': 0} + move = "right" + board_width = 11 + board_height = 11 + snakes = self.game_data['board']['snakes'] + safe_positions = [{'x': 1, 'y': 0}, {'x': 0, 'y': 1}, {'x': 1, 'y': 1}] + new_move = self.snake.avoid_dead_ends(head, move, safe_positions, board_width, board_height, snakes) + self.assertIn(new_move, ["up", "down", "left", "right"]) + +if __name__ == '__main__': + unittest.main()