1 #include	"Engine.h"
2 
3 #include	<strstream>
4 #include	<vector>
5 #include	<stack>
6 
7 
8 #include	"Board.h"
9 #include	"Evaluator.h"
10 #include	"Lawyer.h"
11 #include	"Move.h"
12 #include	"Transposition.h"
13 #include	"OpeningBook.h"
14 #include	"Interface.h"
15 
16 //#include	<ctime>
17 
18 //#include	<sys/timeb.h>
19 
20 #ifdef HAVE_CONFIG_H
21 # include	"../config.h"
22 #endif
23 
24 #ifndef BOOKDIR
25 # define BOOKDIR "."
26 #else
27 # ifdef WIN32
28 #  undef BOOKDIR
29 #  define BOOKDIR "."
30 # endif
31 #endif
32 
33 /* Engine.cpp (c) Noah Roberts 2003-02-27
34  */
35 using namespace std;
36 
37 enum { SEARCH_AB, SEARCH_PV, SEARCH_NS };
38 enum { SEARCHING, DONE_SEARCHING, BETWEEN_SEARCHES };
39 enum { NO_ABORT = 0, ABORT_TIME, ABORT_READ };
40 
Engine(Board * brd,Lawyer * law,Interface * ifc)41 Engine::Engine(Board *brd, Lawyer *law, Interface *ifc)
42 {
43   // associations...
44   board = brd;
45   lawyer = law;
46   evaluator = Evaluator::defaultEvaluator();
47   openingBook = NULL;
48   transposTable = new TranspositionTable(18);
49   iface = ifc;
50 
51   // Options and their defaults...
52   maxPly 		  = 6;
53   quiesc 		  = false;
54   qnull			  = false;
55   qhash			  = true;
56   useOpeningBook 	  = true;
57   displayThinking 	  = false;
58   searchMethod 		  = SEARCH_AB;
59   verifyNull 		  = true;
60   nullMoveReductionFactor = 3;
61   useMTDF 		  = true;
62   allowNull 		  = true;
63   useIterDeep 		  = true;
64   allowTableWindowAdjustments = false;
65   useTable		  = true;
66 
67   searchAborted = NO_ABORT;
68   //continueSearching = true;
69   searchState = BETWEEN_SEARCHES;
70   Timer::setTimers(0, 0, 0); // Shut off timers by default.
71 
72   // Register with Options class
73   Options::defaultOptions()->addObserver(this);
74 
75 
76   // Open opening book if ok to do so.
77   if (useOpeningBook && openingBook == NULL)
78     {
79       openingBook = new OpeningBook(string(BOOKDIR)+"/book.dat");
80       if (!openingBook->valid()) // can't open system book, try working directory
81         {
82           delete openingBook;
83           openingBook = new OpeningBook("book.dat");
84         }
85       if (!openingBook->valid()) // if it still is not a valid book...
86         {
87           delete openingBook;
88           openingBook = NULL;
89           cerr << "Can't open my book!\n";
90         }
91     }
92 }
93 
94 // Move sort comparison function and required variables...
95 static Board *_board = NULL;
96 static vector<Move> priorityTable;
compareMoves(Move & m1,Move & m2)97 bool compareMoves(Move &m1, Move &m2)
98 {
99   Evaluator *e = Evaluator::defaultEvaluator();
100   int m1Index = -1, m2Index = -1;
101   for (int i = 0; i < ::priorityTable.size(); i++)
102     {
103       if (::priorityTable[i] == m1) m1Index = i;
104       if (::priorityTable[i] == m2) m2Index = i;
105     }
106   if (m1Index == -1 && m2Index != -1) return false;
107   if (m2Index == -1 && m1Index != -1) return true;
108   if (m1Index < m2Index) return true;
109   if (m2Index < m1Index) return false;
110 
111   // Now that killer moves and transposition table are taken into account we try to order other moves logically
112 
113   // king moves last...
114   if (_board->pieceAt(m1.origin()) == JIANG && _board->pieceAt(m2.origin()) != JIANG) return false;
115   if (_board->pieceAt(m2.origin()) == JIANG && _board->pieceAt(m1.origin()) != JIANG) return true;
116 
117   // Captures: MVV/LVA (Most Valuable Victem/Least Valuable Attacker)
118   if (e->pieceValue(_board->pieceAt(m1.destination())) < e->pieceValue(_board->pieceAt(m2.destination()))) return false;
119   if (e->pieceValue(_board->pieceAt(m1.destination())) > e->pieceValue(_board->pieceAt(m2.destination()))) return true;
120   if (e->pieceValue(_board->pieceAt(m1.origin())) == e->pieceValue(_board->pieceAt(m2.origin()))) return false;
121   if (e->pieceValue(_board->pieceAt(m1.origin())) < e->pieceValue(_board->pieceAt(m2.origin())))
122     {
123       if (_board->pieceAt(m1.destination()) == EMPTY)
124         return false;
125       else
126         return true;
127     }
128   else
129     {
130       if (_board->pieceAt(m1.destination()) == EMPTY)
131         return true;
132       else
133         return false;
134     }
135 }
136 
137 // Adds killer move to the queue.
newKiller(Move & theMove,int ply)138 void Engine::newKiller(Move& theMove, int ply)
139 {
140   Move temp;
141   if (killer1.size() > ply)
142     {
143       temp = killer1[ply];
144       killer1[ply] = theMove;
145       if (killer2.size() > ply)
146         killer2[ply] = temp;
147       else
148         killer2.push_back(theMove);
149     }
150   else
151     killer1.push_back(theMove);
152 }
153 
154 // Implementation of MTD(f)...
mtd(std::vector<PVEntry> & pv,long guess,int depth)155 long Engine::mtd(std::vector<PVEntry> &pv, long guess, int depth)
156   /* Inputs : pv, estimated best score, max depth.
157      Outputs: pv, actual best score. */
158 {
159   /*
160    * NOTE: it appears that null move heuristics cause too many troubles when used with
161    *       this algorithm.  Therefor null move will not be used when using mtd. */
162   long g = guess;
163   long upperbound = INFIN;
164   long lowerbound = -INFIN;
165   long beta = 0;
166   long best = -INFIN;
167   vector<PVEntry> tempPV;
168   do
169     {
170       pv.clear(); // Clear the principle variation...
171 
172       // Move window.
173       if (g == lowerbound) beta = g+1;
174       else beta = g;
175 
176       // Search a window with 0 thickness - all searches fail...
177       g = search(pv, beta-1, beta, 0, depth, false, true, true);
178 
179       // slide boundaries
180       if (g < beta) upperbound = g;
181       else lowerbound = g;
182 
183     } while (lowerbound < upperbound); // When lower is >= upper then we have zeroed in on
184                                        // the best score.
185 
186 
187   return g;
188 }
189 
think()190 long Engine::think()
191   /* Inputs : NONE
192      Outputs: score of primary line and a filled principle variation. */
193 {
194   if (searchState == DONE_SEARCHING)
195     {
196       cerr << "Shouldn't be happening: think()\n";
197       return 0;
198     }
199   // Before we do ANYTHING else, check if the current position is recorded in the opening
200   // database, if so then use the move returned (if legal).
201   myTimer = Timer::timerForColor(board->sideToMove());
202 
203   //struct timeb startTime;
204   if (searchState == BETWEEN_SEARCHES)
205     {
206       searchState = SEARCHING;
207       principleVariation.clear();
208       //myTimer->startTimer();
209       transposTable->flush();
210       killer1.clear(); killer2.clear();
211       ftime(&startTime);
212       nodeCount = 0;
213     }
214   if (useOpeningBook && openingBook != NULL && openingBook->valid())
215     {
216       unsigned short moveNum = openingBook->getMove(board);
217       if (moveNum)
218         {
219           Move m((int)(moveNum >> 8), (int)(moveNum & 255));
220           if (!(m.origin() == 0 && m.destination() == 0) && // should never fail
221               lawyer->legalMove(m)) // maybe book file has illegal moves in it.
222             {
223               principleVariation.push_back(PVEntry(m,NO_CUTOFF));
224               //myTimer->stopTimer();
225               searchState = DONE_SEARCHING;
226               return evaluator->evaluatePosition(*board, *lawyer);
227             }
228         }
229     }
230 
231   // clear everything that was set by the previous search
232   // Estemate outcome...evaluate current board.
233   long result = evaluator->evaluatePosition(*board, *lawyer);
234 
235   searchAborted = NO_ABORT;
236 
237   for (int i = (useIterDeep ? (principleVariation.size()+1):maxPly);
238        i <= maxPly && !searchAborted;
239        i++)
240     {
241       vector<PVEntry> iterPV;
242       result = (useMTDF ?
243                 mtd((useIterDeep ? iterPV:principleVariation),result,i) :
244                 search((useIterDeep ? iterPV:principleVariation), -INFIN,INFIN,0,i));
245       if (!searchAborted && useIterDeep)
246         principleVariation = iterPV;
247       if (displayThinking)
248         {
249           struct timeb nowTime;
250           int plyMod = (searchAborted ? -1:0);
251           ftime(&nowTime);
252           cout << (searchAborted ? "*":"") << (i+plyMod) << "\t" << result << "\t" << nodeCount << "\t"
253                << (100*(nowTime.time - startTime.time)) + ((nowTime.millitm - startTime.millitm)/10) << "\t"
254                << variationText(principleVariation) << endl;
255           cout.flush();
256         }
257     }
258   // We may continue searching if there is data to be read...depends on what we read.
259   if (searchAborted != ABORT_READ) // a) finished, b) timed out - either way we are done.
260     { // Search is completed then.
261       //myTimer->stopTimer();
262       searchState = DONE_SEARCHING;
263     }
264 
265   return result;
266 }
getMove()267 Move Engine::getMove()
268 {
269   searchState = BETWEEN_SEARCHES; //
270   if (principleVariation.size() > 0) // should never not be when called.
271     return principleVariation[0].move;
272   else
273     return Move();
274 }
275 
optionChanged(string whatOption)276 void Engine::optionChanged(string whatOption)
277 {
278   if (whatOption == "searchPly")
279     {
280       string x = Options::defaultOptions()->getValue(whatOption);
281       istrstream strm((char*)x.c_str(), x.length());
282       strm >> maxPly;
283     }
284   else if (whatOption == "quiescence")
285     {
286       if (Options::defaultOptions()->getValue(whatOption) == "on")
287         quiesc = true;
288       else
289         quiesc = false;
290     }
291   else if (whatOption == "useOpeningBook")
292     {
293       if (Options::defaultOptions()->getValue(whatOption) == "n")
294         useOpeningBook = false;
295       else
296         {
297           useOpeningBook = true;
298         }
299     }
300   else if (whatOption == "search")
301     {
302       string type = Options::defaultOptions()->getValue(whatOption);
303       if (type == "mtd")
304         {
305           useMTDF = true;
306           searchMethod = SEARCH_AB;
307         }
308       else if (type == "alphabeta")
309         {
310           useMTDF = false;
311           searchMethod = SEARCH_AB;
312         }
313       else if (type == "negascout")
314         {
315           useMTDF = false;
316           searchMethod = SEARCH_NS;
317         }
318     }
319   else if (whatOption == "nullmove")
320     {
321       if (Options::defaultOptions()->getValue(whatOption) == "on")
322         allowNull = true;
323       else
324         allowNull = false;
325     }
326   else if (whatOption == "verifynull")
327     {
328       if (Options::defaultOptions()->getValue(whatOption) == "on")
329         {
330           allowNull = true;
331           verifyNull = true;
332           nullMoveReductionFactor = 3;
333         }
334       else
335         {
336           verifyNull = false;
337           nullMoveReductionFactor = 2;
338         }
339     }
340   else if (whatOption == "hash")
341     {
342       if (Options::defaultOptions()->getValue(whatOption) == "on")
343         useTable = true;
344       else
345         useTable = false;
346     }
347   else if (whatOption == "hashadjust")
348     {
349       if (Options::defaultOptions()->getValue(whatOption) == "on")
350         allowTableWindowAdjustments = true;
351       else
352         allowTableWindowAdjustments = false;
353     }
354   else if (whatOption == "iterative")
355     {
356       if (Options::defaultOptions()->getValue(whatOption) == "on")
357         useIterDeep = true;
358       else
359         useIterDeep = false;
360       //searchState = DONE_SEARCHING; // This one changes the search in an incompatable fassion.
361       searchState = BETWEEN_SEARCHES;
362     }
363   else if (whatOption == "post")
364     {
365       if (Options::defaultOptions()->getValue(whatOption) == "on")
366         displayThinking = true;
367       else
368         displayThinking = false;
369     }
370   else if (whatOption == "qnull")
371     {
372       if (Options::defaultOptions()->getValue(whatOption) == "on")
373         qnull = true;
374       else
375         qnull = false;
376     }
377   else if (whatOption == "qhash")
378     {
379       if (Options::defaultOptions()->getValue(whatOption) == "on")
380         qhash = true;
381       else
382         qhash = false;
383     }
384   else if (whatOption == "computerColor")
385     searchState = BETWEEN_SEARCHES;
386 }
387 
388 
filterOutNonCaptures(list<Move> & moveList)389 void Engine::filterOutNonCaptures(list<Move> &moveList)
390 {
391   if (lawyer->inCheck()) return; // In check positions we want to look at all legal moves.
392 
393   list<Move> copy;
394 
395   //cerr << "FILTERING\n";
396 
397   // Otherwise only captures.
398   for (list<Move>::iterator it = moveList.begin(); it != moveList.end();)
399     {
400       if (board->pieceAt(it->destination()) == EMPTY)
401         {
402           break; // captures are on top (assuming sorted) so we can just stop processing here.
403         }
404       else
405         {
406           copy.push_back(*it);
407           it++;
408         }
409     }
410     moveList = copy; // This will be faster because there will be fewer moves...usually
411 }
412 
413 
variationText(vector<PVEntry> pv)414 string Engine::variationText(vector<PVEntry> pv)
415 {
416   string	var;
417 
418   for (vector<PVEntry>::iterator it = pv.begin(); it != pv.end(); it++)
419     var += it->move.getText() + " ";
420   if (pv.size() > 0)
421     switch (pv[pv.size() - 1].cutoff)
422       {
423       case HASH_CUTOFF:
424         var += "{HT}";
425         break;
426       case NULL_CUTOFF:
427         var += "{NM}";
428         break;
429       case MATE_CUTOFF:
430         var += "(MATE}";
431         break;
432       case MISC_CUTOFF:
433         var += "{MC}";
434         break;
435       }
436 
437   return var;
438 }
439 
440 /* Search algorithms - the basics - all use move ordering */
441 
442 
search(vector<PVEntry> & pv,long alpha,long beta,int ply,int depth,bool legalonly,bool nullOk,bool verify)443 long Engine::search(vector<PVEntry> &pv, long alpha, long beta, int ply, int depth,
444                     bool legalonly, bool nullOk, bool verify)
445   /* Inputs : alpha (lower bound), beta (upper bound), ply (depth so far),
446               depth (target depth), legalonly (only generate legal moves - expensive),
447               nullOk (it is ok to try null move), verify (verify beta cutoff on null)
448 
449      Outputs: primary variation (pv), score of line to play for side to move.
450 
451      This function does everything that is generic to the search, seperate from specific
452      search algorithms.
453   */
454 {
455   vector<PVEntry> myPV;
456   long value = 0;
457   bool failHigh = false;
458   ::_board = board;
459   list<Move>		moveList;
460   int			pvPreSize = pv.size();
461 
462   if (searchAborted) return 0;
463 
464   // Check if we have time left...
465   if (!myTimer->haveTimeLeftForMove())
466     {
467       searchAborted = ABORT_TIME;
468       return -INFIN;
469     }
470   if (iface->readReady())
471     {
472       searchAborted = ABORT_READ;
473       return -INFIN;
474     }
475 
476   ::priorityTable.clear();
477 
478   nodeCount++;
479 
480   // Look for check on either side and preform appropriate action.
481   if (lawyer->inCheck((board->sideToMove() == RED ? BLUE:RED)))
482     {
483       if (!pv.empty()) pv[pv.size()-1].cutoff = MISC_CUTOFF;
484       return INFIN; // I can take the king right now...illegal move made.
485     }
486   //if (lawyer->gameWonByChase() != NOCOLOR || lawyer->gameWonByPCheck() != NOCOLOR) return (-CHECKMATE) - ply;
487   //if ((!legalonly) && lawyer->inCheck()) legalonly = true; // I am in check.
488 
489   // Return evaluation if this is a leaf node.
490   if (ply >= depth)
491     {
492       if (quiesc)
493         return quiescence(alpha,beta,ply,depth,nullOk);
494       else
495         return evaluator->evaluatePosition(*board, *lawyer) - ply;
496     }
497 
498   Move m;
499   long score;
500   if (tableSearch(ply, depth, alpha, beta, m, score, nullOk))
501     {
502       pv.push_back(PVEntry(m, HASH_CUTOFF));
503       return score;
504     }
505 
506   verify = (verify && verifyNull);
507   nullOk = (allowNull && nullOk);
508 
509   // Perform null move if appropriate.
510   if (ply > 0 && (!legalonly) && nullOk && ((!verify) || ((depth-ply) > 2)))
511     {
512       vector<PVEntry> ignore;
513       board->makeNullMove();
514       value = -search(ignore, -beta, 1-beta, ply+1, depth-nullMoveReductionFactor,
515                       false, false, verify);
516       board->unmakeMove();
517 
518       if (value > beta)
519         {
520           if (verify) // For verified null move algorithm (Tabibi, Netanyahu 2002)
521             {
522               // Search the rest of the tree with a reduced depth just to be sure
523               // we are not in zugzwang (move to loose).
524               depth-=2;
525               verify = false;  // Use unverified null moves for rest of search
526               failHigh = true; // flag for later use
527               nullOk = false;
528             }
529           else
530             {
531               if (!pv.empty()) pv[pv.size()-1].cutoff = NULL_CUTOFF;
532               return value;
533             }
534         }
535     }
536 
537   setUpKillers(ply);
538 
539   lawyer->generateMoves(moveList, legalonly);
540   if (moveList.empty())
541     {
542       if (!pv.empty()) pv[pv.size()-1].cutoff = MATE_CUTOFF;
543       return CHECKMATE + ply;
544     }
545   moveList.sort(::compareMoves); // The information to sort has been set up by "search".
546 
547 
548 
549 research: // Goto usually bad, but simplifies the code here.
550   switch (searchMethod)
551     /* Call the specific search algorithm the user wants us to use. */
552     {
553     case SEARCH_AB:
554       value = alphaBeta(myPV, moveList, alpha, beta, ply, depth, legalonly, nullOk, verify);
555       break;
556     case SEARCH_PV:
557       //value = pvSearch(pv, moveList, alpha, beta, ply, depth, legalonly, nullOk, verify);
558       break;
559     case SEARCH_NS:
560       value = negaScout(myPV, moveList, alpha, beta, ply, depth, legalonly, nullOk, verify);
561       break;
562     }
563   if (searchAborted)
564     {
565       pv.insert(pv.end(), myPV.begin(), myPV.end());
566       return value; // Don't want timed out search trees to get into the table.
567     }
568 
569   // This is straight out of the text on Verified Null-Move.
570   if ((failHigh) && (value < beta)) // flag is set and our best move is not that great.
571     {
572       myPV.clear();
573       depth++;
574       failHigh = false;
575       verify = true;
576       goto research;  // Re-do search, zugzwang position caused beta cutoff on null when
577                       // the best move is NOT above beta.  These are rare cases so the cost
578                       // is not that bad...the gain in worth it.
579     }
580 
581   if (value > beta && !myPV.empty())
582     newKiller(myPV[0].move, ply);
583 
584   if (!myPV.empty())
585     tableSet(ply, depth, alpha, beta, myPV[0].move, value);
586 
587   pv.insert(pv.end(), myPV.begin(), myPV.end());
588   return value;
589 }
590 
591 // Quescence version of search - enough is altered to warrent a different method.
quiescence(long alpha,long beta,int ply,int depth,bool nullOk)592 long Engine::quiescence(long alpha, long beta, int ply, int depth, bool nullOk)
593     /* Inputs : alpha, beta, ply, depth, nullOk
594        Outputs: score.
595 
596        This function is a special case of the search that only searches captures and
597        check evasions.  It only evaluates when it reaches a quiet position; this happens
598        when there is nothing to capture and the side to move is not in check or a null
599        cutoff occurs.
600     */
601 {
602   long value = 0;
603   bool failHigh = false;
604   ::_board = board;
605   list<Move>	moveList;
606   bool		legalonly = false;
607   vector<PVEntry> myPV;
608   bool verify = false;
609 
610   if (!myTimer->haveTimeLeftForMove())
611     {
612       searchAborted = ABORT_TIME;
613       return -INFIN;
614     }
615   if (iface->readReady())
616     {
617       searchAborted = ABORT_READ;
618       return -INFIN;
619     }
620   // Look for check on either side and preform appropriate action.
621   if (lawyer->inCheck((board->sideToMove() == RED ? BLUE:RED)))
622     {
623       return INFIN; // I can take the king right now...illegal move made.
624     }
625   //if (lawyer->gameWonByChase() != NOCOLOR || lawyer->gameWonByPCheck() != NOCOLOR) return (-CHECKMATE) - ply;
626   //if ((!legalonly) && lawyer->inCheck()) legalonly = true; // I am in check.
627 
628   Move m;
629   long score;
630   if (qhash && tableSearch(ply, depth, alpha, beta, m, score, nullOk))
631     {
632       return score;
633     }
634 
635   if (!legalonly && nullOk && qnull)
636     {
637       vector<PVEntry> ignore;
638       board->makeNullMove();
639       quiesc = false;
640       // Search to depth 1 looking for a beta cutoff - no quiescence during null.
641       value = -search(ignore, -beta, 1-beta, 0,1, false, false, verify);
642       quiesc = true;
643       board->unmakeMove();
644 
645       // This position is quiet, return evaluation.
646       if (value >= beta) return evaluator->evaluatePosition(*board,*lawyer);
647     }
648 
649 
650   lawyer->generateMoves(moveList, legalonly);
651   if (moveList.empty()) return CHECKMATE;
652 
653   setUpKillers(ply);
654   moveList.sort(::compareMoves);
655 
656   // Only pay attention to captures and evasions.
657   filterOutNonCaptures(moveList);
658   // If no capture moves exist then list is empty, this is a quiet position prime for
659   // evaluation.
660   if (moveList.empty()) return evaluator->evaluatePosition(*board, *lawyer);
661 
662   switch (searchMethod)
663     /* Call the specific search algorithm the user wants us to use. */
664     {
665     case SEARCH_AB:
666       value = alphaBeta(myPV, moveList, alpha, beta, ply, depth, legalonly, nullOk, verify);
667       break;
668     case SEARCH_PV:
669       //value = pvSearch(pv, moveList, alpha, beta, ply, depth, legalonly, nullOk, verify);
670       break;
671     case SEARCH_NS:
672       value = negaScout(myPV, moveList, alpha, beta, ply, depth, legalonly, nullOk, verify);
673       break;
674     }
675   if (searchAborted) return value;
676   if (value > beta && !myPV.empty())
677     newKiller(myPV[0].move, ply);
678 
679   if (qhash && !myPV.empty())
680     tableSet(ply, depth, alpha, beta, myPV[0].move, value);
681 
682   return value;
683 }
684 
685 // Traditional AlphaBeta Search...
alphaBeta(vector<PVEntry> & pv,list<Move> moveList,long alpha,long beta,int ply,int depth,bool legalonly,bool nullOk,bool verify)686 long Engine::alphaBeta(vector<PVEntry> &pv, list<Move> moveList, long alpha, long beta,
687                        int ply, int depth, bool legalonly, bool nullOk,
688                        bool verify)
689   /* Inputs : moveList (list of moves to search), alpha (lower bound), beta (upper),
690               ply (current depth), depth (target depth), legalonly (not used),
691               nullOk (not used), verify (passed on).
692 
693      Outputs: principle variation and score for side to move.
694   */
695 {
696   vector<PVEntry>	myPV; // Holds the best PV while we search.
697 
698 
699   long best = -INFIN;//alpha - 1;
700   long value     = 0;
701 
702   // Iterate through the moves to find the best one.
703   for (list<Move>::iterator it = moveList.begin();
704        it != moveList.end() && best < beta; // have moves and haven't surpassed beta
705        it++)
706     // We stop searching at beta because that means this line is worse for the other side
707     // than any that came before, so they won't make that move and so this line never
708     // takes place.  We assume the other player makes the best move possible.
709     {
710       vector<PVEntry> tempPV;
711       tempPV.push_back(PVEntry(*it,NO_CUTOFF));
712       board->makeMove(*it);
713       if (best > alpha)  alpha = best; // We don't care about any lines that score less than
714                                        // our current best.
715       value = -search(tempPV, -beta, -alpha, ply+1, depth, false, true, verify);
716       //value -= ply;
717 
718       if (value > -INFIN && value > best) // found one that is better.
719         {
720           best = value;  // replace best value
721           myPV = tempPV; // replace current PV.
722         }
723       board->unmakeMove();
724     }
725   if (best == -INFIN) return CHECKMATE;
726   pv.insert(pv.end(), myPV.begin(), myPV.end()); // add best line to PV.
727   return best; // return value of line.
728 }
729 
730 
731 // Principle Variation Search...
732 
733 
734 // NegaScout Search... best first algorithm that searches the first move with full
735 // window and then the rest with 0 width windows just to prove the first was the best.
736 // If the first is not the best then we research the one that gave us a better score
737 // with window between beta and the returned score to find a true value.
negaScout(std::vector<PVEntry> & pv,std::list<Move> moveList,long alpha,long beta,int ply,int depth,bool legalonly,bool nullOk,bool verify)738 long Engine::negaScout(std::vector<PVEntry> &pv,  std::list<Move> moveList,
739                        long alpha, long beta, int ply, int depth,
740                        bool legalonly, bool nullOk, bool verify)
741   /* Inputs : moveList (list of moves to search), alpha (lower bound), beta (upper),
742               ply (current depth), depth (target depth), legalonly (not used),
743               nullOk (not used), verify (passed on).
744 
745      Outputs: principle variation and score for side to move.
746   */
747 {
748   // NOTE: not currently working with implementation of MTD(f) - not used when that alg
749   //       selected.  End up with empty or incomplete lines.  NegaScout adds no benefit
750   //       with MTD(f) anyway since the supplied window is null - NegaScout deteriorates into
751   //       AlphaBeta at that point but doesn't work right.
752   vector<PVEntry>	myPV;
753 
754   long a = alpha;
755   long b = beta;
756   long t = 0;
757 
758 
759   for (list<Move>::iterator it = moveList.begin(); it != moveList.end() && a < beta; it++)
760     {
761       vector<PVEntry> tempPV;
762       tempPV.push_back(PVEntry(*it, NO_CUTOFF));
763       board->makeMove(*it);
764       t = -search(tempPV, -b, -a, ply+1, depth, false, true, verify);
765       if (t > a && t < beta && it != moveList.begin() && ply < depth-1)
766         // Best move was not best - must search again.
767         {
768           tempPV.clear();
769           tempPV.push_back(PVEntry(*it, NO_CUTOFF));
770           a = -search(tempPV, -beta, -t, ply+1, depth, false, true, verify);
771         }
772       board->unmakeMove();
773       if (t > a) // We have a better best.
774         {
775           a = t;
776           myPV = tempPV;
777         }
778       b = a+1; // From now on searches are done with 0 width window.
779     }
780   pv.insert(pv.end(), myPV.begin(), myPV.end());
781   return a;
782 }
783 
setUpKillers(int ply)784 void Engine::setUpKillers(int ply)
785 {
786   if (killer1.size() > (ply-1))
787     ::priorityTable.push_back(killer1[ply-1]);
788   if (killer2.size() > (ply-1))
789     ::priorityTable.push_back(killer2[ply-1]);
790 }
791 
792 
793 // looks for position in table.
tableSearch(int ply,int depth,long & alpha,long & beta,Move & m,long & score,bool & nullok)794 bool Engine::tableSearch(int ply, int depth, long &alpha, long &beta, Move &m, long &score,
795                          bool &nullok)
796 {
797   if (!useTable) return false;
798   TNode searchNode;
799   transposTable->find(board, searchNode);
800   if (searchNode.flag() != NOT_FOUND)
801     {
802       m = searchNode.move();
803       score = searchNode.score();
804       if (allowTableWindowAdjustments)
805         {
806           switch (searchNode.flag())
807             {
808             case EXACT_SCORE: break;
809             case UPPER_BOUND:
810               if (beta > score && score > alpha)
811                 {
812                   beta = score;
813                   nullok = false;
814                 }
815               break;
816             case LOWER_BOUND:
817               if (alpha < score && score < beta)
818                 {
819                   alpha = score;
820                   nullok = false;
821                 }
822               break;
823             }
824         }
825       ::priorityTable.push_back(m);
826       if (searchNode.depth() > (depth-ply) && (ply > 0 || searchNode.flag() == EXACT_SCORE))
827         return true;
828       return false;
829     }
830   return false;
831 }
832 // stores position in table.
tableSet(int ply,int depth,long alpha,long beta,Move m,long score)833 void Engine::tableSet(int ply, int depth, long alpha, long beta, Move m, long score)
834 {
835   if (!useTable) return;
836   TNode	storeNode(board);
837   storeNode.move(m);
838   storeNode.score(score);
839   storeNode.depth(depth-ply);
840 
841   if (score < alpha) // UPPER BOUND
842       storeNode.flag(UPPER_BOUND);
843   else if (score < beta) // EXACT_VALUE
844       storeNode.flag(EXACT_SCORE);
845   else // LOWER BOUND
846       storeNode.flag(LOWER_BOUND);
847 
848   transposTable->store(storeNode);
849 }
850 
endSearch()851 void Engine::endSearch() { searchState = DONE_SEARCHING; }
doneThinking()852 bool Engine::doneThinking() { return searchState == DONE_SEARCHING; }
thinking()853 bool Engine::thinking() { return searchState == SEARCHING; }
854