int NegamaxAlphaBetaSearcher::SearchBestPlay(const GameState& state, int depth) { std::vector<int> bestCell; int bestValue = -INFINITY; int bestPos = 0; #ifdef _DEBUG _searcherCounter = 0; #endif ResetTranspositionTable(); GameState tryState = state; int player_id = state.GetCurrentPlayer(); std::vector<MOVES_LIST> moves; int mc = tryState.FindMoves(player_id, moves); if(mc == 0) //遇到无路可走的情况,比如被对方逼着走禁手,可放弃一步 { return -1; } SortMoves(moves); for(int i = 0; i < mc; i++) { tryState.DoPutChess(moves[i].cell, player_id); int value = NegaMax(tryState, depth - 1, -INFINITY, INFINITY, player_id); tryState.UndoPutChess(moves[i].cell); if(value > bestValue) { bestValue = value; bestCell.clear(); bestCell.push_back(moves[i].cell); } else if(value == bestValue) { bestCell.push_back(moves[i].cell); } } if(bestCell.size() > 0) bestPos = rand() % bestCell.size(); #ifdef _DEBUG std::cout << "NegamaxSearcher " << _searcherCounter << " (with Alpha-Beta)" << std::endl; #endif return bestCell[bestPos]; }
int Search (int ply, int depth, int alpha, int beta, int nodetype) /************************************************************************** * * The basic algorithm for this search routine came from Anthony * Marsland. It is a PVS (Principal Variation Search) algorithm. * The fail-soft alpha-beta technique is also used for improved * pruning. * **************************************************************************/ { int best, score, nullscore, savealpha; int side, xside; int rc, t0, t1, firstmove; int fcut, fdel, donull, savenode, nullthreatdone, extend; leaf *p, *pbest; int g0, g1; int upperbound; /* Check if this position is a known draw */ if (EvaluateDraw ()) return (DRAWSCORE); if (GameCnt >= Game50+3 && Repeat()) { RepeatCnt++; return (DRAWSCORE); } side = board.side; xside = 1^side; donull = true; /************************************************************************* * * Perform some basic search extensions. * 1. One reply extensions. * 2. If in check, extend (maximum of Idepth-1). * 3. If there is a threat to the King, extend (not beyond 2*Idepth) * 4. If recapture to same square and not beyond Idepth+2 * 5. If pawn move to 7th rank at the leaf node, extend. * *************************************************************************/ extend = false; InChk[ply] = SqAtakd (board.king[side], xside); if (InChk[ply]) { TreePtr[ply+1] = TreePtr[ply]; GenCheckEscapes (ply); if (TreePtr[ply] == TreePtr[ply+1]) return (-MATE+ply-2); if (TreePtr[ply]+1 == TreePtr[ply+1]) { depth += DEPTH; extend = true; OneRepCnt++; } } /* We've already found a mate at the next ply. If we aren't being mated by a shorter line, so just return the current material value. */ if (rootscore + ply >= MATE) return (MATERIAL); g0 = Game[GameCnt].move; g1 = GameCnt > 0 ? Game[GameCnt-1].move : 0; t0 = TOSQ(g0); t1 = TOSQ(g1); ChkCnt[ply+1] = ChkCnt[ply]; ThrtCnt[ply+1] = ThrtCnt[ply]; KingThrt[white][ply] = MateScan (white); KingThrt[black][ply] = MateScan (black); if (InChk[ply] && /* ChkCnt[ply] < Idepth-1*/ ply <= 2*Idepth/DEPTH) { ChkExtCnt++; ChkCnt[ply+1]++; depth += DEPTH; extend = true; } else if (!KingThrt[side][ply-1] && KingThrt[side][ply] && ply <= 2*Idepth/DEPTH) { KingExtCnt++; extend = true; depth += DEPTH; extend = true; donull = false; } /* Promotion extension */ else if (g0 & PROMOTION) { PawnExtCnt++; depth += DEPTH; extend = true; } /* Recapture extension */ else if ((g0 & CAPTURE) && (board.material[computer] - board.material[1^computer] == RootMaterial)) { RcpExtCnt++; depth += DEPTH; extend = true; } /* 6th or 7th rank extension */ else if (depth <= DEPTH && cboard[t0] == pawn && (RANK(t0) == rank7[xside] || RANK(t0) == rank6[xside])) { PawnExtCnt++; depth += DEPTH; extend = true; } /**************************************************************************** * * The following extension is to handle cases when the opposing side is * delaying the mate by useless interposing moves. * ****************************************************************************/ if (ply > 2 && InChk[ply-1] && cboard[t0] != king && t0 != t1 && !SqAtakd (t0, xside)) { HorzExtCnt++; depth += DEPTH; extend = true; } /*************************************************************************** * * This is a new code to perform search reductiion. We introduce some * form of selectivity here. * **************************************************************************/ if (depth <= 0) return (Quiesce (ply, alpha, beta)); /**************************************************************************** * * Probe the transposition table for a score and a move. * If the score is an upperbound, then we can use it to improve the value * of beta. If a lowerbound, we improve alpha. If it is an exact score, * if we now get a cut-off due to the new alpha/beta, return the score. * ***************************************************************************/ Hashmv[ply] = 0; upperbound = INFINITY; if (flags & USEHASH) { rc = TTGet (side, depth, ply, alpha, beta, &score, &g1); if (rc) { Hashmv[ply] = g1 & MOVEMASK; switch (rc) { case POORDRAFT : break; case EXACTSCORE : return (score); case UPPERBOUND : beta = MIN (beta, score); upperbound = score; donull = false; break; case LOWERBOUND : /*alpha = MAX (alpha, score);*/ alpha = score; break; case QUIESCENT : Hashmv[ply] = 0; break; default : break; } if (alpha >= beta) return (score); } } /***************************************************************************** * * Perform the null move here. There are certain cases when null move * is not done. * 1. When the previous move is a null move. * 2. At the frontier (depth == 1) * 3. At a PV node. * 4. If side to move is in check. * 5. If the material score + pawn value is still below beta. * 6. If we are being mated at next ply. * 7. If hash table indicate the real score is below beta (UPPERBOUND). * 8. If side to move has less than or equal to a bishop in value. * 9. If Idepth <= 3. This allows us to find mate-in 2 problems quickly. * 10. We are looking for a null threat. * *****************************************************************************/ if (ply > 4 && InChk[ply-2] && InChk[ply-4]) donull = false; if (flags & USENULL && g0 != NULLMOVE && depth > DEPTH && nodetype != PV && !InChk[ply] && MATERIAL+ValueP > beta && beta > -MATE+ply && donull && board.pmaterial[side] > ValueB && !threatply) { TreePtr[ply+1] = TreePtr[ply]; MakeNullMove (side); nullscore = -Search (ply+1, depth-DEPTH-R, -beta, -beta+1, nodetype); UnmakeNullMove (xside); if (nullscore >= beta) { NullCutCnt++; return (nullscore); } if ( depth-DEPTH-R >= 1 && MATERIAL > beta && nullscore <= -MATE+256) { depth += DEPTH; extend = true; } } if (InChk[ply] && TreePtr[ply]+1 < TreePtr[ply+1]) SortMoves (ply); pickphase[ply] = PICKHASH; GETNEXTMOVE; /************************************************************************* * * Razoring + Futility. * At depth 3, if there is no extensions and we are really bad, decrease * the search depth by 1. * At depth 2, if there is no extensions and we are quite bad, then we * prune all non checking moves and capturing moves that don't bring us up * back to alpha. * Caveat: Skip all this if we are in the ending. * *************************************************************************/ fcut = false; fdel = MAX (ValueQ, maxposnscore[side]); if (!extend && nodetype != PV && depth == 3*DEPTH && FUTSCORE <= alpha) { depth = 2*DEPTH; RazrCutCnt++; } fdel = MAX (ValueR, maxposnscore[side]); fcut = (!extend && nodetype != PV && depth == 2*DEPTH && FUTSCORE <= alpha); if (!fcut) { fdel = MAX (3*ValueP, maxposnscore[side]); fcut = (nodetype != PV && depth == DEPTH && FUTSCORE <= alpha); } MakeMove (side, &p->move); NodeCnt++; g0 = g1 = 0; while ((g0 = SqAtakd (board.king[side], xside)) > 0 || (fcut && FUTSCORE < alpha && !SqAtakd (board.king[xside], side) && !MateScan (xside))) { if (g0 == 0) g1++; UnmakeMove (xside, &p->move); if (GETNEXTMOVE == false) return (g1 ? Evaluate(alpha,beta) : DRAWSCORE); MakeMove (side, &p->move); NodeCnt++; } firstmove = true; pbest = p; best = -INFINITY; savealpha = alpha; nullthreatdone = false; nullscore = INFINITY; savenode = nodetype; if (nodetype != PV) nodetype = (nodetype == CUT) ? ALL : CUT; while (1) { /* We have already made the move before the loop. */ if (firstmove) { firstmove = false; score = -Search (ply+1, depth-DEPTH, -beta, -alpha, nodetype); } /* Zero window search for rest of moves */ else { if (GETNEXTMOVE == false) break; #ifdef THREATEXT /**************************************************************************** * * This section needs to be explained. We are doing a null threat search * and the previous ply was the null move. Inhibit any move which captures * the fail-high moving piece. Also inhibit any move by the piece which is * captured by the fail-high move. Both these moves cannot be executed in * the actual threat, so..... * Also 3 plies later, inhibit moves out of the target square of the PV/fail * high move as this is also not possible. * ****************************************************************************/ if (threatply+1 == ply) { if ((TOSQ(p->move) == FROMSQ(threatmv)) || (FROMSQ(p->move) == TOSQ(threatmv))) continue; } if (threatply && threatply+3 == ply && FROMSQ(p->move)==TOSQ(threatmv)) continue; #endif MakeMove (side, &p->move); NodeCnt++; if (SqAtakd (board.king[side], xside)) { UnmakeMove (xside, &p->move); continue; } /***************************************************************************** * * Futility pruning. The idea is that at the frontier node (depth == 1), * if the side on the move is materially bad, then if the move doesn't win * back material or the move isn't a check or doesn't threatened the king, * then there is no point in searching this move. So skip it. * Caveat: However if the node is a PV, we skip this test. * *****************************************************************************/ if (fcut && FUTSCORE <= alpha && !SqAtakd (board.king[xside], side) && !MateScan (xside)) { UnmakeMove (xside, &p->move); FutlCutCnt++; NodeCnt--; continue; } NodeCnt++; if (nodetype == PV) nodetype = CUT; alpha = MAX (best, alpha); /* fail-soft condition */ score = -Search (ply+1, depth-DEPTH, -alpha-1, -alpha, nodetype); if (score > best) { if (savenode == PV) nodetype = PV; if (alpha < score && score < beta) { score = -Search (ply+1, depth-DEPTH, -beta, -score, nodetype); } if (nodetype == PV && score <= alpha && Game[GameCnt+1].move == NULLMOVE) { score = -Search (ply+1, depth-DEPTH, -alpha, INFINITY, nodetype); } } } UnmakeMove (xside, &p->move); /* Perform threat extensions code */ #ifdef THREATEXT if ((score >= beta || nodetype == PV) && !InChk[ply] && g0 != NULLMOVE && !threatply && depth == 4 && ThrtCnt[ply] < 1) { if (!nullthreatdone) { threatply = ply; threatmv = p->move; MakeNullMove (side); nullscore = -Search(ply+1, depth-1-R, -alpha+THREATMARGIN, -alpha+THREATMARGIN+1, nodetype); UnmakeNullMove (xside); nullthreatdone = true; threatply = 0; } if (nullscore <= alpha-THREATMARGIN) { ThrtExtCnt++; ThrtCnt[ply+1]++; MakeMove (side, &p->move); score = -Search (ply+1, depth, -beta, -alpha, nodetype); UnmakeMove (xside, &p->move); ThrtCnt[ply+1]--; } } #endif if (score > best) { best = score; pbest = p; if (best >= beta) goto done; } if (flags & TIMEOUT) { best = (ply & 1 ? rootscore : -rootscore); return (best); } if (SearchDepth == 0 && (NodeCnt & TIMECHECK) == 0) { GetElapsed (); if ((et >= SearchTime && (rootscore == -INFINITY-1 || ply1score > lastrootscore - 25 || flags & SOLVE)) || et >= maxtime) SET (flags, TIMEOUT); } /* The following line should be explained as I occasionally forget too :) */ /* This code means that if at this ply, a mating move has been found, */ /* then we can skip the rest of the moves! */ if (MATE+1 == best+ply) goto done; } /***************************************************************************** * * Out of main search loop. * *****************************************************************************/ done: /* if (upperbound < best) printf ("Inconsistencies %d %d\n", upperbound, best); */ /* Save the best move inside the transposition table */ if (flags & USEHASH) TTPut (side, depth, ply, savealpha, beta, best, pbest->move); /* Update history */ if (best > savealpha) history[side][pbest->move & 0x0FFF] += HISTSCORE(depth/DEPTH); /* Don't store captures as killers as they are tried before killers */ if (!(pbest->move & (CAPTURE | PROMOTION)) && best > savealpha) { if (killer1[ply] == 0) killer1[ply] = pbest->move & MOVEMASK; else if ((pbest->move & MOVEMASK) != killer1[ply]) killer2[ply] = pbest->move & MOVEMASK; } return (best); }
static int AlphaBeta(BOARD *board, unsigned int depth, int alpha, int beta, int root, CONTROL *control, char skip_null, MOVE killers[][2]) { int nmoves, nlegal = 0; MOVE moves[MAXMOVES]; MOVE best_move = 0; char str_mov[MVLEN]; int val = ERRORVALUE; char hash_flag = HASH_ALPHA; int reduce = 0, LMR = 0; if(depth){ if(root > 0){ val = GetHashEval(&hash_table, board->zobrist_key, depth, alpha, beta); if(val != ERRORVALUE) return val; } if(depth > 2 && !InCheck(board, 0)){ if(!skip_null && board->piece_material[board->white_to_move] != 0){ MOVE null_mv = NULL_MOVE; MakeMove(board, &null_mv); val = -AlphaBeta(board, depth-3, -beta, -beta+1, root+1, control, 1, killers); Takeback(board, null_mv); if(val >= beta) return beta; } reduce = 1; /*Try Late Move reductions.*/ } nmoves = MoveGen(board, moves, 1); int good = SortMoves(board, moves, nmoves, killers[root]); for(int i = 0; i < nmoves; i++){ MakeMove(board, &moves[i]); control->node_count++; if(!LeftInCheck(board)){ if(root == 0){ if(!control->best_move) control->best_move = moves[i]; /* Better than nothing. */ if(depth > 6 && !control->ponder){ MoveToAlgeb(moves[i], str_mov); printf("info depth %i hashfull %i currmove %s currmovenumber %i\n", depth, hash_table.full/(hash_table.size/1000), str_mov, i+1); } } nlegal++; val = AssesDraw(board); if(val) { if(best_move){ LMR = (reduce && i > good && !CAPTMASK(moves[i]) && !InCheck(board, 0)) ? 1 : 0; val = -AlphaBeta(board, depth-LMR-1, -alpha-1, -alpha, root+1, control, 0, killers); if(val > alpha){ val = -AlphaBeta(board, depth-1, -alpha-1, -alpha, root+1, control, 0, killers); if(val > alpha && val < beta){ val = -AlphaBeta(board, depth-1, -beta, -alpha, root+1, control, 0, killers); } } }else val = -AlphaBeta(board, depth-1, -beta, -alpha, root+1, control, 0, killers); } Takeback(board, moves[i]); if(!control->ponder && control->stop) return alpha; if(val >= beta){ UpdateTable(&hash_table, board->zobrist_key, val, moves[i], depth, HASH_BETA); if(CAPTMASK(moves[i]) == 0 && killers[root][0] != moves[i] && killers[root][1] != moves[i]){ killers[root][1] = killers[root][0]; killers[root][0] = moves[i]; } return beta; } if(val > alpha){ alpha = val; hash_flag = HASH_EXACT; best_move = moves[i]; if(root == 0) control->best_move = best_move; } if(root == 0 && ((clock() - control->init_time) > control->wish_time*CPMS)){ /* if short of time, don't search anymore after current move */ control->stop = 1; return alpha; } }else Takeback(board, moves[i]); } if(nlegal == 0){ if(InCheck(board, 0)){ /*UpdateTable(&hash_table, board->zobrist_key, MATE_VALUE+root, 0, depth, HASH_EXACT, hash_table.entries);*/ return MATE_VALUE; }else{ /*UpdateTable(&hash_table, board->zobrist_key, DRAW_VALUE, 0, depth, HASH_EXACT, hash_table.entries);*/ return DRAW_VALUE; /*Stalemate*/ } }else UpdateTable(&hash_table, board->zobrist_key, alpha, best_move, depth, hash_flag); }else if(InCheck(board, 0)){ alpha = AlphaBeta(board, 1, alpha, beta, root+1, control, 1, killers); }else{ alpha = Quiescent(board, alpha, beta, root, control, killers); } return alpha; }
int NegamaxAlphaBetaSearcher::NegaMax(GameState& state, int depth, int alpha, int beta, int max_player_id) { int alphaOrig = alpha; #if 0 unsigned int state_hash = state.GetHash(); //查询置换表 TT_ENTRY ttEntry = { 0 }; if(LookupTranspositionTable(state_hash, ttEntry) && (ttEntry.depth >= depth)) { if(ttEntry.flag == TT_FLAG_EXACT) return ttEntry.value; else if(ttEntry.flag == TT_FLAG_LOWERBOUND) alpha = std::max(alpha, ttEntry.value); else// if(ttEntry.flag == TT_FLAG_UPPERBOUND) beta = std::min(beta, ttEntry.value); if(beta <= alpha) return ttEntry.value; } #endif if(state.IsGameOver() || (depth == 0)) { #ifdef _DEBUG _searcherCounter++; #endif return EvaluateNegaMax(state, max_player_id); } state.SwitchPlayer(); int score = -INFINITY; int player_id = state.GetCurrentPlayer(); std::vector<MOVES_LIST> moves; int mc = state.FindMoves(player_id, moves); SortMoves(moves); for(int i = 0; i < mc; i++) { state.DoPutChess(moves[i].cell, player_id); int value = -NegaMax(state, depth - 1, -beta, -alpha, max_player_id); state.UndoPutChess(moves[i].cell); score = std::max(score, value); alpha = std::max(alpha, value); if(beta <= alpha) break; } state.SwitchPlayer(); #if 0 //写入置换表 ttEntry.value = score; if(score <= alphaOrig) ttEntry.flag = TT_FLAG_UPPERBOUND; else if(score >= beta) ttEntry.flag = TT_FLAG_LOWERBOUND; else ttEntry.flag = TT_FLAG_EXACT; ttEntry.depth = depth; StoreTranspositionTable(state_hash, ttEntry); #endif return score; }
static int AlphaBeta (BOARD *board, int depth, int alpha, int beta, int root, CONTROL *control, char skip_null, MOVE killers[][2]) { int nmoves, good = 0, nlegal = 0; MOVE moves[MAXMOVES]; MOVE best_move = 0; char str_mov[MVLEN]; int val = ERRORVALUE; char hash_flag = HASH_ALPHA; int in_check = InCheck(board, 0); if (root > control->seldepth) control->seldepth = root; if (depth > 0 || in_check) { if (root > 0) { val = GetHashEval(&hash_table, board->zobrist_key, depth, alpha, beta); if (val != ERRORVALUE) return val; } if (depth > 2 && !in_check) { if (!skip_null && board->piece_material[board->white_to_move] != 0) { MOVE null_mv = NULL_MOVE; MakeMove(board, &null_mv); val = -AlphaBeta(board, depth-3, -beta, -beta+1, root+1, control, 1, killers); Takeback(board, null_mv); if (val >= beta) return beta; } } nmoves = MoveGen(board, moves, 1); good = SortMoves(board, moves, nmoves, killers[root]); } else { if (!control->ponder && clock() - control->init_time >= control->max_time*CPMS) { control->stop = 1; } val = LazyEval(board); if (val-LAZYBETA >= beta) return beta; if (val+LAZYALPHA < alpha) return alpha; val = StaticEval(board); UpdateTable(&hash_table, board->zobrist_key, val, 0, 0, HASH_EXACT); if (val >= beta) return beta; if (val > alpha) alpha = val; nmoves = CaptureGen(board, moves); nmoves = FilterWinning(board, moves, nmoves); } for (int i = 0; i < nmoves; i++) { MakeMove(board, &moves[i]); control->node_count++; if (LeftInCheck(board)) { Takeback(board, moves[i]); continue; } if (root == 0) { if (!control->best_move) control->best_move = moves[i]; /* Better than nothing. */ if (depth > 6 && !control->ponder) { MoveToAlgeb(moves[i], str_mov); printf("info depth %i seldepth %i hashfull %i currmove %s currmovenumber %i\n", depth, control->seldepth, hash_table.full/(hash_table.size/1000), str_mov, i+1); } } nlegal++; val = AssessDraw(board, control->contempt); if (val == ERRORVALUE) { int ext = 0; //InCheck(board, 0) ? 1 : 0; if (best_move) { int LMR = (depth > 2 && !in_check && i > good && !CAPTMASK(moves[i]) && !InCheck(board, 0)) ? 1 : 0; val = -AlphaBeta(board, depth+ext-LMR-1, -alpha-1, -alpha, root+1, control, 0, killers); if (val > alpha) { val = -AlphaBeta(board, depth+ext-1, -alpha-1, -alpha, root+1, control, 0, killers); if (val > alpha && val < beta) { val = -AlphaBeta(board, depth+ext-1, -beta, -alpha, root+1, control, 0, killers); } } } else { val = -AlphaBeta(board, depth+ext-1, -beta, -alpha, root+1, control, 0, killers); } } Takeback(board, moves[i]); if (!control->ponder && control->stop) return alpha; if (val >= beta) { UpdateTable(&hash_table, board->zobrist_key, val, moves[i], depth, HASH_BETA); if (CAPTMASK(moves[i]) == 0 && killers[root][0] != moves[i] && killers[root][1] != moves[i]) { killers[root][1] = killers[root][0]; killers[root][0] = moves[i]; } return beta; } if (val > alpha) { alpha = val; hash_flag = HASH_EXACT; best_move = moves[i]; if (root == 0) control->best_move = best_move; } if (root == 0 && ((clock() - control->init_time) > control->wish_time*CPMS)) { /* if short of time, don't search anymore after current move */ control->stop = 1; return alpha; } } if (nlegal == 0) { if (in_check) { /*UpdateTable(&hash_table, board->zobrist_key, MATE_VALUE+root, 0, depth, HASH_EXACT, hash_table.entries);*/ return MATE_VALUE; } else if (depth > 0) { /*UpdateTable(&hash_table, board->zobrist_key, DRAW_VALUE, 0, depth, HASH_EXACT, hash_table.entries);*/ return DRAW_VALUE; /*Stalemate*/ } } else { UpdateTable(&hash_table, board->zobrist_key, alpha, best_move, depth, hash_flag); } return alpha; }
int Quiesce (uint8_t ply, int alpha, int beta) /************************************************************************** * * Our quiescent search. This quiescent search is able to recognize * mates. * **************************************************************************/ { uint8_t side, xside; int best, delta, score, savealpha; leaf *p, *pbest; if (EvaluateDraw ()) return (DRAWSCORE); side = board.side; xside = 1^side; InChk[ply] = SqAtakd (board.king[side], xside); best = Evaluate (alpha, beta); if (best >= beta && !InChk[ply]) return (best); TreePtr[ply+1] = TreePtr[ply]; if (InChk[ply]) { GenCheckEscapes (ply); if (TreePtr[ply] == TreePtr[ply+1]) return (-MATE+ply-2); if (best >= beta) return (best); SortMoves (ply); } else { GenCaptures (ply); if (TreePtr[ply] == TreePtr[ply+1]) return (best); SortCaptures (ply); } savealpha = alpha; pbest = NULL; alpha = MAX(best, alpha); delta = MAX (alpha - 150 - best, 0); for (p = TreePtr[ply]; p < TreePtr[ply+1]; p++) { pick (p, ply); /* We are in check or capture cannot bring score near alpha, give up */ if (!InChk[ply] && SwapOff (p->move) < delta) continue; /* If capture cannot bring score near alpha, give up */ if (p->score == -INFINITY) continue; #ifdef THREATEXT /* See search.c for an explanation of the code below */ if (threatply+1 == ply) { if ((TOSQ(p->move) == FROMSQ(threatmv)) || (FROMSQ(p->move) == TOSQ(threatmv))) continue; } if (threatply && threatply+3 == ply && FROMSQ(p->move) == TOSQ(threatmv)) continue; #endif MakeMove (side, &p->move); QuiesCnt++; if (SqAtakd (board.king[side], xside)) { UnmakeMove (xside, &p->move); continue; } score = -Quiesce (ply+1, -beta, -alpha); UnmakeMove (xside, &p->move); if (score > best) { best = score; pbest = p; if (best >= beta) goto done; alpha = MAX (alpha, best); } } done: if (flags & USEHASH && pbest != NULL) TTPut (side, 0, ply, savealpha, beta, best, pbest->move); return (best); }