main (#1) #2

Merged
daniel156161 merged 2 commits from dev into main 2024-04-13 03:07:02 +02:00
12 changed files with 478 additions and 114 deletions
Showing only changes of commit 7acc269df4 - Show all commits
+1
View File
@@ -6,3 +6,4 @@
.venv/ .venv/
__pycache__/ __pycache__/
data/ data/
.env
-6
View File
@@ -1,6 +0,0 @@
from snakes.DummSnake import DummSnake
from snakes.LogicSnake import LogicSnake
from snakes.AStarSnake import AStarSnake
from snakes.MasterSnake import MasterSnake
SNAKE = MasterSnake()
Regular → Executable
+21 -47
View File
@@ -1,3 +1,5 @@
#!/usr/bin/env python3
# Welcome to # Welcome to
# __________ __ __ .__ __ # __________ __ __ .__ __
# \______ \_____ _/ |__/ |_| | ____ ______ ____ _____ | | __ ____ # \______ \_____ _/ |__/ |_| | ____ ______ ____ _____ | | __ ____
@@ -10,54 +12,26 @@
# To get you started we've included code to prevent your Battlesnake from moving backwards. # To get you started we've included code to prevent your Battlesnake from moving backwards.
# For more info see docs.battlesnake.com # For more info see docs.battlesnake.com
from server.Files import read_file, save_file from server.SnakeBuilder import SnakeBuilder
from server.GameStorage import GameStorage from server.Server import Server
from config import SNAKE
from datetime import datetime from dotenv import load_dotenv, find_dotenv
import typing import os
import json, os
# info is called when you create your Battlesnake on play.battlesnake.com
# and controls your Battlesnake's appearance
# TIP: If you open your Battlesnake URL in a browser you should see this data
game_state_storage = GameStorage(SNAKE.__class__.__name__)
def info() -> typing.Dict:
CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'data', 'snake-config.json')
snake = read_file(CONFIG_PATH, json.load)
if not snake:
snake = {"apiversion":"1","author":"","color":"#888888","head":"default","tail":"default"}
save_file(CONFIG_PATH, snake, callback=json.dump, indent=2, ensure_ascii=False)
print("INFO", snake)
return snake
print("INFO Snake:", snake)
return snake
# start is called when your Battlesnake begins a game
def start(game_state: typing.Dict):
game_state_storage.start_new_game(game_state["game"], game_state["board"], game_state["you"])
SNAKE.clear_history()
print("GAME START:", game_state["game"])
# move is called when your Battlesnake game is running game
def move(game_state: typing.Dict) -> typing.Dict:
next_move = SNAKE.choose_move(game_state)
game_state_storage.add_moves(game_state["board"], next_move)
print("MOVE:", f"{next_move},", "Me:", {"body": game_state["you"]["body"], "head": game_state["you"]["head"], "length": game_state["you"]["length"]})
return {"move": next_move}
# end is called when your Battlesnake finishes a game
def end(game_state: typing.Dict):
HISTORY_PATH = os.path.join(os.path.dirname(__file__), 'data', 'history')
game_state_storage.add_end_state(game_state["board"], SNAKE.get_history())
game_state_storage.save(os.path.join(HISTORY_PATH, f"{SNAKE.__class__.__name__}_{datetime.now().strftime('%d.%m.%Y_%H%M%S')}_{game_state['game']['id']}.json"), callback=json.dump, indent=2, ensure_ascii=False)
print("GAME OVER\n")
# Start server when `python main.py` is run # Start server when `python main.py` is run
if __name__ == "__main__": if __name__ == "__main__":
from server.server import run_server load_dotenv(find_dotenv())
run_server({"info": info, "start": start, "move": move, "end": end})
server = Server(
data_path=os.path.dirname(__file__),
snake=SnakeBuilder.build(os.environ.get("SNAKE", "DummSnake")),
)
if os.environ.get("STORE_GAME_HISTORY", None):
server.enable_store_game_state()
server.run(
host=os.environ.get("HOST", "0.0.0.0"),
port=int(os.environ.get("PORT", "8000")),
debug=bool(os.environ.get("DEBUG", False))
)
+1
View File
@@ -5,5 +5,6 @@ itsdangerous==2.1.2
Jinja2==3.1.3 Jinja2==3.1.3
MarkupSafe==2.1.5 MarkupSafe==2.1.5
numpy==1.26.4 numpy==1.26.4
python-dotenv==1.0.1
scipy==1.12.0 scipy==1.12.0
Werkzeug==3.0.1 Werkzeug==3.0.1
+12 -3
View File
@@ -1,8 +1,10 @@
from server.Files import save_file from server.Files import save_file
import os
class GameStorage: class GameStorage:
def __init__(self, snake:str): def __init__(self, snake:str, path:str):
self.snake_type = snake self.snake_type = snake
self.folder = path
def start_new_game(self, game_type:dict, game_board:dict, snake:dict): def start_new_game(self, game_type:dict, game_board:dict, snake:dict):
self.game_type = game_type self.game_type = game_type
@@ -18,8 +20,14 @@ class GameStorage:
self.game_board.append(game_board) self.game_board.append(game_board)
self.snake_history = snake_history_state self.snake_history = snake_history_state
def set_winner_snake_name(self, snakes:list[dict]):
if self.start_position["id"] in [ x["id"] for x in snakes]:
self.winner_snake_names = "me"
else:
self.winner_snake_names = [ x["name"] for x in snakes]
def save(self, path:str, callback=None, **kwargs): def save(self, path:str, callback=None, **kwargs):
save_file(path, { save_file(os.path.join(self.folder, path), {
"snake": { "snake": {
"type": self.snake_type, "type": self.snake_type,
"choices": self.snake_history, "choices": self.snake_history,
@@ -29,5 +37,6 @@ class GameStorage:
"snake_start": self.start_position, "snake_start": self.start_position,
"gameboard": self.game_board, "gameboard": self.game_board,
"my_moves": self.moves, "my_moves": self.moves,
} },
"winner": self.winner_snake_names,
}, callback=callback, **kwargs) }, callback=callback, **kwargs)
+103
View File
@@ -0,0 +1,103 @@
from server.Files import read_file, save_file
from server.GameStorage import GameStorage
from snakes.TemplateSnake import TemplateSnake
from datetime import datetime
from flask import Flask
from flask import request
import logging, json, os
class Server:
default_snake_config = {"apiversion":"1","author":"","color":"#888888","head":"default","tail":"default"}
def __init__(self, data_path:str, snake:TemplateSnake, debug:bool=False):
self.debug = debug
self.snake = snake
self.config_file = os.path.join(data_path, 'data', 'snake-config.json')
self.game_state_storage = GameStorage(snake.__class__.__name__, path=os.path.join(data_path, 'data', 'history'))
self.store_game_state = False
self.app = Flask("Battlesnake")
@self.app.get("/")
def on_info():
return self._info()
@self.app.post("/start")
def on_start():
game_state = request.get_json()
self._start(game_state)
return "ok"
@self.app.post("/move")
def on_move():
game_state = request.get_json()
return self._move(game_state)
@self.app.post("/end")
def on_end():
game_state = request.get_json()
self._end(game_state)
return "ok"
@self.app.after_request
def identify_server(response):
response.headers.set(
"server", "battlesnake/github/starter-snake-python"
)
return response
def run(self, host:str="0.0.0.0", port:str="8000", debug:bool=False):
logging.getLogger("werkzeug").setLevel(logging.ERROR)
print(f"\nRunning Battlesnake at http://{host}:{port} with the {self.snake.__class__.__name__.replace('Snake', '')} Snake")
self.app.run(host=host, port=port, debug=debug)
def _read_json_config_or_create(self):
snake_config = read_file(self.config_file, json.load)
if not snake_config:
snake_config = self.default_snake_config
save_file(self.config_file, snake_config, callback=json.dump, indent=2, ensure_ascii=False)
return snake_config
def enable_store_game_state(self):
self.store_game_state = True
# info is called when you create your Battlesnake on play.battlesnake.com
# and controls your Battlesnake's appearance
# TIP: If you open your Battlesnake URL in a browser you should see this data
def _info(self) -> dict:
snake_config = self._read_json_config_or_create()
print("INFO Snake:", snake_config)
return snake_config
# start is called when your Battlesnake begins a game
def _start(self, game_state:dict):
if self.store_game_state:
self.game_state_storage.start_new_game(game_state["game"], game_state["board"], game_state["you"])
self.snake.clear_history()
print("GAME START:", game_state["game"])
# move is called when your Battlesnake game is running game
def _move(self, game_state:dict) -> dict:
next_move = self.snake.choose_move(game_state)
if self.store_game_state:
self.game_state_storage.add_moves(game_state["board"], next_move)
print("MOVE:", f"{next_move:5},", "Me:", {"head": game_state["you"]["head"], "length": game_state["you"]["length"]})
return {"move": next_move}
# end is called when your Battlesnake finishes a game
def _end(self, game_state:dict):
if self.store_game_state:
self.game_state_storage.add_end_state(game_state["board"], self.snake.get_history())
self.game_state_storage.set_winner_snake_name(game_state["board"]['snakes'])
self.game_state_storage.save(
f"{self.snake.__class__.__name__}_{datetime.now().strftime('%d.%m.%Y_%H%M%S')}_{game_state['game']['id']}.json",
callback=json.dump, indent=2, ensure_ascii=False
)
print("GAME OVER:\n- Winner is", [ x["name"] for x in game_state["board"]['snakes']])
+7
View File
@@ -0,0 +1,7 @@
class SnakeBuilder:
@classmethod
def build(self, selected_snake:str):
snake_module = __import__(f'snakes.{selected_snake}', fromlist=[selected_snake])
snake_class = getattr(snake_module, selected_snake)
return snake_class()
-45
View File
@@ -1,45 +0,0 @@
import logging
import os
import typing
from flask import Flask
from flask import request
def run_server(handlers: typing.Dict):
app = Flask("Battlesnake")
@app.get("/")
def on_info():
return handlers["info"]()
@app.post("/start")
def on_start():
game_state = request.get_json()
handlers["start"](game_state)
return "ok"
@app.post("/move")
def on_move():
game_state = request.get_json()
return handlers["move"](game_state)
@app.post("/end")
def on_end():
game_state = request.get_json()
handlers["end"](game_state)
return "ok"
@app.after_request
def identify_server(response):
response.headers.set(
"server", "battlesnake/github/starter-snake-python"
)
return response
host = "0.0.0.0"
port = int(os.environ.get("PORT", "8000"))
logging.getLogger("werkzeug").setLevel(logging.ERROR)
print(f"\nRunning Battlesnake at http://{host}:{port}")
app.run(host=host, port=port)
+74 -13
View File
@@ -4,6 +4,13 @@ class MasterSnake(TemplateSnake):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.name = "MasterSnake" self.name = "MasterSnake"
self.disabled_find_near_by_food = True
def is_food_nearby(self, head, food_positions):
for food in food_positions:
if abs(head['x'] - food['x']) <= 1 and abs(head['y'] - food['y']) <= 1:
return True
return False
def avoid_snake_body(self, snakes, board_width, board_height): 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 # Konvertiere die Körperpositionen der Schlangen in ein Set von Tupeln für schnellen Zugriff
@@ -37,22 +44,38 @@ class MasterSnake(TemplateSnake):
# Finde die nächstgelegene Nahrungsquelle, wenn Nahrung vorhanden ist # Finde die nächstgelegene Nahrungsquelle, wenn Nahrung vorhanden ist
try: try:
path_to_food = self.find_path_to_food(game_data) if self.is_food_nearby(my_head, game_data['board']['food']) or self.disabled_find_near_by_food:
if path_to_food: path_to_food = self.find_path_to_food(game_data)
# Implementiere Logik, um in Richtung der Nahrungsquelle zu bewegen, falls sicher if path_to_food:
move = self.move_towards_food(my_head, path_to_food[0], safe_positions) # Implementiere Logik, um in Richtung der Nahrungsquelle zu bewegen, falls sicher
move = self.move_towards(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 keine Nahrung vorhanden ist
move = self.find_direction(my_head, safe_positions)
self.add_to_history({"my_head": my_head, "move": move})
else: else:
# Einfache Logik, um eine Bewegungsrichtung zu wählen, wenn keine Nahrung vorhanden ist # Wenn keine Nahrung in der Nähe ist, bewege dich in eine Richtung, die dich nahe an deinem eigenen Körper hält
move = self.find_direction(my_head, safe_positions) move = self.find_direction(my_head, safe_positions)
self.add_to_history({"my_head": my_head, "move": move})
except ValueError: except ValueError:
move = self.find_direction(my_head, safe_positions) move = self.find_direction(my_head, safe_positions)
self.add_to_history({"my_head": my_head, "move": move})
# Finde den größten sicheren Bereich
max_area_start, max_area = self.flood_fill(my_head, safe_positions)
# Wenn der Schwanz der Schlange im größten sicheren Bereich liegt, bewege dich in Richtung des Schwanzes
my_tail = (my_snake['body'][-1]['x'], my_snake['body'][-1]['y']) # Convert to tuple
if my_tail in max_area:
move = self.move_towards(my_head, my_tail, safe_positions)
# Überprüfe zukünftige Bewegungen, um Sackgassen zu vermeiden # Überprüfe zukünftige Bewegungen, um Sackgassen zu vermeiden
move = self.avoid_dead_ends(my_head, move, safe_positions, board_width, board_height, snakes) move = self.avoid_dead_ends(my_head, move, safe_positions, snakes)
self.add_to_history({"my_head": my_head, "move": move})
return move return move
def move_towards_food(self, head, food, safe_positions): def move_towards(self, head, target, safe_positions):
directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)} directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)}
best_direction = None best_direction = None
min_distance = float('inf') min_distance = float('inf')
@@ -62,7 +85,7 @@ class MasterSnake(TemplateSnake):
for direction, (dx, dy) in directions.items(): for direction, (dx, dy) in directions.items():
next_position = {'x': head['x'] + dx, 'y': head['y'] + dy} next_position = {'x': head['x'] + dx, 'y': head['y'] + dy}
if next_position in safe_positions: if next_position in safe_positions:
distance = abs(food[0] - next_position['x']) + abs(food[1] - next_position['y']) distance = abs(target[0] - next_position['x']) + abs(target[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) 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): if distance < min_distance or (distance == min_distance and distance_to_body < min_distance_to_body):
best_direction = direction best_direction = direction
@@ -150,16 +173,21 @@ class MasterSnake(TemplateSnake):
return direction return direction
return "up" # Standardbewegung, falls keine sichere Position gefunden wird return "up" # Standardbewegung, falls keine sichere Position gefunden wird
def avoid_dead_ends(self, head, move, safe_positions, board_width, board_height, snakes): def avoid_self_collision(self, future_head, body_positions):
# Überprüft, ob die zukünftige Kopfposition im Körper der Schlange liegt
return (future_head['x'], future_head['y']) not in body_positions
def avoid_dead_ends(self, head, move, safe_positions, snakes):
directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)} directions = {'up': (0, 1), 'down': (0, -1), 'left': (-1, 0), 'right': (1, 0)}
dx, dy = directions[move] dx, dy = directions[move]
future_head = {'x': head['x'] + dx, 'y': head['y'] + dy} future_head = {'x': head['x'] + dx, 'y': head['y'] + dy}
body_positions = set((part['x'], part['y']) for part in snakes[0]['body'])
if not self.is_future_move_safe(future_head, safe_positions, board_width, board_height, snakes): if not self.is_future_move_safe(future_head, safe_positions, snakes) or not self.avoid_self_collision(future_head, body_positions):
for alternative_move in directions.keys(): for alternative_move in directions.keys():
dx, dy = directions[alternative_move] dx, dy = directions[alternative_move]
alternative_future_head = {'x': head['x'] + dx, 'y': head['y'] + dy} 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): if self.is_future_move_safe(alternative_future_head, safe_positions, snakes) and self.avoid_self_collision(alternative_future_head, body_positions):
return alternative_move return alternative_move
return move return move
@@ -171,7 +199,7 @@ class MasterSnake(TemplateSnake):
future_body_positions.add((part['x'], part['y'])) future_body_positions.add((part['x'], part['y']))
return future_body_positions return future_body_positions
def is_future_move_safe(self, future_head, safe_positions, board_width, board_height, snakes): def is_future_move_safe(self, future_head, safe_positions, snakes):
# Simuliere die Bewegung der Schlange und aktualisiere die Positionen des eigenen Körpers # Simuliere die Bewegung der Schlange und aktualisiere die Positionen des eigenen Körpers
future_body_positions = self.simulate_snake_movement(snakes) future_body_positions = self.simulate_snake_movement(snakes)
# Konvertiere safe_positions in ein Set von Tupeln für den Flood Fill Algorithmus # Konvertiere safe_positions in ein Set von Tupeln für den Flood Fill Algorithmus
@@ -181,5 +209,38 @@ class MasterSnake(TemplateSnake):
# Füge die zukünftige Kopfposition hinzu, um sie als Startpunkt zu verwenden # Füge die zukünftige Kopfposition hinzu, um sie als Startpunkt zu verwenden
safe_positions_set.add((future_head['x'], future_head['y'])) safe_positions_set.add((future_head['x'], future_head['y']))
# Berechne die Anzahl der erreichbaren sicheren Positionen von der zukünftigen Kopfposition aus # Berechne die Anzahl der erreichbaren sicheren Positionen von der zukünftigen Kopfposition aus
reachable_positions = self.flood_fill((future_head['x'], future_head['y']), safe_positions_set)
# Entscheide, ob die Bewegung sicher ist, basierend auf der Anzahl der erreichbaren Positionen # Entscheide, ob die Bewegung sicher ist, basierend auf der Anzahl der erreichbaren Positionen
return safe_positions_set # oder wähle einen anderen Schwellenwert
fill_bool = len(reachable_positions) > len(safe_positions_set) * 0.25
if fill_bool:
return fill_bool
return len(safe_positions_set) >= len(snakes[0]['body'])
def flood_fill(self, start, safe_positions):
stack = [start]
visited = set()
max_area = 0
max_area_start = None
while stack:
position = stack.pop()
if isinstance(position, dict):
position = tuple(position.values())
else:
position = tuple(position)
if position not in visited:
visited.add(position)
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: # links, rechts, oben, unten
next_position = tuple([position[0] + dx, position[1] + dy])
if next_position in safe_positions:
stack.append(next_position)
# Überprüfe, ob der aktuelle Bereich größer ist als der bisher größte Bereich
if len(visited) > max_area:
max_area = len(visited)
max_area_start = position
return max_area_start, visited
+256
View File
@@ -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
+3
View File
@@ -10,3 +10,6 @@ class TemplateSnake:
def get_history(self): def get_history(self):
return self.history return self.history
def choose_move(self, game_data:dict):
pass
View File