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