add enemy cut off trap check
This commit is contained in:
+108
-1
@@ -295,6 +295,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
next_options = self._next_turn_option_count(
|
next_options = self._next_turn_option_count(
|
||||||
future_body, blocked, width, height
|
future_body, blocked, width, height
|
||||||
)
|
)
|
||||||
|
enemy_safe_options = self._safe_next_turn_option_count(
|
||||||
|
future_body=future_body,
|
||||||
|
other_snakes=other_snakes,
|
||||||
|
food_set=food_set,
|
||||||
|
is_constrictor=is_constrictor,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
territory = self._territory_control_score(
|
territory = self._territory_control_score(
|
||||||
my_start=point,
|
my_start=point,
|
||||||
enemy_starts=enemy_heads,
|
enemy_starts=enemy_heads,
|
||||||
@@ -320,12 +329,17 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score += reachable_space * 2.6
|
score += reachable_space * 2.6
|
||||||
score += liberties * 18.0
|
score += liberties * 18.0
|
||||||
score += next_options * 10.0
|
score += next_options * 10.0
|
||||||
|
score += enemy_safe_options * 24.0
|
||||||
score += territory * 0.35
|
score += territory * 0.35
|
||||||
|
|
||||||
if reachable_space < required_space:
|
if reachable_space < required_space:
|
||||||
score -= 1200.0
|
score -= 1200.0
|
||||||
if liberties == 0:
|
if liberties == 0:
|
||||||
score -= 900.0
|
score -= 900.0
|
||||||
|
if enemy_safe_options == 0:
|
||||||
|
score -= 1700.0
|
||||||
|
elif enemy_safe_options == 1:
|
||||||
|
score -= 420.0
|
||||||
|
|
||||||
enemy_len = enemy_attack_map.get(point)
|
enemy_len = enemy_attack_map.get(point)
|
||||||
if enemy_len is not None:
|
if enemy_len is not None:
|
||||||
@@ -382,9 +396,11 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
move_safety[move] = {
|
move_safety[move] = {
|
||||||
"is_survivable": (not is_dead_end)
|
"is_survivable": (not is_dead_end)
|
||||||
and (not is_losing_head_to_head)
|
and (not is_losing_head_to_head)
|
||||||
|
and enemy_safe_options > 0
|
||||||
and health_after_move > 0,
|
and health_after_move > 0,
|
||||||
"reachable_space": reachable_space,
|
"reachable_space": reachable_space,
|
||||||
"next_options": next_options,
|
"next_options": next_options,
|
||||||
|
"enemy_safe_options": enemy_safe_options,
|
||||||
"tail_escape": has_tail_escape,
|
"tail_escape": has_tail_escape,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,6 +490,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
next_options = self._next_turn_option_count(
|
next_options = self._next_turn_option_count(
|
||||||
future_body, blocked, width, height
|
future_body, blocked, width, height
|
||||||
)
|
)
|
||||||
|
enemy_safe_options = self._safe_next_turn_option_count(
|
||||||
|
future_body=future_body,
|
||||||
|
other_snakes=other_snakes,
|
||||||
|
food_set=food_set,
|
||||||
|
is_constrictor=False,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
|
|
||||||
nearest_food_dist = self._nearest_food_distance(point, food_set, blocked, width, height)
|
nearest_food_dist = self._nearest_food_distance(point, food_set, blocked, width, height)
|
||||||
future_tail = future_body[-1]
|
future_tail = future_body[-1]
|
||||||
@@ -507,6 +532,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score += reachable_space * 2.8
|
score += reachable_space * 2.8
|
||||||
score += liberties * 18.0
|
score += liberties * 18.0
|
||||||
score += next_options * 10.0
|
score += next_options * 10.0
|
||||||
|
score += enemy_safe_options * 24.0
|
||||||
score += territory * 0.50
|
score += territory * 0.50
|
||||||
|
|
||||||
if likely_dead_end:
|
if likely_dead_end:
|
||||||
@@ -514,6 +540,10 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
|
|
||||||
if losing_head_to_head:
|
if losing_head_to_head:
|
||||||
score -= 1500.0
|
score -= 1500.0
|
||||||
|
if enemy_safe_options == 0:
|
||||||
|
score -= 1800.0
|
||||||
|
elif enemy_safe_options == 1:
|
||||||
|
score -= 450.0
|
||||||
|
|
||||||
is_safe_tightening_move = not likely_dead_end and not losing_head_to_head
|
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:
|
if is_safe_tightening_move and enemy_space <= encase_target_space:
|
||||||
@@ -578,8 +608,10 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
move_safety[move] = {
|
move_safety[move] = {
|
||||||
"is_survivable": (not likely_dead_end)
|
"is_survivable": (not likely_dead_end)
|
||||||
and (not losing_head_to_head)
|
and (not losing_head_to_head)
|
||||||
|
and enemy_safe_options > 0
|
||||||
and health_after_move > 0,
|
and health_after_move > 0,
|
||||||
"reachable_space": reachable_space,
|
"reachable_space": reachable_space,
|
||||||
|
"enemy_safe_options": enemy_safe_options,
|
||||||
"tail_escape": has_tail_escape,
|
"tail_escape": has_tail_escape,
|
||||||
}
|
}
|
||||||
scores[move] = round(score, 5)
|
scores[move] = round(score, 5)
|
||||||
@@ -644,6 +676,15 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
next_options = self._next_turn_option_count(
|
next_options = self._next_turn_option_count(
|
||||||
future_body, blocked, width, height
|
future_body, blocked, width, height
|
||||||
)
|
)
|
||||||
|
enemy_safe_options = self._safe_next_turn_option_count(
|
||||||
|
future_body=future_body,
|
||||||
|
other_snakes=other_snakes,
|
||||||
|
food_set=food_set,
|
||||||
|
is_constrictor=True,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
)
|
||||||
territory = self._territory_control_score(
|
territory = self._territory_control_score(
|
||||||
my_start=point,
|
my_start=point,
|
||||||
enemy_starts=enemy_heads,
|
enemy_starts=enemy_heads,
|
||||||
@@ -669,6 +710,7 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score += reachable_space * 3.8
|
score += reachable_space * 3.8
|
||||||
score += liberties * 24.0
|
score += liberties * 24.0
|
||||||
score += next_options * 16.0
|
score += next_options * 16.0
|
||||||
|
score += enemy_safe_options * 26.0
|
||||||
score += territory * 0.65
|
score += territory * 0.65
|
||||||
score += (reachable_space - enemy_best_space) * 3.2
|
score += (reachable_space - enemy_best_space) * 3.2
|
||||||
score += (max(0, 8 - enemy_total_options)) * 18.0
|
score += (max(0, 8 - enemy_total_options)) * 18.0
|
||||||
@@ -685,6 +727,10 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score -= 2400.0
|
score -= 2400.0
|
||||||
elif enemy_len is not None:
|
elif enemy_len is not None:
|
||||||
score += 90.0
|
score += 90.0
|
||||||
|
if enemy_safe_options == 0:
|
||||||
|
score -= 2200.0
|
||||||
|
elif enemy_safe_options == 1:
|
||||||
|
score -= 520.0
|
||||||
|
|
||||||
score -= self._revisit_penalty(point)
|
score -= self._revisit_penalty(point)
|
||||||
|
|
||||||
@@ -698,8 +744,11 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
score -= 35.0
|
score -= 35.0
|
||||||
|
|
||||||
move_safety[move] = {
|
move_safety[move] = {
|
||||||
"is_survivable": (not is_dead_end) and (not is_losing_head_to_head),
|
"is_survivable": (not is_dead_end)
|
||||||
|
and (not is_losing_head_to_head)
|
||||||
|
and enemy_safe_options > 0,
|
||||||
"reachable_space": reachable_space,
|
"reachable_space": reachable_space,
|
||||||
|
"enemy_safe_options": enemy_safe_options,
|
||||||
}
|
}
|
||||||
scores[move] = round(score, 5)
|
scores[move] = round(score, 5)
|
||||||
|
|
||||||
@@ -997,6 +1046,64 @@ class BestBattleSnake(TemplateSnake):
|
|||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_next_turn_option_count(self, future_body:list[Coord], other_snakes:list[SnakeState], food_set:set[Point], is_constrictor:bool, enemy_can_grow_cache:dict[Any, bool]|None, width:int, height:int) -> int:
|
||||||
|
"""Count next-turn moves that stay safe from enemy head contests."""
|
||||||
|
if not future_body:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
my_len = len(future_body)
|
||||||
|
future_snake = {
|
||||||
|
"head": future_body[0],
|
||||||
|
"body": future_body,
|
||||||
|
"length": my_len,
|
||||||
|
}
|
||||||
|
enemy_attack_map = self._build_enemy_attack_map(
|
||||||
|
my_snake=future_snake,
|
||||||
|
other_snakes=other_snakes,
|
||||||
|
food_set=food_set,
|
||||||
|
is_constrictor=is_constrictor,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
|
)
|
||||||
|
blocked = self._simulation_blocked(
|
||||||
|
future_body=future_body,
|
||||||
|
other_snakes=other_snakes,
|
||||||
|
food_set=food_set,
|
||||||
|
is_constrictor=is_constrictor,
|
||||||
|
enemy_can_grow_cache=enemy_can_grow_cache,
|
||||||
|
)
|
||||||
|
|
||||||
|
next_head = future_body[0]
|
||||||
|
own_tail = (future_body[-1]["x"], future_body[-1]["y"])
|
||||||
|
own_tail_stacked = self._is_tail_stacked(future_body)
|
||||||
|
safe_count = 0
|
||||||
|
|
||||||
|
for dx, dy in self.DIRECTIONS.values():
|
||||||
|
point = (next_head["x"] + dx, next_head["y"] + dy)
|
||||||
|
if not self._in_bounds(point, width, height):
|
||||||
|
continue
|
||||||
|
|
||||||
|
ate_food = point in food_set
|
||||||
|
can_step_on_tail = self._can_step_on_own_tail(
|
||||||
|
point=point,
|
||||||
|
own_tail=own_tail,
|
||||||
|
own_tail_is_stacked=own_tail_stacked,
|
||||||
|
ate_food=ate_food,
|
||||||
|
is_constrictor=is_constrictor,
|
||||||
|
)
|
||||||
|
if point in blocked and not can_step_on_tail:
|
||||||
|
continue
|
||||||
|
|
||||||
|
enemy_len = enemy_attack_map.get(point)
|
||||||
|
if enemy_len is not None and enemy_len >= my_len:
|
||||||
|
continue
|
||||||
|
|
||||||
|
safe_count += 1
|
||||||
|
|
||||||
|
return safe_count
|
||||||
|
|
||||||
def _revisit_penalty(self, point:Point) -> float:
|
def _revisit_penalty(self, point:Point) -> float:
|
||||||
"""Return penalty for revisiting recent head positions."""
|
"""Return penalty for revisiting recent head positions."""
|
||||||
if not self.recent_heads:
|
if not self.recent_heads:
|
||||||
|
|||||||
@@ -468,6 +468,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_avoids_enemy_block_in_trap(self):
|
||||||
|
game_state = {
|
||||||
|
"game": {
|
||||||
|
"id": "test-enemy-block-in-trap",
|
||||||
|
"ruleset": {"name": "standard", "version": "v1.0.0"},
|
||||||
|
"source": "custom",
|
||||||
|
"map": "standard",
|
||||||
|
},
|
||||||
|
"turn": 24,
|
||||||
|
"board": {
|
||||||
|
"height": 7,
|
||||||
|
"width": 7,
|
||||||
|
"food": [{"x": 3, "y": 3}],
|
||||||
|
"hazards": [],
|
||||||
|
"snakes": [
|
||||||
|
{
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 72,
|
||||||
|
"length": 4,
|
||||||
|
"head": {"x": 3, "y": 2},
|
||||||
|
"body": [
|
||||||
|
{"x": 3, "y": 2},
|
||||||
|
{"x": 3, "y": 1},
|
||||||
|
{"x": 2, "y": 1},
|
||||||
|
{"x": 2, "y": 2},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "enemy-a",
|
||||||
|
"name": "enemy-a",
|
||||||
|
"health": 90,
|
||||||
|
"length": 6,
|
||||||
|
"head": {"x": 4, "y": 4},
|
||||||
|
"body": [
|
||||||
|
{"x": 4, "y": 4},
|
||||||
|
{"x": 4, "y": 3},
|
||||||
|
{"x": 4, "y": 2},
|
||||||
|
{"x": 5, "y": 2},
|
||||||
|
{"x": 5, "y": 3},
|
||||||
|
{"x": 5, "y": 4},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "enemy-b",
|
||||||
|
"name": "enemy-b",
|
||||||
|
"health": 90,
|
||||||
|
"length": 6,
|
||||||
|
"head": {"x": 1, "y": 3},
|
||||||
|
"body": [
|
||||||
|
{"x": 1, "y": 3},
|
||||||
|
{"x": 1, "y": 4},
|
||||||
|
{"x": 0, "y": 4},
|
||||||
|
{"x": 0, "y": 3},
|
||||||
|
{"x": 0, "y": 2},
|
||||||
|
{"x": 1, "y": 2},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"you": {
|
||||||
|
"id": "me",
|
||||||
|
"name": "me",
|
||||||
|
"health": 72,
|
||||||
|
"length": 4,
|
||||||
|
"head": {"x": 3, "y": 2},
|
||||||
|
"body": [
|
||||||
|
{"x": 3, "y": 2},
|
||||||
|
{"x": 3, "y": 1},
|
||||||
|
{"x": 2, "y": 1},
|
||||||
|
{"x": 2, "y": 2},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
move = make_board(game_state).snake_neat_make_a_move()
|
||||||
|
self.assertEqual(move, "left")
|
||||||
|
|
||||||
def test_royale_uses_ruleset_hazard_damage_setting(self):
|
def test_royale_uses_ruleset_hazard_damage_setting(self):
|
||||||
game_state = {
|
game_state = {
|
||||||
"game": {
|
"game": {
|
||||||
|
|||||||
Reference in New Issue
Block a user