Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Napoleon - search.cpp

search.cpp

Caricato da: Crybot
Scarica il programma completo

  1. #include "search.h"
  2. #include "board.h"
  3. #include "movegenerator.h"
  4. #include "evaluation.h"
  5. #include "uci.h"
  6. #include <cassert>
  7. #include <cstring>
  8. #include <cstdio>
  9. #include <sstream>
  10. #include <boost/lexical_cast.hpp>
  11.  
  12. namespace Napoleon
  13. {
  14.     const int Search::AspirationValue = 50;
  15.     bool Search::MoveTime;
  16.     SearchTask Search::Task = Stop;
  17.     StopWatch Search::Timer;
  18.     int Search::Time[2] = { 60000, 60000 };
  19.     int Search::ThinkTime;
  20.     int Search::moveScores[Constants::MaxMoves];
  21.     int Search::history[2][4096];
  22.     int Search::nodes;
  23.     Move Search::killerMoves[Constants::MaxPly][2];
  24.  
  25.     // direct interface to the client.
  26.     // it sends the move to the uci gui
  27.     void Search::StartThinking(Board& board)
  28.     {
  29.         int time = Time[board.SideToMove];
  30.  
  31.         // if we have an exact movetime we use that value, else we use
  32.         // a fraction of the side to move remaining time
  33.         ThinkTime = MoveTime ? ThinkTime : (time / (30 - (time/(60*1000))));
  34.  
  35.         if (Task != Infinite) Task = Think;
  36.  
  37.         Move move = iterativeSearch(board);
  38.         Uci::SendCommand<Command::Move>(move.ToAlgebraic());
  39.     }
  40.  
  41.     void Search::StopThinking()
  42.     {
  43.         Task = Stop;
  44.     }
  45.  
  46.     // iterative deepening
  47.     Move Search::iterativeSearch(Board& board)
  48.     {
  49.         Move move;
  50.         Move toMake = Constants::NullMove;
  51.         int score;
  52.         int temp;
  53.         int i = 1;
  54.  
  55.         memset(history, 0, sizeof(history));
  56.  
  57.         Timer.Start();
  58.         nodes = 0;
  59.  
  60.         score = searchRoot(i++, -Constants::Infinity, Constants::Infinity, move, board);
  61.  
  62.         while((i<100 && Timer.Stop().ElapsedMilliseconds() < ThinkTime && Timer.Stop().ElapsedMilliseconds() / ThinkTime < 0.50) || Task == Infinite )
  63.         {
  64.             if (Task == Stop)
  65.                 break;
  66.  
  67.             nodes = 0;
  68.             //            aspiration search
  69.             temp = searchRoot(i, score - AspirationValue, score + AspirationValue, move, board);
  70.  
  71.             if (temp <= score - AspirationValue || temp >= score + AspirationValue)
  72.                 temp = searchRoot(i, -Constants::Infinity, Constants::Infinity, move, board);
  73.             score = temp;
  74.  
  75.             if (score != -Constants::Unknown)
  76.                 toMake = move;
  77.  
  78.             i++;
  79.         }
  80.  
  81.         StopThinking();
  82.  
  83.         return toMake;
  84.  
  85.         //       std::cout << "Cutoff on first move: " << board.FirstMoveCutoff << std::endl;
  86.         //       std::cout << "Total Cutoffs: " << board.TotalCutoffs << std::endl;
  87.         //       std::cout <<  Console::Green << "First Move Cutoff percentage: " << ((float)board.FirstMoveCutoff / (float)board.TotalCutoffs) * 100 << "%" << std::endl;
  88.         //       std::cout <<  Console::Reset << std::endl;
  89.         //       board.FirstMoveCutoff = 0;
  90.         //       board.TotalCutoffs = 0;
  91.  
  92.     }
  93.  
  94.     int Search::searchRoot(int depth, int alpha, int beta, Move& moveToMake, Board& board)
  95.     {
  96.         int pos = 0;
  97.         int move = 0;
  98.         int score;
  99.         int startTime = Timer.Stop().ElapsedMilliseconds();
  100.         Move moves[Constants::MaxMoves];
  101.  
  102.         MoveGenerator::GetLegalMoves(moves, pos, board);
  103.  
  104.         for (int i=0; i<pos; i++)
  105.         {
  106.             if ((Timer.Stop().ElapsedMilliseconds() >= ThinkTime || Timer.Stop().ElapsedMilliseconds()/ThinkTime >= 0.65 || Task == Stop) && Task != Infinite)
  107.                 return -Constants::Unknown;
  108.  
  109.             board.MakeMove(moves[i]);
  110.             score = -Search::search(depth-1, -beta, -alpha, board);
  111.             board.UndoMove(moves[i]);
  112.  
  113.             if (score > alpha)
  114.             {
  115.                 move = i;
  116.                 if (score >= beta)
  117.                 {
  118.                     moveToMake = moves[i];
  119.                     Uci::SendCommand<Command::Info>(GetInfo(board, moveToMake, beta, depth, startTime)); // sends info to the gui
  120.                     return beta;
  121.                 }
  122.  
  123.                 alpha = score;
  124.             }
  125.         }
  126.  
  127.         moveToMake = moves[move];
  128.         Uci::SendCommand<Command::Info>(GetInfo(board, moveToMake, alpha, depth, startTime)); // sends info to the gui
  129.  
  130.         return alpha;
  131.     }
  132.  
  133.     int Search::search(int depth, int alpha, int beta, Board& board)
  134.     {
  135.         nodes++;
  136.  
  137.         bool futility = false;
  138.         int bound = Alpha;
  139.         int pos = 0;
  140.         int score;
  141.         int legal = 0;
  142.         Move best = Constants::NullMove;
  143.         Move moves[Constants::MaxMoves];
  144.  
  145.         // Transposition table lookup
  146.         if((score = board.Table.Probe(board.zobrist, depth, alpha, &best, beta)) != TranspositionTable::Unknown)
  147.             return score;
  148.  
  149.         // call to quiescence search
  150.         if(depth == 0)
  151.             return quiescence(alpha, beta, board);
  152.  
  153.         BitBoard pinned = board.GetPinnedPieces();
  154.  
  155.         BitBoard attackers = board.KingAttackers(board.KingSquare(board.SideToMove), board.SideToMove);
  156.  
  157.         // enhanced deep razoring
  158.         if (depth < 4
  159.                 && !attackers
  160.                 && board.Material(board.SideToMove) > Constants::Eval::MiddleGameMat
  161.                 && best.IsNull()
  162.                 && !board.IsPromotingPawn())
  163.         {
  164.             score = Evaluation::Evaluate(board);
  165.  
  166.             int margin = razorMargin(depth);
  167.  
  168.             if (score + margin <= alpha)
  169.             {
  170.                 int s = quiescence(alpha-margin, beta-margin, board);
  171.                 if (s <= alpha - margin)
  172.                     return s;
  173.             }
  174.         }
  175.  
  176.         // adaptive null move pruning
  177.         if(board.AllowNullMove
  178.                 && depth >= 3
  179.                 && !attackers
  180.                 && board.Material(board.SideToMove) > Constants::Eval::MiddleGameMat)
  181.         {
  182.             int R = depth > 5 ? 3 : 2; // dynamic depth-based reduction
  183.  
  184.             board.MakeNullMove();
  185.             score = -search(depth - R - 1 , -beta, -beta+1, board); // make a null-window search (we don't care by how much it fails high, if it does)
  186.             board.UndoNullMove();
  187.  
  188.             if(score >= beta)
  189.                 return beta;
  190.         }
  191.  
  192.         // internal iterative deepening (IID)
  193.         if (depth >= 3 && best.IsNull())
  194.         {
  195.             int R = 2;
  196.  
  197.             search(depth - R - 1, Constants::Infinity, -Constants::Infinity, board); // make a full width search to find a new bestmove
  198.  
  199.             //Transposition table lookup
  200.             if((score = board.Table.Probe(board.zobrist, depth, alpha, &best, beta)) != TranspositionTable::Unknown)
  201.                 return score;
  202.         }
  203.  
  204.         // make best move (hash move)
  205.         if(!best.IsNull())
  206.         {
  207.             assert(board.IsMoveLegal(best, pinned));
  208.             legal++;
  209.             board.MakeMove(best);
  210.             score = -search(depth - 1, -beta, -alpha, board);
  211.             board.UndoMove(best);
  212.  
  213.             if(score >= beta)
  214.             {
  215.                 board.FirstMoveCutoff++; // DEBUG
  216.                 board.TotalCutoffs++; // DEBUG
  217.                 return beta;
  218.             }
  219.  
  220.             if(score > alpha)
  221.             {
  222.                 bound = Exact;
  223.                 alpha = score;
  224.             }
  225.         }
  226.  
  227.                 if (board.IsRepetition())
  228.                         return 0;
  229.  
  230.         // extended futility pruning condition
  231.         if (!attackers
  232.                 && depth <=2
  233.                 && std::abs(alpha) < Constants::Infinity-100
  234.                 && Evaluation::Evaluate(board) + futilityMargin(depth) <= alpha)
  235.         {
  236.             futility = true;
  237.         }
  238.  
  239.  
  240.         MoveGenerator::GetPseudoLegalMoves<false>(moves, pos, attackers, board); // get captures and non-captures
  241.         setScores(moves, board, depth, pos); // set moves score used by 'pickMove' for picking the best untried move
  242.  
  243.         // principal variation search
  244.         bool PVS = true;
  245.         bool capture;
  246.  
  247.         for(int i=0; i<pos; i++)
  248.         {
  249.             pickMove(moves, i, pos);
  250.             if(board.IsMoveLegal(moves[i], pinned))
  251.             {
  252.                 legal++;
  253.                 capture = board.IsCapture(moves[i]);
  254.                 board.MakeMove(moves[i]);
  255.  
  256.                 // extended futility pruning application
  257.                 if (futility
  258.                         && i > 0
  259.                         && !capture
  260.                         && !moves[i].IsPromotion()
  261.                         && !board.KingAttackers(board.KingSquare(board.SideToMove), board.SideToMove)
  262.                         )
  263.                 {
  264.                     board.UndoMove(moves[i]);
  265.                     continue;
  266.                 }
  267.  
  268.                 if (PVS)
  269.                 {
  270.                     score = -search(depth-1, -beta, -alpha, board);
  271.                 }
  272.                 else
  273.                 {
  274.                     score = -search(depth-1, -alpha-1, -alpha, board);
  275.                     if (score > alpha/* && score < beta*/)
  276.                         score = -search(depth-1, -beta, -alpha, board);
  277.                 }
  278.  
  279.                 board.UndoMove(moves[i]);
  280.  
  281.                 if( score >= beta )
  282.                 {
  283.                     //killer moves and history heuristic
  284.                     if(!board.IsCapture(moves[i]))
  285.                     {
  286.                         if (moves[i] != killerMoves[depth][0])
  287.                         {
  288.                             killerMoves[depth][1] = killerMoves[depth][0];
  289.                         }
  290.                         killerMoves[depth][0] = moves[i];
  291.                         history[board.SideToMove][moves[i].ButterflyIndex()] += depth*depth;
  292.                     }
  293.  
  294.                     board.Table.Save(board.zobrist, depth, beta, best, Beta);
  295.  
  296.                     if (i == 0) // DEBUG
  297.                         board.FirstMoveCutoff++; // DEBUG
  298.  
  299.                     board.TotalCutoffs++; // DEBUG
  300.  
  301.                     return beta;   //  fail hard beta-cutoff
  302.                 }
  303.                 if(score > alpha)
  304.                 {
  305.                     PVS = false;
  306.                     bound = Exact;
  307.                     alpha = score; // alpha acts like max in MiniMax
  308.                     best = moves[i];
  309.                 }
  310.             }
  311.         }
  312.  
  313.         // check for stalemate and checkmate
  314.         if (legal == 0)
  315.         {
  316.             if (board.IsCheck)
  317.                 alpha = -Constants::Infinity - depth; // return best score (for the minimazer) for the deepest mate
  318.             else
  319.                 alpha = 0; // return draw score (TODO contempt factor)
  320.         }
  321.  
  322.         // check for fifty moves rule
  323.         if (board.HalfMoveClock >= 100)
  324.             alpha = 0;
  325.  
  326.         board.Table.Save(board.zobrist, depth, alpha, best, bound);
  327.         return alpha;
  328.     }
  329.  
  330.     // quiescence is called at horizon nodes (depth = 0)
  331.     int Search::quiescence(int alpha, int beta, Board& board)
  332.     {
  333.         nodes++;
  334.         int stand_pat = Evaluation::Evaluate(board);
  335.         if( stand_pat >= beta )
  336.             return beta;
  337.  
  338.         int Delta = Constants::Piece::PieceValue[PieceType::Queen];
  339.  
  340.         if (board.IsPromotingPawn())
  341.             Delta += Constants::Piece::PieceValue[PieceType::Queen] - Constants::Piece::PieceValue[PieceType::Pawn];
  342.  
  343.         // big delta futility pruning
  344.         if ( stand_pat < alpha - Delta)
  345.             return alpha;
  346.  
  347.         if( alpha < stand_pat )
  348.             alpha = stand_pat;
  349.  
  350.         int pos = 0;
  351.         int score;
  352.         Move moves[Constants::MaxMoves];
  353.  
  354.         BitBoard attackers = board.KingAttackers(board.KingSquare(board.SideToMove), board.SideToMove);
  355.  
  356.         if (attackers)
  357.             return search(1, alpha, beta, board);
  358.  
  359.         MoveGenerator::GetPseudoLegalMoves<true>(moves, pos, attackers, board); // get only captures
  360.  
  361.         BitBoard pinned = board.GetPinnedPieces();
  362.         for(int i=0; i<pos; i++)
  363.         {
  364.             orderCaptures(moves, board, i, pos);
  365.             if (board.IsMoveLegal(moves[i], pinned))
  366.             {
  367.                 // delta futility pruning
  368.                 if (Constants::Piece::PieceValue[board.PieceSet[moves[i].ToSquare()].Type] + stand_pat + 200 < alpha && !moves[i].IsPromotion())
  369.                     continue;
  370.  
  371.                 board.MakeMove(moves[i]);
  372.                 score = -quiescence( -beta, -alpha, board);
  373.                 board.UndoMove(moves[i]);
  374.  
  375.                 if( score >= beta )
  376.                     return beta;
  377.                 if( score > alpha )
  378.                     alpha = score;
  379.             }
  380.         }
  381.         return alpha;
  382.     }
  383.  
  384.     /// set scores for sorting moves
  385.     /// 1) winning captures
  386.     /// 2) equal captures
  387.     /// 3) killer moves
  388.     /// 4) losing captures
  389.     /// 5) all other moves in history heuristic order
  390.     ///
  391.     /// Every history move has a positive score, so to order them such that they
  392.     /// are always after losing captures (which have a score <= 0) we found
  393.     /// the minimum score of the captures and the maximum score of the history moves.
  394.     /// Then we assing to each history move a score calculated with this formula:
  395.     /// Score = HistoryScore - HistoryMax + CapturesMin - 3
  396.     /// The - 3 factor handle the situation where there are no losing captures,
  397.     /// but history moves should still stay after killer moves
  398.     /// (which have score -1 and -2). Without that, the best history move
  399.     /// would score 0 and would be analyzed before killer moves.
  400.  
  401.     void Search::setScores(Move moves[], Board& board, int depth, int high)
  402.     {
  403.         using namespace Constants::Piece;
  404.  
  405.         int min = 0;
  406.         int max = 0;
  407.         int captureScore;
  408.         int historyScore;
  409.         int captured;
  410.  
  411.         for (int i=0; i<high; i++)
  412.         {
  413.             captured = board.PieceSet[moves[i].ToSquare()].Type;
  414.             // MVV-LVA
  415.             if (board.IsCapture(moves[i]))
  416.             {
  417.                 moveScores[i] = captureScore = PieceValue[captured] - PieceValue[board.PieceSet[moves[i].FromSquare()].Type];
  418.                 if (captureScore < min)
  419.                     min = captureScore;
  420.             }
  421.             else if (moves[i] == killerMoves[depth][0])
  422.                 moveScores[i] = - 1 ;
  423.             else if (moves[i] == killerMoves[depth][1])
  424.                 moveScores[i] = - 2 ;
  425.             else if ((historyScore = history[board.SideToMove][moves[i].ButterflyIndex()]) > max)
  426.                 max = historyScore;
  427.         }
  428.  
  429.         for (int i=0; i<high; i++)
  430.         {
  431.             if (!board.IsCapture(moves[i]) && moves[i] != killerMoves[depth][0] && moves[i] != killerMoves[depth][1])
  432.                 moveScores[i] = history[board.SideToMove][moves[i].ButterflyIndex()] - max + min - 3;
  433.         }
  434.     }
  435.  
  436.     // make a selection sort on the move array for picking the best untried move
  437.     void Search::pickMove(Move moves[], int low, int high)
  438.     {
  439.         int max = low;
  440.  
  441.         for (int i=low+1; i<high; i++)
  442.             if (moveScores[i] > moveScores[max])
  443.                 max = i;
  444.  
  445.         if (max != low)
  446.         {
  447.             Move temp = moves[low];
  448.             moves[low] = moves[max];
  449.             moves[max] = temp;
  450.  
  451.             int tempScore = moveScores[low];
  452.             moveScores[low] = moveScores[max];
  453.             moveScores[max] = tempScore;
  454.         }
  455.     }
  456.  
  457.     // sort only captures (only used in quiescence search)
  458.     void Search::orderCaptures(Move moves[], Board& board, int low, int high)
  459.     {
  460.         /// MVV-LVA
  461.         /// (Most Valuable Victim - Least Valuable Aggressor)
  462.  
  463.         using namespace Constants::Piece;
  464.  
  465.         int min = low;
  466.         int from;
  467.         int to;
  468.         Byte captured;
  469.  
  470.         for (int i=low+1; i<high; i++)
  471.         {
  472.             if (board.IsCapture(moves[i]))
  473.             {
  474.                 from = moves[i].FromSquare();
  475.                 to = moves[i].ToSquare();
  476.                 captured = board.PieceSet[to].Type;
  477.  
  478.                 if (PieceValue[captured] >  PieceValue[board.PieceSet[moves[min].ToSquare()].Type])
  479.                 {
  480.                     if (min != low)
  481.                     {
  482.                         if (PieceValue[board.PieceSet[from].Type] < PieceValue[board.PieceSet[moves[min].FromSquare()].Type])
  483.                             min = i;
  484.                     }
  485.                     else
  486.                         min = i;
  487.                 }
  488.             }
  489.         }
  490.  
  491.         if (min != low)
  492.         {
  493.             Move temp = moves[low];
  494.             moves[low] = moves[min];
  495.             moves[min] = temp;
  496.         }
  497.     }
  498.  
  499.     // extract the pv line from transposition table
  500.     std::string Search::GetPv(Board& board, Move toMake, int depth)
  501.     {
  502.         std::string pv;
  503.  
  504.         if (toMake.IsNull() || depth == 0)
  505.             return pv;
  506.         else
  507.         {
  508.             pv = toMake.ToAlgebraic() + " ";
  509.  
  510.             board.MakeMove(toMake);
  511.             pv += GetPv(board, board.Table.GetPv(board.zobrist), depth-1);
  512.             board.UndoMove(toMake);
  513.  
  514.             return pv;
  515.         }
  516.     }
  517.  
  518.     // return search info
  519.     std::string Search::GetInfo(Board& board, Move toMake, int score, int depth, int lastTime)
  520.     {
  521.         std::ostringstream info;
  522.         double delta = Timer.Stop().ElapsedMilliseconds() - lastTime;
  523.         double nps = (delta > 0 ? nodes / delta : nodes / 1)*1000;
  524.  
  525.         info << "depth " << depth << " score cp " << score << " time " << Timer.Stop().ElapsedMilliseconds() << " nodes "
  526.              << nodes << " nps " << int(nps) << " pv " << GetPv(board, toMake, depth);
  527.  
  528.         return info.str();
  529.     }
  530. }