Understanding AlphaBetaMinMax

Discussion of chess software programming and technical issues.

Moderator: Ras

User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Understanding AlphaBetaMinMax

Post by mvanthoor »

Also, you should do make(), "continue" when the make() turned out to be illegal, if legal then search, then unmake().

If I'm reading this correctly, you're now only unmaking moves when they give you a bèta cutoff.

So:

Code: Select all

if (depth == 0)
{
    auto eval = QSearch(board, alpha, beta);
    return eval;
}

for (moves)
{
     makemove(move);
     
     // Skip illegal moves (and the rest of the loop)
     if (inCheck) {
     	continue;
     }
     
	score = -AlphaBetaMinMax(board, -beta, -alpha, depth - 1);

	unmake(move)
	
          if (score > alpha)
          {
             if (score >= beta)
             {
                 unmakeMove(move);
                 return beta;
             }
             
             alpha = score;
         }
}

return alpha;
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
User avatar
Ras
Posts: 2703
Joined: Tue Aug 30, 2016 8:19 pm
Full name: Rasmus Althoff

Re: Understanding AlphaBetaMinMax

Post by Ras »

msarchet wrote: Fri Oct 08, 2021 5:20 am
R. Tomasi wrote: Fri Oct 08, 2021 2:20 am For +/- INF I'd actually go for the maximum/minimum safe value (i.e. +INT16_MAX for positive infinity and -INT16_MAX for negative infinity), since those are supposed to be larger/smaller than any mating score.
Doesn't this run me into the same problem with -(-INF) being -INT16_MIN?
No. INT16_MAX is +32767, and int16_t can represent -32767. It's just that int16_t can represent -32768 (i.e. INT16_MIN), but not +32768.
Rasmus Althoff
https://www.ct800.net
User avatar
Desperado
Posts: 879
Joined: Mon Dec 15, 2008 11:45 am

Re: Understanding AlphaBetaMinMax

Post by Desperado »

Code: Select all

for (moves)
{
     makemove(move);
     
     // Skip illegal moves (and the rest of the loop)
     if (inCheck) {
     	continue;
     }
     
	score = -AlphaBetaMinMax(board, -beta, -alpha, depth - 1);

	unmake(move)
	
          if (score > alpha)
          {
             if (score >= beta)
             {
                 unmakeMove(move);
                 return beta;
             }
             
             alpha = score;
         }
}
Well, it is only a short look at you code snippet (maybe i am overlooking sth. trivial), but you call unmakeMove(move) twice if you have a beta cut.
Do i miss something? If that is a bug, the board will be shuffeld somehow and the result will be unpredictable.

Another point, might be the checkmate detection. It is not obvious to me how your report a mate score. (no legal moves available).
When people handle mate scores, sometimes the initial score at a node will be set to sth. like "NO_SCORE" or "MIN_SCORE".
If this isn't done you might get an undefined score from nowhere.

Regards
dangi12012
Posts: 1062
Joined: Tue Apr 28, 2020 10:03 pm
Full name: Daniel Infuehr

Re: Understanding AlphaBetaMinMax

Post by dangi12012 »

I want to add that having a legal movegenerator would remove all that Incheck() checking - and would improve speed by 2x at least. Probably more since that is an expensive check to do for each position.
Worlds-fastest-Bitboard-Chess-Movegenerator
Daniel Inführ - Software Developer
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Understanding AlphaBetaMinMax

Post by mvanthoor »

Desperado wrote: Fri Oct 08, 2021 4:03 pm Well, it is only a short look at you code snippet (maybe i am overlooking sth. trivial), but you call unmakeMove(move) twice if you have a beta cut. Do i miss something?
Argh. No. The unmake move does not belong in the beta cut. I forgot to delete it :shock:
Another point, might be the checkmate detection. It is not obvious to me how your report a mate score.
In my snippet, I don't cover that.

In my engine, -CHECKMATE is returned if the king is in check and there are no legal moves. STALEMATE is returned if the king is not in check and there are no legal moves. This is right after the move loop.
dangi12012 wrote: Fri Oct 08, 2021 4:10 pm I want to add that having a legal movegenerator would remove all that Incheck() checking - and would improve speed by 2x at least. Probably more since that is an expensive check to do for each position.
True, but a pseudo-legal move generator is easier to write, so most people start there (myself included). Staged move generation is an even better optimization than a fully legal move generator: not generating any moves is faster than the fastest move generator.
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Understanding AlphaBetaMinMax

Post by R. Tomasi »

mvanthoor wrote: Fri Oct 08, 2021 4:13 pm True, but a pseudo-legal move generator is easier to write, so most people start there (myself included). Staged move generation is an even better optimization than a fully legal move generator: not generating any moves is faster than the fastest move generator.
That, and it's then also possible to sort the stages (instead of only the moves). Generating potentially winning captures first in QS, for example. I have a kind of history heuristics which sorts the stages in Pygmalion. Only if not in QS I will then also sort the generated moves. Not sorting the individual moves, and only generating potentially winning stages first proved to be better for me.

Edit: Btw, it seems debatable to me whether a pure legal-movegen will in the end be faster. It will have to do the legality check in some form or another during generation, which make the generation slower than pseudo-legal. And with a pseudo-legal movegen you will skip all this checking for moves that come behind a beta cut-off. To me the best option (I'm not doing this currently, though, and haven't tried), seems to be to do pure legal move-gen in expected all-nodes and staged pseudo-legal in expected cut-nodes.
msarchet
Posts: 13
Joined: Tue Sep 21, 2021 4:07 am
Full name: Michael Sarchet

Re: Understanding AlphaBetaMinMax

Post by msarchet »

mvanthoor wrote: Fri Oct 08, 2021 7:42 am Also, you should do make(), "continue" when the make() turned out to be illegal, if legal then search, then unmake().

If I'm reading this correctly, you're now only unmaking moves when they give you a bèta cutoff.

So:

Code: Select all

if (depth == 0)
{
    auto eval = QSearch(board, alpha, beta);
    return eval;
}

for (moves)
{
     makemove(move);
     
     // Skip illegal moves (and the rest of the loop)
     if (inCheck) {
     	continue;
     }
     
	score = -AlphaBetaMinMax(board, -beta, -alpha, depth - 1);

	unmake(move)
	
          if (score > alpha)
          {
             if (score >= beta)
             {
                 unmakeMove(move);
                 return beta;
             }
             
             alpha = score;
         }
}

return alpha;

This isn't my actual code I do handle the unmake move properly.

R. Tomasi wrote: Fri Oct 08, 2021 4:48 pm
mvanthoor wrote: Fri Oct 08, 2021 4:13 pm True, but a pseudo-legal move generator is easier to write, so most people start there (myself included). Staged move generation is an even better optimization than a fully legal move generator: not generating any moves is faster than the fastest move generator.
That, and it's then also possible to sort the stages (instead of only the moves). Generating potentially winning captures first in QS, for example. I have a kind of history heuristics which sorts the stages in Pygmalion. Only if not in QS I will then also sort the generated moves. Not sorting the individual moves, and only generating potentially winning stages first proved to be better for me.

Edit: Btw, it seems debatable to me whether a pure legal-movegen will in the end be faster. It will have to do the legality check in some form or another during generation, which make the generation slower than pseudo-legal. And with a pseudo-legal movegen you will skip all this checking for moves that come behind a beta cut-off. To me the best option (I'm not doing this currently, though, and haven't tried), seems to be to do pure legal move-gen in expected all-nodes and staged pseudo-legal in expected cut-nodes.
This is something that I plan to look into. My move generation from profiling is only about 1-2% of the cost of executing a search, so I haven't been worrying about it at this point. For QSearch I do only generate captures.
Panzer (still very in progress) - C++ bitboard engine https://github.com/msarchet/panzer
msarchet
Posts: 13
Joined: Tue Sep 21, 2021 4:07 am
Full name: Michael Sarchet

Re: Understanding AlphaBetaMinMax

Post by msarchet »

This is something that I plan to look into. My move generation from profiling is only about 1-2% of the cost of executing a search, so I haven't been worrying about it at this point. For QSearch I do only generate captures.
Actually looking again it's about 5%
Panzer (still very in progress) - C++ bitboard engine https://github.com/msarchet/panzer
R. Tomasi
Posts: 307
Joined: Wed Sep 01, 2021 4:08 pm
Location: Germany
Full name: Roland Tomasi

Re: Understanding AlphaBetaMinMax

Post by R. Tomasi »

msarchet wrote: Fri Oct 08, 2021 6:33 pm
R. Tomasi wrote: Fri Oct 08, 2021 4:48 pm That, and it's then also possible to sort the stages (instead of only the moves). Generating potentially winning captures first in QS, for example. I have a kind of history heuristics which sorts the stages in Pygmalion. Only if not in QS I will then also sort the generated moves. Not sorting the individual moves, and only generating potentially winning stages first proved to be better for me.

Edit: Btw, it seems debatable to me whether a pure legal-movegen will in the end be faster. It will have to do the legality check in some form or another during generation, which make the generation slower than pseudo-legal. And with a pseudo-legal movegen you will skip all this checking for moves that come behind a beta cut-off. To me the best option (I'm not doing this currently, though, and haven't tried), seems to be to do pure legal move-gen in expected all-nodes and staged pseudo-legal in expected cut-nodes.
This is something that I plan to look into. My move generation from profiling is only about 1-2% of the cost of executing a search, so I haven't been worrying about it at this point. For QSearch I do only generate captures.
I plan so, too. It's however not very high on the to-do list currently.

What I do/meant is not to generate only captures in QS (I think almost everybody does that). The idea is to generate captures in stages, and only generate the potentially winning stages first. For example when generating captures for the queen: capturing a pawn is with very high probability not a winning capture (but there probably are lot's of "queen captures pawn" cases, because of the high queen mobility). I generate (probably) winning captures in a first stage, then (probably) equal captures, and finally the (probably) loosing captures in QS. Within the individual stages I have passes (for example winning queen captures, winning rook captures, etc.). Those get sorted by a history score.

A typical approach in QS would then be to sort the generated captures by SEE. That I do not do. It may be because my SEE implementation is poor/slow, but the cost of that last sort outweighs any benefits in my engine. And it's not like the moves in QS aren't ordered - I just don't sort the moves within individual passes.

Edit: well, the (probably) winning queen captures is a BS example :oops: They don't exist, becuase the queen can at best capture a queen, which would be a (probably) equal capture. But you get the idea...