silly quiescence search bug

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

flok

silly quiescence search bug

Post by flok »

Hi,

For the past days I've been trying to fix a bug in my chess program. I fixed all kinds of unrelated bugs but the main problem persists.
What happens is that if I disable quiescence-search I get what I think are sane moves (tried depth 1-9, in the start-position):

Code: Select all

go depth 1
info score cp 30
bestmove e2e3
but with q-search enabled I get (but only for depth 1 and 2!):

Code: Select all

go depth 1
info score cp -3
bestmove a2a3

Code: Select all

go depth 2
info score cp 0
bestmove a2a3
Disabling threading doesn't help (just to be sure I also tried comparing the result of a threaded search with a regular search and for every subtree that is calculated this way the scores always match). Disabling the transposition table doesn't help.

Any ideas?
AlvaroBegue
Posts: 931
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: silly quiescence search bug

Post by AlvaroBegue »

If you have a problem at depth 1, the main idea is to step through your program using a debugger.
flok

Re: silly quiescence search bug

Post by flok »

AlvaroBegue wrote:If you have a problem at depth 1, the main idea is to step through your program using a debugger.
I did that, of course.
User avatar
cdani
Posts: 2204
Joined: Sat Jan 18, 2014 10:24 am
Location: Andorra

Re: silly quiescence search bug

Post by cdani »

Your quiescence has hash? Maybe it works bad, for example if it does not find something.

Also is possible a bug related to some initializations.

Maybe you can try to start with depth 3 and see if happens the same.

Also at depth 3 there is already something to capture, for example e4 a6 Ba6, but not previously.

Also you can try with a different start position.
mar
Posts: 2559
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: silly quiescence search bug

Post by mar »

flok wrote:I did that, of course.
Black can't capture or check, no position leads to black being in check, no extensions trigger so qs should simply return static eval in this case.
You have 20 moves and you can't debug it. I hope you're not serious :)
mar
Posts: 2559
Joined: Fri Nov 26, 2010 2:00 pm
Location: Czech Republic
Full name: Martin Sedlak

Re: silly quiescence search bug

Post by mar »

seriously: if you want us to help, perhaps you could post your qs code (unless it's top secret)
flok

Re: silly quiescence search bug

Post by flok »

mar wrote:seriously: if you want us to help, perhaps you could post your qs code (unless it's top secret)
Sure, no secrets:

Code: Select all

  int Brain::quiesceSearch(Scene & s, const int currentSearchDistance, const PlayerColor c, int alpha, int beta, const zobrist_t & zh, std::atomic_bool *const stop  Flag, calc_stats_t *const cs)
  {
          const int ttDepth = depth_sanity_limit - currentSearchDistance;
  
          int temp = 0;
          if (evalTT(tt, ttDepth, zh.newHash, &alpha, &beta, &temp))
          {
                  cs -> qtpt_hit_cnt++;
                  return temp;
          }
  
          s.generateMovelists(c, true);
  
          int bestVal = evaluate(s, c, currentSearchDistance);
          if (bestVal > alpha)
          {
                  alpha = bestVal;
  
                  if (bestVal >= beta)
                          return bestVal;
          }
  
          if (currentSearchDistance > MAX_QUIESCENCE_DEPTH)
          {
                  cs -> q_limit++;
                  return bestVal;
          }
  
          MoveList *const ml = s.getMoveList(c);
          const int nMoves = (int)ml -> size();
  
          const int newSearchDistance = currentSearchDistance + 1;

          const Board *const b = s.getBoard();
  
          const PlayerColor co = opponentColor(c);
  
          for&#40;int idx=0; likely&#40;idx<nMoves && !*stopFlag && !globalSearchStopFlag&#41;; idx++)
          &#123;
                  const Move & m = ml -> at&#40;idx&#41;;
  
                  if &#40;m.isCatchMove || m.promoteTo == QUEEN&#41;
                  &#123;
// check for en-passant move
                          const Move *epMove = isEpMove&#40;b, &m&#41; ? &m &#58; NULL;

// make move
                          const BoardState bs = s.doMove&#40;c, m&#41;;
                          zobrist_t newHash = updateZobrist&#40;zh, bs, b, co, epMove&#41;;
  
                          int score = -quiesceSearch&#40;s, newSearchDistance, co, -beta, -alpha, newHash, stopFlag, cs&#41;;
  
// unmake
                          s.undoMove&#40;);
  
                          if &#40;score > bestVal&#41;
                          &#123;
                                  bestVal = score;
  
                                  if &#40;score > alpha&#41;
                                  &#123;
                                          alpha = score;
                                          // save pv here
  
                                          if &#40;score >= beta&#41;
                                                  break;
                                  &#125;
                          &#125;
                  &#125;
          &#125;
  
          return bestVal;
&#125;
AlvaroBegue
Posts: 931
Joined: Tue Mar 09, 2010 3:46 pm
Location: New York
Full name: Álvaro Begué (RuyDos)

Re: silly quiescence search bug

Post by AlvaroBegue »

I am concerned that your quiescence search doesn't seem to handle check, checkmate and stalemate correctly. But I don't see anything that would mess up a depth-1 search on the root...
flok

Re: silly quiescence search bug

Post by flok »

cdani wrote:Your quiescence has hash? Maybe it works bad, for example if it does not find something.
Same problem without hash.
Also is possible a bug related to some initializations.

Maybe you can try to start with depth 3 and see if happens the same.
yeah it indeed only happens with depth 1 and 2
but:
Also at depth 3 there is already something to capture, for example e4 a6 Ba6, but not previously.
so depth 1/2 is directly the result of evaluate() while depth 3 sometimes hits the inner if () { } in that loop.
that makes it even more strange.
If I disable quiesce search, then the regular search does:

(shortened)

Code: Select all

int search&#40;...) &#123;
         check_transposition_table&#40;)

          MoveList *const moves = s.getMoveList&#40;c&#41;;
          long unsigned int nMoves = moves -> size&#40;);
  
          if &#40;nMoves == 0 || depthLeft == 0&#41;
                  return evaluate&#40;s, c, curMaxDepth - depthLeft&#41;;
So not much different there.
Also you can try with a different start position.
Yes, I'll compare that with the stockfish results.
Thanks.
flok

Re: silly quiescence search bug

Post by flok »

AlvaroBegue wrote:I am concerned that your quiescence search doesn't seem to handle check, checkmate and stalemate correctly. But I don't see anything that would mess up a depth-1 search on the root...
I'm fairly certain that the check-check is valid: it has played quite a couple of games and if it would not check for check, then the king would disappear leading to an assert-fail during movelist generation.

Then check-mate and stale-mate are also not very complicated:

Code: Select all

bool Scene&#58;&#58;isCheckMate&#40;const PlayerColor side&#41; const
&#123;
        return getMoveList&#40;side&#41; -> empty&#40;) && isKingUnderAttack&#40;side&#41; == true;
&#125;

bool Scene&#58;&#58;isStaleMate&#40;const PlayerColor side&#41; const
&#123;
        return getMoveList&#40;side&#41; -> empty&#40;) && isKingUnderAttack&#40;side&#41; == false;
&#125;