int NegaMax::negaMax(int depth, bool is_white_turn) { int score = -EVAL_MAX; int maxscore = -EVAL_MAX; // 叶子节点 评估返回 if(depth <= 0) return _evaluator.Evaluate(_chessboard, is_white_turn); // 非叶子节点 游戏结束 返回评估值 if (_evaluator.IsGameOver(_chessboard, is_white_turn, score)) return score; ChessType cur_chess = is_white_turn ? CT_WHITE : CT_BLACK; int count = _move_generator.GenAllPossibleMove(_chessboard, depth); for (int i=0; i<count; i++) { assert(_move_generator.MakeMove(_chessboard, depth, i, cur_chess)); score = -negaMax(depth-1, !is_white_turn); if(score > maxscore) { maxscore = score; if(depth == _search_depth) _bestmove = _move_generator.GetMovement(depth, i); } assert(_move_generator.UnMakeMove(_chessboard, depth, i)); } return maxscore; }
float negaMax(Node *node, int depth, int origDepth) { if (depth == 0) { // eval for even depths, -eval for odd depths if (origDepth % 2 == 0) return node->nodeVal; else return -node->nodeVal; } // choose the best child float bestScore = -INF; int bestChild = 0; for (int i = 0; i < node->nChildren; i++) { float curScore = -negaMax(&node->children[i], depth - 1, origDepth); if (curScore > bestScore) { bestScore = curScore; bestChild = i; } } node->nodeVal = bestScore; node->bestChild = bestChild; return bestScore; }
/** Un exemplu de functie de gandire care foloseste rezultatele furnizate de * o abordare negaMax exhaustiva. */ XOBoard negaMaxThink(XOBoard::Player player, XOBoard board) { /* La inceput, consideram alpha -8 si beta 8. Cu alte cuvinte, daca tabla ar * fi plina de marcajele oponentului, el ar castiga 3 linii, 3 coloane si 2 * diagonale (deci eu n-am cum sa fac mai rau de -8). Similar, din ce stiu eu * pana acum, oponentul n-are cum sa ma forteze sa joc in vreun fel, deci * consider ca as putea sa castig, ipotetic, pana la 8 (beta). */ return (negaMax(player, board, -INF, +INF)).second; }
int main2() { printf("\n\nSize of node is %zd bytes\n\n", sizeof(Node)); int randSeed = time(NULL); printf("Random Seed: %d\n", randSeed); srand(randSeed); // 3, and 4 are good. 6,7,8 are very good // 10 is excellent srand(10); // generate a random tree printf ("generating random tree of depth %d\n", g_depth); Node root = {0}; START_TIMER genTree(&root, g_depth); STOP_TIMER printf("random tree generated, total nodes: %d, leaf nodes: %d, time: %g ms\n", gTotalNodes, gLeafNodes, gTime); float bestVal = 0; // search the best move using min-max search printf("searching the tree using min-max\n"); START_TIMER bestVal = negaMax(&root, g_depth, g_depth); STOP_TIMER printf ("best move %d, score: %f, time: %g\n", root.bestChild, root.nodeVal, gTime); // search the best move using alpha-beta search printf("searching the tree using alpha-beta\n"); START_TIMER bestVal = alphabeta(&root, g_depth, g_depth, -INF, INF); STOP_TIMER printf ("best move %d, score: %f\nnodes visited (leaves/interior/total): %d/%d/%d\n", root.bestChild, root.nodeVal, gLeafNodesVisited, gInteriorNodesVisited, gLeafNodesVisited + gInteriorNodesVisited); printf("time taken: %g\n", gTime); START_TIMER exploreTree(&root, g_depth); STOP_TIMER printf("time taken: %g\n", gTime); float val; START_TIMER val = SSS_star(&root, g_depth); STOP_TIMER printf("SSS* best node: %d, score: %f, nodes explored: %d, time taken: %g\n", root.bestChild, val, g_sssNodes, gTime); freeTree(&root); getchar(); return 0; }
int ChessBoard::negaMax(int depth, int& mv) { if (depth==0) { return m_sdPlayer?m_vlBlack - m_vlRed:m_vlRed - m_vlBlack; } m_searchCallTimes++; int best = INT_MIN; Moves mvs; generateMoves(mvs); for(int i=0; i<mvs.count(); i++) { int oldVal = m_vlBlack - m_vlRed; int pcCapture; //qDebug()<<mvString(mvs[i]); if (makeMove(mvs[i], pcCapture)) { int temp_mv; int val = -1 * negaMax(depth-1, temp_mv); if (temp_mv) { //qDebug()<<mvString(temp_mv)<<" 评分"<<val; } if (val>best) { best = val; mv = mvs[i]; } undoMakeMove(mvs[i], pcCapture); //qDebug()<<"UndoMakeMove"; } int newVal = m_vlBlack - m_vlRed; Q_ASSERT(oldVal==newVal); } return best; }
bool NegaMax::searchAGoodMove(bool is_white_turn, Movement& bestmove) { negaMax(_search_depth, is_white_turn); bestmove = _bestmove; return true; }
/** Puteti folosi functia negaMax pentru a implementa un AI pe baza de negaMAX. * * Functia primeste ca parametri: * * player = Jucatorul care trebuie sa mute in continuare (identitatea * calculatorului care gandeste cu aceasta functie). * Valorile posibile sunt { XOBoard::PlayerX, XOBoard::PlayerO } * * board = Tabla pe care o vede jucatorul care trebuie sa mute in continuare. * * alpha = Inseamna ca player a gasit deja o cale prin care pot sa termin * jocul cu un scor cel putin egal cu alpha. * * beta = Inseamna ca OPPONENT(player) a gasit o cale prin care sa-l forteze pe * player sa termine jocul cu un scor cel mult egal cu beta (cu alte * cuvinte daca player gaseste o modalitate sa castige mai mult de beta, * cel mai probabil analizeaza un scenariu nerealist in care a presupus * ca OPPONENT(player) a fost prost la un moment dat si a facut o * greseala. */ std::pair<int, XOBoard> negaMax(XOBoard::Player player, XOBoard board, int alpha, int beta) { /* Daca s-a terminat jocul, scorul este cel raportat. */ if (board.game_over()) { int myScore = board.get_score(player) - board.get_score(OPPONENT(player)); return std::pair<int, XOBoard>(myScore, board); } /* Generam lista de expansiuni ale tablei (toate mutarile viitoare). */ std::vector<XOBoard> expansions; for (unsigned int i = 0; i < 3; ++i) { for (unsigned int j = 0; j < 3; ++j) { if (board.get(i, j) == '_') { board.put(player, i, j); expansions.push_back(board); board.erase(i, j); } } } /* Verificam care este mutarea cea mai inteleapta. */ XOBoard nextMove; for (unsigned int i = 0; i < expansions.size(); ++i) { /* Fiindca urmatorul nivel de negaMax este privit din partea oponentului, * cand apelez functia trebuie sa neg pe alfa si sa i-l servesc drept * beta pentru ca asta inseamna ca il "avertizez" ca nu sunt fraier si ca * deja stiu un mod prin care el nu poate sa faca mai mult decat -alpha. * * Pe de alta parte, desi eu il limitez pe el superior, din punct de vedere * inferior nu am nici un motiv sa-l limitez, asa ca ii voi servi un alpha * egal cu -INF (nu stiu cat de prost poate el sa joace, n-am cum sa-mi dau * seama). */ /* Acum ne gandim cum s-ar descurca el in situatia asta. */ std::pair<int, XOBoard> outcome = negaMax( OPPONENT(player), expansions[i], -INF, -alpha); /* Vedem ce miscare a reusit sa scoata el in conditiile date. */ int myScore = -outcome.first; /* Analizam jocul din perspectiva taierii alfa-beta. */ if (myScore > beta) { /* Inseamna ca asta e un scenariu in care el ar fi facut o greseala. Noi * stim ca el nu e prost, asa ca din moment ce a gasit deja mai sus in * arbore o modalitate prin care sa ma faca sa termin jocul cu cel mult * beta, n-o sa joace in asa fel incat sa ma puna pe mine in situatia * asta de acum. Aplicam deci, taierea beta. */ return std::pair<int, XOBoard>(beta, nextMove); } else if (myScore > alpha) { /* Inseamna ca tocmai am gasit o miscare prin care eu sa castig la sigur * mai mult decat stiam inainte ca pot sa castig (daca vreti, un fel de * plan "la sigur" mai bun). */ alpha = myScore; nextMove = expansions[i]; } } /* Raportam mutarea aleasa ca fiind cea mai buna. */ return std::pair<int, XOBoard>(alpha, nextMove); }