[update] Improve heuristic.

This commit is contained in:
2026-03-27 17:24:02 +01:00
parent f5f5e46b32
commit d88a23bd5c
2 changed files with 51 additions and 9 deletions
+22 -5
View File
@@ -1,6 +1,6 @@
/* ============================================================ /* ============================================================
* Connect Four — Browser Edition * 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. * game log (localStorage), blunder mode, idle timeout.
* *
* Include this script in an HTML page that has: * Include this script in an HTML page that has:
@@ -168,6 +168,7 @@ function scanBoard(b) {
function evaluateBoard(b, aiP, huP) { function evaluateBoard(b, aiP, huP) {
let score = 0; let score = 0;
let aiThreats = 0, huThreats = 0;
// Center column bonus // Center column bonus
for (let r = 0; r < ROWS; r++) { 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 // Score a window of 4 cells by piece counts
function scoreWindow(c, r, dc, dr) { 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++) { 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++; if (v === aiP) ai++;
else if (v === huP) hu++; else if (v === huP) hu++;
else { emptyC = cc; emptyR = rr; }
} }
if (ai > 0 && hu > 0) return 0; 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 (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; if (hu === 2) return -5;
return 0; return 0;
} }
@@ -208,6 +220,10 @@ function evaluateBoard(b, aiP, huP) {
for (let c = 0; c <= COLS - 4; c++) for (let c = 0; c <= COLS - 4; c++)
score += scoreWindow(c, r, 1, -1); 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; return score;
} }
@@ -324,6 +340,7 @@ function checkGameEnd() {
if (gameState !== State.DEMO) { if (gameState !== State.DEMO) {
games = logGame(games, gameMenuMode, gameLevel, won ? w : 0, currentMoves); 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; gameState = won ? State.FINISHED_WIN : State.FINISHED_DRAW;
demoResetTimer = performance.now() / 1000; demoResetTimer = performance.now() / 1000;
+29 -4
View File
@@ -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 evaluateBoardB(const int8_t b[][ROWS], int8_t aiP, int8_t huP)
{ {
int score = 0; int score = 0;
int aiThreats = 0, huThreats = 0;
// Center column bonus
for (int r = 0; r < ROWS; r++) { for (int r = 0; r < ROWS; r++) {
if (b[3][r] == aiP) score += 3; if (b[3][r] == aiP) score += 3;
else if (b[3][r] == huP) 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 { auto sw = [&](int c, int r, int dc, int dr) -> int {
int ai = 0, hu = 0; int ai = 0, hu = 0;
int emptyC = -1, emptyR = -1;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
int8_t v = b[c + i*dc][r + i*dr]; int cc = c + i * dc, rr = r + i * dr;
if (v == aiP) ai++; else if (v == huP) hu++; 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 && hu) return 0;
if (ai == 3) return 50; if (ai == 2) return 5; if (ai == 3) {
if (hu == 3) return -50; if (hu == 2) return -5; 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; 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 < 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 < 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 = 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); 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; return score;
} }