I have made a connect four game in JS and currently have a functioning minimax algorithm. The problem I'm having is that it is very, very easy to beat, even with a large depth. This is leading me to believe that I need a better heuristic function, but I cannot come up with much better than what I already have. So, I thought I would ask if anyone has any experience, or just a good idea, for a very good heuristic to use. I would also accept improvement ideas on my current heuristic below. Thank you in advance!
EDIT: I'm going to go ahead and post my total minimax in here (3 functions total), because I have added disjoints and made my 4-in-a-row higher, but my AI is still terrible. I know my 2,3 and 4-in-a-rows work because I tested them, but I can't pin-point why I'm still having trouble even at high depths.
function getBestMove(currBoard,depth,who) {
var opp;
//Get opponent for next piece
if(who == 'a') {
opp = 'p';
} else {
opp = 'a';
}
var tBoard = new Array(rows);
for(var i=0; i<tBoard.length; i++) {
tBoard[i] = new Array(cols);
}
var moves = new Array(aiOpenCols.length);
//Drop each piece and use minimax function until depth == 0
for(var i=0; i<aiOpenCols.length; i++) {
for(var j=0; j<rows; j++) {
for(var k=0; k<cols; k++) {
tBoard[j][k] = currBoard[j][k];
}
}
tBoard = dropPiece(aiOpenCols[i],who,tBoard);
moves[i] = minimax(tBoard,(+depth - 1),opp,aiOpenCols[i]);
}
var bestAlpha = -100000; //Large negative
//Use random column if no moves are "good"
var bestMove;// = Math.floor(Math.random() * aiOpenCols.length);
//bestMove = +aiOpenCols[bestMove];
//Get largest value from moves for best move
for(var i=0; i<aiOpenCols.length; i++) {
if(+moves[i] > bestAlpha) {
bestAlpha = moves[i];
bestMove = aiOpenCols[i];
}
}
bestMove++; //Offset by 1 due to actual drop function
return bestMove;
}
function minimax(currBoard,depth,who,col) {
//Drop current piece, called from getBestMove function
currBoard = dropPiece(col,who,currBoard);
//When depth == 0 return heuristic/eval of board
if(+depth == 0) {
var ev = evalMove(currBoard);
return ev;
}
var alpha = -100000; //Large negative
var opp;
//Get opponent for next piece
if(who == 'a') {
opp = 'p';
} else {
opp = 'a';
}
//Loop through all available moves
for(var i=0; i<aiOpenCols.length; i++) {
var tBoard = new Array(rows);
for(var i=0; i<tBoard.length; i++) {
tBoard[i] = new Array(cols);
}
for(var j=0; j<rows; j++) {
for(var k=0; k<cols; k++) {
tBoard[j][k] = currBoard[j][k];
}
}
//Continue recursive minimax until depth == 0
var next = minimax(tBoard,(+depth - 1),opp,aiOpenCols[i]);
//Alpha = max(alpha, -minimax()) for negamax
alpha = Math.max(alpha, (0 - +next));
}
return alpha;
}
function evalMove(currBoard) {
//heuristic function
//AI = # of 4 streaks + # of 3 streaks + # of 2 streaks - # of 3 streaks opp - # of 2 streaks opp
var fours = checkFours(currBoard,'b');
//If win return large positive
if(fours > 0) return 100000;
var threes = checkThrees(currBoard,'b') * 1000;
var twos = checkTwos(currBoard,'b') * 10;
var oppThrees = checkThrees(currBoard,'r') * 1000;
var oppTwos = checkTwos(currBoard,'r') * 10;
var scores = threes + twos - oppThrees - oppTwos;
//If opponent wins, return large negative
var oppFours = checkFours(currBoard,'r');
if(+oppFours > 0) {
return -100000;
} else {
return scores;
}
}