Belofte  version 2.1.5
A promising chess program using the UCI or Winboard interface
search_ab.cpp
Go to the documentation of this file.
1 /*---------------------------------------------------------------------+
2  * File: search_ab.cpp
3  * Project: part of belofte - A Promising Chess Program
4  * Author: yves
5  * SPDX-License-Identifier: GPL-2.0-only
6 +----------------------------------------------------------------------*/
7 
8 #include "belofte.h"
9 
10 //-----------------------------------------------------------------------
11 
12 #if defined(BELOFTE_NOUNICODE)
13 #define GTOREQUAL " >= "
14 #else
15 #define GTOREQUAL " ≥ "
16 #endif
17 
18 //-----------------------------------------------------------------------
19 // class SearchAlphaBeta
20 //-----------------------------------------------------------------------
21 
22 #if defined(__GNUC__)
23 #pragma GCC diagnostic push
24 #pragma GCC diagnostic ignored "-Weffc++"
25 #endif
26 
28  : bSearchAlgorithm("AB")
29  , m_bEarlyBetaCutOff{false}
30  , m_bEarlyQSBetaCutOff{true}
31  , m_bFailSoft{true}
32  , m_nBetaCutOffMargin{SCORE_BETAMARGIN}
33  , m_bAttenuateScore{true}
34 {
35  m_iterativesearch = true;
36 }
37 
38 SearchAlphaBeta::SearchAlphaBeta(std::string const& s)
39  : bSearchAlgorithm(s)
40  , m_bEarlyBetaCutOff{false}
41  , m_bEarlyQSBetaCutOff{true}
42  , m_bFailSoft{true}
43  , m_nBetaCutOffMargin{SCORE_BETAMARGIN}
44  , m_bAttenuateScore{true}
45 {
46  m_iterativesearch = true;
47 }
48 
50 {
51 }
52 
54 {
55  return CalcBestMove(b, 0,
58 }
59 
60 //-----------------------------------------------------------------------
61 // class SearchAlphaBetaFS
62 //-----------------------------------------------------------------------
63 
65  : SearchAlphaBeta("ABFS")
66 {
67  m_bEarlyBetaCutOff = true;
68 }
69 
71 {
72 }
73 
74 //-----------------------------------------------------------------------
75 // class SearchAlphaBetaFH
76 //-----------------------------------------------------------------------
77 
79  : SearchAlphaBeta("ABFH")
80 {
81  m_bEarlyBetaCutOff = true;
82  m_bFailSoft = false;
84 }
85 
87 {
88 }
89 
90 //-----------------------------------------------------------------------
91 // class TerminalNode - helper class to quit early search
92 //-----------------------------------------------------------------------
93 
95  bSearchScore const& alpha, bSearchScore const& beta, bSearchAlgorithm* algo)
96  : m_isTerminal{true}
97 {
98  movenum_t n_moves = b.generateAtLeastOneMove();
99  m_terminalscore = algo->RetrieveBoardEvaluation(b);
100 
101  algo->sendInfoSearching(b, nDepth, alpha
102  + " " + beta
103  + " static: " + belofte::scoreAsStr(m_terminalscore)
104  + " material: " + belofte::scoreAsStr(b.getMaterialEvaluation())
105  + (b.isInCheck() ? " check" : ""), m_terminalscore);
106 
107  if ((m_terminalscore & SCORE_POSITIVE) == SCORE_THEORETIC_DRAW) {
108  m_terminalscore = algo->sendInfoSearching(b, nDepth, "(th. draw)", SCORE_THEORETIC_DRAW);
109  } else {
110  if (!n_moves) {
111  algo->sendInfoSearching(b, nDepth, "(dead position)", m_terminalscore);
112  } else {
113  m_isTerminal = false;
114  }
115  }
116 }
117 
119  bSearchScore const& alpha, bSearchScore const& beta, uint8_t const& nCheckCount,
120  bSearchAlgorithm* algo)
121  : m_isTerminal{true}
122 {
123  movenum_t n_moves = b.generateAtLeastOneMove();
124  m_terminalscore = algo->RetrieveBoardEvaluation(b);
125 
126  algo->sendInfoSearching(b, nDepth, "qs " + alpha
127  + " " + beta
128  + " static: " + belofte::scoreAsStr(m_terminalscore)
129  + (b.isInCheck() ? " check" : "")
130  + " +: " + belofte::to_string(nCheckCount), m_terminalscore);
131 
132  if ((m_terminalscore & SCORE_POSITIVE) == SCORE_THEORETIC_DRAW) {
133  m_terminalscore = algo->sendInfoSearching(b, nDepth, "qs (th. draw)", SCORE_THEORETIC_DRAW);
134  } else {
135  if (!n_moves) {
136  algo->sendInfoSearching(b, nDepth, "qs (dead position)", m_terminalscore);
137  } else {
138  m_isTerminal = false;
139  }
140  }
141 }
142 
143 #if defined(__GNUC__)
144 #pragma GCC diagnostic pop
145 #endif
146 
147 //-----------------------------------------------------------------------
148 // class SearchAlphaBeta
149 //-----------------------------------------------------------------------
150 
151 /// @todo TOCHECK: why not test for winning before evaluating moves
153  bSearchScore alpha, bSearchScore beta)
154 {
155  bBestMoveInfo result;
156  if (getLevel()->depthReached(nDepth)) {
157  uint8_t inCheck = b.isInCheck() ? 1 : 0;
158  result = Quiescence(b, nDepth, alpha, beta, inCheck);
159  return result;
160  }
161 
162  if (nDepth > m_maxDepth) m_maxDepth = nDepth;
163 
164  TerminalNode n(b, nDepth, alpha, beta, this);
165  if (n.isTerminal()) { ++m_leafnodes; return n.getNode(); }
166 
168  && (belofte::realScore(n.getScore()) - m_nBetaCutOffMargin >= beta)) {
169  result = sendInfoSearching(b, nDepth, "(early beta cut-off) static: " + belofte::scoreAsStr(n.getScore())
170  + GTOREQUAL + beta, (m_bFailSoft ? n.getScore() : beta.getScore()));
171  ++m_leafnodes;
172  return result;
173  }
174 
176 
177  movenum_t n_moves = b.generateMoves();
178  bMoveList& ml = b.getMoves();
179  for (movenum_t moveid = 1; moveid <= n_moves; moveid++) {
180  handleInfoCurrMove(b, ml, nDepth, moveid);
181  bBoard chldbrd(b, ml[moveid], ml.getMoveT(moveid));
182  bSearchScore chldscore(CalcBestMove(chldbrd, nDepth + 1, -beta, -alpha), tScoreType::SC_CHILD);
183  if (m_bAttenuateScore) chldscore = -attenuateScore(chldscore.getScore());
184  else chldscore = -chldscore;
185  ml.updateScoreOfBestMove(moveid, chldscore.getScore());
186  if (chldscore > SCORE_WINNING) {
187  b.setVariation(chldbrd);
188  result = sendInfoSearching(b, nDepth, "(winning)", chldscore);
189  ++m_nonleafnodes;
190  return result;
191  }
192  if (chldscore.getRealScore() - m_nBetaCutOffMargin >= beta) {
193  ++m_nonleafnodes;
194  if (m_bFailSoft)
195  result = sendInfoSearching(b, nDepth, "(fail soft) " + chldscore
196  + GTOREQUAL + beta, chldscore);
197  else
198  result = sendInfoSearching(b, nDepth, "(fail hard) " + chldscore
199  + GTOREQUAL + beta, beta);
200  return result;
201  }
202  if (chldscore > alpha) {
203  b.setVariation(chldbrd);
204  sendInfoSearching(b, nDepth, "alpha & best update " + chldscore
205  + " > " + alpha, chldscore);
206  bestscore = chldscore.getScore();
207  alpha = chldscore.getScore(); // alpha does not change type
208 #if defined(_DEBUG)
209  } else {
210  sendInfoSearching(b, nDepth, "no update " + chldscore, bestscore);
211 #endif
212  }
213  }
214 
215  ++m_nonleafnodes;
216  result = sendInfoSearching(b, nDepth, "(return best)", bestscore);
217  return result;
218 }
219 
220 //-----------------------------------------------------------------------
221 
222 /// @todo TOCHECK: why not test for winning before evaluating moves
223 bScore SearchAlphaBeta::Quiescence(bBoard& b, depth_t const& nDepth,
224  bSearchScore alpha, bSearchScore beta, uint8_t& nCheckCount)
225 {
226  if (nDepth > m_maxDepth) m_maxDepth = nDepth;
227 
228  TerminalNode n(b, nDepth, alpha, beta, nCheckCount, this);
229  if (n.isTerminal()) { ++m_leafnodes; return n.getScore(); }
230 
231  if (getLevel()->qsDepthReached(nDepth)) {
232  return sendInfoSearching(b, nDepth, "qs (qs depth reached)", n.getScore());
233  }
234 
235  bool isAllMoves = false;
236  if (!b.generateAtLeastOneQSMove()) {
237  if ((nCheckCount < 1) && (b.isInCheck())) {
238  // make sure QS does not end up in endless checks
239  isAllMoves = true;
240  } else {
241  ++m_leafnodes;
242  return sendInfoSearching(b, nDepth, "qs (qs dead position)", n.getScore());
243  }
244  }
245  if (b.isInCheck()) nCheckCount++;
246 
248  && (belofte::realScore(n.getScore()) - m_nBetaCutOffMargin >= beta)) {
249  ++m_leafnodes;
250  return sendInfoSearching(b, nDepth, "qs (beta early cut-off) static: " + belofte::scoreAsStr(n.getScore())
251  + GTOREQUAL + beta, n.getScore());
252  }
253 
254  // apply null-move simulation
255  if (belofte::realScore(n.getScore()) > alpha) {
256  sendInfoSearching(b, nDepth, "qs early alpha & best update " + belofte::scoreAsStr(n.getScore())
257  + " > " + alpha, n.getScore());
258  //b.setBestScore(n.getScore()); // already done above
259  alpha = n.getScore();
260  }
261 
262  bSearchScore bestscore(n.getScore(), tScoreType::SC_BEST);
263 
264  movenum_t n_moves = b.generateMoves();
265  bMoveList& ml = b.getMoves();
266  for (movenum_t moveid = 1; moveid <= n_moves; moveid++) {
267  // TODO: count move 0 as null-move (n.getScore())
268  if (isAllMoves || (ml[moveid].isNonSilent())
269  || ((!nCheckCount) && (b.isInCheck()))) {
270  handleInfoCurrMove(b, ml, nDepth, moveid);
271  bBoard chldbrd(b, ml[moveid], ml.getMoveT(moveid));
272  bSearchScore chldscore(Quiescence(chldbrd, nDepth + 1, -beta, -alpha, nCheckCount), tScoreType::SC_CHILD);
273  if (m_bAttenuateScore) chldscore = -attenuateScore(chldscore.getScore());
274  else chldscore = -chldscore;
275  ml.updateScoreOfBestMove(moveid, chldscore.getScore());
276  if (chldscore > SCORE_WINNING) {
277  b.setVariation(chldbrd);
278  ++m_nonleafnodes;
279  return sendInfoSearching(b, nDepth, "qs (winning)", chldscore);
280  }
281  if (chldscore.getRealScore() - m_nBetaCutOffMargin >= beta) {
282  ++m_nonleafnodes;
283  return sendInfoSearching(b, nDepth, "qs (fail soft) " + chldscore
284  + GTOREQUAL + beta, chldscore);
285  }
286  if (chldscore > alpha) {
287  b.setVariation(chldbrd);
288  sendInfoSearching(b, nDepth, "qs alpha & best update: " + chldscore
289  + " > " + alpha, chldscore);
290  bestscore = chldscore.getScore();
291  alpha = chldscore.getScore(); // alpha does not change type
292 #if defined(_DEBUG)
293  } else {
294  sendInfoSearching(b, nDepth, "qs no update " + chldscore, bestscore);
295 #endif
296  }
297 #if defined(_DEBUG)
298  } else {
299  sendInfoSearching(b, nDepth, "qs skipped #" + belofte::to_string(moveid) +
300  " till #" + belofte::to_string(n_moves), bestscore);
301 #endif
302  break; // all QS moves are sorted in front, so break on first non-QS
303  }
304  }
305 
306  ++m_nonleafnodes;
307  return sendInfoSearching(b, nDepth, "qs (return best)", bestscore);
308 }
309 
310 // eof
This is the main include file, needs to be included before any other include.
uint_fast16_t movenum_t
moveflags (high order word) & basicmove (low order word)
Definition: belofte.h:103
int_fast16_t bScore
used to return id of move in movelist
Definition: belofte.h:104
int_fast8_t depth_t
Definition: belofte.h:105
~SearchAlphaBetaFH() final
Definition: search_ab.cpp:86
~SearchAlphaBetaFS() override
Definition: search_ab.cpp:70
~SearchAlphaBeta() override
Definition: search_ab.cpp:49
bBestMoveInfo CalcBestMove(bBoard &b) final
Definition: search_ab.cpp:53
bScore m_nBetaCutOffMargin
Definition: search_ab.h:31
bool m_bFailSoft
Definition: search_ab.h:30
bool m_bEarlyBetaCutOff
Definition: search_ab.h:28
bool m_bEarlyQSBetaCutOff
Definition: search_ab.h:29
bool m_bAttenuateScore
Definition: search_ab.h:32
TerminalNode(bBoard &b, depth_t const &nDepth, bSearchScore const &alpha, bSearchScore const &beta, bSearchAlgorithm *algo)
Definition: search_ab.cpp:94
board
Definition: board.h:111
movenum_t generateMoves()
generate moves if not yet generated
Definition: board.cpp:710
bool isInCheck() const
Definition: board.h:139
void setVariation(bBoard const &chldbrd)
Definition: board.cpp:744
bMoveList & getMoves()
return moves in position, initialise structure if needed
Definition: board.cpp:731
bScore getMaterialEvaluation() const
Definition: board.h:148
movenum_t generateAtLeastOneQSMove()
Definition: board.cpp:723
movenum_t generateAtLeastOneMove()
see if at least one move can be played e.g.
Definition: board.cpp:718
movenum_t updateScoreOfBestMove(movenum_t const moveid, bScore const score)
Store score of move.
Definition: movelist.cpp:203
move_t getMoveT(movenum_t const moveid) const
Definition: movelist.cpp:391
bScore attenuateScore(bScore const sc) const
converge score towards zero in order to force immediate best move first
Definition: search.cpp:261
depth_t m_maxDepth
Definition: search.h:56
bScore RetrieveBoardEvaluation(bBoard &b) const
Cache score of board.
Definition: search.cpp:248
bool m_iterativesearch
Definition: search.h:59
bLevel * getLevel()
Definition: search.h:66
void handleInfoCurrMove(bBoard const &b, bMoveList const &ml, depth_t const &nDepth, movenum_t const moveid) const
Definition: search.cpp:232
int64_t m_nonleafnodes
Definition: search.h:58
bScore sendInfoSearching(bBoard const &b, depth_t const nDepth, std::string const &c, bScore const sc) const
Definition: search.cpp:191
int64_t m_leafnodes
Definition: search.h:57
bScore getScore() const
Definition: searchscore.h:38
constexpr bScore SCORE_WINNING
Definition: eval.h:24
constexpr bScore SCORE_POSITIVE
Definition: eval.h:16
constexpr bScore SCORE_BETAMARGIN
Definition: eval.h:30
constexpr bScore SCORE_THEORETIC_DRAW
Definition: eval.h:17
constexpr bScore SCORE_INFINITE
Definition: eval.h:15
std::string scoreAsStr(bScore const sc)
Definition: util.cpp:250
bScore realScore(bScore const sc)
Definition: eval.cpp:23
std::string to_string(long value)
std::to_string not compatible on Mac OS (Apple LLVM version 5.0) provide generic utility function
Definition: util.cpp:168
#define GTOREQUAL
Definition: search_ab.cpp:15
@ SC_BETA
Definition: searchscore.h:15
@ SC_BEST
Definition: searchscore.h:15
@ SC_CHILD
Definition: searchscore.h:15
@ SC_ALPHA
Definition: searchscore.h:15