"""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