implement royale game mode and tighten spaces in duel mode
This commit is contained in:
@@ -31,6 +31,37 @@ default:
|
|||||||
run:
|
run:
|
||||||
"{{justfile_directory()}}/main.py"
|
"{{justfile_directory()}}/main.py"
|
||||||
|
|
||||||
|
run-snake port="8000" snake="BestBattleSnake":
|
||||||
|
HOST="127.0.0.1" PORT="{{port}}" SNAKE="{{snake}}" DEBUG="false" DEBUG_SERVER="false" "{{justfile_directory()}}/main.py"
|
||||||
|
|
||||||
|
run-4-snakes base_port="9101" snake="BestBattleSnake":
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
pids=()
|
||||||
|
for i in 0 1 2 3; do
|
||||||
|
port="$(({{base_port}} + i))"
|
||||||
|
echo "Starting snake on :$port"
|
||||||
|
HOST="127.0.0.1" PORT="$port" SNAKE="{{snake}}" DEBUG="false" DEBUG_SERVER="false" "{{justfile_directory()}}/main.py" &
|
||||||
|
pids[$i]="$!"
|
||||||
|
done
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for pid in "${pids[@]}"; do
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
wait || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
wait
|
||||||
|
|
||||||
|
bench-best-snake iterations="1000":
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PYTHONPATH="{{justfile_directory()}}" python "{{justfile_directory()}}/tests/bench_best_battle_snake.py" --iterations "{{iterations}}"
|
||||||
|
|
||||||
build-battlesnake-cli:
|
build-battlesnake-cli:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -76,6 +107,57 @@ test-seed: build-battlesnake-cli
|
|||||||
BATTLESNAKE_CLI="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
|
BATTLESNAKE_CLI="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
|
||||||
"$BATTLESNAKE_CLI" play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g solo --browser --seed 1713099635738952360
|
"$BATTLESNAKE_CLI" play -W 11 -H 11 --name 'Python Starter Project' --url http://localhost:8000 -g solo --browser --seed 1713099635738952360
|
||||||
|
|
||||||
|
test-local-4 mode="standard" map="standard" base_port="9101" snake="BestBattleSnake" seed="1713099635738952360" browser="true": build-battlesnake-cli
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BATTLESNAKE_CLI="{{justfile_directory()}}/{{BATTLESNAKE_CLI_BIN}}"
|
||||||
|
LOG_DIR="{{justfile_directory()}}/.tools/snake-logs"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
|
||||||
|
pids=()
|
||||||
|
for i in 0 1 2 3; do
|
||||||
|
port="$(({{base_port}} + i))"
|
||||||
|
log_file="$LOG_DIR/snake-$((i+1)).log"
|
||||||
|
echo "Starting snake-$((i+1)) on :$port (log: $log_file)"
|
||||||
|
HOST="127.0.0.1" PORT="$port" SNAKE="{{snake}}" DEBUG="false" DEBUG_SERVER="false" "{{justfile_directory()}}/main.py" > >(tee "$log_file") 2>&1 &
|
||||||
|
pids[$i]="$!"
|
||||||
|
done
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for pid in "${pids[@]}"; do
|
||||||
|
kill "$pid" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
wait || true
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
for i in 0 1 2 3; do
|
||||||
|
port="$(({{base_port}} + i))"
|
||||||
|
for _ in $(seq 1 30); do
|
||||||
|
if curl -fsS "http://127.0.0.1:$port" >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
if ! curl -fsS "http://127.0.0.1:$port" >/dev/null 2>&1; then
|
||||||
|
echo "Snake on :$port did not start correctly. Check logs in $LOG_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
BROWSER_FLAG=""
|
||||||
|
if [ "{{browser}}" = "true" ]; then
|
||||||
|
BROWSER_FLAG="--browser"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$BATTLESNAKE_CLI" play -W 11 -H 11 \
|
||||||
|
--name "Snake 1" --url "http://127.0.0.1:{{base_port}}" \
|
||||||
|
--name "Snake 2" --url "http://127.0.0.1:$(({{base_port}} + 1))" \
|
||||||
|
--name "Snake 3" --url "http://127.0.0.1:$(({{base_port}} + 2))" \
|
||||||
|
--name "Snake 4" --url "http://127.0.0.1:$(({{base_port}} + 3))" \
|
||||||
|
-g "{{mode}}" --map "{{map}}" --seed "{{seed}}" $BROWSER_FLAG
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Fataset helpers
|
# Fataset helpers
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|||||||
+8
-2
@@ -61,6 +61,12 @@ class GameBoard:
|
|||||||
def get_type(self):
|
def get_type(self):
|
||||||
return self.type
|
return self.type
|
||||||
|
|
||||||
|
def get_map(self):
|
||||||
|
return self.map
|
||||||
|
|
||||||
|
def get_ruleset(self):
|
||||||
|
return self.ruleset
|
||||||
|
|
||||||
def get_my_snake_head(self):
|
def get_my_snake_head(self):
|
||||||
return self.my_snake["head"]
|
return self.my_snake["head"]
|
||||||
|
|
||||||
@@ -79,8 +85,8 @@ class GameBoard:
|
|||||||
"width": self.width,
|
"width": self.width,
|
||||||
"snakes": snakes,
|
"snakes": snakes,
|
||||||
"food": self.food,
|
"food": self.food,
|
||||||
"hazards": self.hazards
|
"hazards": self.hazards,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Game Functions
|
# Game Functions
|
||||||
def read_game_data(self, game_data:dict):
|
def read_game_data(self, game_data:dict):
|
||||||
|
|||||||
+115
-79
@@ -26,6 +26,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
self.recent_heads = deque(maxlen=14)
|
self.recent_heads = deque(maxlen=14)
|
||||||
self.last_move = None
|
self.last_move = None
|
||||||
self.last_game_id = None
|
self.last_game_id = None
|
||||||
|
self.previous_hazards = set()
|
||||||
self.duel_style = self._get_duel_style()
|
self.duel_style = self._get_duel_style()
|
||||||
|
|
||||||
def _get_duel_style(self):
|
def _get_duel_style(self):
|
||||||
@@ -67,6 +68,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
if game_id != self.last_game_id or turn <= 1:
|
if game_id != self.last_game_id or turn <= 1:
|
||||||
self.recent_heads.clear()
|
self.recent_heads.clear()
|
||||||
self.last_move = None
|
self.last_move = None
|
||||||
|
self.previous_hazards = set()
|
||||||
self.last_game_id = game_id
|
self.last_game_id = game_id
|
||||||
|
|
||||||
my_snake = cast(dict[str, Any], game_data.get_my_snake())
|
my_snake = cast(dict[str, Any], game_data.get_my_snake())
|
||||||
@@ -87,6 +89,16 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
|
|
||||||
food_set = {(food["x"], food["y"]) for food in foods}
|
food_set = {(food["x"], food["y"]) for food in foods}
|
||||||
hazard_set = {(hazard["x"], hazard["y"]) for hazard in hazards}
|
hazard_set = {(hazard["x"], hazard["y"]) for hazard in hazards}
|
||||||
|
previous_hazard_set = set(self.previous_hazards)
|
||||||
|
hazard_damage = self._hazard_damage_per_turn(game_data)
|
||||||
|
enemy_heads = [
|
||||||
|
(snake["head"]["x"], snake["head"]["y"]) for snake in other_snakes
|
||||||
|
]
|
||||||
|
enemy_can_grow_cache = {
|
||||||
|
snake["id"]: self._enemy_can_grow_this_turn(snake, food_set)
|
||||||
|
for snake in other_snakes
|
||||||
|
if "id" in snake
|
||||||
|
}
|
||||||
current_head_point = (my_head["x"], my_head["y"])
|
current_head_point = (my_head["x"], my_head["y"])
|
||||||
|
|
||||||
safe_moves = self._legal_moves(
|
safe_moves = self._legal_moves(
|
||||||
@@ -110,6 +122,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
"reason": "no_safe_moves",
|
"reason": "no_safe_moves",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
self.previous_hazards = set(hazard_set)
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
enemy_attack_map = self._build_enemy_attack_map(
|
enemy_attack_map = self._build_enemy_attack_map(
|
||||||
@@ -119,6 +132,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
is_constrictor=is_constrictor,
|
is_constrictor=is_constrictor,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_constrictor:
|
if is_constrictor:
|
||||||
@@ -129,12 +143,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
other_snakes=other_snakes,
|
other_snakes=other_snakes,
|
||||||
food_set=food_set,
|
food_set=food_set,
|
||||||
enemy_attack_map=enemy_attack_map,
|
enemy_attack_map=enemy_attack_map,
|
||||||
|
enemy_heads=enemy_heads,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
)
|
)
|
||||||
self.recent_heads.append(current_head_point)
|
self.recent_heads.append(current_head_point)
|
||||||
self.last_move = best_move
|
self.last_move = best_move
|
||||||
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
||||||
|
self.previous_hazards = set(hazard_set)
|
||||||
return best_move
|
return best_move
|
||||||
|
|
||||||
if len(other_snakes) == 1:
|
if len(other_snakes) == 1:
|
||||||
@@ -143,18 +160,20 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
my_body=my_body,
|
my_body=my_body,
|
||||||
my_len=my_len,
|
my_len=my_len,
|
||||||
my_health=my_health,
|
my_health=my_health,
|
||||||
foods=foods,
|
|
||||||
food_set=food_set,
|
food_set=food_set,
|
||||||
hazards=hazards,
|
|
||||||
hazard_set=hazard_set,
|
hazard_set=hazard_set,
|
||||||
other_snakes=other_snakes,
|
other_snakes=other_snakes,
|
||||||
enemy_attack_map=enemy_attack_map,
|
enemy_attack_map=enemy_attack_map,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
|
previous_hazard_set=previous_hazard_set,
|
||||||
|
hazard_damage=hazard_damage,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
)
|
)
|
||||||
self.recent_heads.append(current_head_point)
|
self.recent_heads.append(current_head_point)
|
||||||
self.last_move = best_move
|
self.last_move = best_move
|
||||||
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
||||||
|
self.previous_hazards = set(hazard_set)
|
||||||
return best_move
|
return best_move
|
||||||
|
|
||||||
scores: dict[str, float] = {}
|
scores: dict[str, float] = {}
|
||||||
@@ -169,6 +188,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
other_snakes=other_snakes,
|
other_snakes=other_snakes,
|
||||||
food_set=food_set,
|
food_set=food_set,
|
||||||
is_constrictor=is_constrictor,
|
is_constrictor=is_constrictor,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
)
|
)
|
||||||
blocked.discard(point)
|
blocked.discard(point)
|
||||||
|
|
||||||
@@ -180,17 +200,13 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
)
|
)
|
||||||
territory = self._territory_control_score(
|
territory = self._territory_control_score(
|
||||||
my_start=point,
|
my_start=point,
|
||||||
enemy_starts=[
|
enemy_starts=enemy_heads,
|
||||||
(snake["head"]["x"], snake["head"]["y"]) for snake in other_snakes
|
|
||||||
],
|
|
||||||
blocked=blocked,
|
blocked=blocked,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
)
|
)
|
||||||
|
|
||||||
nearest_food_dist = self._nearest_food_distance(
|
nearest_food_dist = self._nearest_food_distance(point, food_set, blocked, width, height)
|
||||||
point, foods, blocked, width, height
|
|
||||||
)
|
|
||||||
future_tail = future_body[-1]
|
future_tail = future_body[-1]
|
||||||
tail_point = (future_tail["x"], future_tail["y"])
|
tail_point = (future_tail["x"], future_tail["y"])
|
||||||
tail_dist = self._path_distance(
|
tail_dist = self._path_distance(
|
||||||
@@ -242,7 +258,9 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score -= 40.0
|
score -= 40.0
|
||||||
|
|
||||||
if point in hazard_set:
|
if point in hazard_set:
|
||||||
score -= 70.0 if my_health > 35 else 250.0
|
hazard_scale = max(0.5, hazard_damage / 14.0)
|
||||||
|
if not ate_food:
|
||||||
|
score -= (70.0 if my_health > 35 else 250.0) * hazard_scale
|
||||||
|
|
||||||
score -= self._revisit_penalty(point)
|
score -= self._revisit_penalty(point)
|
||||||
|
|
||||||
@@ -256,8 +274,9 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score -= 20.0
|
score -= 20.0
|
||||||
|
|
||||||
health_after_move = 100 if ate_food else my_health - 1
|
health_after_move = 100 if ate_food else my_health - 1
|
||||||
if point in hazard_set:
|
hazard_active = self._hazard_is_active(point, ate_food, hazard_set, previous_hazard_set)
|
||||||
health_after_move -= 15
|
if hazard_active:
|
||||||
|
health_after_move -= hazard_damage
|
||||||
if health_after_move <= 0:
|
if health_after_move <= 0:
|
||||||
score -= 10000.0
|
score -= 10000.0
|
||||||
|
|
||||||
@@ -305,27 +324,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
self.recent_heads.append(current_head_point)
|
self.recent_heads.append(current_head_point)
|
||||||
self.last_move = best_move
|
self.last_move = best_move
|
||||||
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
self.add_to_history({"turn": turn, "move": best_move, "scores": scores})
|
||||||
|
self.previous_hazards = set(hazard_set)
|
||||||
return best_move
|
return best_move
|
||||||
|
|
||||||
def _choose_duel_move(
|
def _choose_duel_move(self, safe_moves, my_body, my_len, my_health, food_set, hazard_set, other_snakes, enemy_attack_map, enemy_can_grow_cache, previous_hazard_set, hazard_damage, width, height):
|
||||||
self,
|
|
||||||
safe_moves,
|
|
||||||
my_body,
|
|
||||||
my_len,
|
|
||||||
my_health,
|
|
||||||
foods,
|
|
||||||
food_set,
|
|
||||||
hazards,
|
|
||||||
hazard_set,
|
|
||||||
other_snakes,
|
|
||||||
enemy_attack_map,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
):
|
|
||||||
duel_weights = self._duel_weights(self.duel_style)
|
duel_weights = self._duel_weights(self.duel_style)
|
||||||
enemy = other_snakes[0]
|
enemy = other_snakes[0]
|
||||||
enemy_head = (enemy["head"]["x"], enemy["head"]["y"])
|
enemy_head = (enemy["head"]["x"], enemy["head"]["y"])
|
||||||
enemy_len = enemy.get("length", len(enemy["body"]))
|
enemy_len = enemy.get("length", len(enemy["body"]))
|
||||||
|
encase_target_space = max(8, enemy_len * 2)
|
||||||
|
|
||||||
scores: dict[str, float] = {}
|
scores: dict[str, float] = {}
|
||||||
move_safety: dict[str, dict[str, Any]] = {}
|
move_safety: dict[str, dict[str, Any]] = {}
|
||||||
@@ -342,6 +349,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
other_snakes=other_snakes,
|
other_snakes=other_snakes,
|
||||||
food_set=food_set,
|
food_set=food_set,
|
||||||
is_constrictor=False,
|
is_constrictor=False,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
)
|
)
|
||||||
blocked.discard(point)
|
blocked.discard(point)
|
||||||
|
|
||||||
@@ -352,9 +360,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
future_body, blocked, width, height
|
future_body, blocked, width, height
|
||||||
)
|
)
|
||||||
|
|
||||||
nearest_food_dist = self._nearest_food_distance(
|
nearest_food_dist = self._nearest_food_distance(point, food_set, blocked, width, height)
|
||||||
point, foods, blocked, width, height
|
|
||||||
)
|
|
||||||
future_tail = future_body[-1]
|
future_tail = future_body[-1]
|
||||||
tail_point = (future_tail["x"], future_tail["y"])
|
tail_point = (future_tail["x"], future_tail["y"])
|
||||||
tail_dist = self._path_distance(
|
tail_dist = self._path_distance(
|
||||||
@@ -380,6 +386,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
enemy_attack_len is not None and enemy_attack_len >= my_len
|
enemy_attack_len is not None and enemy_attack_len >= my_len
|
||||||
)
|
)
|
||||||
direct_head_distance = self._manhattan(point, enemy_head)
|
direct_head_distance = self._manhattan(point, enemy_head)
|
||||||
|
enemy_space, enemy_options = self._enemy_confinement_metrics(enemy_head, blocked, width, height)
|
||||||
|
|
||||||
score = 0.0
|
score = 0.0
|
||||||
score += reachable_space * 2.8
|
score += reachable_space * 2.8
|
||||||
@@ -393,6 +400,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
if losing_head_to_head:
|
if losing_head_to_head:
|
||||||
score -= 1500.0
|
score -= 1500.0
|
||||||
|
|
||||||
|
is_safe_tightening_move = not likely_dead_end and not losing_head_to_head
|
||||||
|
if is_safe_tightening_move and enemy_space <= encase_target_space:
|
||||||
|
score += (encase_target_space - enemy_space) * 42.0
|
||||||
|
score += max(0, 3 - enemy_options) * 95.0
|
||||||
|
if reachable_space > enemy_space:
|
||||||
|
score += 120.0
|
||||||
|
if direct_head_distance <= 2:
|
||||||
|
score += 40.0
|
||||||
|
|
||||||
if my_len > enemy_len:
|
if my_len > enemy_len:
|
||||||
if direct_head_distance == 1:
|
if direct_head_distance == 1:
|
||||||
score += 220.0 * duel_weights["head_pressure"]
|
score += 220.0 * duel_weights["head_pressure"]
|
||||||
@@ -420,7 +436,9 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score -= 50.0
|
score -= 50.0
|
||||||
|
|
||||||
if point in hazard_set:
|
if point in hazard_set:
|
||||||
score -= 70.0 if my_health > 35 else 250.0
|
hazard_scale = max(0.5, hazard_damage / 14.0)
|
||||||
|
if not ate_food:
|
||||||
|
score -= (70.0 if my_health > 35 else 250.0) * hazard_scale
|
||||||
|
|
||||||
score -= self._revisit_penalty(point)
|
score -= self._revisit_penalty(point)
|
||||||
|
|
||||||
@@ -434,8 +452,9 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score -= 20.0
|
score -= 20.0
|
||||||
|
|
||||||
health_after_move = 100 if ate_food else my_health - 1
|
health_after_move = 100 if ate_food else my_health - 1
|
||||||
if point in hazard_set:
|
hazard_active = self._hazard_is_active(point, ate_food, hazard_set, previous_hazard_set)
|
||||||
health_after_move -= 15
|
if hazard_active:
|
||||||
|
health_after_move -= hazard_damage
|
||||||
if health_after_move <= 0:
|
if health_after_move <= 0:
|
||||||
score -= 10000.0
|
score -= 10000.0
|
||||||
|
|
||||||
@@ -478,17 +497,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
]
|
]
|
||||||
return random.choice(top_moves), scores
|
return random.choice(top_moves), scores
|
||||||
|
|
||||||
def _choose_constrictor_move(
|
def _choose_constrictor_move(self, safe_moves, my_body, my_len, other_snakes, food_set, enemy_attack_map, enemy_heads, enemy_can_grow_cache, width, height):
|
||||||
self,
|
|
||||||
safe_moves,
|
|
||||||
my_body,
|
|
||||||
my_len,
|
|
||||||
other_snakes,
|
|
||||||
food_set,
|
|
||||||
enemy_attack_map,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
):
|
|
||||||
scores: dict[str, float] = {}
|
scores: dict[str, float] = {}
|
||||||
move_safety: dict[str, dict[str, Any]] = {}
|
move_safety: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
@@ -502,6 +511,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
other_snakes=other_snakes,
|
other_snakes=other_snakes,
|
||||||
food_set=food_set,
|
food_set=food_set,
|
||||||
is_constrictor=True,
|
is_constrictor=True,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
)
|
)
|
||||||
blocked.discard(point)
|
blocked.discard(point)
|
||||||
|
|
||||||
@@ -513,9 +523,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
)
|
)
|
||||||
territory = self._territory_control_score(
|
territory = self._territory_control_score(
|
||||||
my_start=point,
|
my_start=point,
|
||||||
enemy_starts=[
|
enemy_starts=enemy_heads,
|
||||||
(snake["head"]["x"], snake["head"]["y"]) for snake in other_snakes
|
|
||||||
],
|
|
||||||
blocked=blocked,
|
blocked=blocked,
|
||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
@@ -583,16 +591,14 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
]
|
]
|
||||||
return random.choice(top_moves), scores
|
return random.choice(top_moves), scores
|
||||||
|
|
||||||
def _legal_moves(
|
def _legal_moves(self, my_head, my_body, other_snakes, food_set, is_constrictor, width, height):
|
||||||
self, my_head, my_body, other_snakes, food_set, is_constrictor, width, height
|
|
||||||
):
|
|
||||||
occupied = self._occupied_cells(my_body, other_snakes)
|
occupied = self._occupied_cells(my_body, other_snakes)
|
||||||
own_tail = (my_body[-1]["x"], my_body[-1]["y"])
|
own_tail = (my_body[-1]["x"], my_body[-1]["y"])
|
||||||
own_tail_stacked = self._is_tail_stacked(my_body)
|
own_tail_stacked = self._is_tail_stacked(my_body)
|
||||||
|
|
||||||
safe_moves = {}
|
safe_moves = {}
|
||||||
for move, pos in self.get_possible_moves(my_head).items():
|
for move, (dx, dy) in self.DIRECTIONS.items():
|
||||||
point = (pos["x"], pos["y"])
|
point = (my_head["x"] + dx, my_head["y"] + dy)
|
||||||
if not self._in_bounds(point, width, height):
|
if not self._in_bounds(point, width, height):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -608,7 +614,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
if point in occupied and not can_step_on_tail:
|
if point in occupied and not can_step_on_tail:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
safe_moves[move] = pos
|
safe_moves[move] = {"x": point[0], "y": point[1]}
|
||||||
|
|
||||||
return safe_moves
|
return safe_moves
|
||||||
|
|
||||||
@@ -618,7 +624,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
occupied |= {(segment["x"], segment["y"]) for segment in snake["body"]}
|
occupied |= {(segment["x"], segment["y"]) for segment in snake["body"]}
|
||||||
return occupied
|
return occupied
|
||||||
|
|
||||||
def _simulation_blocked(self, future_body, other_snakes, food_set, is_constrictor):
|
def _simulation_blocked(self, future_body, other_snakes, food_set, is_constrictor, enemy_can_grow_cache=None):
|
||||||
blocked = {(segment["x"], segment["y"]) for segment in future_body}
|
blocked = {(segment["x"], segment["y"]) for segment in future_body}
|
||||||
|
|
||||||
if not is_constrictor and not self._is_tail_stacked(future_body):
|
if not is_constrictor and not self._is_tail_stacked(future_body):
|
||||||
@@ -632,7 +638,13 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
if is_constrictor:
|
if is_constrictor:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._enemy_can_grow_this_turn(snake, food_set):
|
snake_id = snake.get("id")
|
||||||
|
enemy_can_grow = (
|
||||||
|
enemy_can_grow_cache.get(snake_id)
|
||||||
|
if enemy_can_grow_cache and snake_id is not None
|
||||||
|
else self._enemy_can_grow_this_turn(snake, food_set)
|
||||||
|
)
|
||||||
|
if enemy_can_grow:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._is_tail_stacked(snake["body"]):
|
if self._is_tail_stacked(snake["body"]):
|
||||||
@@ -643,20 +655,25 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
|
|
||||||
return blocked
|
return blocked
|
||||||
|
|
||||||
def _build_enemy_attack_map(
|
def _build_enemy_attack_map(self, my_snake, other_snakes, food_set, is_constrictor, width, height, enemy_can_grow_cache=None):
|
||||||
self, my_snake, other_snakes, food_set, is_constrictor, width, height
|
|
||||||
):
|
|
||||||
occupied = self._occupied_cells(my_snake["body"], other_snakes)
|
occupied = self._occupied_cells(my_snake["body"], other_snakes)
|
||||||
my_id = my_snake["id"]
|
my_body_points = {(segment["x"], segment["y"]) for segment in my_snake["body"]}
|
||||||
attack_map = {}
|
attack_map = {}
|
||||||
|
|
||||||
for enemy in other_snakes:
|
for enemy in other_snakes:
|
||||||
enemy_len = enemy.get("length", len(enemy["body"]))
|
enemy_len = enemy.get("length", len(enemy["body"]))
|
||||||
enemy_tail = (enemy["body"][-1]["x"], enemy["body"][-1]["y"])
|
enemy_tail = (enemy["body"][-1]["x"], enemy["body"][-1]["y"])
|
||||||
enemy_tail_stacked = self._is_tail_stacked(enemy["body"])
|
enemy_tail_stacked = self._is_tail_stacked(enemy["body"])
|
||||||
|
snake_id = enemy.get("id")
|
||||||
|
enemy_can_grow = (
|
||||||
|
enemy_can_grow_cache.get(snake_id)
|
||||||
|
if enemy_can_grow_cache and snake_id is not None
|
||||||
|
else self._enemy_can_grow_this_turn(enemy, food_set)
|
||||||
|
)
|
||||||
|
|
||||||
for pos in self.get_possible_moves(enemy["head"]).values():
|
enemy_head = enemy["head"]
|
||||||
point = (pos["x"], pos["y"])
|
for dx, dy in self.DIRECTIONS.values():
|
||||||
|
point = (enemy_head["x"] + dx, enemy_head["y"] + dy)
|
||||||
if not self._in_bounds(point, width, height):
|
if not self._in_bounds(point, width, height):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -664,18 +681,14 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
not is_constrictor
|
not is_constrictor
|
||||||
and point == enemy_tail
|
and point == enemy_tail
|
||||||
and not enemy_tail_stacked
|
and not enemy_tail_stacked
|
||||||
and not self._enemy_can_grow_this_turn(enemy, food_set)
|
and not enemy_can_grow
|
||||||
)
|
)
|
||||||
|
|
||||||
if point in occupied and not can_step_on_enemy_tail:
|
if point in occupied and not can_step_on_enemy_tail:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Do not consider impossible overlap directly into my own occupied body except head swap possibilities.
|
# Do not consider impossible overlap directly into my own occupied body except head swap possibilities.
|
||||||
if point in {
|
if point in my_body_points:
|
||||||
(segment["x"], segment["y"])
|
|
||||||
for segment in my_snake["body"]
|
|
||||||
if my_snake["id"] == my_id
|
|
||||||
}:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
previous = attack_map.get(point)
|
previous = attack_map.get(point)
|
||||||
@@ -694,9 +707,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
next_body.pop()
|
next_body.pop()
|
||||||
return next_body
|
return next_body
|
||||||
|
|
||||||
def _can_step_on_own_tail(
|
def _can_step_on_own_tail(self, point, own_tail, own_tail_is_stacked, ate_food, is_constrictor):
|
||||||
self, point, own_tail, own_tail_is_stacked, ate_food, is_constrictor
|
|
||||||
):
|
|
||||||
if is_constrictor:
|
if is_constrictor:
|
||||||
return False
|
return False
|
||||||
if ate_food:
|
if ate_food:
|
||||||
@@ -717,17 +728,31 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _nearest_food_distance(self, start, foods, blocked, width, height):
|
def _hazard_damage_per_turn(self, game_data):
|
||||||
if not foods:
|
ruleset = {}
|
||||||
return None
|
if hasattr(game_data, "get_ruleset"):
|
||||||
|
ruleset = game_data.get_ruleset() or {}
|
||||||
|
elif hasattr(game_data, "ruleset"):
|
||||||
|
ruleset = game_data.ruleset or {}
|
||||||
|
settings = ruleset.get("settings", {}) if isinstance(ruleset, dict) else {}
|
||||||
|
return int(settings.get("hazardDamagePerTurn", 15))
|
||||||
|
|
||||||
targets = {(food["x"], food["y"]) for food in foods}
|
def _hazard_is_active(self, point, ate_food, hazard_set, previous_hazard_set):
|
||||||
|
if point not in hazard_set:
|
||||||
|
return False
|
||||||
|
if ate_food:
|
||||||
|
return False
|
||||||
|
return point in previous_hazard_set
|
||||||
|
|
||||||
|
def _nearest_food_distance(self, start, food_set, blocked, width, height):
|
||||||
|
if not food_set:
|
||||||
|
return None
|
||||||
queue = deque([(start, 0)])
|
queue = deque([(start, 0)])
|
||||||
seen = {start}
|
seen = {start}
|
||||||
|
|
||||||
while queue:
|
while queue:
|
||||||
point, distance = queue.popleft()
|
point, distance = queue.popleft()
|
||||||
if point in targets:
|
if point in food_set:
|
||||||
return distance
|
return distance
|
||||||
|
|
||||||
for neighbor in self._neighbors(point):
|
for neighbor in self._neighbors(point):
|
||||||
@@ -735,7 +760,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
continue
|
continue
|
||||||
if not self._in_bounds(neighbor, width, height):
|
if not self._in_bounds(neighbor, width, height):
|
||||||
continue
|
continue
|
||||||
if neighbor in blocked and neighbor not in targets:
|
if neighbor in blocked and neighbor not in food_set:
|
||||||
continue
|
continue
|
||||||
seen.add(neighbor)
|
seen.add(neighbor)
|
||||||
queue.append((neighbor, distance + 1))
|
queue.append((neighbor, distance + 1))
|
||||||
@@ -797,8 +822,8 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
|
|
||||||
next_head = future_body[0]
|
next_head = future_body[0]
|
||||||
count = 0
|
count = 0
|
||||||
for pos in self.get_possible_moves(next_head).values():
|
for dx, dy in self.DIRECTIONS.values():
|
||||||
point = (pos["x"], pos["y"])
|
point = (next_head["x"] + dx, next_head["y"] + dy)
|
||||||
if not self._in_bounds(point, width, height):
|
if not self._in_bounds(point, width, height):
|
||||||
continue
|
continue
|
||||||
if point in blocked:
|
if point in blocked:
|
||||||
@@ -807,6 +832,8 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
def _revisit_penalty(self, point):
|
def _revisit_penalty(self, point):
|
||||||
|
if not self.recent_heads:
|
||||||
|
return 0.0
|
||||||
penalty = 0.0
|
penalty = 0.0
|
||||||
for index, old_point in enumerate(reversed(self.recent_heads), start=1):
|
for index, old_point in enumerate(reversed(self.recent_heads), start=1):
|
||||||
if old_point != point:
|
if old_point != point:
|
||||||
@@ -867,6 +894,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
|
|
||||||
return distances
|
return distances
|
||||||
|
|
||||||
|
def _enemy_confinement_metrics(self, enemy_head, blocked, width, height):
|
||||||
|
enemy_blocked = set(blocked)
|
||||||
|
enemy_blocked.discard(enemy_head)
|
||||||
|
enemy_space = self._flood_fill_count(enemy_head, enemy_blocked, width, height)
|
||||||
|
enemy_options = self._open_neighbor_count(
|
||||||
|
enemy_head, enemy_blocked, width, height
|
||||||
|
)
|
||||||
|
return enemy_space, enemy_options
|
||||||
|
|
||||||
def _neighbors(self, point):
|
def _neighbors(self, point):
|
||||||
for dx, dy in self.DIRECTIONS.values():
|
for dx, dy in self.DIRECTIONS.values():
|
||||||
yield (point[0] + dx, point[1] + dy)
|
yield (point[0] + dx, point[1] + dy)
|
||||||
@@ -878,8 +914,8 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
return 0 <= point[0] < width and 0 <= point[1] < height
|
return 0 <= point[0] < width and 0 <= point[1] < height
|
||||||
|
|
||||||
def _fallback_move(self, head, width, height):
|
def _fallback_move(self, head, width, height):
|
||||||
for move, pos in self.get_possible_moves(head).items():
|
for move, (dx, dy) in self.DIRECTIONS.items():
|
||||||
point = (pos["x"], pos["y"])
|
point = (head["x"] + dx, head["y"] + dy)
|
||||||
if self._in_bounds(point, width, height):
|
if self._in_bounds(point, width, height):
|
||||||
return move
|
return move
|
||||||
return "up"
|
return "up"
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
|
from server.GameBoard import GameBoard
|
||||||
|
from snakes.BestBattleSnake import BestBattleSnake
|
||||||
|
|
||||||
|
def build_game_state() -> dict:
|
||||||
|
return {
|
||||||
|
"game": {
|
||||||
|
"id": "bench-best-snake",
|
||||||
|
"ruleset": {
|
||||||
|
"name": "standard",
|
||||||
|
"version": "v1.0.0",
|
||||||
|
"settings": {"hazardDamagePerTurn": 14},
|
||||||
|
},
|
||||||
|
"source": "custom",
|
||||||
|
"map": "standard",
|
||||||
|
},
|
||||||
|
"turn": 42,
|
||||||
|
"board": {
|
||||||
|
"height": 11,
|
||||||
|
"width": 11,
|
||||||
|
"food": [{"x": 1, "y": 9}, {"x": 9, "y": 1}],
|
||||||
|
"hazards": [],
|
||||||
|
"snakes": [
|
||||||
|
{
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 74,
|
||||||
|
"length": 8,
|
||||||
|
"head": {"x": 5, "y": 5},
|
||||||
|
"body": [
|
||||||
|
{"x": 5, "y": 5},
|
||||||
|
{"x": 5, "y": 4},
|
||||||
|
{"x": 5, "y": 3},
|
||||||
|
{"x": 4, "y": 3},
|
||||||
|
{"x": 3, "y": 3},
|
||||||
|
{"x": 3, "y": 4},
|
||||||
|
{"x": 3, "y": 5},
|
||||||
|
{"x": 4, "y": 5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "enemy",
|
||||||
|
"name": "enemy",
|
||||||
|
"health": 70,
|
||||||
|
"length": 8,
|
||||||
|
"head": {"x": 7, "y": 7},
|
||||||
|
"body": [
|
||||||
|
{"x": 7, "y": 7},
|
||||||
|
{"x": 7, "y": 6},
|
||||||
|
{"x": 7, "y": 5},
|
||||||
|
{"x": 8, "y": 5},
|
||||||
|
{"x": 9, "y": 5},
|
||||||
|
{"x": 9, "y": 6},
|
||||||
|
{"x": 9, "y": 7},
|
||||||
|
{"x": 8, "y": 7},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"you": {
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 74,
|
||||||
|
"length": 8,
|
||||||
|
"head": {"x": 5, "y": 5},
|
||||||
|
"body": [
|
||||||
|
{"x": 5, "y": 5},
|
||||||
|
{"x": 5, "y": 4},
|
||||||
|
{"x": 5, "y": 3},
|
||||||
|
{"x": 4, "y": 3},
|
||||||
|
{"x": 3, "y": 3},
|
||||||
|
{"x": 3, "y": 4},
|
||||||
|
{"x": 3, "y": 5},
|
||||||
|
{"x": 4, "y": 5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--iterations", type=int, default=1000)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
game_state = build_game_state()
|
||||||
|
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=BestBattleSnake(),
|
||||||
|
)
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
for i in range(args.iterations):
|
||||||
|
game_state["turn"] = i + 1
|
||||||
|
board.read_game_data(game_state)
|
||||||
|
board.snake_neat_make_a_move()
|
||||||
|
elapsed = time.perf_counter() - start
|
||||||
|
|
||||||
|
avg_ms = (elapsed / max(1, args.iterations)) * 1000.0
|
||||||
|
print(f"BestBattleSnake benchmark: {args.iterations} moves in {elapsed:.4f}s ({avg_ms:.3f} ms/move)")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -328,6 +328,84 @@ class TestBestBattleSnake(unittest.TestCase):
|
|||||||
move = make_board(game_state).snake_neat_make_a_move()
|
move = make_board(game_state).snake_neat_make_a_move()
|
||||||
self.assertNotEqual(move, "up")
|
self.assertNotEqual(move, "up")
|
||||||
|
|
||||||
|
def test_royale_uses_ruleset_hazard_damage_setting(self):
|
||||||
|
game_state = {
|
||||||
|
"game": {
|
||||||
|
"id": "test-royale-hazard-setting",
|
||||||
|
"ruleset": {
|
||||||
|
"name": "standard",
|
||||||
|
"version": "v1.0.0",
|
||||||
|
"settings": {"hazardDamagePerTurn": 22},
|
||||||
|
},
|
||||||
|
"source": "custom",
|
||||||
|
"map": "royale",
|
||||||
|
},
|
||||||
|
"turn": 5,
|
||||||
|
"board": {
|
||||||
|
"height": 11,
|
||||||
|
"width": 11,
|
||||||
|
"food": [],
|
||||||
|
"hazards": [],
|
||||||
|
"snakes": [
|
||||||
|
{
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 80,
|
||||||
|
"length": 3,
|
||||||
|
"head": {"x": 5, "y": 5},
|
||||||
|
"body": [
|
||||||
|
{"x": 5, "y": 5},
|
||||||
|
{"x": 5, "y": 4},
|
||||||
|
{"x": 5, "y": 3},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"you": {
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 80,
|
||||||
|
"length": 3,
|
||||||
|
"head": {"x": 5, "y": 5},
|
||||||
|
"body": [
|
||||||
|
{"x": 5, "y": 5},
|
||||||
|
{"x": 5, "y": 4},
|
||||||
|
{"x": 5, "y": 3},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
board = make_board(game_state)
|
||||||
|
snake = board.snake_class
|
||||||
|
self.assertEqual(snake._hazard_damage_per_turn(board), 22)
|
||||||
|
|
||||||
|
def test_royale_new_hazard_has_spawn_grace(self):
|
||||||
|
snake = BestBattleSnake()
|
||||||
|
point = (4, 4)
|
||||||
|
hazard_set = {point}
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
snake._hazard_is_active(
|
||||||
|
point, ate_food=False, hazard_set=hazard_set, previous_hazard_set=set()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
snake._hazard_is_active(
|
||||||
|
point,
|
||||||
|
ate_food=False,
|
||||||
|
hazard_set=hazard_set,
|
||||||
|
previous_hazard_set=hazard_set,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
snake._hazard_is_active(
|
||||||
|
point,
|
||||||
|
ate_food=True,
|
||||||
|
hazard_set=hazard_set,
|
||||||
|
previous_hazard_set=hazard_set,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_constrictor_avoids_growth_dead_end(self):
|
def test_constrictor_avoids_growth_dead_end(self):
|
||||||
game_state = {
|
game_state = {
|
||||||
"game": {
|
"game": {
|
||||||
@@ -393,6 +471,75 @@ class TestBestBattleSnake(unittest.TestCase):
|
|||||||
move = make_board(game_state).snake_neat_make_a_move()
|
move = make_board(game_state).snake_neat_make_a_move()
|
||||||
self.assertEqual(move, "up")
|
self.assertEqual(move, "up")
|
||||||
|
|
||||||
|
def test_duel_tightens_space_when_enemy_is_encased(self):
|
||||||
|
game_state = {
|
||||||
|
"game": {
|
||||||
|
"id": "test-encase-tighten",
|
||||||
|
"ruleset": {"name": "standard", "version": "v1.0.0"},
|
||||||
|
"source": "custom",
|
||||||
|
"map": "standard",
|
||||||
|
},
|
||||||
|
"turn": 40,
|
||||||
|
"board": {
|
||||||
|
"height": 7,
|
||||||
|
"width": 7,
|
||||||
|
"food": [{"x": 0, "y": 0}],
|
||||||
|
"hazards": [],
|
||||||
|
"snakes": [
|
||||||
|
{
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 92,
|
||||||
|
"length": 8,
|
||||||
|
"head": {"x": 2, "y": 3},
|
||||||
|
"body": [
|
||||||
|
{"x": 2, "y": 3},
|
||||||
|
{"x": 2, "y": 2},
|
||||||
|
{"x": 1, "y": 2},
|
||||||
|
{"x": 1, "y": 3},
|
||||||
|
{"x": 1, "y": 4},
|
||||||
|
{"x": 2, "y": 4},
|
||||||
|
{"x": 3, "y": 4},
|
||||||
|
{"x": 3, "y": 5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "enemy",
|
||||||
|
"name": "enemy",
|
||||||
|
"health": 88,
|
||||||
|
"length": 5,
|
||||||
|
"head": {"x": 4, "y": 3},
|
||||||
|
"body": [
|
||||||
|
{"x": 4, "y": 3},
|
||||||
|
{"x": 5, "y": 3},
|
||||||
|
{"x": 5, "y": 2},
|
||||||
|
{"x": 4, "y": 2},
|
||||||
|
{"x": 4, "y": 1},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"you": {
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 92,
|
||||||
|
"length": 8,
|
||||||
|
"head": {"x": 2, "y": 3},
|
||||||
|
"body": [
|
||||||
|
{"x": 2, "y": 3},
|
||||||
|
{"x": 2, "y": 2},
|
||||||
|
{"x": 1, "y": 2},
|
||||||
|
{"x": 1, "y": 3},
|
||||||
|
{"x": 1, "y": 4},
|
||||||
|
{"x": 2, "y": 4},
|
||||||
|
{"x": 3, "y": 4},
|
||||||
|
{"x": 3, "y": 5},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
move = make_board(game_state).snake_neat_make_a_move()
|
||||||
|
self.assertEqual(move, "right")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user