Files
snake-python/snakes/engine/perimeter.py
T
daniel156161 f14d780f29
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
feat: add live replays and PostgreSQL maintenance tools
- Stream compact live replay updates across local and clustered dashboards.
- Render responsive snake bodies as SVG paths with aligned custom icons.
- Add cache-busted assets, replay fallback routes, and live-follow playback.
- Support PostgreSQL benchmark sampling and idempotent SQLite migration.
- Add dry-run cleanup for old low-quality PostgreSQL replay payloads.
- Reward safe perimeter lanes and bump Prism to version 1.5.0.
- Add backend, migration, dashboard, and perimeter regression coverage.
2026-08-02 00:50:46 +02:00

80 lines
2.4 KiB
Python

"""Safety-gated board geometry scoring for perimeter-aware snakes."""
from __future__ import annotations
def _on_edge(point: tuple[int, int], width: int, height: int) -> bool:
x, y = point
return x in {0, width - 1} or y in {0, height - 1}
def _same_edge(
first: tuple[int, int], second: tuple[int, int], width: int, height: int,
) -> bool:
boundaries = ((0, 0), (0, width - 1), (1, 0), (1, height - 1))
return any(
first[index] == boundary and second[index] == boundary
for index, boundary in boundaries
)
def perimeter_geometry_score(
*,
point: tuple[int, int],
current_head: tuple[int, int],
width: int,
height: int,
occupancy: float,
snake_length: int,
reachable_space: int,
required_space: int,
liberties: int,
next_options: int,
safe_next_options: int,
tail_escape: bool,
dead_end: bool,
losing_head_to_head: bool,
) -> float:
"""Reward useful perimeter lanes without overriding tactical safety.
The normal move scorer already values liberties heavily, which naturally
makes wall cells less attractive. This adjustment offsets that bias only
when the wall position has room, a tail route, and multiple safe exits.
"""
x, y = point
cx, cy = (width - 1) / 2.0, (height - 1) / 2.0
center_score = 1.0 - (abs(x - cx) + abs(y - cy)) / max(1.0, cx + cy)
center_weight = max(2.0, 6.0 * (1.0 - min(1.0, occupancy / 0.5)))
score = center_score * center_weight
if not _on_edge(point, width, height):
return score
safely_usable = (
not dead_end
and not losing_head_to_head
and tail_escape
and liberties >= 2
and next_options >= 2
and safe_next_options >= 2
and reachable_space >= required_space + max(4, required_space // 2)
)
if not safely_usable:
return score
phase = min(1.0, occupancy / 0.34)
length_factor = min(1.0, snake_length / 12.0)
space_margin = min(
1.0,
max(0, reachable_space - required_space) / max(1, required_space),
)
score += 24.0 + phase * 12.0 + length_factor * 8.0 + space_margin * 8.0
# Continuing along one edge is more useful than repeatedly entering and
# leaving it: it keeps the body ordered and leaves the interior available.
if _same_edge(current_head, point, width, height):
score += 10.0
# Corners remove two exits. They remain usable, but should not become goals.
if x in {0, width - 1} and y in {0, height - 1}:
score -= 18.0
return score