// executes time-limited alpha/beta pruning depth-limited MiniMax search public static int PVABMiniMax(int depth, int alpha, int beta) { // initialize principal variant flag bool pvfound = false; // update evaluated node count AI.nodes++; // check to see if we have time left if (AI.timer.ElapsedMilliseconds > TIME_PER_MOVE) { AI.TIME_EXPIRED = true; return(Score.TIME_EXPIRED_SCORE); } // initialize variables List <ChessMove> moves = new List <ChessMove>(0); ChessMove bestAction = new ChessMove(); int value = SMALL_NUM, bestValue = SMALL_NUM, color = -1; if (depth % 2 != 0) { bestValue = LARGE_NUM; } // check for draws if (Score.IsDrawByHundredMoves()) // 100 move draw; update history table { return(Score.DRAW_SCORE); } if (Score.IsDrawByRepetition()) // repitition draw { return(Score.DRAW_SCORE); } if (Score.IsDrawByInsufficientMaterial()) // insufficient material draw { return(Score.DRAW_SCORE); } // set color of current player if (depth % 2 == 0) { color = AI.board.myColor; } else { color = AI.board.oppColor; } // if depth limit has been reached, return quiescence search value for this node if (depth >= MAX_DEPTH) { // set follow pv flag FOLLOW_PV = false; // go one node deeper if player to move is currently in check if (MoveGen.IsKingAttacked(color)) { MAX_DEPTH++; value = PVABMiniMax(depth, alpha, beta); MAX_DEPTH--; return(value); } // return quiescence search value return(QSearch(alpha, beta, color)); } // if allowed, try a null move to get an early prune if (NULLMOVE_ALLOWED && !FOLLOW_PV) { // if game could be in zugzwang for player to move or player to move is in check, do not attempt null move if (((color == ChessBoard.WHITE && Score.GetMaterialScore(color) > Score.NULLMOVE_LIMIT_SCORE) || (color == ChessBoard.BLACK && Score.GetMaterialScore(color) > Score.NULLMOVE_LIMIT_SCORE)) && !MoveGen.IsKingAttacked(color)) { // set allow null move flag NULLMOVE_ALLOWED = false; // if we are next player to move, run zero-window search for alpha; else use beta if (color == AI.board.oppColor) { value = PVABMiniMax(depth + NULLMOVE_DEPTH_GAP, alpha, alpha + 1); } else if (color == AI.board.myColor) { value = PVABMiniMax(depth + NULLMOVE_DEPTH_GAP, beta, beta + 1); } // reset allow null move flag NULLMOVE_ALLOWED = true; // if alpha/beta was not improved, prune if (color == AI.board.myColor && value >= beta) { return(value); } else if (color == AI.board.oppColor && value <= alpha) { return(value); } } } NULLMOVE_ALLOWED = true; // generate moves for player based on depth moves = MoveGen.GenerateMoves(color, false); // iterate through generated moves for (int i = 0; i < moves.Count; i++) { // order remaining moves according to history table AI.history.OrderMoves(ref moves, color, i); // make current move moves[i].DoMove(MAKE); // if move is illegal, unmake move; continue otherwise if (!MoveGen.IsKingAttacked(color)) { // if principal variant flag is set, run zero-window search; else, run normal search if (pvfound) { // if we are next player to move, use alpha for zero-window search; else, use beta if (depth % 2 != 0) { value = PVABMiniMax(depth + 1, alpha, alpha + 1); } else if (depth % 2 == 0) { value = PVABMiniMax(depth + 1, beta, beta + 1); } // if value returned falls within alpha/beta window, run normal search with normal alpha/beta window if (value > alpha && value < beta) { value = PVABMiniMax(depth + 1, alpha, beta); } } else { value = PVABMiniMax(depth + 1, alpha, beta); } // unmake current move moves[i].DoMove(UNMAKE); // check to see if search found checkmate if (value == Score.CHECKMATE_WIN_SCORE && depth == 0) { // update PV PV.Clear(); PV.Add(moves[i]); // return checkmate score return(Score.CHECKMATE_WIN_SCORE); } // check to see if time has expired if (value == Score.TIME_EXPIRED_SCORE) { return(Score.TIME_EXPIRED_SCORE); } // evaluate minimax search value if (depth % 2 == 0) // maximize { if (value >= beta) // fail-high, prune; update history table { if (color == ChessBoard.WHITE) { AI.history.whiteValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } else if (color == ChessBoard.BLACK) { AI.history.blackValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } return(value + 1); } if (value > alpha) // set new alpha, set principal variant flag { alpha = value; pvfound = true; } if (value >= bestValue) // set new best action, best value { // if alpha improves at root, update PV if (depth == 0 && value > bestValue) { PV.Clear(); PV.Add(moves[i]); } else if (depth == 0 && value == bestValue) { PV.Add(moves[i]); } bestAction = moves[i]; bestValue = value; } } else if (depth % 2 == 1) // minimize { if (value <= alpha) // fail-low, prune; update history table { if (color == ChessBoard.WHITE) { AI.history.whiteValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } else if (color == ChessBoard.BLACK) { AI.history.blackValue[moves[i].GetFromSq()][moves[i].GetToSq()] += (MAX_DEPTH - depth) * (MAX_DEPTH - depth); } return(value - 1); } if (value < beta) // set new beta, set principal variant flag { beta = value; pvfound = true; } if (value < bestValue) // set new best action, best value { bestAction = moves[i]; bestValue = value; } } } else { moves[i].DoMove(UNMAKE); } } // no legal moves for this state if (value == SMALL_NUM) { // if in check, checkmate; else stalemate if (MoveGen.IsKingAttacked(color)) { if (color == AI.board.myColor) // we are in checkmate { return(Score.CHECKMATE_LOSE_SCORE); } else if (color == AI.board.oppColor) // opp is in checkmate { return(Score.CHECKMATE_WIN_SCORE); } } else { return(Score.DRAW_SCORE); } } // return best value from current depth; update history table if (color == ChessBoard.WHITE) { AI.history.whiteValue[bestAction.GetFromSq()][bestAction.GetToSq()] += ((MAX_DEPTH - depth) * (MAX_DEPTH - depth) + 1); } else if (color == ChessBoard.BLACK) { AI.history.blackValue[bestAction.GetFromSq()][bestAction.GetToSq()] += ((MAX_DEPTH - depth) * (MAX_DEPTH - depth) + 1); } return(bestValue); }