[fix] Add heuristic evaluation, fork detection, and Phase 1 win/block split to AI.
Minimax leaf nodes now return a positional score instead of 0, using playable-threat detection (±100), non-playable threats (±40), fork bonus (±200), two-in-a-row (±5), and center control (±3). Phase 1 is split into two passes so the AI never blocks when it can win. Game sequence is now auto-logged to the browser console on game end. Applied to all three implementations (C++, JS, Python). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-5
@@ -1,4 +1,4 @@
|
||||
"""Connect Four terminal game with AI, using Rich for display."""
|
||||
"""Connect Four terminal game with AI (minimax + alpha-beta + heuristic), using Rich for display."""
|
||||
|
||||
import os
|
||||
import queue
|
||||
@@ -271,6 +271,8 @@ def log_game(games: list[dict], game_menu_mode: int, level: int, winner: int, mo
|
||||
|
||||
def evaluate_board(board: list[list[int]], ai_p: int, hu_p: int) -> int:
|
||||
score = 0
|
||||
ai_threats = 0
|
||||
hu_threats = 0
|
||||
|
||||
# Center column bonus
|
||||
for r in range(ROWS):
|
||||
@@ -281,21 +283,30 @@ def evaluate_board(board: list[list[int]], ai_p: int, hu_p: int) -> int:
|
||||
|
||||
# Score a window of 4 cells by piece counts
|
||||
def score_window(c: int, r: int, dc: int, dr: int) -> int:
|
||||
ai, hu = 0, 0
|
||||
nonlocal ai_threats, hu_threats
|
||||
ai, hu, empty_c, empty_r = 0, 0, -1, -1
|
||||
for i in range(4):
|
||||
v = board[c + i * dc][r + i * dr]
|
||||
cc = c + i * dc
|
||||
rr = r + i * dr
|
||||
v = board[cc][rr]
|
||||
if v == ai_p:
|
||||
ai += 1
|
||||
elif v == hu_p:
|
||||
hu += 1
|
||||
else:
|
||||
empty_c, empty_r = cc, rr
|
||||
if ai > 0 and hu > 0:
|
||||
return 0
|
||||
if ai == 3:
|
||||
return 50
|
||||
ai_threats += 1
|
||||
playable = empty_r == 0 or board[empty_c][empty_r - 1] != 0
|
||||
return 100 if playable else 40
|
||||
if ai == 2:
|
||||
return 5
|
||||
if hu == 3:
|
||||
return -50
|
||||
hu_threats += 1
|
||||
playable = empty_r == 0 or board[empty_c][empty_r - 1] != 0
|
||||
return -100 if playable else -40
|
||||
if hu == 2:
|
||||
return -5
|
||||
return 0
|
||||
@@ -317,6 +328,12 @@ def evaluate_board(board: list[list[int]], ai_p: int, hu_p: int) -> int:
|
||||
for c in range(COLS - 3):
|
||||
score += score_window(c, r, 1, -1)
|
||||
|
||||
# Fork bonus: multiple threats are disproportionately dangerous
|
||||
if ai_threats >= 2:
|
||||
score += 200
|
||||
if hu_threats >= 2:
|
||||
score -= 200
|
||||
|
||||
return score
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user