Files
snake-python/snakes/BetterMasterSnake.py
T

344 lines
16 KiB
Python

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.board = game_data['board']
self.find_safe_positions()
if self.eat_the_snake_overwrite:
if len(self.safe_positions) > 1:
first_key = list(self.safe_positions.keys())[0]
self.add_calculations({"function": "eat_the_snake_overwrite", "my_head": self.my_head, "move": move, "safe_positions": self.safe_positions, "selected_move": self.safe_positions[first_key]})
self.add_to_history({"turn": game_data["turn"], "data": self.calculations})
return self.safe_positions[first_key]
self.add_calculations({"function": "eat_the_snake_overwrite", "my_head": self.my_head, "move": move, "safe_positions": self.safe_positions})
self.add_to_history({"turn": game_data["turn"], "data": self.calculations})
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)
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):
# 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
# TODO: Fix - Snake Neat to find the best way - Close to the Tail and maybe fill most free cells as posible
#if self.will_end_in_dead_end(self.safe_positions[move], depth=20):
# return move
return move
def will_end_in_dead_end(self, start, depth=10):
start = (start['x'], start['y'])
return self.dfs_dead_end(self.board, start, depth, set([start]))
def dfs_dead_end(self, board, position, depth, path):
# Abbruchbedingung der Rekursion: Wenn Tiefe erreicht ist
if depth == 0:
return False # Nicht genügend Tiefe, um eine Sackgasse zu bestätigen
# Bewege nach oben
next_position = position
if next_position[0] < 0 or next_position in board['snakes'] or next_position in path:
return True # Sackgasse gefunden
# Füge aktuelle Position zum Pfad hinzu
path.add(next_position)
# Rekursive Überprüfung der nächsten Position
result = self.dfs_dead_end(board, next_position, depth - 1, path)
# Entferne aktuelle Position vom Pfad
path.remove(next_position)
return result
def is_position_safe(self, position):
return 0 <= position['x'] < self.board_width and 0 <= position['y'] < self.board_height and (position['x'], position['y']) not in self.my_body
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
def add_calculations(self, calculations:dict):
self.calculations.append(calculations)