"""Deterministic standard-Gomoku computer: exact-five windows, broken threats, forced replies and a bounded tactical search. No network or learned weights. BOARD replaces the position; both protocol colours are relative to this brain. """ import sys import time N, board, me = 15, {}, 1 D = ((1, 0), (0, 1), (1, 1), (1, -1)) WIN = 10_000_000 turn_ms = 1000 def candidates(): if not board: return [(N // 2, N // 2)] near = {(x + a, y + b) for x, y in board for a in range(-2, 3) for b in range(-2, 3)} return sorted(p for p in near if 0 <= p[0] < N and 0 <= p[1] < N and p not in board) def pattern(p, colour): """Score exact-five windows through a hypothetical stone, including gaps. A window with our stone just beyond it cannot be an exact five. Distinct winning points matter: two windows sharing one gap are only one threat. """ x, y = p wins, threes, twos = set(), set(), set() for dx, dy in D: cells = [(x + i * dx, y + i * dy) for i in range(-5, 6)] row = [colour if i == 5 else board.get(q, 0) if 0 <= q[0] < N and 0 <= q[1] < N else -1 for i, q in enumerate(cells)] for start in range(1, 6): if row[start - 1] == colour or row[start + 5] == colour: continue line = row[start:start + 5] if any(v not in (0, colour) for v in line): continue empty = tuple(cells[j] for j in range(start, start + 5) if row[j] == 0) if not empty: return WIN if len(empty) == 1: wins.add(empty[0]) elif len(empty) == 2: threes.add(empty) elif len(empty) == 3: twos.add(empty) return ((900_000 if len(wins) >= 2 else 0) + 90_000 * len(wins) + 2_400 * len(threes) + 90 * len(twos)) def centre(p): return -abs(p[0] - N // 2) - abs(p[1] - N // 2) def ranked(colour): rows = [(p, pattern(p, colour), pattern(p, 3 - colour)) for p in candidates()] return sorted(rows, key=lambda r: (r[1] * 1.05 + r[2], centre(r[0]), -r[0][1], -r[0][0]), reverse=True) class Budget(Exception): pass def move(): if not board: return N // 2, N // 2 deadline = time.monotonic() + min(0.65, max(0.015, turn_ms / 1000 * 0.65)) root = ranked(me) for p, attack, _ in root: if attack == WIN: return p blocks = [p for p, _, defence in root if defence == WIN] if blocks: return blocks[0] for p, attack, _ in root: if attack >= 900_000: return p def search(colour, depth, alpha, beta, extensions=3): if time.monotonic() >= deadline: raise Budget rows = ranked(colour) if not rows: return 0 if any(a == WIN for _, a, _ in rows): return WIN + depth forced = [r for r in rows if r[2] == WIN] if len(forced) > 1: return -WIN - depth if not depth and (not forced or not extensions): own = sorted((r[1] for r in rows), reverse=True) other = sorted((r[2] for r in rows), reverse=True) return own[0] * 1.1 + sum(own[1:3]) * 0.15 - other[0] - sum(other[1:3]) * 0.15 best = -2 * WIN for p, _, _ in forced or rows[:4]: board[p] = colour try: value = -search(3 - colour, max(0, depth - 1), -beta, -alpha, extensions - int(depth == 0)) finally: del board[p] best = max(best, value) alpha = max(alpha, value) if alpha >= beta: break return best best, best_value = root[0][0], -3 * WIN # Complete a candidate before adopting it; the static fallback is always legal. for p, attack, defence in root[:8]: board[p] = me try: value = -search(3 - me, 2, -2 * WIN, -best_value) value += (attack * 1.05 + defence) * 0.01 except Budget: break finally: del board[p] if value > best_value: best, best_value = p, value return best def play(): x, y = move() board[(x, y)] = me print(f'{x},{y}', flush=True) def main(): global N, turn_ms for raw in sys.stdin: cmd, _, arg = raw.strip().partition(' ') cmd = cmd.upper() if cmd == 'START': N = int(arg) board.clear() print('OK', flush=True) elif cmd == 'RESTART': board.clear() print('OK', flush=True) elif cmd == 'BEGIN': play() elif cmd == 'TURN': x, y = map(int, arg.split(',')) board[(x, y)] = 2 play() elif cmd == 'BOARD': board.clear() for line in sys.stdin: if line.strip().upper() == 'DONE': break x, y, colour = map(int, line.split(',')) board[(x, y)] = colour play() elif cmd == 'INFO': key, _, value = arg.partition(' ') if key == 'timeout_turn': turn_ms = int(value) elif cmd == 'ABOUT': print('name="threats", version="2", author="shoal-games"', flush=True) elif cmd == 'END': break else: print('UNKNOWN', flush=True) if __name__ == '__main__': main()