Data hosted with ♥ by Pastebin.com - Download Raw - See Original
  1.   // Iterative Deepening Search
  2.   // Calls mtdf at each depth until we hit the depth limit or run out of time
  3.   List itSearch(Position pos, int timeLimit){
  4.     nodesSearched = 0;
  5.     tpLookups = 0;
  6.     tpScore.clear(); // clear the transposition table for scores but not for moves
  7.  
  8.     Stopwatch timer = new Stopwatch()..start();
  9.     int startTime = timer.elapsedMilliseconds;
  10.     Move bestMove;
  11.     double f = 0;
  12.     int maxDepthSearched = 0;
  13.  
  14.     for(int depth = 1; depth <= MAX_SEARCH_DEPTH; depth++){
  15.       int nodesSoFar = nodesSearched;
  16.       tpScore.clear();
  17.       pos.stage = 0;
  18.       maxDepthSearched = depth;
  19.       List _l = mtdf(pos, f, depth, timer, timeLimit);
  20.       f = _l[0];
  21.       if(true) bestMove = tpMove[pos.hash]; // if(_l[1]) -> only uses moves computed by a complete mtdf evaluation
  22.  
  23.       double timeTaken = (timer.elapsedMilliseconds - startTime) / 1000;
  24.       int nodesHere = nodesSearched - nodesSoFar;
  25.       double nps = nodesHere/timeTaken;
  26.       print("Searched depth $depth: $nodesHere nodes in ${timeTaken.toStringAsFixed(2)}s (${nps.toStringAsFixed(2)} NPS)");
  27.       print("Best move so far is ${bestMove.name}");
  28.       if(timer.elapsedMilliseconds - startTime > timeLimit){
  29.         print("Time limit hit");
  30.         break;
  31.       }
  32.     }
  33.  
  34.     // This part is just for formatting lines to print to the console
  35.     Map lines = Map();
  36.     for(Move m in pos.moves){
  37.       String mvName = formatMove(m);
  38.       lines[mvName] = [(pos.board[PLAYER_TURN] == WHITE)?"${pos.board[FULLMOVE_COUNTER]} ":"", m.endPosition.eval];
  39.       Position p = m.endPosition;
  40.       Move nextMove = p.bestMove;
  41.       bool first = true;
  42.       while(p != null && nextMove != null){
  43.         String next = formatMoveForList(p.board, nextMove, first);
  44.         first = false;
  45.         lines[mvName][0] += next;
  46.         //print("Checking ${nextMove.name}");
  47.         p = nextMove.endPosition;
  48.         //print(p.bestMove);
  49.         if(p != null) nextMove = p.bestMove;
  50.         if(nextMove == null){
  51.           //print("next move after ${p.previousMove.name} is null");
  52.           break;
  53.         }
  54.       }
  55.     }
  56.     double timeTaken = (timer.elapsedMilliseconds - startTime) / 1000;
  57.     double nps = nodesSearched/timeTaken;
  58.     print("Iterative search terminated at depth $maxDepthSearched, having searched $nodesSearched nodes in ${timeTaken.toStringAsFixed(2)}s (${nps.toStringAsFixed(2)} NPS)");
  59.     print("$tpLookups transposition table lookups");
  60.     return [bestMove, f, lines];
  61.   }
  62.  
  63.   // Aske Plaat's MTD(f) algorithm
  64.   // Calls abm (Alpha-Beta with Memory) a number of times with a gradually converging search window
  65.   List mtdf(Position pos, double target, int depth, Stopwatch timer, int timeLimit){
  66.     double currentEstimate = target;
  67.     var upper = MATE_UPPER;
  68.     var lower = -MATE_UPPER;
  69.     int pass = 1;
  70.     while(lower < upper){
  71.       double beta = max(currentEstimate, lower + 0.01);
  72.       currentEstimate = -abm(pos, 1-pos.board[PLAYER_TURN], depth, 0, beta - 0.01, beta, true, false);
  73.       print("MTDF pass $pass (currentEstimate: $currentEstimate), beta: ${beta.toStringAsFixed(2)} lower: ${lower.toStringAsFixed(2)}, upper: ${upper.toStringAsFixed(2)}");
  74.       if(currentEstimate < beta) upper = currentEstimate;
  75.       else lower = currentEstimate;
  76.       pass++;
  77.       if(timer.elapsedMilliseconds > timeLimit){
  78.         print("Time limit hit in mtdf");
  79.         return [currentEstimate, false];
  80.         break;
  81.       }
  82.     }
  83.     return [currentEstimate, true];
  84.   }
  85.  
  86.   // Alpha-Beta with Memory (Negamax)
  87.   double abm(Position pos, int player, int depth, int stage, double alpha, double beta, bool root, [bool allowNull = true]){
  88.     //print("ABM");
  89.     nodesSearched++;
  90.  
  91.     // Check transposition tables haven't grown too large
  92.     if(tpMove.length > MAX_TRANSPOSITIONS){
  93.       print("Clearing transposition table for moves (${tpMove.length})");
  94.       tpMove.clear();
  95.     }
  96.     if(tpScore.length > MAX_TRANSPOSITIONS){
  97.       print("Clearing transposition table for scores (${tpScore.length})");
  98.       tpScore.clear();
  99.     }
  100.    
  101.     // retrieve stored values from transposition table
  102.     List _tpScore = tpScore[pos.dhash];
  103.     if(_tpScore != null){
  104.       tpLookups++;
  105.       if(_tpScore[0] >= beta) return _tpScore[0];
  106.       if(_tpScore[1] <= alpha) return _tpScore[1];
  107.       alpha = max(alpha, _tpScore[0]);
  108.       beta = min(beta, _tpScore[1]);
  109.       //print("($stage) tp lookup $alpha $beta");
  110.     }else{
  111.       tpScore[pos.dhash] = [-MATE_UPPER, MATE_UPPER];
  112.     }
  113.    
  114.     double g;
  115.     if(tpMove[pos.hash] == null) tpMove[pos.hash] = pos.moves[0];
  116.     bool qSearch = (depth <= 0); // are we in quiescence search?
  117.                                  // if so, only generate noisy moves (captures)
  118.     List<Move> _moves = (!qSearch)?pos.moves:pos.moves.where((_m) => !_m.quiet).toList();
  119.     //print("Moves: ${_moves.map((x) => x.name).toList()}");
  120.    
  121.     // Null-Move Heuristic
  122.     // CURRENTLY DISABLED FOR SIMPLIITY / DEBUGGING
  123.     if(allowNull){
  124.       Move _nullMove = Move.nullMove(pos);
  125.       double eval = -abm(_nullMove.endPosition, 1-player, depth - 3, stage + 1, -beta, -beta+0.0001, false, false);
  126.       if(eval >= beta){
  127.         pos.eval = eval;
  128.         return eval;
  129.       }
  130.     }
  131.     // ---------
  132.  
  133.     // -10 means 10 moves into quiescence search, increasing doesn't change much except massively slowing everything down
  134.     if(_moves.length == 0 || depth <= -10) g = (player != pos.analysisPlayer)?pos.score:-pos.score; // if we've hit the end of the evaluation
  135.     else{
  136.       if(qSearch){ // STAND PAT
  137.         double stand_pat = (player != pos.analysisPlayer)?pos.score:-pos.score;
  138.         if(stand_pat >= beta) return beta;
  139.         if(alpha < stand_pat) alpha = stand_pat;
  140.       }
  141.      
  142.       g = -MATE_UPPER;
  143.       double a = alpha;
  144.  
  145.       for(Move m in _moves){
  146.         if(m.valid){
  147.           m.endPosition.eval = -abm(m.endPosition, 1-player, depth - 1, stage + 1, -beta, -a, false, false);
  148.          
  149.           if(m.endPosition.eval > g){
  150.             tpMove[pos.hash] = m;
  151.             pos.bestMove = m;
  152.             g = m.endPosition.eval;
  153.           }
  154.  
  155.           if(g > a){
  156.             a = g;
  157.             tpMove[pos.hash] = m;
  158.             pos.bestMove = m;
  159.           }
  160.         }
  161.  
  162.         // CUTOFF
  163.         //if(a >= beta) break;
  164.       }
  165.     }
  166.  
  167.     if(g <= alpha) tpScore[pos.dhash][1] = g; // fail low implies an upper bound
  168.     if(g > alpha && g < beta){ // found an accurate minimax value
  169.       tpScore[pos.dhash] = [g,g];
  170.     }
  171.     if(g >= beta) tpScore[pos.dhash][0] = g; // fail high implies a lower bound
  172.  
  173.     return g;
  174.   }