From d88a23bd5ce57d0a673f1951982fd3ccd223065c Mon Sep 17 00:00:00 2001 From: Seppe De Loore Date: Fri, 27 Mar 2026 17:24:02 +0100 Subject: [PATCH] [update] Improve heuristic. --- connect_four.js | 27 ++++++++++++++++++++++----- src/game.cpp | 33 +++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/connect_four.js b/connect_four.js index 4be09ac..fdd4850 100644 --- a/connect_four.js +++ b/connect_four.js @@ -1,6 +1,6 @@ /* ============================================================ * Connect Four — Browser Edition - * A single-file game: AI (minimax + alpha-beta), demo mode, + * A single-file game: AI (minimax + alpha-beta + heuristic), demo mode, * game log (localStorage), blunder mode, idle timeout. * * Include this script in an HTML page that has: @@ -168,6 +168,7 @@ function scanBoard(b) { function evaluateBoard(b, aiP, huP) { let score = 0; + let aiThreats = 0, huThreats = 0; // Center column bonus for (let r = 0; r < ROWS; r++) { @@ -177,16 +178,27 @@ function evaluateBoard(b, aiP, huP) { // Score a window of 4 cells by piece counts function scoreWindow(c, r, dc, dr) { - let ai = 0, hu = 0; + let ai = 0, hu = 0, emptyC = -1, emptyR = -1; for (let i = 0; i < 4; i++) { - const v = b[c + i * dc][r + i * dr]; + const cc = c + i * dc; + const rr = r + i * dr; + const v = b[cc][rr]; if (v === aiP) ai++; else if (v === huP) hu++; + else { emptyC = cc; emptyR = rr; } } if (ai > 0 && hu > 0) return 0; - if (ai === 3) return 50; + if (ai === 3) { + aiThreats++; + const playable = emptyR === 0 || b[emptyC][emptyR - 1] !== 0; + return playable ? 100 : 40; + } if (ai === 2) return 5; - if (hu === 3) return -50; + if (hu === 3) { + huThreats++; + const playable = emptyR === 0 || b[emptyC][emptyR - 1] !== 0; + return playable ? -100 : -40; + } if (hu === 2) return -5; return 0; } @@ -208,6 +220,10 @@ function evaluateBoard(b, aiP, huP) { for (let c = 0; c <= COLS - 4; c++) score += scoreWindow(c, r, 1, -1); + // Fork bonus: multiple threats are disproportionately dangerous + if (aiThreats >= 2) score += 200; + if (huThreats >= 2) score -= 200; + return score; } @@ -324,6 +340,7 @@ function checkGameEnd() { if (gameState !== State.DEMO) { games = logGame(games, gameMenuMode, gameLevel, won ? w : 0, currentMoves); + console.log(`Game: ${currentMoves} → ${won ? playerName(w) + " wins" : "Draw"}`); } gameState = won ? State.FINISHED_WIN : State.FINISHED_DRAW; demoResetTimer = performance.now() / 1000; diff --git a/src/game.cpp b/src/game.cpp index 9617e0b..639f7f5 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -189,25 +189,50 @@ int8_t scanBoardB(const int8_t b[][ROWS]) int evaluateBoardB(const int8_t b[][ROWS], int8_t aiP, int8_t huP) { int score = 0; + int aiThreats = 0, huThreats = 0; + + // Center column bonus for (int r = 0; r < ROWS; r++) { if (b[3][r] == aiP) score += 3; else if (b[3][r] == huP) score -= 3; } + + // Score a window of 4 cells — playability-aware for 3-in-a-row auto sw = [&](int c, int r, int dc, int dr) -> int { int ai = 0, hu = 0; + int emptyC = -1, emptyR = -1; for (int i = 0; i < 4; i++) { - int8_t v = b[c + i*dc][r + i*dr]; - if (v == aiP) ai++; else if (v == huP) hu++; + int cc = c + i * dc, rr = r + i * dr; + int8_t v = b[cc][rr]; + if (v == aiP) ai++; + else if (v == huP) hu++; + else { emptyC = cc; emptyR = rr; } } if (ai && hu) return 0; - if (ai == 3) return 50; if (ai == 2) return 5; - if (hu == 3) return -50; if (hu == 2) return -5; + if (ai == 3) { + aiThreats++; + bool playable = (emptyR == 0) || (b[emptyC][emptyR - 1] != 0); + return playable ? 100 : 40; + } + if (ai == 2) return 5; + if (hu == 3) { + huThreats++; + bool playable = (emptyR == 0) || (b[emptyC][emptyR - 1] != 0); + return playable ? -100 : -40; + } + if (hu == 2) return -5; return 0; }; + for (int r = 0; r < 6; r++) for (int c = 0; c < 4; c++) score += sw(c,r,1,0); for (int r = 0; r < 3; r++) for (int c = 0; c < 7; c++) score += sw(c,r,0,1); for (int r = 0; r < 3; r++) for (int c = 0; c < 4; c++) score += sw(c,r,1,1); for (int r = 3; r < 6; r++) for (int c = 0; c < 4; c++) score += sw(c,r,1,-1); + + // Fork bonus: multiple simultaneous threats are disproportionately dangerous + if (aiThreats >= 2) score += 200; + if (huThreats >= 2) score -= 200; + return score; }