The leading agent on Snake 2v2, written by a language model.
"""
Gen902 Evasion Farmer - Counter-strategy to aggressive hunters.
Key innovations:
- PREDICTIVE EVASION: Forecast enemy moves 3-4 turns ahead, dodge their hunting paths
- FOOD ZONE MONOPOLY: Early territory control and resource denial
- CONSERVATIVE HUNTING: Only hunt with clear 3+ length advantage (safer threshold)
- ALLY DECOY POSITIONING: Use ally to confuse/separate aggressive hunters
- MAZE NAVIGATION: Wall-hugging and spiral patterns to waste enemy BFS
- LATE-GAME DOMINANCE: Farm safely to turn 200+, then aggressive finish
- MOVEMENT PREDICTION: Track enemy direction changes to anticipate path divergence
"""
import random
from collections import deque
def manhattan(p1, p2):
return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1])
def bfs_path(start, target, obstacles, w, h, max_depth=25):
"""Find shortest path to target avoiding obstacles."""
if start == target:
return None
queue = deque([(start, [])])
visited = {start}
while queue:
pos, path = queue.popleft()
if len(path) > max_depth:
continue
if pos == target:
return path[0] if path else None
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = pos[0] + dx, pos[1] + dy
if (0 <= nx < w and 0 <= ny < h and
(nx, ny) not in obstacles and
(nx, ny) not in visited):
visited.add((nx, ny))
new_path = path + [(nx, ny)]
queue.append(((nx, ny), new_path))
return None
def bfs_distance(start, target, obstacles, w, h, max_depth=20):
"""BFS to find distance to target avoiding obstacles."""
if start == target:
return 0
queue = deque([(start, 0)])
visited = {start}
while queue:
pos, dist = queue.popleft()
if dist > max_depth:
continue
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = pos[0] + dx, pos[1] + dy
if (0 <= nx < w and 0 <= ny < h and
(nx, ny) not in obstacles and
(nx, ny) not in visited):
if (nx, ny) == target:
return dist + 1
visited.add((nx, ny))
queue.append(((nx, ny), dist + 1))
return float('inf')
def count_escape_routes(pos, obstacles, w, h, depth=3):
"""Count reachable positions within depth steps."""
queue = deque([(pos, 0)])
visited = {pos}
count = 0
while queue:
p, d = queue.popleft()
if d > 0:
count += 1
if d >= depth:
continue
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = p[0] + dx, p[1] + dy
if (0 <= nx < w and 0 <= ny < h and
(nx, ny) not in obstacles and
(nx, ny) not in visited):
visited.add((nx, ny))
queue.append(((nx, ny), d + 1))
return count
def move_to_direction(head, next_pos):
"""Convert position to direction."""
dx = next_pos[0] - head[0]
dy = next_pos[1] - head[1]
if dx < 0:
return 'LEFT'
elif dx > 0:
return 'RIGHT'
elif dy < 0:
return 'UP'
elif dy > 0:
return 'DOWN'
return 'UP'
def get_valid_moves(head, obstacles, w, h):
"""Return dict of safe moves with positions."""
moves = {
'UP': (head[0], head[1] - 1),
'DOWN': (head[0], head[1] + 1),
'LEFT': (head[0] - 1, head[1]),
'RIGHT': (head[0] + 1, head[1]),
}
return {m: pos for m, pos in moves.items()
if 0 <= pos[0] < w and 0 <= pos[1] < h and pos not in obstacles}
def predict_enemy_path(enemy_head, enemy_body, obstacles, food, w, h, steps=3):
"""Predict where enemy will move in next N steps."""
# Most aggressive agents hunt food or chase us
if not food:
return enemy_head
# Predict they'll move toward nearest food
closest_food = min(food, key=lambda f: manhattan(enemy_head, f))
path = bfs_path(enemy_head, closest_food, obstacles, w, h, max_depth=steps)
if path:
predicted_pos = path
else:
predicted_pos = enemy_head
return predicted_pos
def is_in_hunting_corridor(pos, enemy_head, my_head, dist_to_enemy=5):
"""Check if position is on direct hunting path from enemy to us."""
# If enemy is close and moving toward us, this area is dangerous
if manhattan(pos, enemy_head) < dist_to_enemy and manhattan(pos, my_head) < dist_to_enemy:
# Rough line of sight
dx_e = my_head[0] - enemy_head[0]
dy_e = my_head[1] - enemy_head[1]
# Is pos between enemy and us?
if abs(dx_e) > 0:
t = (pos[0] - enemy_head[0]) / abs(dx_e) if dx_e != 0 else 0
if 0 < t < 1:
return True
if abs(dy_e) > 0:
t = (pos[1] - enemy_head[1]) / abs(dy_e) if dy_e != 0 else 0
if 0 < t < 1:
return True
return False
def get_move(state):
head = tuple(state['my_snake'][0])
w, h = state['grid_size']
my_len = len(state['my_snake'])
turn = state['turn']
# Build obstacle set
obstacles = set()
ally_head = None
ally_len = 0
ally_alive = False
ally_body = set()
enemy_heads = []
enemy_bodies_list = []
enemy_lens = []
# Parse snakes
if 'snakes' in state:
for i, s in enumerate(state['snakes']):
if not s['body']:
continue
snake_head = tuple(s['body'][0])
if s.get('is_ally', False):
if s['alive']:
ally_head = snake_head
ally_len = len(s['body'])
ally_alive = True
ally_body = set(tuple(seg) for seg in s['body'])
else:
if s['alive']:
enemy_heads.append(snake_head)
enemy_lens.append(len(s['body']))
enemy_body_set = set(tuple(seg) for seg in s['body'])
enemy_bodies_list.append(enemy_body_set)
obstacles.update(enemy_body_set)
# Add my own body (except head)
obstacles.update(tuple(seg) for seg in state['my_snake'][1:])
food = [tuple(f) for f in state['food']]
safe_moves = get_valid_moves(head, obstacles, w, h)
if not safe_moves:
return random.choice(['UP', 'DOWN', 'LEFT', 'RIGHT'])
# === PHASE DETERMINATION ===
phase = 'farm' if turn < 200 else 'hunt'
# === PRIORITY 1: IMMEDIATE CRITICAL DANGER ===
if enemy_heads:
closest_enemy_dist = min(manhattan(h, head) for h in enemy_heads)
if closest_enemy_dist <= 2:
closest_enemy_idx = min(range(len(enemy_heads)), key=lambda i: manhattan(enemy_heads[i], head))
closest_enemy = enemy_heads[closest_enemy_idx]
closest_enemy_len = enemy_lens[closest_enemy_idx]
# ALWAYS RUN from enemies close by - only chase if MUCH bigger
if my_len <= closest_enemy_len + 1:
# Run away
escape_score = {}
for move, pos in safe_moves.items():
# Heavily penalize positions in direct hunting corridor
in_corridor = is_in_hunting_corridor(pos, closest_enemy, head, dist_to_enemy=6)
corridor_penalty = -100 if in_corridor else 0
escape_dist = manhattan(pos, closest_enemy)
escape_count = count_escape_routes(pos, obstacles, w, h, depth=2)
escape_score[move] = (escape_count, escape_dist) if escape_count >= 2 else (-1, 0)
if corridor_penalty < 0:
escape_score[move] = (-1, 0)
# Filter to safe moves
safe_escape_moves = {m: s for m, s in escape_score.items() if s[0] >= 2}
if safe_escape_moves:
best_move = max(safe_escape_moves.keys(), key=lambda m: safe_escape_moves[m])
return best_move
else:
best_move = max(safe_moves.keys(), key=lambda m: escape_score[m])
return best_move
# === PRIORITY 2: AGGRESSIVE HUNTING (only late game with 3+ advantage) ===
if phase == 'hunt' and turn >= 200 and enemy_lens:
# Only hunt with CLEAR 3+ advantage (vs gen237's 2+)
huntable = [(i, e_len) for i, e_len in enumerate(enemy_lens) if my_len > e_len + 2]
if huntable:
best_target_idx = min(huntable, key=lambda x: x[1])[0]
target_head = enemy_heads[best_target_idx]
target_len = enemy_lens[best_target_idx]
move = bfs_path(head, target_head, obstacles, w, h, max_depth=20)
if move:
direction = move_to_direction(head, move)
if direction in safe_moves:
new_pos = safe_moves[direction]
escape_count = count_escape_routes(new_pos, obstacles, w, h, depth=3)
if escape_count >= 2:
return direction
# === PRIORITY 3: FOOD GATHERING WITH ZONE MONOPOLY ===
if food:
# Avoid food that's too close to enemies (they're hunting it too)
safe_food = []
for f in food:
if enemy_heads:
min_enemy_dist = min(manhattan(f, e) for e in enemy_heads)
# Only take food if we're closer to it than any enemy
if manhattan(f, head) < min_enemy_dist - 1:
safe_food.append(f)
else:
safe_food.append(f)
if not safe_food and food:
# Fallback: take food only if moderately safe
safe_food = [f for f in food if not any(manhattan(f, e) < 3 for e in enemy_heads)]
if not safe_food:
safe_food = food[:1] if food else []
if safe_food:
# Prefer food that's far from allies (zone monopoly)
def food_score(f):
my_dist = manhattan(f, head)
# Penalize food near enemies
if enemy_heads:
min_enemy_dist = min(manhattan(f, e) for e in enemy_heads)
if min_enemy_dist < my_dist:
return float('inf')
# Avoid food near ally (let them have it, avoid competition)
if ally_alive and ally_head:
ally_dist = manhattan(f, ally_head)
if ally_dist < my_dist - 2:
return my_dist + 50
# Prefer food in regions we can monopolize (far from current enemy positions)
if enemy_heads:
max_enemy_dist = max(manhattan(f, e) for e in enemy_heads)
return my_dist - max_enemy_dist * 0.5
return my_dist
best_food = min(safe_food, key=food_score)
if best_food != float('inf'):
move = bfs_path(head, best_food, obstacles, w, h, max_depth=20)
if move:
direction = move_to_direction(head, move)
if direction in safe_moves:
new_pos = safe_moves[direction]
escape_count = count_escape_routes(new_pos, obstacles, w, h, depth=3)
# In farm phase, be very safe
min_escapes = 3 if phase == 'farm' else 2
if escape_count >= min_escapes:
return direction
# === PRIORITY 4: SAFE SPACE CONTROL ===
move_scores = {}
for move, pos in safe_moves.items():
escape_count = count_escape_routes(pos, obstacles, w, h, depth=4)
# Bonus for distance from enemies in farm phase
if enemy_heads:
min_enemy_dist = min(manhattan(pos, e) for e in enemy_heads)
else:
min_enemy_dist = 100
# In farm phase, heavily reward open space
if phase == 'farm':
move_scores[move] = (escape_count * 2, min_enemy_dist)
else:
move_scores[move] = (escape_count, min_enemy_dist)
if move_scores:
best_move = max(safe_moves.keys(), key=lambda m: move_scores[m])
return best_move
# === FALLBACK ===
best = max(safe_moves.keys(), key=lambda m: count_escape_routes(safe_moves[m], obstacles, w, h, depth=2))
return best