int Player1::alphaBeta (int depth, int alpha, int beta, bool MAXplayer) { int heuristic = 0; bool go = false; int temp; char winner = 'N'; // 'N' for NULL go = isSolved(winner); // get heur info if (depth == 3 || go ) // or node is a terminal node { heuristic = getHeuristic(depth, winner, !(MAXplayer)); return heuristic;// value of the terminal game tree node } if (MAXplayer) //for each child of node { for (int p = 0; p < 19; p++){ for (int q = 0; q < 19; q++) { if (pOneBoard[p][q] == 'E' && playerDetected(p,q)) { pOneBoard[p][q] = 'P'; // make move on board temp = alphaBeta(depth+1, alpha, beta, !(MAXplayer)); // total recursive calls boardsExamined++; // -- maxmax start- //if (temp > alpha) alpha = temp; //a = max() //pOneBoard[p][q] = 'E'; //unmake move on board // -- maxmax end--- // -- ab code start- if (temp < alpha) alpha = temp; //alpha = max() if (beta <= alpha) { pOneBoard[p][q] = 'E';break; } else pOneBoard[p][q] = 'E'; // -- ab cod end --- }}} return alpha; // α } else //MINplayer //for each child of node { for (int p = 0; p < 19; p++){ for (int q = 0; q < 19; q++) { if (pOneBoard[p][q] == 'E' && playerDetected(p,q)) { pOneBoard[p][q] = 'T'; // make move on board temp = alphaBeta(depth+1, alpha, beta, !(MAXplayer)); // total recursive calls boardsExamined++; // -- maxmax start- //if (temp > beta) beta = temp; // β := max() //pOneBoard[p][q] = 'E'; //unmake move on board // -- maxmax end--- // -- ab code start- if (temp > beta) beta = temp; // β := min() if (beta <= alpha) { pOneBoard[p][q] = 'E'; break; } else pOneBoard[p][q] = 'E'; // -- ab cod end --- }}} return beta; // β } }
std::pair<int, Move> GameState::alphaBeta(int depth, int alpha, int beta, Player* player, Player* maximizingPlayer) { int bestValue; Move bestMove; std::vector<Move> moves = getMoves(player); if (depth == 0 || moves.size() == 0) { return std::make_pair(getScore(maximizingPlayer), Move()); } if (player == maximizingPlayer) { bestValue = -581357; for (Move move : moves) { board->doMove(move); std::pair <int, Move> pair = alphaBeta(depth - 1, alpha, beta, player->getOpponent(), maximizingPlayer); if (pair.first > bestValue) { bestValue = pair.first; bestMove = move; if (bestValue > alpha) { alpha = bestValue; } } board->undoMove(move); if (beta <= alpha) { break; } } return std::make_pair(bestValue, bestMove); } else { bestValue = 581357; for (Move move : moves) { board->doMove(move); std::pair <int, Move> pair = alphaBeta(depth - 1, alpha, beta, player->getOpponent(), maximizingPlayer); if (pair.first < bestValue) { bestValue = pair.first; bestMove = move; if (bestValue < beta) { beta = bestValue; } } board->undoMove(move); if (beta <= alpha) { break; } } return std::make_pair(bestValue, bestMove); } }
int Board::alphaBeta(int depthLeft, int alpha, int beta, int doNullMove) { int check = 0; int oldAlpha = alpha; int numSearched = 0; int bestMove = 0; int hashMove = 0; int stage; int i = currentState->firstMove; int result; nodes++; nodesUntilUpdate--; if(nodesUntilUpdate <= 0) { switch(game->interfaceUpdate()) { case OUT_OF_TIME: abortingSearch = 1; return 0; break; case STOP_SEARCH: abortingSearch = 1; return 0; break; } nodesUntilUpdate = nodesPerUpdate; } if(isRepetition()) { returnMove = 0; return DRAW; } HashEntry *e; e = hash->probe(currentState->hashKey); if(e!=NULL) { hashHits++; //Is it useful for replacing this search - otherwise just find a best move hashMove = e->bestMove; returnMove = hashMove; #ifndef DISABLE_HASH if(depth > 0 && e->depth >= depthLeft) { switch(e->flags & 3) { case HASH_EXACT: return e->score; break; case HASH_LOWER: if(e->score >= beta) return e->score; if(e->score > alpha) alpha = e->score; break; case HASH_UPPER: if(e->score <= alpha) return e->score; if(e->score < beta) beta = e->score; break; } if(e->flags & HASH_NO_NULL) { doNullMove = 0; } } #endif } #ifdef DEBUG_HASH if(hashMove > 0 && !testLegality(hashMove)) { print(); cout << "Illegal hash move! "; printMove(hashMove); cout << endl; } #endif if(depthLeft == 0) { int value = quiescence(alpha, beta); //hash->store(0, value, 0, HASH_EXACT); return value; } check = inCheck(); /* if(doNullMove && !check && !nullRisk() && depth >= 2) { int nullDepth = depthLeft - 2; makeNullMove(); if(nullDepth > 0) { result = alphaBeta(nullDepth, -beta, -beta+1, NO_NULL_MOVE); } else { result = quiescence(-beta, -beta+1); } retractNullMove(); if(nullDepth > 0 && result >= beta) { result = alphaBeta(nullDepth, beta-1, beta, DO_NULL_MOVE); numMoves = currentState->firstMove; } if(result >= beta) { returnMove = 0; return beta; } } */ if(!hashMove && depthLeft >= 3) { result = alphaBeta(depthLeft - 2, alpha, beta, DO_NULL_MOVE); numMoves = currentState->firstMove; //Failed low so have to research with new bounds if(result <= alpha) result = alphaBeta(depthLeft - 2, -MATE, alpha + 1, DO_NULL_MOVE); numMoves = currentState->firstMove; hashMove = returnMove; } if(hashMove > 0) { stage = HASH_MOVE; } else { stage = ALL_MOVES; } while(stage != FINISHED) { switch(stage) { case HASH_MOVE: moveStack[numMoves++] = hashMove; stage = ALL_MOVES; break; case ALL_MOVES: if(check) { generateCheckEvasions(); sortNonCaptures(i, hashMove); stage = FINISHED; } else { generateCaptures(); sortCaptures(i, hashMove); stage = NON_CAPTURES; } break; case NON_CAPTURES: generateNonCaptures(); sortNonCaptures(i, hashMove); stage = FINISHED; break; } for(; i<numMoves; i++) { int move = moveStack[i]; makeMove(move); #ifdef DEBUG_BITBOARDS if(isMessedUp()) { printMoves(); print(); cout << "Hash move = "; printMove(hashMove); cout << endl; cout << "MakeMove" << endl; exit(0); } #endif if(isLegal()) { numSearched++; result = -alphaBeta(depthLeft - 1, -beta, -alpha, DO_NULL_MOVE); retractMove(move); if(abortingSearch) { returnMove = bestMove; return result; } if(result > alpha) { if(result >= beta) { returnMove = move; hash->store(move, result, depthLeft, HASH_LOWER); #ifdef EXTRA_STATS cutoffs++; if(i == currentState->firstMove) firstCutoffs++; #endif return result; } /* if(depth == 0) { printMove(move); cout << " "; printScore(result); cout << " hashMove: "; printMove(hashMove); cout << endl; }*/ alpha = result; bestMove = move; } } else { //Illegal move retractMove(move); } #ifdef DEBUG_BITBOARDS if(isMessedUp()) { printMoves(); print(); cout << "Hash move = "; printMove(hashMove); cout << endl; cout << "RetractMove" << endl; exit(0); } #endif } } //While we have moves //No legal moves.. if(numSearched ==0) { returnMove = 0; if(check) { return -(MATE - depth); } else { return DRAW; } } if(alpha == oldAlpha) { hash->store(bestMove, alpha, depthLeft, HASH_UPPER); } else { hash->store(bestMove, alpha, depthLeft, HASH_EXACT); } returnMove = bestMove; return alpha; }
int Search::alphaBeta(bool white_turn, int depth, int alpha, int beta, Board& board, Transposition *tt, bool null_move_in_branch, Move (&killers)[32][2], int (&history)[64][64], int ply) { // If, mate we do not need search at greater depths if (board.b[WHITE][KING] == 0) { return -10000; } else if (board.b[BLACK][KING] == 0) { return 10000; } if (depth == 0) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } if (depth == 1) { // futility pruning. we do not hope for improving a position more than 300 in one move... int static_eval = evaluate(board); if (white_turn && (static_eval + 300) < alpha) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } if (!white_turn && (static_eval - 300) > beta) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } } if (depth == 2) { // extended futility pruning. we do not hope for improving a position more than 500 in two plies... // not really proven to +ELO but does not worse performance at least int static_eval = evaluate(board); if ((white_turn && (static_eval + 500) < alpha) || (!white_turn && (static_eval - 500) > beta)) { return capture_quiescence_eval_search(white_turn, alpha, beta, board); } } // null move heuristic - we do this despite in check.. if (!null_move_in_branch && depth > 3) { // skip a turn and see if and see if we get a cut-off at shallower depth // it assumes: // 1. That the disadvantage of forfeiting one's turn is greater than the disadvantage of performing a shallower search. // 2. That the beta cut-offs prunes enough branches to be worth the time searching at reduced depth int R = 2; // depth reduction int res = alphaBeta(!white_turn, depth - 1 - R, alpha, beta, board, tt, true, killers, history, ply + 1); if (white_turn && res > alpha) { alpha = res; } else if (!white_turn && res < beta) { beta = res; } if (beta <= alpha) { if (white_turn) { return alpha; } return beta; } } MoveList moves = get_children(board, white_turn); if (moves.empty()) { return 0; } Transposition tt_pv = tt[board.hash_key % HASH_SIZE]; for (auto it = moves.begin(); it != moves.end(); ++it) { // sort pv moves first if (tt_pv.next_move != 0 && tt_pv.hash == board.hash_key && tt_pv.next_move == it->m) { it->sort_score += 1100000; } // ...then captures in MVVLVA order if (!is_capture(it->m)) { // killer moves are quite moves that has previously led to a cut-off if (killers[ply - 1][0].m == it->m) { it->sort_score += 999999; } else if (killers[ply - 1][1].m == it->m) { it->sort_score += 899999; } else { // "history heuristics" // the rest of the quite moves are sorted based on how often they increase score in the search tree it->sort_score += history[from_square(it->m)][to_square(it->m)]; } } } total_generated_moves += moves.size(); Transposition t; t.hash = board.hash_key; int next_move = 0; for (unsigned int i = 0; i < moves.size(); ++i) { // late move reduction. // we assume sort order is good enough to not search later moves as deep as the first 4 if (depth > 5 && i == 10) { depth -= 2; } pick_next_move(moves, i); Move child = moves[i]; node_count++; make_move(board, child); int res = alphaBeta(!white_turn, depth - 1, alpha, beta, board, tt, null_move_in_branch, killers, history, ply + 1); unmake_move(board, child); if (res > alpha && res < beta) { // only cache exact scores } if (res > alpha && white_turn) { next_move = child.m; alpha = res; //history heuristics if (!is_capture(child.m)) { history[from_square(child.m)][to_square(child.m)] += depth; } // update pv } if (res < beta && !white_turn) { next_move = child.m; beta = res; //history heuristics if (!is_capture(child.m)) { history[from_square(child.m)][to_square(child.m)] += depth; } // update pv } if (beta <= alpha || time_to_stop()) { if (!is_capture(child.m)) { if (killers[ply - 1][0].m != child.m) { killers[ply - 1][1] = killers[ply - 1][0]; killers[ply - 1][0] = child; } } break; } } t.next_move = next_move; tt[board.hash_key % HASH_SIZE] = t; if (white_turn) { return alpha; } return beta; }
void Search::search_best_move(const Board& board, const bool white_turn, list history) { start = clock.now(); Board b2 = board; MoveList root_moves = get_children(b2, white_turn); for (auto it = root_moves.begin(); it != root_moves.end(); ++it) { make_move(b2, *it); it->sort_score = evaluate(b2); unmake_move(b2, *it); } int alpha = INT_MIN; int beta = INT_MAX; int START_WINDOW_SIZE = 32; Move killers2[32][2]; int quites_history[64][64] = { }; Transposition * tt = new Transposition[HASH_SIZE]; b2.hash_key = 0; for (int depth = 1; depth < 30;) { if (!white_turn) { for (auto it = root_moves.begin(); it != root_moves.end(); ++it) { it->sort_score = -it->sort_score; } } int score = white_turn ? alpha : beta; int a = alpha; int b = beta; MoveList next_iteration_root_moves; int pv[depth]; for (unsigned int i = 0; i < root_moves.size(); i++) { // TODO only search best move with alpha, alpha + 1, if fail high/low there is another better move and we have to search all moves pick_next_move(root_moves, i); Move root_move = root_moves[i]; if (b > a) { //strict? if (is_castling(root_move.m)) { Transposition * empty_tt = new Transposition[1000]; int empty_quites_history[64][64] = { }; int one_depth_score = alphaBeta(!white_turn, 1, INT_MIN / 2, INT_MAX / 2, b2, empty_tt, true, killers2, empty_quites_history, 1); delete empty_tt; if ((white_turn && one_depth_score < -5000) || (!white_turn && one_depth_score > 5000)) { // we are in check and castling is illegal. The move is not copied to the next iteration. root_move.sort_score = one_depth_score; continue; } } node_count++; make_move(b2, root_move); int res = -1; // TODO clean up code for (auto hit = history.begin(); hit != history.end(); ++hit) { if (is_equal(b2, *hit)) { // draw by repetition res = 0; } } if (res == -1) { res = alphaBeta(!white_turn, depth - 1, a, b, b2, tt, false, killers2, quites_history, 1); } unmake_move(b2, root_move); if (res > a && white_turn) { score = res; a = res; // TODO extract method get_pv for (int p = 1; p < depth; p++) { pv[p] = 0; } pv[0] = root_move.m; int next_pv_move = root_move.m; int hash = b2.hash_key; for (int p = 1; p < depth - 1; p++) { hash ^= next_pv_move; Transposition next = tt[hash % HASH_SIZE]; if (next.hash == hash && next.next_move != 0) { pv[p] = next.next_move; next_pv_move = next.next_move; } else { break; } } } if (res < b && !white_turn) { score = res; b = res; for (int p = 1; p < depth; p++) { pv[p] = 0; } pv[0] = root_move.m; int next_pv_move = root_move.m; int hash = b2.hash_key; for (int p = 1; p < depth - 1; p++) { hash ^= next_pv_move; Transposition next = tt[hash % HASH_SIZE]; if (next.hash == hash && next.next_move != 0) { pv[p] = next.next_move; next_pv_move = next.next_move; } else { break; } } } root_move.sort_score = res; } else { // beta fail // we just know that the rest of the moves are worse than the best move, or is this un-reachable code? root_move.sort_score = white_turn ? a - i : b + i; // keep sort order } next_iteration_root_moves.push_back(root_move); } int time_elapsed_last_depth_ms = std::chrono::duration_cast < std::chrono::milliseconds > (clock.now() - start).count(); if (score > alpha && score < beta) { std::string pvstring = pvstring_from_stack(pv, depth); // uci score is from engine perspective int engine_score = white_turn ? score : -score; std::cout << "info score cp " << engine_score << " depth " << depth << " time " << time_elapsed_last_depth_ms << " nodes " << node_count << " pv " << pvstring << "\n" << std::flush; std::stringstream ss(pvstring); getline(ss, best_move, ' '); } if (time_to_stop()) { break; } // aspiration window based on previous score - use delta to increase if no moves are found... int window_size = beta - alpha; if (score <= alpha) { // failed low, search again at same depth alpha = alpha - window_size; continue; } if (score >= beta) { // failed high, search again at same depth beta = beta + window_size; continue; } root_moves = next_iteration_root_moves; // aspiration window size alpha = score - START_WINDOW_SIZE / 2; beta = score + START_WINDOW_SIZE / 2; // if mate is found at this depth, just stop searching for better moves. Cause there are none.. The best move at the last depth will prolong the inevitably as long as possible. if ((white_turn && score < -5000) || (!white_turn && score > 5000)) { // we are mated... break; } if (abs(score) > 5000) { // deliver mate break; } // save some time for next move if ((4 * time_elapsed_last_depth_ms) > max_think_time_ms) { break; } depth++; } delete tt; std::cout << "bestmove " << best_move << std::endl << std::flush; return; }
/* Performs an alpha-beta search from the given board and returns the number of nodes visited */ int getAlphaBetaTreeSize(rep_t rep, int side, int depth, heuristics_t heuristic , int budget) { numNodes = termCount = 0; // reset node and terminal node counts alphaBeta(rep , 0, depth, side, _DOM->min_wins, _DOM->max_wins, heuristic, budget, id); // run specified alpha-beta search return numNodes; // return number of nodes expanded (updated by call to alphaBeta above) }
/* Perform alpha-beta search from given board position, and make the best move The set of bestMoves is returned via parameter bestMoves[] */ int makeMinmaxMove(rep_t rep, int* side, int depth, heuristics_t heuristic, int budget, int randomTieBreaks, int noisyMM, int* bestMoves, int* numBestMoves, double* termPercentage, double *moveVals) { int i; rep_t dummyRep; double val; int bestMove = NULL_MOVE; int secondBestMove = NULL_MOVE; double bestScore; double secondBestScore = -INF; int origSide = *side; double scores[_DOM->getNumOfChildren(rep, origSide)]; double die; numNodes = 0; // reset node count *numBestMoves = 0; // reset number of best moves termCount = 0; // reset terminal nodes count for (i = 1; i < _DOM->getNumOfChildren(rep, origSide); i++) { // iterate over all possible moves *side = origSide; if (!_DOM->isValidChild(rep,*side,i)) continue; dummyRep = _DOM->cloneRep(rep);// clone starting board so it can be restored on next pass through loop _DOM->makeMove(dummyRep,side,i);// generate i^th child // Compute MM-(k-1) value of i^th child (since the children are already at depth 1 from the root node, // and we do a search from each child) val = alphaBeta(dummyRep , 1, depth, *side, _DOM->min_wins, _DOM->max_wins, heuristic, budget, id); _DOM->destructRep(dummyRep); // If this was min's move, negate the utility value (this makes things a little cleaner // as we can then always take the max of the children, since min(s1,s2,...) = -max(-s1,-s2,...)) scores[i] = (origSide == min) ? -val : val; if (moveVals != 0) moveVals[i-1] = val; // If this is either the first child, or the best scoring child, store it if ((*numBestMoves == 0) || (scores[i] > bestScore)) { bestMoves[0] = i; bestScore = scores[i]; *numBestMoves = 1; } // If this child ties with the best scoring child, and random tie-breaking is enabled, store it else if ((scores[i] == bestScore) && (randomTieBreaks)) bestMoves[(*numBestMoves)++] = i; if (verbose) printf("Move # %d -- Value %f\n", i, val); } *side = origSide; bestMove = bestMoves[random() % *numBestMoves]; // pick a best move at random among the set of best moves if (verbose) printf("Value of root node: %f\n", (origSide == min) ? -bestScore : bestScore); // Find second best move -- note that the score of the second best move must be within 3 of the best move // In case there is no such second best move, the index remains set to its initial value of NULL_MOVE for (i = 1; i < _DOM->getNumOfChildren(rep,*side); i++) { if (!_DOM->isValidChild(rep,*side,i)) continue; if (scores[i] != bestScore) { if ((bestScore - scores[i] <= 3.0) && (scores[i] > secondBestScore)) { secondBestScore = scores[i]; secondBestMove = i; } } } if (verbose) printf("Best move: %d \t Second best move: %d\n", bestMove, secondBestMove); // If 'noisy MM' option was selected at command-line and there is a candidate second-best move, // flip a coin and determine whether to pick that over the best move if ((secondBestMove != NULL_MOVE) && (noisyMM)) { assert(heuristic != (heuristics_t)_DOM->hFunctions.h3); // could mess up if using playouts to estimate leaves, so flag for now die = (double)(random() % 10000) / (double)(10000 - 1); if (die <= EPSILON) bestMove = secondBestMove; } if (verbose) printf("Examined %d nodes\n", numNodes); *termPercentage = (double)termCount / (double)numNodes; // compute percentage of nodes in tree that were terminal return bestMove; }
//////////////////////////////////////////////////////////////////////////////// // alphaBeta //////////////////////////////////////////////////////////////////////////////// int Game::alphaBeta(ChessBoard *board, const int depth, int alpha, const int beta) { if(m_evalCount % 1000 == 0) { if(timeUp()) m_bInterrupted = true; } if(m_bInterrupted){ return alpha; // } int value = 0; // 50 move rule and repetition check if(board->checkEnded()){ return ChessBoard::VALUATION_DRAW; } board->scoreMovesPV(m_arrPVMoves[m_searchDepth - depth]); int best = (-ChessBoard::VALUATION_MATE)-1; int move = 0; ChessBoard *nextBoard = m_boardFactory[depth]; while (board->hasMoreMoves()) { move = board->getNextScoredMove(); board->makeMove(move, nextBoard); // self check is illegal! if(nextBoard->checkInSelfCheck()){ // not valid, remove this move and continue //DEBUG_PRINT("#", 0); board->removeMoveElementAt(); continue; } // generate the moves for this next board in order to validate the board nextBoard->genMoves(); if(nextBoard->checkInCheck()){ //DEBUG_PRINT("+", 0); nextBoard->setMyMoveCheck(); move = Move_setCheck(move); } // ok valid move #if DEBUG_LEVEL & 1 char buf[20]; Move::toDbgString(move, buf); DEBUG_PRINT("\n%d %s > ", depth, buf); #endif // at depth one is at the leaves, so call quiescent search if(depth == 1) { value = -quiesce(nextBoard, QUIESCE_DEPTH, -beta, -alpha); } else { value = -alphaBeta(nextBoard, depth-1,-beta,-alpha); } if(value > best) { best = value; m_arrPVMoves[m_searchDepth - depth] = move; } if(best > alpha) { #if DEBUG_LEVEL & 1 DEBUG_PRINT("$ best > alpha %d > %d ", best, alpha); #endif alpha = best; } if(best >= beta){ #if DEBUG_LEVEL & 1 DEBUG_PRINT("$ best >= beta break, %d > %d\n", best, beta); #endif break; } } // no valid moves, so mate or stalemate if(board->getNumMoves() == 0){ //DEBUG_PRINT("@", 0); if(Move_isCheck(board->getMyMove())){ #if DEBUG_LEVEL & 1 DEBUG_PRINT("\nMate #\n", 0); #endif return (-ChessBoard::VALUATION_MATE); } #if DEBUG_LEVEL & 1 DEBUG_PRINT("\nStalemate\n", 0); #endif return ChessBoard::VALUATION_DRAW; } #if DEBUG_LEVEL & 4 DEBUG_PRINT("\n==> alphaBeta #moves: %d\n", board->getNumMoves()); #endif return best; }
/* Recursively compute the minimax value of a given node, with alpha-beta pruning. The search is cutoff at a maximum depth given by depth. searchDepth keeps track of how deep we already are in the tree */ static double alphaBeta(rep_t rep, int searchDepth, int depth, int side, double alpha, double beta,heuristics_t heuristic, int budget, int currentNodeId) { double val1, val2; int i; int origSide = side; rep_t dummyRep; numNodes++; // track number of nodes expanded // Hit terminal node -- return value is one of {MAX_WINS, MIN_WINS, DRAW} if ((val1 = _DOM->getGameStatus(rep)) != _DOM->incomplete) { termCount++; // update terminal node count if (dotFormat) printf("n%d [color=\"red\"];", currentNodeId); // terminal nodes are colored red if (_DOM->dom_name == GGP && heuristic == _DOM->hFunctions.h2) val1 /= _DOM->max_wins; return val1; } // Hit search depth cutoff -- apply the chosen heuristic function to this node and return that value if (searchDepth >= depth) { val1 = heuristic(rep, side, budget); // Ensure that the heuristic value is always bounded by the values assigned to terminal nodes (i.e. true win/loss // positions) assert ((val1 <= _DOM->max_wins) && (val1 >= _DOM->min_wins)); return val1; } // Otherwise, recurse if (side == max) { // at a Max node val1 = alpha; for (i = 1; i < _DOM->getNumOfChildren(rep, side); i++) { // iterate over all possible moves side = origSide; if (!_DOM->isValidChild(rep,side,i)) continue; dummyRep = _DOM->cloneRep(rep);// clone starting board so it can be restored on next pass through loop _DOM->makeMove(dummyRep,&side,i);// generate i^th child // About to visit a new node, so add the edge to the graph if (dotFormat) printf("n%d -> n%d;\n", currentNodeId, ++id); val2 = alphaBeta(dummyRep , searchDepth+1, depth, side, val1, beta, heuristic, budget, id); // compute minimax value of this child _DOM->destructRep(dummyRep); val1 = (val2 > val1) ? val2 : val1; // we have tightened our alpha bound if (val1 >= beta) // we have a beta-cutoff return beta; } } else { // at a Min node val1 = beta; for (i = 1; i < _DOM->getNumOfChildren(rep, side); i++) { // iterate over all possible moves side = origSide; if (!_DOM->isValidChild(rep,side,i)) continue; dummyRep = _DOM->cloneRep(rep);// clone starting board so it can be restored on next pass through loop _DOM->makeMove(dummyRep,&side,i);// generate i^th child // About to visit a new node, so add the edge to the graph if (dotFormat) printf("n%d -> n%d;\n", currentNodeId, ++id); val2 = alphaBeta(dummyRep, searchDepth+1, depth, side, alpha, val1, heuristic, budget, id); // compute minimax value of this child _DOM->destructRep(dummyRep); val1 = (val2 < val1) ? val2 : val1; // we have tightened our beta bound if (val1 <= alpha) // we have an alpha-cutoff return alpha; } } return val1; }
int alphaBeta(vertex Node,int alpha, int beta,int player,int depth,int maxdepth,linked_list (*create_children)(int *gameMatrix, int player,int *error),int* error){ int aboveAlpha=alpha,aboveBeta=beta; int temp,childError; vertex child; element run; linked_list new_children; /* end-cases, return a score for some leaf */ if (depth>=maxdepth || Node->edges->head==NULL){ temp= Node->score; if(depth>1){ remove_tree(Node,0,0); } *error=0; return temp; } if (player==ENUM_PLAYER_1){ /* for every node */ for (run = Node->edges->head;run != NULL; run = run->next){ child=run->node; if (depth<maxdepth-1){ int create_childrenError=0; /*create a new level of children*/ new_children=create_children(child->game_state, player*-1,&create_childrenError); if(create_childrenError==-1){ printf("ERROR: can't create children\n"); *error=-1; return 0; } child->edges=new_children; } /* continue this process on the next level */ temp=alphaBeta(child,alpha,beta,player*-1,depth+1,maxdepth,create_children,&childError); if (childError==-1){ *error=-1; return 0; } /* we now have a score from the subtreee*/ if(aboveAlpha < temp){ aboveAlpha=temp; } /* perfom pruning on this level */ if (aboveBeta<=aboveAlpha){ for ( run = run->next;run != NULL; run = run->next){ child=run->node; if(depth>0){ remove_tree(child,0,0); } } break; } } /* taking max */ Node->score=aboveAlpha; if(depth>1){ remove_tree(Node,0,0); } *error=0; /*return some score*/ return aboveAlpha; } /* perform the same process with roles reversed */ else { for (run = Node->edges->head;run != NULL; run = run->next){ child=run->node; if (depth<maxdepth-1){ int create_childrenError=0; new_children=create_children(child->game_state, player*-1,&create_childrenError); // add children if(create_childrenError==-1){ printf("ERROR: can't create children\n"); *error=-1; return 0; } child->edges=new_children; } temp=alphaBeta(child,alpha,beta,player*-1,depth+1,maxdepth,create_children,&childError); if (childError==-1){ *error=-1; return 0; } if(aboveBeta > temp){ aboveBeta=temp; } if (aboveBeta<=aboveAlpha){ for ( run = run->next;run != NULL; run = run->next){ child=run->node; if(depth>0){ remove_tree(child,0,0); } } break; } } Node->score=aboveBeta; if(depth>1){ remove_tree(Node,0,0); } *error=0; return aboveBeta; } }
boolean Game::alphaBetaRoot(const int depth, int alpha, const int beta) { if(m_bInterrupted){ return false; // } int value = 0; m_board->scoreMovesPV(m_arrPVMoves[m_searchDepth - depth]); int best = (-ChessBoard::VALUATION_MATE)-1; int move = 0, bestMove = 0; ChessBoard *nextBoard = m_boardFactory[depth]; while (m_board->hasMoreMoves()) { move = m_board->getNextScoredMove(); m_board->makeMove(move, nextBoard); // self check is illegal! if(nextBoard->checkInSelfCheck()){ // not valid, remove this move and continue //DEBUG_PRINT("#", 0); m_board->removeMoveElementAt(); continue; } // generate the moves for this next board in order to validate the board nextBoard->genMoves(); #if DEBUG_LEVEL & 1 //char bbuf[2048]; //nextBoard->printB(bbuf); //DEBUG_PRINT("\n%s\n", bbuf); #endif if(nextBoard->checkInCheck()){ #if DEBUG_LEVEL & 1 DEBUG_PRINT("!", 0); #endif nextBoard->setMyMoveCheck(); move = Move_setCheck(move); } // ok valid move #if DEBUG_LEVEL & 1 char buf[20]; Move::toDbgString(move, buf); DEBUG_PRINT("\n====== %s [ > ", buf); #endif // at depth one is at the leaves, so call quiescent search if(depth == 1) { value = -quiesce(nextBoard, QUIESCE_DEPTH, -beta, -alpha); } else { // if not PV then narrow search window value = -alphaBeta(nextBoard, depth-1,-beta,-alpha); } #if DEBUG_LEVEL & 1 DEBUG_PRINT(":: %d ", value); #endif if(value > best) { best = value; bestMove = move; m_arrPVMoves[m_searchDepth - depth] = move; } if(best > alpha) { //co.pl(" a= " + best); alpha = best; } if(best >= beta){ #if DEBUG_LEVEL & 1 DEBUG_PRINT("\nBreak out main loop %d\n", best); #endif break; } } #if DEBUG_LEVEL & 1 DEBUG_PRINT("]",0); #endif // we're interrupted, no move and no value! if(m_bInterrupted) return false; m_bestMove = bestMove; m_bestValue = best; if(m_board->getNumMoves() <= 1){ if(m_board->getNumMoves() == 0){ DEBUG_PRINT("NO moves in aphaBetaRoot!", 0); } #if DEBUG_LEVEL & 1 DEBUG_PRINT("\n==> alphaBetaRoot NO continuation %d\n", m_board->getNumMoves()); #endif // do not contine, so return false return false; } #if DEBUG_LEVEL & 1 DEBUG_PRINT("\n==> alphaBetaRoot continue %d\n", m_board->getNumMoves()); #endif return true; }
const SEARCH_RETURN AI::alphaBeta(Board& board, EXPECTED_VALUE alpha, EXPECTED_VALUE beta, int depth) { #ifdef DEBUG string merp(board.toString()); cout<<endl<<endl; for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<" 0 1 2 3 4 5 6 7 8 9"<<endl<<endl; for(Uint8 y = 0; y < board.getDim().y(); y++) { for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<char('A'+y)<<' '; for(Uint8 x=0; merp[y*20+x] != '\n'; x++) { cout<<merp[y*20+x]; } cout<<endl; } for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<"Value for current game: "<<evaluate(board)<<endl; #endif if(depth == 0 || board.won() != NULL) { return SEARCH_RETURN(quiEvaluate(board) - depth, Move(NULL, NULL, NULL)); //return quiesce(n); } EXPECTED_VALUE score, bestScore = -10000000; Move ret(NULL, NULL, NULL); //BoardNode next {n.b, n.depth, NULL, NULL, n.b.nextIn(n.b.whosTurn())==NULL? -n.beta:n.alpha, n.b.nextIn(n.b.whosTurn())==NULL? -n.alpha:n.beta}; vector<Piece> &pieces = board.getPieceList(board.whosTurn()); Move next(NULL, NULL, &board); for(auto i = pieces.begin(); i != pieces.end(); i++) { if(next.setPiece(board.nextIn(board.whosTurn()))) { if(next.getPiece() != &*i) continue; } auto validLocations = board.getValid(&*i); for(auto c = validLocations.begin(); c != validLocations.end(); c++) { next.setPiece(&*i); next.setCell(*c); if(!next.doMove()) continue; if(board.nextIn(board.whosTurn())) { score = alphaBeta(board, alpha, beta, depth-1).val; } else { score = -alphaBeta(board, -beta, -alpha, depth-1).val; } next.undoMove(); #ifdef DEBUG if(merp != string(board.toString())) { for(Uint8 fucksGiven = 99; fucksGiven > 0; fucksGiven--) cout<<"YOU F****D UP! "; cout<<endl<<"I guess you really don't give a f**k"<<endl; merp = board.toString(); for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<" 0 1 2 3 4 5 6 7 8 9"<<endl<<endl; for(Uint8 y = 0; y < board.getDim().y(); y++) { for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<char('A'+y)<<' '; for(Uint8 x=0; merp[y*20+x] != '\n'; x++) { cout<<merp[y*20+x]; } cout<<endl; } for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<"Value for current game: "<<evaluate(board)<<endl; while(getchar() != '\n'); } for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<" 0 1 2 3 4 5 6 7 8 9"<<endl<<endl; for(Uint8 y = 0; y < board.getDim().y(); y++) { for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<char('A'+y)<<' '; for(Uint8 x=0; merp[y*20+x] != '\n'; x++) { cout<<merp[y*20+x]; } cout<<endl; } for(Uint8 space = 6 - depth; space > 0; space--) cout<<' '; cout<<"Value for current game: "<<evaluate(board)<<endl; #endif if(score >= beta) return SEARCH_RETURN(beta, next); if(score > bestScore) { bestScore = score; if(score > alpha) alpha = score; ret = next; } } } return SEARCH_RETURN(bestScore, ret); }
std::pair<int, int> algorithm(const Board& board, int depth, bool whiteOnTurn) { Move currentBestMove = { 0, 0, 0 }; // Container for best value and the move to get there int a = MIN; // Alpha int b = MAX; // Beta currentBestMove.value = (whiteOnTurn) ? a : b; // 1. Get number of threads that system can manage unsigned int num_of_threads = THREADS; std::srand((int)std::time(0)); // 2. Get all possible moves and divide them as tasks to threads std::vector<Move> possibleMoves = getAllPossibleMoves(board, whiteOnTurn); // In other cases can proceed to dividing tasks for threads taskVectors.clear(); // Must be cleared so it's empty on start taskVectors = divideForThreads(possibleMoves, (int)num_of_threads); // 3. Create threadpool and run tasks with threads std::vector<std::thread> threads; // Lambda auto lambda = [depth, whiteOnTurn](Board board, int i, int la, int lb) { // This is what the thread does to its tasks // This also works as 1st level of algorithm (because of alphaBeta structure) std::srand((int)std::time(0)); // Use current time as a seed for random // Copy taskVektor so it can be used threadMutex.lock(); std::vector<Move> tasks = taskVectors[i]; threadMutex.unlock(); Move bestThreadMove = { 0, 0, 0}; bestThreadMove.value = (whiteOnTurn) ? la : lb; for (auto move : tasks) { move.value = (whiteOnTurn) ? la : lb; // For white turn=>MIN black=>MAX Board new_board = board; new_board.movePiece(move.origin, move.destination); new_board.updateState(move.destination, 1); int temp = alphaBeta(new_board, depth - 1, la, lb, !whiteOnTurn); // Recursive part depth needs to decrease now depth at its maximum if (whiteOnTurn) { if ((bestThreadMove.value < temp) || (bestThreadMove.value == temp && (rand() % 8 == 1))) { bestThreadMove = move; bestThreadMove.value = temp; } la = std::max(la, bestThreadMove.value); } else { if ((bestThreadMove.value > temp) || (bestThreadMove.value == temp && (rand() % 8 == 1))) { bestThreadMove = move; bestThreadMove.value = temp; } lb = std::min(lb, bestThreadMove.value); } if (lb <= la) { break; // Cut off bad branch } // Randomly pick if as good as current } threadMutex.lock(); // Must be logged so that threads wont do this same time { taskVectors[i].clear(); taskVectors[i].push_back(bestThreadMove); } threadMutex.unlock(); }; // Create threads and push them in vector for (unsigned int i = 0; i < taskVectors.size(); i++) { // Some magic with lambda functions threads.push_back(std::thread(lambda, board, i, a, b)); //std::cout << "Started thread number " << i + 1 << std::endl; } //int i = 1; //This is for the cout below // 4. Wait threads to finish for (auto thread = threads.begin(); thread != threads.end(); thread++) { //std::cout << "Waiting for thread number " << i << " to finish." << std::endl; thread->join(); //std::cout << "Thread number " << i++ << " finished." << std::endl; } // 5. Choose best value for (auto list : taskVectors) { for (auto move : list) { // White turn if (move.value > currentBestMove.value && whiteOnTurn) { currentBestMove = move; // Replace with better one } // Black turn else if (move.value < currentBestMove.value && !whiteOnTurn) { // To have some random factor if same values currentBestMove = move; } else if (move.value == currentBestMove.value && (rand() % 8 == 1)) { currentBestMove = move; } } } if (currentBestMove.origin == 0 && currentBestMove.destination == 0) { std::cout << "Illegal move [0,0]" << std::endl; } //std::cout << "Returning with move [" << currentBestMove.origin << "," << currentBestMove.destination << "]" << ", its value is " << currentBestMove.value << std::endl; return std::make_pair(currentBestMove.origin ,currentBestMove.destination); }
int alphaBeta(const Board& board, int depth, int a, int b, bool maximizingPlayer) { if( depth == 0 ) // Check if the algorithm has reached it's depth { if(board.isStaleMate(maximizingPlayer)) //before evaluation see if it's stalemate or checkmate { // Do some stuff to end the game if(board.isCheck(maximizingPlayer)) { if(maximizingPlayer) return MIN + 100; else return MAX - 100; } else { return 0; } } return evaluate(board); } // Find all own pieces if (maximizingPlayer) // White players turn { int v = MIN; // Smallest int for(int i = 0; i < 64; i++)// Iterate through the board { if(board.getBoard()[i]%2 == 1)// If piece is white { for(auto j:board.possibleMoves(i)) { Board newboard = board; newboard.movePiece(i, j);// Supposing possibleMoves doesn't return origin newboard.updateState(j, 1); v = std::max(v, alphaBeta(newboard, depth-1, a, b, false)); // Call alphabeta for black player a = std::max(a, v); if (b <= a) { break; // Cut off } } } } if(v == MIN) // Has not chosen a move -> possiblemoves must be empty { if(board.isCheck(maximizingPlayer)) { return (MIN+100-depth); // If it's check it's also checkmate } else // Otherwise it must be stalemate { return 0; } } return v; } else // Black players turn { int v = MAX; for(int i = 0; i < 64; i++) { if(board.getBoard()[i]%2 == 0 && board.getBoard()[i] != 0) { for(auto j:board.possibleMoves(i)) { Board newboard = board; newboard.movePiece(i, j); newboard.updateState(j, 1); v = std::min(v, alphaBeta(newboard, depth-1, a, b, true)); b = std::min(b,v); if(b<=a) { break; // Cut off } } } } if(v == MAX) // Has not chosen a move -> possiblemoves must be empty { if(board.isCheck(maximizingPlayer)) // If it's check it's also checkmate { return (MAX-100+depth); } else // Otherwise it must be stalemate { return 0; } } return v; } }
Board AIEngine::alphaBeta(Board board, char curpawn, int stopper, bool once, pair<int, int> node, int a, int b) //1 = max, 0 = min { int alpha = a; int beta = b; char pawn = curpawn; Board current = board; /*CHECKS FOR VICTORY OR DEFEAT*/ if (stopper <= 0) return board; if (board.checkVictory() == 1 && pawn != curpawn) { board.score -= 1000; return board; } if (board.checkVictory() == 1 && pawn == curpawn) { board.score += 1000; return board; } if (board.checkVictory() == 2 && pawn != curpawn) { board.score -= 1000; return board; } if (board.checkVictory() == 2 && pawn == curpawn) { board.score += 1000; return board; } /*GET THE NEXT PAWN*/ char nextPawn; if (curpawn == 'X') nextPawn = 'O'; else nextPawn = 'X'; /*FIND AVAILABLE PAWNS (NODES) TO MOVE FOR THE CURRENT PAWN TYPE ONLY DONE ONCE*/ if (once) { board.score = 0; int bestScore = INT_MIN; Board best; char savePawn = pawn; pawn = curpawn; vector<pair<int, int>> peices; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { if (isValidPiece(i, j)) { peices.push_back(pair<int, int>(i, j)); } } } pawn = savePawn; for (int i = 0; i < peices.size(); i++) { node = peices[i]; Board temp = bestMove(board, curpawn, stopper, false, node); if (temp.score > bestScore) { best = temp; bestScore = best.score; } } return best; } /*Check on the possible moves for the one node pawn, and evaluate them DONE IF THE FUNCTION WAS CALLED RECURSIVELY*/ if (!once) { current = board; char savePawn = pawn; pawn = curpawn; vector<pair<int, int>> validMoves = getValidMoves(node); pawn = savePawn; /*ERROR CHECKING*/ /*cout << "Stopper: " << stopper << endl; cout << "Node: " << get<0>(node) << get<1>(node) << endl; cout << "Valid moves: "; for (int i = 0; i < validMoves.size(); i++) { cout << get<0>(validMoves[i]) << get<1>(validMoves[i]) << " "; } cout << endl;*/ /*END OF ERROR CHECKING*/ Board best; Board temp = board; Board temp2; int baseline; if (curpawn == pieceType) { baseline = INT_MIN; } else { baseline = INT_MAX; } int v; for (int i = 0; i < validMoves.size(); i++) { temp = board; /*SCORING THE MOVE*/ if (curpawn == pieceType) { if (temp.getIndex(get<0>(validMoves[i]), get<1>(validMoves[i])) == nextPawn) { temp.score += 5; } else { temp.score += 1; } } else { if (temp.getIndex(get<0>(validMoves[i]), get<1>(validMoves[i])) == pieceType) { temp.score -= 4; } else { temp.score += 0; } } temp.setIndex(get<0>(node), get<1>(node), '\0'); temp.setIndex(get<0>(validMoves[i]), get<1>(validMoves[i]), curpawn); //IF AI WINS if (get<0>(validMoves[i]) == 7 && curpawn == pawn) { temp.score = INT_MAX; return temp; } if (curpawn == pieceType) { /*Maximize*/ temp2 = alphaBeta(temp, nextPawn, stopper - 1, false, validMoves[i], alpha, beta); /* if (temp2.score > baseline) { baseline = temp2.score; best = temp; best.score = baseline; }*/ if (temp2.score > baseline) v = temp2.score; else v = baseline; if (alpha < v) alpha = alpha; else alpha = v; if (beta <= alpha) return temp; } else { /*Minimize*/ pawn = nextPawn; vector<pair<int, int>> peices; for (int j = 0; j < 8; j++) { for (int k = 0; k < 8; k++) { if (isValidPiece(j, k)) { peices.push_back(pair<int, int>(j, k)); } } } pawn = savePawn; for (int j = 0; j < peices.size(); j++) { temp2 = alphaBeta(temp, nextPawn, stopper - 1, false, peices[j], alpha, beta); /*if (temp2.score < baseline) { baseline = temp2.score; best = temp; best.score = baseline; }*/ if (temp2.score < baseline) v = temp2.score; else v = baseline; if (beta > v) beta = beta; else beta = v; if (beta <= alpha) return temp; } } } return best; } }
void genmove(char **board,int sizeBoard,int position[2][2],int walls[2],int depth,int maxPL,int maxPlayer){ MOVE bestMove; int rows = 2*sizeBoard; int columns = 4* sizeBoard; int winningRow,flag=0,i; double alpha,beta; const int initialDepth = depth; alpha = -INFINITY; beta = INFINITY; if (maxPL) winningRow = 1; else winningRow = 2*sizeBoard -1; /*Checks if there is an immediate move that makes the player win.If there is,there is no need to run alphaBeta.*/ for (i=0;i<12;i++){ if (moves[i][4] == 0){ if(position[maxPL][0] != moves[i][2] && position[maxPL][1] != moves[i][3] && (check_Legal_Move(board,rows,columns,position[maxPL][0] + moves[i][0],position[maxPL][1] + moves[i][1],position[maxPL][0],position[maxPL][1])) && position[maxPL][0] + moves[i][0] == winningRow ){ flag = 1; break; } } else if (moves[i][4] == 1){ if((position[maxPL][0]*moves[i][5] > moves[i][2]*moves[i][5])&&(position[maxPL][1]*moves[i][6] > moves[i][3]*moves[i][6])&& (check_Legal_Move(board,rows,columns,position[maxPL][0] + moves[i][0],position[maxPL][1] + moves[i][1],position[maxPL][0],position[maxPL][1])) && position[maxPL][0] + moves[i][0] == winningRow){ flag = 1; break; } } } if (flag == 1){ bestMove.isMove =1; bestMove.row = position[maxPL][0] + moves[i][0]; bestMove.col = position[maxPL][1] + moves[i][1]; } else bestMove = alphaBeta(board,sizeBoard,position,walls,depth,initialDepth,maxPL,1,alpha,beta,maxPL); if(bestMove.isMove == 1){ simpleMove(board,maxPL,rows,bestMove.row,bestMove.col,position); insertMove(maxPL,0,bestMove.row,bestMove.col,-1); printf("= %c%d\n\n",(position[maxPL][1]-2)/4 + 'a',(rows - position[maxPL][0] + 1)/2); fflush(stdout); } else if (bestMove.isMove == 0){ simpleWall(board,bestMove.ori,rows,bestMove.row,bestMove.col); insertMove(maxPL,1,bestMove.row,bestMove.col,bestMove.ori); if (bestMove.ori == 0){ printf("= %c%d vertical\n\n",(bestMove.col-2)/4 + 'a',(rows - bestMove.row + 1)/2); fflush(stdout); } else{ printf("= %c%d horizontal\n\n",(bestMove.col-2)/4 + 'a',(rows - bestMove.row + 1)/2); fflush(stdout); } walls[maxPL]--; } else printf("TO THE NOPEVILLE!!!! \n\n"); }