Input
Click or press 1-7
Rules
Validate, drop, win/draw check
Search
Minimax with optional pruning
Render
Animate, explain, record replay
01
Board Model
The board is a 6 by 7 array where 0 is empty, 1 is red, and
2 is yellow. Drops scan from the bottom row upward so every piece lands in the lowest open cell.
createBoard()getLegalMoves()dropPiece()
Show key code
function createBoard() {
return Array.from({ length: ROWS }, () => Array(COLS).fill(EMPTY));
}
function dropPiece(state, col, player) {
const row = getOpenRow(state, col);
if (row === -1) return null;
state[row][col] = player;
return row;
}
02
Game Rules
Every move checks legal columns, full columns, horizontal wins, vertical wins, both diagonal directions,
and natural draws. The same rule functions are used by the browser and the headless test runner.
checkWin()isDraw()getWinner()
Show key code
function checkWin(state, player) {
const directions = [[0, 1], [1, 0], [1, 1], [-1, 1]];
// Scan every cell and count four matching pieces in each direction.
}
function isDraw(state) {
return getLegalMoves(state).length === 0
&& !checkWin(state, HUMAN).won
&& !checkWin(state, AI).won;
}
03
Minimax
Yellow is the maximizing player and red is the minimizing player. Search stops at terminal positions or the
selected depth limit, then backs scores up through the game tree.
minimax()chooseComputerMove()
Show key code
function minimax(state, depth, alpha, beta, maximizingPlayer, useAlphaBeta, stats) {
const legalMoves = orderMoves(getLegalMoves(state));
if (terminal) return { column: null, score: getTerminalScore(...) };
if (depth <= 0) return { column: null, score: evaluateBoard(state) };
// MAX chooses the highest child score.
// MIN chooses the lowest child score.
}
04
Alpha-Beta
Alpha-beta keeps the same minimax choice while skipping branches that cannot change the result. Moves are
searched from the center outward because stronger early moves usually improve pruning.
orderMoves()alphabeta
Show key code
function orderMoves(moves) {
const center = Math.floor(COLS / 2);
return moves.slice().sort((a, b) => Math.abs(a - center) - Math.abs(b - center));
}
if (useAlphaBeta) {
alpha = Math.max(alpha, value);
if (alpha >= beta) break;
}
05
Heuristic
When the depth limit is reached, the evaluation scores four-cell windows, center control, immediate wins,
threats, and defensive blocks. Terminal scores prefer quicker wins and delay losses.
evaluateBoard()scoreWindow()
Show key code
function scoreWindow(windowCells) {
if (aiCount === 4) return 100000;
if (humanCount === 4) return -100000;
if (aiCount === 3 && emptyCount === 1) return 100;
if (humanCount === 3 && emptyCount === 1) return -120;
}
06
Evidence
Each AI move stores depth, nodes searched, time, score, and a short reason. Replay uses the move history,
while the Experiment Lab and Node runner reuse the same search code for repeatable checks.
recordMove()runHeadlessSuite()
Show key code
function createReplayController(options = {}) {
let moveHistory = [];
function recordMove(row, col, player, metadata = {}) {
moveHistory.push({ row, col, player, ...metadata });
}
}
function runHeadlessSuite(options = {}) {
// Reuses the same rules and search functions outside the browser.
}