strange: semi-valid moves give different game play

Discussion of chess software programming and technical issues.

Moderator: Ras

Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: strange: semi-valid moves give different game play

Post by Sven »

flok wrote:Found it or at least something: it's the stale mate detection what makes it different.

At the top of search() I check for stale mate. If so, then I return 0. Else I'll continue with eg the check if nMoves == 0 and then I do an evaluate.

Now due to the semi-valid movelist, stale mate does not trigger. I can move that to just after the move-iteration but then the depthLeft == 0 (triggering evaluate/qs) is triggered first.
Again, as already stated by someone else: calling evaluate() if nMoves == 0 has to be considered *suboptimal* in full-width search. You only do this (implicitly) in qsearch. If nMoves == 0 (where nMoves is the number of legal moves) then return an appropriate score representing either "checkmated" or "draw". So there is no separate "check for stalemate", it consists merely of the combination of nMoves == 0 with not being in check.

When using a pseudo-legal move generator (you call the resulting move list "semi-valid") you do the same, only after the move loop instead of before. So even in this case you can detect stalemate.

A typical problem arises at horizon nodes where depthLeft == 0. What should be done first: calling qSearch, testing for being in check or not, generating moves (and possibly finding that there are none)? I usually solve this as follows:

- "qSearch" is never called when the moving side is in check
- full-width search has two functions, one for positions where the moving side is in check ("evasionSearch") and one for all other positions ("search")
- selection of the correct function to search the subtree of the current move is already done within the move loop itself: if the move gives check then call "evasionSearch" (this implicitly includes check extension at least for horizon nodes), else if depthLeft (in the calling function which is either "search" or "evasionSearch") == 1 then call "qsearch", else call "search"

By doing so, "search" can assert depthLeft > 0 and not being in check, so nMoves == 0 directly implies stalemate. "evasionSearch" can assert depthLeft > 0 and being in check, so nMoves == 0 directly implies being checkmated. And "qSearch" asserts not being in check, where I accept to be unable to detect stalemate (but I could add an additional quick test that still detects many stalemate positions where the moving side only has the king). This partitioning allows me to keep all search functions simple enough to "see" by reading the code whether they work as intended.
User avatar
hgm
Posts: 28457
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: strange: semi-valid moves give different game play

Post by hgm »

flok wrote:Found it or at least something: it's the stale mate detection what makes it different.

At the top of search() I check for stale mate. If so, then I return 0. Else I'll continue with eg the check if nMoves == 0 and then I do an evaluate.

Now due to the semi-valid movelist, stale mate does not trigger. I can move that to just after the move-iteration but then the depthLeft == 0 (triggering evaluate/qs) is triggered first.
There are two ways to employ a pseudo-legal move generator: one is to merely postpone the legality checking to MakeMove(), to benefit from the fact that many moves do not make it to that stage, because an earlier move causes a beta cutoff.

The other way is to make the moves as usual, and test in the daughter node for King capture. Where you then return a +INF score if it is detected (e.g. during move generation, or through a special test based on the preceding move). In the parent illegal moves then show up with score -INF in the parent. So if after searching all moves bestScore is still at -INF, you know that all moves must have been illegal. In such a case you are either checkmated or stalemated, depending on whether you are in check or not. When in check you would already have an appropriate score -INF, which would be returned as -INF+1, the 'checkmated' score, due to the delayed-loss bonus. Only when stalemated (i.e. not in check) the score has to be corrected to 0 (draw).

But this should not cause any differences with using legal moves and detecting mate at the top of Search. None of the moves was legal, so the daughter node would have returned without searching anything. The only difference is that you do have an extra call to Search() for every pseudo-legal move, which then returns without doing anything, with a score that would not affect the score in the current node.