Compare commits
89 Commits
v1.0
..
c458219125
| Author | SHA1 | Date | |
|---|---|---|---|
|
c458219125
|
|||
|
a7a463ed91
|
|||
|
5c1ef7f05f
|
|||
|
d32568cdf2
|
|||
|
61721a7eb6
|
|||
|
bcc9c71c30
|
|||
|
58bbbf3cbd
|
|||
|
4d515f0784
|
|||
|
8424c324e8
|
|||
|
c5c2652f3a
|
|||
|
0768e7f254
|
|||
|
31d2e7ea55
|
|||
|
4b51ddc84d
|
|||
|
31f5225100
|
|||
|
deb95c6246
|
|||
|
600cde4a3e
|
|||
|
e068fb8614
|
|||
|
80b7c4df89
|
|||
|
da0347731c
|
|||
|
a09c05b6ec
|
|||
|
aba457423e
|
|||
|
bb92715de1
|
|||
|
cf45aa60aa
|
|||
|
a58e9695dd
|
|||
|
b57ae5eab2
|
|||
|
817b970623
|
|||
|
c9e6947758
|
|||
|
4a1fbf2752
|
|||
|
5b8bf0da31
|
|||
|
4a8cb40bde
|
|||
|
c5342c1f4d
|
|||
|
ac7c397093
|
|||
|
7dd46dd72b
|
|||
|
10c7f2656c
|
|||
|
f00efe607f
|
|||
|
c333706b75
|
|||
|
917bd3f6bd
|
|||
|
83bcf4f194
|
|||
|
ef4dca447f
|
|||
|
db3a353090
|
|||
|
1f4d17d42f
|
|||
|
f98430462b
|
|||
|
c26824aeaf
|
|||
|
87690177a5
|
|||
|
8a2a62ef57
|
|||
|
5796ce0a6e
|
|||
|
5522a52227
|
|||
|
5743f5c111
|
|||
|
9950fa1952
|
|||
|
4620ee31eb
|
|||
|
9103e3e139
|
|||
|
04eef9229c
|
|||
|
7cb1fdc57d
|
|||
|
cceded8468
|
|||
|
8c57e48f60
|
|||
|
5ce12d70c1
|
|||
|
950351b407
|
|||
|
12ac257d19
|
|||
|
d4b54d48b9
|
|||
|
034b0e361a
|
|||
|
a57536b7cb
|
|||
|
a606ae6f94
|
|||
|
b364c6454e
|
|||
|
b9a0bca4c6
|
|||
|
ea36d60b4d
|
|||
|
0fe4e6ac83
|
|||
|
93b2c8ba99
|
|||
|
f6db5cb96a
|
|||
|
e3d7cccb64
|
|||
|
0eecbb774b
|
|||
|
9e0a919233
|
|||
|
6d9df32076
|
|||
|
863ca1b277
|
|||
|
16cab3a9ca
|
|||
|
281b52e71d
|
|||
|
38ba576de9
|
|||
|
7457e66339
|
|||
|
b601b378c8
|
|||
|
24e744f705
|
|||
|
39b16a1702
|
|||
|
87fe6550b2
|
|||
|
c854b5fec9
|
|||
|
51108ce21c
|
|||
|
2f8e35aced
|
|||
|
9869f14bbe
|
|||
|
1154127a40
|
|||
|
ae1489240d
|
|||
|
0af6d862d4
|
|||
|
f472ddd0d9
|
@@ -0,0 +1,34 @@
|
||||
name: Build and Push Docker Container
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: '${{ secrets.ACTION_ACCESS_TOKEN }}'
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ vars.DOCKER_REGISTRY_URL }}
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.ACTION_ACCESS_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image for latest tag
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ vars.DOCKER_REGISTRY_URL }}/daniel156161/battlesnake:latest
|
||||
platforms: linux/amd64
|
||||
@@ -7,3 +7,4 @@
|
||||
__pycache__/
|
||||
data/
|
||||
.env
|
||||
dbschema/migrations/
|
||||
|
||||
+3
-1
@@ -1,4 +1,6 @@
|
||||
FROM python:3.10.6-slim
|
||||
FROM python:alpine
|
||||
|
||||
RUN apk add --no-cache build-base
|
||||
|
||||
# Install app
|
||||
COPY . /app
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
module default {
|
||||
function is_winner_me(winner: str) -> bool
|
||||
using (winner = "me");
|
||||
|
||||
function gameboard_url(id: uuid) -> str
|
||||
using ("https://play.battlesnake.com/game/" ++ <str>id);
|
||||
|
||||
type GameBoard {
|
||||
overloaded required id: uuid {
|
||||
readonly := true;
|
||||
constraint exclusive;
|
||||
}
|
||||
url := gameboard_url(.id);
|
||||
|
||||
required created_at: datetime {
|
||||
readonly := true;
|
||||
}
|
||||
required turns: int32 {
|
||||
readonly := true;
|
||||
}
|
||||
required map: str {
|
||||
readonly := true;
|
||||
default := "standard";
|
||||
}
|
||||
required single type: GameType {
|
||||
readonly := true;
|
||||
on source delete delete target if orphan;
|
||||
}
|
||||
required single ruleset: Ruleset {
|
||||
readonly := true;
|
||||
on source delete delete target if orphan;
|
||||
}
|
||||
required winner: str {
|
||||
readonly := true;
|
||||
}
|
||||
multi moves: Moves {
|
||||
default := <Moves>{};
|
||||
on source delete delete target;
|
||||
on target delete allow;
|
||||
}
|
||||
required single snake: Snake {
|
||||
readonly := true;
|
||||
on source delete delete target if orphan;
|
||||
}
|
||||
|
||||
is_winner_me := is_winner_me(.winner);
|
||||
has_moves := exists(.moves);
|
||||
}
|
||||
|
||||
type GameType {
|
||||
required name: str {
|
||||
readonly := true;
|
||||
}
|
||||
required is_ladder: bool {
|
||||
readonly := true;
|
||||
}
|
||||
constraint exclusive on ( (.name, .is_ladder) );
|
||||
}
|
||||
|
||||
type Ruleset {
|
||||
required name: str {
|
||||
readonly := true;
|
||||
}
|
||||
required version: str {
|
||||
readonly := true;
|
||||
}
|
||||
required settings: json {
|
||||
readonly := true;
|
||||
}
|
||||
constraint exclusive on ( (.name, .version, .settings) );
|
||||
}
|
||||
|
||||
type Snake {
|
||||
required type: str {
|
||||
readonly := true;
|
||||
}
|
||||
constraint exclusive on ( .type );
|
||||
}
|
||||
|
||||
type Moves {
|
||||
required turn: int32 {
|
||||
readonly := true;
|
||||
}
|
||||
required snake_move: str {
|
||||
readonly := true;
|
||||
}
|
||||
required game_board: json {
|
||||
readonly := true;
|
||||
}
|
||||
calculations: array<json> {
|
||||
readonly := true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,4 +11,9 @@ services:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile
|
||||
#environment:
|
||||
# - SNAKE_COLOR=blue
|
||||
# - SNAKE_HEAD=caffeine
|
||||
# - SNAKE_TAIL=mlh-gene
|
||||
# - STORE_GAME_HISTORY=True
|
||||
restart: always
|
||||
|
||||
@@ -12,18 +12,23 @@
|
||||
# To get you started we've included code to prevent your Battlesnake from moving backwards.
|
||||
# For more info see docs.battlesnake.com
|
||||
|
||||
from server.CreateEnvironmentFile import CreateEnvironmentFile
|
||||
from server.Server import Server
|
||||
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
import os
|
||||
|
||||
# Start server when `python main.py` is run
|
||||
if __name__ == "__main__":
|
||||
load_dotenv(find_dotenv())
|
||||
if os.environ.get("CREATE_ENV_FILE", None):
|
||||
CreateEnvironmentFile.load_dotenv({"STORE_GAME_HISTORY": True, "DEBUG": True, "SNAKE": "TemplateSnake", "STORE_IF_WIN_AND_MOVES_ARE_BIGGER_AS": 10})
|
||||
|
||||
server = Server(
|
||||
data_path=os.path.dirname(__file__),
|
||||
snake_type=os.environ.get("SNAKE", "DummSnake"),
|
||||
snake_type=os.environ.get("SNAKE", "TemplateSnake"),
|
||||
storage_type=os.environ.get("STORAGE", "LocalStorage"),
|
||||
store_game_when_win_and_moves_are_bigger_as=int(os.environ.get("STORE_IF_WIN_AND_MOVES_ARE_BIGGER_AS", 10)),
|
||||
debug=os.environ.get("DEBUG_SERVER", False),
|
||||
check_tls_security=False
|
||||
)
|
||||
|
||||
if os.environ.get("STORE_GAME_HISTORY", None):
|
||||
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
blinker==1.7.0
|
||||
click==8.1.7
|
||||
Flask==3.0.2
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.3
|
||||
MarkupSafe==2.1.5
|
||||
numpy==1.26.4
|
||||
asgiref==3.8.1
|
||||
blinker==1.9.0
|
||||
click==8.1.8
|
||||
Flask==3.1.0
|
||||
gel==3.1.0
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.5
|
||||
MarkupSafe==3.0.2
|
||||
python-dotenv==1.0.1
|
||||
scipy==1.12.0
|
||||
Werkzeug==3.0.1
|
||||
Werkzeug==3.1.3
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
import os
|
||||
|
||||
class CreateEnvironmentFile:
|
||||
def __init__(self):
|
||||
self.path = find_dotenv()
|
||||
|
||||
def create_file(self, environment_vars:dict[str], path:str="./.env"):
|
||||
if environment_vars:
|
||||
data = self.convert_dict_to_list(environment_vars)
|
||||
with open(path, 'w') as f:
|
||||
f.writelines(data)
|
||||
|
||||
def convert_dict_to_list(self, data_dict:dict):
|
||||
data = []
|
||||
for k, v in data_dict.items():
|
||||
data.append(f"{k}={v}\n")
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def load_dotenv(cls, environment_vars:dict[str]=None):
|
||||
new_class = cls()
|
||||
if os.path.exists(new_class.path):
|
||||
return load_dotenv(new_class.path)
|
||||
else:
|
||||
return new_class.create_file(environment_vars)
|
||||
@@ -0,0 +1,140 @@
|
||||
from datetime import datetime
|
||||
|
||||
class GameBoard:
|
||||
def __init__(self, game_id:str, width:int, height:int, ruleset:dict, source:str, map:str, snake_class):
|
||||
self.id = game_id
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.type = ruleset["name"]
|
||||
self.snake_class = snake_class
|
||||
|
||||
# What will get Stored
|
||||
self.winner_snake_names = None
|
||||
self.now_date = datetime.now()
|
||||
self.turns = []
|
||||
self.is_ladder = True if source == "ladder" else False
|
||||
self.ruleset = ruleset
|
||||
self.map = map
|
||||
self.url = self._get_game_url(True if ruleset["version"] == "cli" else False)
|
||||
|
||||
# Setter Functions
|
||||
def _set_snakes(self, snakes:list[dict]):
|
||||
self.other_snakes = [ x for x in snakes if x["id"] != self.my_snake["id"] ]
|
||||
|
||||
def _set_my_snake(self, my_snake:str):
|
||||
self.my_snake = my_snake
|
||||
|
||||
def _set_food(self, food:list[dict]):
|
||||
self.food = food
|
||||
|
||||
def _set_hazards(self, hazards:list[dict]):
|
||||
self.hazards = hazards
|
||||
|
||||
def _set_turn(self, turn:int):
|
||||
self.turn = turn
|
||||
|
||||
# Getter Functions
|
||||
def get_other_snakes(self):
|
||||
return self.other_snakes
|
||||
|
||||
def get_my_snake(self):
|
||||
return self.my_snake
|
||||
|
||||
def get_food(self):
|
||||
return self.food
|
||||
|
||||
def get_hazard(self):
|
||||
return self.hazards
|
||||
|
||||
def get_turn(self):
|
||||
return self.turn
|
||||
|
||||
def get_dimension(self):
|
||||
return {"width": self.width, "height": self.height}
|
||||
|
||||
def get_width(self):
|
||||
return self.width
|
||||
|
||||
def get_height(self):
|
||||
return self.height
|
||||
|
||||
def get_type(self):
|
||||
return self.type
|
||||
|
||||
def get_my_snake_head(self):
|
||||
return self.my_snake["head"]
|
||||
|
||||
def get_my_snake_body(self):
|
||||
return self.my_snake["body"]
|
||||
|
||||
def get_my_snake_tail(self):
|
||||
return self.my_snake["body"][-1]
|
||||
|
||||
def get_game_board_as_dict(self):
|
||||
snakes = [self.my_snake]
|
||||
snakes.extend(self.other_snakes)
|
||||
|
||||
return {
|
||||
"height": self.height,
|
||||
"width": self.width,
|
||||
"snakes": snakes,
|
||||
"food": self.food,
|
||||
"hazards": self.hazards
|
||||
}
|
||||
|
||||
# Game Functions
|
||||
def read_game_data(self, game_data:dict):
|
||||
self._set_food(game_data['board']['food'])
|
||||
self._set_hazards(game_data['board']['hazards'])
|
||||
|
||||
self._set_my_snake(game_data['you'])
|
||||
self._set_snakes(game_data['board']['snakes'])
|
||||
|
||||
self._set_turn(game_data["turn"])
|
||||
|
||||
def start_game(self, game_data:dict):
|
||||
self.init_snakes = len(game_data['board']['snakes'])
|
||||
|
||||
def end_game(self, game_data:dict):
|
||||
self._set_winner_snake_name(game_data['board']['snakes'])
|
||||
self.get_type_of_game()
|
||||
|
||||
# Function get Called from Server
|
||||
def snake_neat_make_a_move(self):
|
||||
move = self.snake_class.choose_move(self)
|
||||
|
||||
self.turns.append({
|
||||
"turn": self.turn,
|
||||
"move": move,
|
||||
"game_board": self.get_game_board_as_dict()
|
||||
})
|
||||
|
||||
return move
|
||||
|
||||
# Save functions
|
||||
def _get_game_url(self, local_game:bool):
|
||||
if local_game:
|
||||
return None
|
||||
return f"https://play.battlesnake.com/game/{self.id}"
|
||||
|
||||
def _set_winner_snake_name(self, snakes:list[dict]):
|
||||
if self.my_snake["id"] in [ x["id"] for x in snakes]:
|
||||
self.winner_snake_names = ["me"]
|
||||
else:
|
||||
self.winner_snake_names = [ x["name"] for x in snakes]
|
||||
if len(self.winner_snake_names) == 0:
|
||||
self.winner_snake_names = None
|
||||
|
||||
def get_winner(self):
|
||||
return self.winner_snake_names
|
||||
|
||||
def get_type_of_game(self):
|
||||
if self.init_snakes == 2:
|
||||
return {"name": "duel", "is_ladder": self.is_ladder}
|
||||
|
||||
return {"name": self.type, "is_ladder": self.is_ladder}
|
||||
|
||||
def save(self, store_class, **kwargs):
|
||||
store = store_class(**kwargs)
|
||||
store.save(self)
|
||||
del store
|
||||
@@ -1,59 +0,0 @@
|
||||
from server.Files import save_file
|
||||
import os
|
||||
|
||||
class GameStorage:
|
||||
def __init__(self, snake:str, path:str):
|
||||
self.snake_type = snake
|
||||
self.folder = path
|
||||
self.winner_snake_names = None
|
||||
|
||||
def start_new_game(self, game_type:dict, game_board:dict, snake:dict):
|
||||
self.game_type = game_type
|
||||
self.start_position = snake
|
||||
self.game_board = [game_board]
|
||||
self.moves = []
|
||||
|
||||
def add_moves(self, game_board:dict, my_move:str):
|
||||
self.game_board.append(game_board)
|
||||
self.moves.append(my_move)
|
||||
|
||||
def add_end_state(self, game_board:dict, snake_history_state:list[dict], final_turns:int):
|
||||
self.game_board.append(game_board)
|
||||
self.snake_history = snake_history_state
|
||||
self._set_winner_snake_name(game_board['snakes'])
|
||||
self.final_turns = final_turns
|
||||
|
||||
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 _get_type_of_gameboard(self):
|
||||
if len(self.game_board[0]["snakes"]) == 2:
|
||||
return "duel"
|
||||
|
||||
return "standart"
|
||||
|
||||
def save(self, path:str, callback=None, **kwargs):
|
||||
if self.winner_snake_names == "me" and self.final_turns <= 10:
|
||||
return None
|
||||
|
||||
save_file(os.path.join(self.folder, path), {
|
||||
"snake": {
|
||||
"type": self.snake_type,
|
||||
"choices": self.snake_history,
|
||||
},
|
||||
"game": {
|
||||
"type": self._get_type_of_gameboard(),
|
||||
"infos": self.game_type,
|
||||
"snake_start": self.start_position,
|
||||
"final_turns": self.final_turns,
|
||||
"gameboard": self.game_board,
|
||||
"my_moves": self.moves,
|
||||
},
|
||||
"winner": self.winner_snake_names,
|
||||
}, callback=callback, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"<{self.__class__.__name__}> Snake: {self.snake_type}, Folder: {self.folder}, Winner: {self.winner_snake_names}, Old Moves: {self.moves}"
|
||||
+87
-37
@@ -1,69 +1,113 @@
|
||||
from server.Files import read_file, save_file
|
||||
from server.GameStorage import GameStorage
|
||||
from snakes.TemplateSnake import TemplateSnake
|
||||
from server.Files import read_file
|
||||
from server.GameBoard import GameBoard
|
||||
from server.SnakeBuilder import SnakeBuilder
|
||||
|
||||
from datetime import datetime
|
||||
from flask import Flask
|
||||
from server.storage.StorageLoader import StorageLoader
|
||||
|
||||
from flask import Flask, jsonify
|
||||
from flask import request
|
||||
import logging, json, os
|
||||
import logging, json, os, re
|
||||
|
||||
class Server:
|
||||
default_snake_config = {"apiversion":"1","author":"","color":"#888888","head":"default","tail":"default"}
|
||||
|
||||
def __init__(self, data_path:str, snake_type:str, debug:bool=False):
|
||||
def __init__(self, data_path:str, snake_type:str, storage_type:str, debug:bool=False, store_game_when_win_and_moves_are_bigger_as:int=10, check_tls_security:bool=False):
|
||||
self.debug = debug
|
||||
self.snake_type = snake_type
|
||||
self.storage_type = storage_type
|
||||
|
||||
self.config_file = os.path.join(data_path, 'data', 'snake-config.json')
|
||||
self.data_path = data_path
|
||||
self.check_tls_security = check_tls_security
|
||||
|
||||
self.store_game_state = False
|
||||
self.running_games:dict[str, GameStorage] = {}
|
||||
self.running_snake:dict[str, TemplateSnake] = {}
|
||||
self.store_game_when_win_and_moves_are_bigger_as = store_game_when_win_and_moves_are_bigger_as
|
||||
|
||||
self.running_games:dict[str, GameBoard] = {}
|
||||
|
||||
self.app = Flask("Battlesnake")
|
||||
|
||||
@self.app.get("/")
|
||||
def on_info():
|
||||
async def on_info():
|
||||
return self._info()
|
||||
|
||||
@self.app.post("/start")
|
||||
def on_start():
|
||||
async def on_start():
|
||||
game_state = request.get_json()
|
||||
self._start(game_state)
|
||||
return "ok"
|
||||
|
||||
@self.app.post("/move")
|
||||
def on_move():
|
||||
async def on_move():
|
||||
game_state = request.get_json()
|
||||
return self._move(game_state)
|
||||
|
||||
@self.app.post("/end")
|
||||
def on_end():
|
||||
async def on_end():
|
||||
game_state = request.get_json()
|
||||
self._end(game_state)
|
||||
return "ok"
|
||||
|
||||
@self.app.after_request
|
||||
def identify_server(response):
|
||||
async def identify_server(response):
|
||||
response.headers.set(
|
||||
"server", "battlesnake/github/starter-snake-python"
|
||||
)
|
||||
return response
|
||||
|
||||
@self.app.get("/cleanup")
|
||||
async def cleanup():
|
||||
results = self._cleanup_database()
|
||||
return jsonify(data=json.loads(results), status=200)
|
||||
|
||||
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_type.replace('Snake', '')} Snake")
|
||||
print(f"\nRunning Battlesnake at http://{host}:{port} with the {' '.join(re.findall('[A-Z][^A-Z]*', self.snake_type))}")
|
||||
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
|
||||
snake_config = self._override_snake_config_with_environment_variables(self.default_snake_config)
|
||||
|
||||
return self._override_snake_config_with_environment_variables(snake_config)
|
||||
|
||||
def _override_snake_config_with_environment_variables(self, config:dict[str]):
|
||||
for key in ["author", "color", "head", "tail"]:
|
||||
if os.environ.get(f"SNAKE_{key.upper()}", None):
|
||||
config[key.lower()] = os.environ.get(f"SNAKE_{key.upper()}")
|
||||
return config
|
||||
|
||||
def _create_game_board(self, game_state:dict):
|
||||
new_game_board = GameBoard(
|
||||
game_id=game_state["game"]["id"],
|
||||
width=game_state['board']['width'],
|
||||
height=game_state['board']['height'],
|
||||
ruleset=game_state['game']["ruleset"],
|
||||
source=game_state['game']['source'],
|
||||
map=game_state['game']['map'],
|
||||
snake_class=SnakeBuilder.build(self.snake_type)
|
||||
)
|
||||
new_game_board.start_game(game_state)
|
||||
|
||||
self.running_games[game_state["game"]["id"]] = new_game_board
|
||||
return new_game_board
|
||||
|
||||
def _delete_game_board(self, game_state):
|
||||
del self.running_games[game_state["game"]["id"]]
|
||||
|
||||
def _get_game_board(self, game_state:str, end:bool=False):
|
||||
try:
|
||||
game_board = self.running_games[game_state["game"]["id"]]
|
||||
except KeyError:
|
||||
game_board = self._create_game_board(game_state)
|
||||
|
||||
game_board.read_game_data(game_state)
|
||||
if end:
|
||||
game_board.end_game(game_state)
|
||||
|
||||
return game_board
|
||||
|
||||
def enable_store_game_state(self):
|
||||
self.store_game_state = True
|
||||
@@ -73,41 +117,47 @@ class Server:
|
||||
# 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.running_games[game_state["game"]["id"]] = GameStorage(self.snake.__class__.__name__, path=os.path.join(self.data_path, 'data', 'history'))
|
||||
self.running_games[game_state["game"]["id"]].start_new_game(game_state["game"], game_state["board"], game_state["you"])
|
||||
|
||||
self.running_snake[game_state["game"]["id"]] = SnakeBuilder.build(self.snake_type)
|
||||
self._create_game_board(game_state)
|
||||
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.running_snake[game_state["game"]["id"]].choose_move(game_state)
|
||||
game_board = self._get_game_board(game_state)
|
||||
next_move = game_board.snake_neat_make_a_move()
|
||||
|
||||
if self.store_game_state:
|
||||
self.running_games[game_state["game"]["id"]].add_moves(game_state["board"], next_move)
|
||||
if self.debug:
|
||||
print(self.running_games[game_state["game"]["id"]])
|
||||
print("TURN:", f'{game_state["turn"]:3},', "MOVE:", f"{next_move:5}")
|
||||
|
||||
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:
|
||||
snake = self.running_snake[game_state["game"]["id"]]
|
||||
|
||||
self.running_games[game_state["game"]["id"]].add_end_state(game_state["board"], snake.get_history(), game_state["turn"])
|
||||
self.running_games[game_state["game"]["id"]].save(
|
||||
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
|
||||
game_board = self._get_game_board(game_state, end=True)
|
||||
#if not game_board.get_winner() == "me" and not game_board.get_turn() <= self.store_game_when_win_and_moves_are_bigger_as:
|
||||
if self.check_tls_security:
|
||||
game_board.save(
|
||||
StorageLoader.build(self.storage_type),
|
||||
file_path=os.path.join(self.data_path, 'data'),
|
||||
database=os.getenv("EDGEDB_DATABASE", None),
|
||||
tls_security=None
|
||||
)
|
||||
else:
|
||||
game_board.save(
|
||||
StorageLoader.build(self.storage_type),
|
||||
file_path=os.path.join(self.data_path, 'data'),
|
||||
database=os.getenv("EDGEDB_DATABASE", None),
|
||||
)
|
||||
del self.running_games[game_state["game"]["id"]]
|
||||
|
||||
print("GAME OVER:\n- Winner is", [ x["name"] for x in game_state["board"]['snakes']])
|
||||
del self.running_snake[game_state["game"]["id"]]
|
||||
print("GAME ENDED: Winner is", [ x["name"] for x in game_state["board"]['snakes']])
|
||||
self._delete_game_board(game_state)
|
||||
|
||||
def _cleanup_database(self):
|
||||
storage = StorageLoader.build(self.storage_type)()
|
||||
return storage.cleanup()
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
from server.GameBoard import GameBoard
|
||||
|
||||
from datetime import datetime
|
||||
import gel, json, time
|
||||
|
||||
class EdgeDB:
|
||||
def __init__(self, database:str=None, tls_security:str='insecure', **kwargs):
|
||||
self.database = database
|
||||
self.tls_security = tls_security
|
||||
self._connect()
|
||||
|
||||
def _connect(self):
|
||||
self.client = gel.create_client(
|
||||
tls_security=self.tls_security,
|
||||
database=self.database
|
||||
)
|
||||
|
||||
def run_query_with_reconnection(self, function, *args, **kwargs):
|
||||
while True:
|
||||
try:
|
||||
return function(*args, **kwargs)
|
||||
except gel.errors.ClientConnectionFailedError:
|
||||
self._connect()
|
||||
time.sleep(0.5)
|
||||
|
||||
def insert_game_type(self, name:str, is_ladder:bool):
|
||||
return self.run_query_with_reconnection(
|
||||
self.client.query_required_single,
|
||||
"""
|
||||
insert GameType {
|
||||
name := <str>$name,
|
||||
is_ladder := <bool>$is_ladder
|
||||
}""",
|
||||
name=name,
|
||||
is_ladder=is_ladder
|
||||
)
|
||||
|
||||
def create_moves_with_calculations(self, game_board:GameBoard):
|
||||
data = []
|
||||
moves = game_board.turns
|
||||
snake_calulations = [[calc for calc in ele["data"]] for ele in game_board.snake_class.get_history() ]
|
||||
|
||||
for i in range(len(moves)):
|
||||
data.append({"turn": moves[i]["turn"], "move": moves[i]["move"], "game_board": moves[i]["game_board"], "calculations": snake_calulations[i]})
|
||||
|
||||
return data
|
||||
|
||||
def insert(self, game_board:GameBoard):
|
||||
game_type = game_board.get_type_of_game()
|
||||
|
||||
self.run_query_with_reconnection(
|
||||
self.client.query,
|
||||
"""
|
||||
insert GameBoard {
|
||||
id := <uuid>$id,
|
||||
created_at := <datetime>$created_at,
|
||||
turns := <int32>$turns,
|
||||
map := <str>$map,
|
||||
winner := <str>$winner,
|
||||
moves := (
|
||||
with input_data := <array <tuple <turn: int32, `move`: str, game_board: json, calculations: array<json> >>>$moves
|
||||
for data in array_unpack(input_data)
|
||||
insert Moves {
|
||||
turn := data.turn,
|
||||
snake_move := data.`move`,
|
||||
game_board := data.game_board,
|
||||
calculations := data.calculations
|
||||
}
|
||||
),
|
||||
type := (
|
||||
insert GameType {
|
||||
name := <str>$game_type,
|
||||
is_ladder := <bool>$is_ladder
|
||||
} unless conflict on (.name, .is_ladder) else GameType
|
||||
),
|
||||
ruleset := (
|
||||
insert Ruleset {
|
||||
name := <str>$ruleset,
|
||||
version := <str>$version,
|
||||
settings := to_json(<str>$settings)
|
||||
} unless conflict on (.name, .version, .settings) else Ruleset
|
||||
),
|
||||
snake := (
|
||||
insert Snake {
|
||||
type := <str>$snake_type
|
||||
} unless conflict on .type else Snake
|
||||
)
|
||||
}""",
|
||||
|
||||
id=game_board.id,
|
||||
created_at=datetime.fromtimestamp(game_board.now_date.timestamp(), game_board.now_date.astimezone().tzinfo),
|
||||
turns=game_board.turn,
|
||||
map=game_board.map if game_board.map else "standard",
|
||||
winner=', '.join(game_board.winner_snake_names) if game_board.winner_snake_names else "",
|
||||
moves=[ tuple([x["turn"], x["move"], json.dumps(x["game_board"]), [ json.dumps(ele) for ele in x["calculations"] ] ]) for x in self.create_moves_with_calculations(game_board) ],
|
||||
|
||||
game_type=game_type["name"],
|
||||
is_ladder=game_type["is_ladder"],
|
||||
|
||||
ruleset=game_board.ruleset["name"],
|
||||
version=game_board.ruleset["version"],
|
||||
settings=json.dumps(game_board.ruleset["settings"]),
|
||||
|
||||
snake_type=game_board.snake_class.__class__.__name__,
|
||||
)
|
||||
|
||||
def save(self, game_board:GameBoard):
|
||||
self.insert(game_board)
|
||||
|
||||
def __del__(self):
|
||||
self.client.close()
|
||||
|
||||
def cleanup(self):
|
||||
return self.run_query_with_reconnection(
|
||||
self.client.query_json,
|
||||
"""
|
||||
delete Moves { };
|
||||
|
||||
with gameboard := (delete GameBoard filter .turns < <std::int32>"200" or .is_winner_me = <std::bool>"false")
|
||||
select gameboard {id, url, winner, turns, type: { is_ladder, name } } order by .turns desc;
|
||||
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
from server.GameBoard import GameBoard
|
||||
from server.Files import save_file
|
||||
|
||||
import json, os
|
||||
|
||||
class LocalStorage:
|
||||
def __init__(self, file_path:str, **kwargs):
|
||||
self.save_folder_dict = {
|
||||
"standard": "01_Standard",
|
||||
"duel": "02_Duels",
|
||||
"constrictor": "04_Constrictor",
|
||||
"solo": "05_Solo",
|
||||
}
|
||||
self.file_path = file_path
|
||||
|
||||
def _get_correct_folder_for_save_file(self, game_board:GameBoard, file_name:str, game_type:str, leader_board:bool, winner:bool):
|
||||
storage_folder = self.file_path
|
||||
if leader_board:
|
||||
storage_folder = os.path.join(storage_folder, "00_Leaderboards")
|
||||
|
||||
storage_folder = os.path.join(storage_folder, self.save_folder_dict[game_type])
|
||||
storage_folder = os.path.join(storage_folder, game_board.now_date.strftime('%Y'), game_board.now_date.strftime('%m_%B'), game_board.now_date.strftime('%d'))
|
||||
|
||||
if winner:
|
||||
storage_folder = os.path.join(storage_folder, "Winner")
|
||||
else:
|
||||
storage_folder = os.path.join(storage_folder, "Lost")
|
||||
|
||||
return os.path.join(storage_folder, file_name)
|
||||
|
||||
def save(self, game_board:GameBoard):
|
||||
game_type = game_board.get_type_of_game()
|
||||
save_file_path = self._get_correct_folder_for_save_file(
|
||||
game_board,
|
||||
f"{game_board.snake_class.__class__.__name__}_{game_board.now_date.strftime('%H-%M-%S')}_{game_board.id}.json",
|
||||
game_type["name"],
|
||||
game_type["is_ladder"],
|
||||
True if game_board.winner_snake_names and "me" in game_board.winner_snake_names else False
|
||||
)
|
||||
|
||||
save_file(save_file_path, {
|
||||
"winner": game_board.winner_snake_names,
|
||||
"game": {
|
||||
"url": game_board.url,
|
||||
"id": game_board.id,
|
||||
"final_turns": game_board.turn,
|
||||
"map": game_board.map,
|
||||
"type": game_type,
|
||||
"ruleset": game_board.ruleset,
|
||||
},
|
||||
"moves": game_board.turns,
|
||||
"snake": {
|
||||
"type": game_board.snake_class.__class__.__name__,
|
||||
"calculations": game_board.snake_class.get_history(),
|
||||
},
|
||||
}, callback=json.dump, indent=2, ensure_ascii=False)
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
class StorageLoader:
|
||||
@classmethod
|
||||
def build(self, selected_storage:str):
|
||||
storage_module = __import__(f'server.storage.{selected_storage}', fromlist=[selected_storage])
|
||||
storage_class = getattr(storage_module, selected_storage)
|
||||
return storage_class
|
||||
@@ -1,165 +0,0 @@
|
||||
from snakes.TemplateSnake import TemplateSnake
|
||||
|
||||
from queue import PriorityQueue, Queue
|
||||
import random
|
||||
|
||||
class AStarSnake(TemplateSnake):
|
||||
def avoid_my_body(self, my_body, possible_moves: dict) -> list:
|
||||
"""
|
||||
my_body: Set of tuples representing x/y coordinates for every segment of a Battlesnake.
|
||||
e.g. {(0, 0), (1, 0), (2, 0)}
|
||||
possible_moves: Dictionary of moves to pick from, with coordinates as tuples.
|
||||
e.g. {"up": (0, 1), "down": (0, -1), "left": (-1, 0), "right": (1, 0)}
|
||||
|
||||
return: The dictionary of remaining possible_moves, with the moves leading to self-collision removed
|
||||
"""
|
||||
remove = []
|
||||
for direction, location in possible_moves.items():
|
||||
if location in my_body:
|
||||
remove.append(direction)
|
||||
|
||||
for direction in remove:
|
||||
del possible_moves[direction]
|
||||
|
||||
return possible_moves
|
||||
|
||||
def avoid_walls(self, board_width: int, board_height: int, possible_moves: dict):
|
||||
remove = []
|
||||
for direction, location in possible_moves.items():
|
||||
x_out_range = (location[0] < 0 or location[0] == board_width)
|
||||
y_out_range = (location[1] < 0 or location[1] == board_height)
|
||||
if x_out_range or y_out_range:
|
||||
remove.append(direction)
|
||||
|
||||
for direction in remove:
|
||||
del possible_moves[direction]
|
||||
|
||||
return possible_moves
|
||||
|
||||
def avoid_snakes(self, snakes: list, possible_moves: dict):
|
||||
remove = []
|
||||
for snake in snakes:
|
||||
for direction, location in possible_moves.items():
|
||||
if location in snake["body"]:
|
||||
remove.append(direction)
|
||||
|
||||
remove = set(remove)
|
||||
for direction in remove:
|
||||
del possible_moves[direction]
|
||||
|
||||
return possible_moves
|
||||
|
||||
def get_target_close(self, foods: list, my_head: tuple):
|
||||
if len(foods) == 0:
|
||||
return None
|
||||
return min(foods, key=lambda food: abs(food["x"] - my_head[0]) + abs(food["y"] - my_head[1]))
|
||||
|
||||
def flood_fill(self, board_width: int, board_height: int, my_body: set):
|
||||
"""
|
||||
Perform Flood Fill to identify safe areas on the board.
|
||||
"""
|
||||
visited = set()
|
||||
safe_cells = set()
|
||||
|
||||
# Define directions (up, down, left, right)
|
||||
directions = [(0, 1), (0, -1), (-1, 0), (1, 0)]
|
||||
|
||||
# Perform Flood Fill from each cell not occupied by the snake's body
|
||||
for x in range(board_width):
|
||||
for y in range(board_height):
|
||||
if (x, y) not in my_body and (x, y) not in visited:
|
||||
# Start Flood Fill from this cell
|
||||
q = Queue()
|
||||
q.put((x, y))
|
||||
visited.add((x, y))
|
||||
safe_cells.add((x, y))
|
||||
|
||||
# Continue Flood Fill until the queue is empty
|
||||
while not q.empty():
|
||||
current_cell = q.get()
|
||||
for dx, dy in directions:
|
||||
new_cell = (current_cell[0] + dx, current_cell[1] + dy)
|
||||
if 0 <= new_cell[0] < board_width and 0 <= new_cell[1] < board_height:
|
||||
if new_cell not in my_body and new_cell not in visited:
|
||||
q.put(new_cell)
|
||||
visited.add(new_cell)
|
||||
safe_cells.add(new_cell)
|
||||
|
||||
return safe_cells
|
||||
|
||||
def choose_move(self, data: dict) -> str:
|
||||
my_head = (data["you"]["head"]["x"], data["you"]["head"]["y"])
|
||||
my_body = {(part["x"], part["y"]) for part in data["you"]["body"]}
|
||||
board_height = data["board"]["height"]
|
||||
board_width = data["board"]["width"]
|
||||
foods = data["board"]["food"]
|
||||
|
||||
# Perform Flood Fill to identify safe areas on the board
|
||||
safe_cells = self.flood_fill(board_width, board_height, my_body)
|
||||
|
||||
# Find the nearest food located in a safe area using A* algorithm
|
||||
def heuristic(a, b):
|
||||
return abs(a[0] - b[0]) + abs(a[1] - b[1])
|
||||
|
||||
def a_star(start, goal:set=()):
|
||||
open_set = Queue()
|
||||
open_set.put(start)
|
||||
came_from = {}
|
||||
g_score = {start: 0}
|
||||
|
||||
while not open_set.empty():
|
||||
current = open_set.get()
|
||||
|
||||
if current == goal:
|
||||
path = []
|
||||
while current in came_from:
|
||||
path.append(current)
|
||||
current = came_from[current]
|
||||
return path[::-1][0]
|
||||
|
||||
for dx, dy in [(0, 1), (0, -1), (-1, 0), (1, 0)]:
|
||||
new_cell = (current[0] + dx, current[1] + dy)
|
||||
if new_cell in safe_cells:
|
||||
tentative_g_score = g_score[current] + 1
|
||||
if new_cell not in g_score or tentative_g_score < g_score[new_cell]:
|
||||
came_from[new_cell] = current
|
||||
g_score[new_cell] = tentative_g_score
|
||||
open_set.put(new_cell)
|
||||
|
||||
return None
|
||||
|
||||
try:
|
||||
nearest_food = min(foods, key=lambda food: heuristic(my_head, (food["x"], food["y"])))
|
||||
target_position = (nearest_food["x"], nearest_food["y"])
|
||||
move_target = a_star(my_head, target_position)
|
||||
except ValueError:
|
||||
# TODO: What to do when no food is available?
|
||||
# - Avoid own body and other snakes
|
||||
# - Flut fill?
|
||||
move_target = a_star(my_head, safe_cells)
|
||||
print(move_target)
|
||||
|
||||
# Choose the next move based on the path obtained from A* algorithm
|
||||
if move_target:
|
||||
dx = move_target[0] - my_head[0]
|
||||
dy = move_target[1] - my_head[1]
|
||||
|
||||
self.add_to_history({"my_head": my_head, "my_body": tuple(my_body), "target": move_target, "target_position": target_position, "nearest_food": nearest_food, "dx": dx, "dy": dy})
|
||||
if dx == 1:
|
||||
return "right"
|
||||
elif dx == -1:
|
||||
return "left"
|
||||
elif dy == 1:
|
||||
return "up"
|
||||
elif dy == -1:
|
||||
return "down"
|
||||
|
||||
# If no safe path to food is found, choose a random move
|
||||
random_move = random.choice(["up", "down", "left", "right"])
|
||||
|
||||
try:
|
||||
self.add_to_history({"my_head": my_head, "my_body": tuple(my_body), "target": move_target, "target_position": target_position, "nearest_food": nearest_food, "random_move": random_move})
|
||||
except UnboundLocalError:
|
||||
self.add_to_history({"my_head": my_head, "my_body": tuple(my_body), "target": move_target, "random_move": random_move})
|
||||
|
||||
return random_move
|
||||
@@ -0,0 +1,221 @@
|
||||
from snakes.TemplateSnake import TemplateSnake
|
||||
from server.GameBoard import GameBoard
|
||||
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:GameBoard):
|
||||
self.game_board = game_data
|
||||
self.calculations = []
|
||||
self.eat_the_snake_overwrite = False
|
||||
|
||||
self.safe_positions = self.find_safe_positions(add_to_calculations=True)
|
||||
if self.eat_the_snake_overwrite:
|
||||
return self.overwrite_eat_the_other_snake(game_data.get_turn())
|
||||
|
||||
if game_data.get_type() == "constrictor":
|
||||
move = self.selected_move_constrictor()
|
||||
else:
|
||||
move = self.selected_move_standard()
|
||||
|
||||
self.add_to_history({"turn": game_data.get_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.game_board.get_my_snake_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.game_board.get_my_snake_head(), "move": move})
|
||||
move = self.ensure_escape_route(move)
|
||||
self.add_calculations({"function": "ensure_escape_route", "my_head": self.game_board.get_my_snake_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.game_board.get_my_snake_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.game_board.get_my_snake_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.game_board.get_my_snake_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.game_board.get_my_snake_body())
|
||||
|
||||
for snake in self.game_board.get_other_snakes():
|
||||
for part in snake['body']:
|
||||
obstacles.add((part['x'], part['y']))
|
||||
|
||||
other_snakes_other_snake_posible_moves_set = {(d['x'], d['y']) for d in self.other_snake_posible_moves}
|
||||
removed_elements_set = set([(elem['x'], elem['y']) for elem in self.game_board.get_food() if (elem['x'], elem['y']) in other_snakes_other_snake_posible_moves_set])
|
||||
obstacles |= removed_elements_set
|
||||
|
||||
self.food_positions = [elem for elem in self.game_board.get_food() if (elem['x'], elem['y']) not in other_snakes_other_snake_posible_moves_set]
|
||||
|
||||
if len(self.food_positions) > 0:
|
||||
# Choose the closest food source based on the heuristic
|
||||
closest_food = min(self.food_positions, key=lambda food: abs(food['x'] - self.game_board.get_my_snake_head()['x']) + abs(food['y'] - self.game_board.get_my_snake_head()['y']))
|
||||
self.set_target_food(closest_food)
|
||||
|
||||
# Use A* to search for a safe path
|
||||
return self.a_star_search(self.game_board.get_my_snake_head(), closest_food, obstacles)
|
||||
return None
|
||||
|
||||
def find_path_to_tail(self):
|
||||
# Exclude other snake's body from obstacles
|
||||
obstacles = set((part['x'], part['y']) for part in self.game_board.get_my_snake_body())
|
||||
for snake in self.game_board.get_other_snakes():
|
||||
for part in snake['body']:
|
||||
obstacles.add((part['x'], part['y']))
|
||||
|
||||
my_snake_tail = {"x": self.game_board.get_my_snake_tail()['x'], "y": self.game_board.get_my_snake_tail()['y']}
|
||||
|
||||
# Use A* to search for a safe path
|
||||
path = self.a_star_search(self.game_board.get_my_snake_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.game_board.get_my_snake_body())
|
||||
tail_position = (self.game_board.get_my_snake_tail()['x'], self.game_board.get_my_snake_tail()['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.game_board.get_my_snake_tail()['x'], self.game_board.get_my_snake_tail()['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.game_board.get_my_snake_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.game_board.get_width() and 0 <= position['y'] < self.game_board.get_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
|
||||
@@ -1,256 +0,0 @@
|
||||
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
|
||||
+183
-2
@@ -1,6 +1,10 @@
|
||||
from server.GameBoard import GameBoard
|
||||
import random
|
||||
|
||||
class TemplateSnake:
|
||||
def __init__(self):
|
||||
self.history = []
|
||||
self.target_food = None
|
||||
|
||||
def clear_history(self):
|
||||
self.history = []
|
||||
@@ -11,5 +15,182 @@ class TemplateSnake:
|
||||
def get_history(self):
|
||||
return self.history
|
||||
|
||||
def choose_move(self, game_data:dict):
|
||||
pass
|
||||
def add_calculations(self, calculations:dict):
|
||||
self.calculations.append(calculations)
|
||||
|
||||
def choose_move(self, game_data:GameBoard):
|
||||
self.game_board = game_data
|
||||
self.calculations = []
|
||||
self.eat_the_snake_overwrite = False
|
||||
|
||||
self.safe_positions = self.find_safe_positions(add_to_calculations=True)
|
||||
moves = list(self.safe_positions.keys())
|
||||
if len(moves) > 0:
|
||||
move = random.choice(moves)
|
||||
else:
|
||||
print("No safe positions left - Going to Die")
|
||||
move = None
|
||||
|
||||
self.add_to_history({"turn": game_data.get_turn(), "data": self.calculations})
|
||||
return move if move else "up"
|
||||
|
||||
def get_possible_moves(self, snake_head):
|
||||
return {
|
||||
"up": {
|
||||
"x": snake_head["x"],
|
||||
"y": snake_head["y"] + 1
|
||||
},
|
||||
"down": {
|
||||
"x": snake_head["x"],
|
||||
"y": snake_head["y"] - 1
|
||||
},
|
||||
"left": {
|
||||
"x": snake_head["x"] - 1,
|
||||
"y": snake_head["y"]
|
||||
},
|
||||
"right": {
|
||||
"x": snake_head["x"] + 1,
|
||||
"y": snake_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, my_body:list[dict], my_head:dict, safe_positions:dict[str, dict], add_to_calculations:bool=False) -> 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 = []
|
||||
my_body = self.did_snake_eat_food(my_body, my_head, add_to_calculations)
|
||||
for direction, location in safe_positions.items():
|
||||
if location in my_body:
|
||||
remove.append(direction)
|
||||
|
||||
for direction in remove:
|
||||
del safe_positions[direction]
|
||||
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "avoid_my_body", "my_body": my_body, "safe_positions": safe_positions})
|
||||
|
||||
return safe_positions
|
||||
|
||||
def avoid_walls(self, safe_positions:dict[str, dict], add_to_calculations:bool=False):
|
||||
remove = []
|
||||
for direction, location in list(safe_positions.items()):
|
||||
x_out_range = (location["x"] < 0 or location["x"] == self.game_board.get_width())
|
||||
y_out_range = (location["y"] < 0 or location["y"] == self.game_board.get_height())
|
||||
if x_out_range or y_out_range:
|
||||
remove.append(direction)
|
||||
|
||||
for direction in remove:
|
||||
del safe_positions[direction]
|
||||
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "avoid_walls", "board_width": self.game_board.get_width(), "board_height": self.game_board.get_height(), "safe_positions": safe_positions})
|
||||
|
||||
return safe_positions
|
||||
|
||||
def avoid_snakes(self, other_snakes:list[dict], safe_positions:dict[str, dict], add_to_calculations:bool=False):
|
||||
remove = []
|
||||
for snake in other_snakes:
|
||||
for direction, location in 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 safe_positions[direction]
|
||||
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "avoid_snakes", "other_snakes": other_snakes, "safe_positions": safe_positions})
|
||||
|
||||
return safe_positions
|
||||
|
||||
def avoid_get_eaten_by_other_snakes(self, other_snakes:list[dict], safe_positions:dict[str, dict], add_to_calculations:bool=False):
|
||||
remove = []
|
||||
no_way_out = {}
|
||||
self.other_snake_posible_moves = []
|
||||
|
||||
for snake in other_snakes:
|
||||
for direction, location in safe_positions.items():
|
||||
if len(safe_positions) > 1:
|
||||
self.other_snake_posible_moves = [{"x": v["x"], "y": v["y"]} for k, v in self.get_possible_moves(snake["head"]).items()]
|
||||
if snake["length"] < self.game_board.get_my_snake()["length"] and location in self.other_snake_posible_moves:
|
||||
self.eat_the_snake_overwrite = True
|
||||
self.kill_the_snake = direction
|
||||
#TODO: Testing - Check if snake on the way to the food here and only remove this pos
|
||||
elif location in self.other_snake_posible_moves and location in [{"x": food["x"], "y": food["y"]} for food in self.game_board.get_food()]:
|
||||
remove.append(direction)
|
||||
elif location in self.other_snake_posible_moves:
|
||||
no_way_out[direction] = location
|
||||
remove.append(direction)
|
||||
|
||||
remove = set(remove)
|
||||
for direction in remove:
|
||||
del safe_positions[direction]
|
||||
|
||||
if len(safe_positions) == 0:
|
||||
safe_positions = no_way_out
|
||||
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "avoid_get_eaten_by_other_snakes", "other_snakes": other_snakes, "safe_positions": safe_positions})
|
||||
|
||||
return safe_positions
|
||||
|
||||
def find_safe_positions(self, add_to_calculations:bool=False):
|
||||
safe_positions = self.get_possible_moves(self.game_board.get_my_snake_head())
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "get_possible_moves", "safe_positions": safe_positions})
|
||||
|
||||
safe_positions = self.avoid_my_body(self.game_board.get_my_snake_body(), self.game_board.get_my_snake_head(), safe_positions, add_to_calculations)
|
||||
safe_positions = self.avoid_walls(safe_positions, add_to_calculations)
|
||||
safe_positions = self.avoid_snakes(self.game_board.get_other_snakes(), safe_positions, add_to_calculations)
|
||||
safe_positions = self.avoid_get_eaten_by_other_snakes(self.game_board.get_other_snakes(), safe_positions, add_to_calculations)
|
||||
return safe_positions
|
||||
|
||||
def calculate_new_body_position(self, move:str=None, with_tail:bool=False):
|
||||
if move:
|
||||
head = self.get_possible_moves(self.game_board.get_my_snake_head())[move]
|
||||
|
||||
body = [head]
|
||||
body.extend(self.game_board.get_my_snake_body())
|
||||
body.pop()
|
||||
|
||||
if not with_tail:
|
||||
body.pop()
|
||||
|
||||
return body
|
||||
return move
|
||||
|
||||
def did_snake_eat_food(self, my_body:list[dict], my_head:dict, add_to_calculations:bool=False):
|
||||
if self.target_food is None:
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "did_snake_eat_food", "my_body": my_body, "my_head": my_head, "target_food": self.target_food, "action": "No Target Food"})
|
||||
return my_body
|
||||
|
||||
if self.target_food["x"] == my_head["x"] and self.target_food["y"] == my_head["y"]:
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "did_snake_eat_food", "my_body": my_body, "my_head": my_head, "target_food": self.target_food, "action": "Snake Eat no food"})
|
||||
return my_body
|
||||
|
||||
if add_to_calculations:
|
||||
self.add_calculations({"function": "did_snake_eat_food", "my_body": my_body[:-1], "my_head": my_head, "target_food": self.target_food, "action": "Remove Tail from Body"})
|
||||
return my_body[:-1]
|
||||
|
||||
def set_target_food(self, target_food:dict):
|
||||
self.target_food = target_food
|
||||
return True
|
||||
|
||||
+5
-2
@@ -1,4 +1,7 @@
|
||||
BATTLESNAKE_CLI=battlesnake_cli_1.2.3_Linux_x86_64/battlesnake
|
||||
|
||||
|
||||
$BATTLESNAKE_CLI play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g solo --browser
|
||||
if [ -z $1 ]; then
|
||||
$BATTLESNAKE_CLI play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g solo --browser --seed 1713099635738952360
|
||||
else
|
||||
$BATTLESNAKE_CLI play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g constrictor --browser --minimumFood 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user