feat: add live replays and PostgreSQL maintenance tools
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
- 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.
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@ SNAKE_REGISTRATIONS = {
|
||||
"TemplateSnake": SnakeRegistration("snakes.core.template", "1.0.0"),
|
||||
"ApexBattleSnake": SnakeRegistration("snakes.strategies.apex", "1.0.0"),
|
||||
"PrismBattleSnake_GPT_5_6_Sol": SnakeRegistration(
|
||||
"snakes.strategies.prism", "1.4.0"
|
||||
"snakes.strategies.prism", "1.5.0"
|
||||
),
|
||||
"DummSnake": SnakeRegistration("snakes.legacy.DummSnake", "1.0.0"),
|
||||
"LogicSnake": SnakeRegistration("snakes.legacy.LogicSnake", "1.1.0"),
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""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
|
||||
+19
-16
@@ -8,6 +8,7 @@ from quart_common.web.env import env_int
|
||||
|
||||
from server.dataset.RLBootstrapDataset import RLBootstrapDataset
|
||||
from snakes.core.template import TemplateSnake
|
||||
from snakes.engine.perimeter import perimeter_geometry_score
|
||||
from server.GameBoard import GameBoard
|
||||
|
||||
class ApexBattleSnake(TemplateSnake):
|
||||
@@ -686,19 +687,22 @@ class ApexBattleSnake(TemplateSnake):
|
||||
or (next_opts == 0 and not has_tail_escape)
|
||||
)
|
||||
|
||||
cx, cy = (width - 1) / 2.0, (height - 1) / 2.0
|
||||
center_score = 1.0 - (abs(point[0] - cx) + abs(point[1] - cy)) / max(1.0, cx + cy)
|
||||
|
||||
min_wall_dist = min(point[0], width - 1 - point[0], point[1], height - 1 - point[1])
|
||||
if total_occupancy > 0.25:
|
||||
if min_wall_dist == 0:
|
||||
edge_penalty = 35.0 * total_occupancy
|
||||
elif min_wall_dist == 1:
|
||||
edge_penalty = 15.0 * total_occupancy
|
||||
else:
|
||||
edge_penalty = 0.0
|
||||
else:
|
||||
edge_penalty = 0.0
|
||||
geometry_score = perimeter_geometry_score(
|
||||
point=point,
|
||||
current_head=(my_body[0]["x"], my_body[0]["y"]),
|
||||
width=width,
|
||||
height=height,
|
||||
occupancy=total_occupancy,
|
||||
snake_length=len(future_body),
|
||||
reachable_space=reachable_space,
|
||||
required_space=required_space,
|
||||
liberties=liberties,
|
||||
next_options=next_opts,
|
||||
safe_next_options=en_safe_opts,
|
||||
tail_escape=has_tail_escape,
|
||||
dead_end=dead_end,
|
||||
losing_head_to_head=losing_h2h,
|
||||
)
|
||||
|
||||
hunger = max(0.0, (60.0 - my_health) / 60.0)
|
||||
|
||||
@@ -719,7 +723,7 @@ class ApexBattleSnake(TemplateSnake):
|
||||
score += liberties * 20.0
|
||||
score += next_opts * 10.0
|
||||
score += en_safe_opts * 24.0
|
||||
score += center_score * 14.0
|
||||
score += geometry_score
|
||||
|
||||
if en_safe_opts == 0:
|
||||
score -= 1700.0
|
||||
@@ -727,7 +731,6 @@ class ApexBattleSnake(TemplateSnake):
|
||||
score -= 420.0
|
||||
|
||||
score -= art_penalty
|
||||
score -= edge_penalty
|
||||
score -= h2h_dist2_penalty
|
||||
|
||||
if dead_end:
|
||||
@@ -1421,7 +1424,7 @@ class ApexBattleSnake(TemplateSnake):
|
||||
can_grow = self._enemy_can_grow_this_turn(snake, food_set)
|
||||
if not can_grow:
|
||||
enemy_vacating_tails.add((snake["body"][-1]["x"], snake["body"][-1]["y"]))
|
||||
safe: MoveMap = {}
|
||||
safe: ApexBattleSnake.MoveMap = {}
|
||||
for move, (dx, dy) in self.DIRECTIONS.items():
|
||||
pt = (my_head["x"] + dx, my_head["y"] + dy)
|
||||
if not self._in_bounds(pt, width, height):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""PrismBattleSnake_GPT_5_6_Sol v1.4.0
|
||||
"""PrismBattleSnake_GPT_5_6_Sol v1.5.0
|
||||
|
||||
Built on ApexBattleSnake v1.0.0. All strategic logic is inherited.
|
||||
Performance improvement: all spatial primitives (flood fill, territory,
|
||||
@@ -54,7 +54,7 @@ class PrismBattleSnake_GPT_5_6_Sol(
|
||||
BitboardSpatialMixin,
|
||||
ApexBattleSnake,
|
||||
):
|
||||
VERSION = "1.4.0"
|
||||
VERSION = "1.5.0"
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
Reference in New Issue
Block a user