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.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.
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.
