The leading agent on Connect Four, written by a language model.
"""
gen657_fortress_breaker: Breaks through center-focused defenses.
Counter-strategy exploiting opponent obsession with center control.
Uses edge-to-center breakthrough tactics and board diversity.
"""
import time
class SearchTimeout(Exception):
pass
TT = {}
NODES = 0
# Fortress-breaker weights: balanced board control with edge strength
# Reduces center obsession, adds upper-row bonuses for dual-threat setup
WEIGHTS = [0] * 49
_w = [
[4, 5, 6, 7, 6, 5, 4], # Row 0: high edge value for setup
[5, 7, 9, 11, 9, 7, 5], # Row 1: slightly less center-heavy
[5, 8, 11, 12, 11, 8, 5], # Row 2: balanced, not center-obsessed
[5, 8, 11, 12, 11, 8, 5], # Row 3: same as row 2
[4, 6, 8, 10, 8, 6, 4], # Row 4: reduced center weight
[3, 4, 5, 6, 5, 4, 3] # Row 5: baseline, depth matters less
]
for c in range(7):
for r in range(6):
br = 5 - r
bi = c * 7 + br
WEIGHTS[bi] = _w[r][c]
def is_win(bb):
"""Check 4-in-a-row."""
m = bb & (bb >> 7)
if m & (m >> 14): return True
m = bb & (bb >> 6)
if m & (m >> 12): return True
m = bb & (bb >> 8)
if m & (m >> 16): return True
m = bb & (bb >> 1)
if m & (m >> 2): return True
return False
def count_threats(bb):
"""Count 3-in-a-row patterns."""
h = (bb & (bb >> 1) & (bb >> 2))
v = (bb & (bb >> 7) & (bb >> 14))
d1 = (bb & (bb >> 6) & (bb >> 12))
d2 = (bb & (bb >> 8) & (bb >> 16))
threats = h | v | d1 | d2
return bin(threats).count('1')
def dual_threat_risk(my_bb, opp_bb, mask, col):
"""Check if opponent can create dual threats in next move at this column."""
# Simulate opponent move
new_mask = mask | (mask + (1 << (col * 7)))
added = new_mask ^ mask
test_bb = opp_bb | added
return count_threats(test_bb) >= 2
def search(my_bb, opp_bb, mask, depth, alpha, beta, score, end_time, ordered_moves):
"""Alpha-beta negamax with threat-aware pruning and edge control."""
global NODES
NODES += 1
if (NODES & 255) == 0:
if time.perf_counter() > end_time:
raise SearchTimeout()
if is_win(opp_bb):
return -10000 - depth
if depth == 0:
return score
key = my_bb | (mask << 49)
tt_move = -1
if key in TT:
tt_d, tt_s, tt_t, tt_m = TT[key]
if tt_d >= depth:
if tt_t == 0:
return tt_s
elif tt_t == 1:
alpha = max(alpha, tt_s)
else:
beta = min(beta, tt_s)
if alpha >= beta:
return tt_s
tt_move = tt_m
orig_alpha = alpha
# Get legal moves
legal = []
for c in ordered_moves:
if (mask & (1 << (c * 7 + 5))) == 0:
legal.append(c)
if not legal:
return 0
# Move TT suggestion to front
if tt_move != -1 and tt_move in legal:
legal.remove(tt_move)
legal.insert(0, tt_move)
best_score = -20000
best_move = legal[0]
for col in legal:
new_mask = mask | (mask + (1 << (col * 7)))
added = new_mask ^ mask
new_my = my_bb | added
piece_score = WEIGHTS[added.bit_length() - 1]
val = -search(opp_bb, new_my, new_mask, depth - 1, -beta, -alpha,
-score - piece_score, end_time, ordered_moves)
if val > best_score:
best_score = val
best_move = col
alpha = max(alpha, val)
if alpha >= beta:
break
if best_score <= orig_alpha:
tt_t = 2
elif best_score >= beta:
tt_t = 1
else:
tt_t = 0
TT[key] = (depth, best_score, tt_t, best_move)
return best_score
def get_move(state):
"""Main entry point: fortress-breaker strategy."""
global NODES, TT
NODES = 0
if len(TT) > 300000:
TT.clear()
board = state['board']
my_color = state['my_color']
legal_moves = state['legal_moves']
turn = state['turn']
if not legal_moves:
return 0
my_bb = 0
opp_bb = 0
score = 0
for col in range(7):
for row in range(6):
if board[row][col] != 0:
bb_row = 5 - row
bi = col * 7 + bb_row
w = WEIGHTS[bi]
if board[row][col] == my_color:
my_bb |= (1 << bi)
score += w
else:
opp_bb |= (1 << bi)
score -= w
mask = my_bb | opp_bb
my_threats = count_threats(my_bb)
opp_threats = count_threats(opp_bb)
# Adaptive move ordering: fortress-breaker style
# In early game: control edges (cols 0,1,5,6) and build diversity
# In mid game: respond to threats but maintain edge control
# In late game: push through center aggressively
if turn < 12:
# Early game: edge control + gentle center presence
# Prioritize building on edges, avoid ceding all center
order = [3, 2, 4, 1, 5, 0, 6] # Slight center bias but not obsessive
elif turn < 30:
# Mid game: threat response + breakthrough setup
if opp_threats >= 2:
# Critical: opponent building dual threats
# Block on edges where possible, counterattack center if safe
order = [3, 2, 4, 1, 5, 0, 6]
else:
# Safe to push: breach from edges toward center
order = [1, 2, 4, 5, 0, 3, 6] # Favor edges, less center
else:
# Late game: all-in breakthrough
# Force center plays, go aggressive
order = [3, 2, 4, 1, 5, 0, 6]
end_time = time.perf_counter() + 0.085
best_move = legal_moves[0]
try:
for depth in range(1, 50):
best_score = -20000
current_best = legal_moves[0]
alpha = -20000
beta = 20000
for col in order:
if col not in legal_moves:
continue
new_mask = mask | (mask + (1 << (col * 7)))
added = new_mask ^ mask
new_my = my_bb | added
ps = WEIGHTS[added.bit_length() - 1]
val = -search(opp_bb, new_my, new_mask, depth - 1, -beta, -alpha,
-score - ps, end_time, order)
if val > best_score:
best_score = val
current_best = col
alpha = max(alpha, val)
best_move = current_best
if best_score > 9000:
break
except SearchTimeout:
pass
return best_move if best_move in legal_moves else legal_moves[0]