phhnguyen wrote: ↑Sat Nov 02, 2024 2:40 pmIMHO, your thinking is in the right direction. However, you have been distracted by many details.
Well, the strategy of the algorithm is known. (Fan et al.) But before I can covert it to code, I must solve the implementation details.
Let me show my way. It works for both checks and chases.
We start at positions with UNSET values (after a traditional generator stops due to data becoming stable/no change). All those positions are untouched/un-progressed (typically they will be replaced by DRAW value at the end by the traditional generator). Each position from them sooner or later must lead to a repetition. That is why we check only them if they are PCC or not. If one is PCC, we will set its score as -PERPERTUAL_CHECK/-PERPERTUAL_CHASE.
In the EGT I want to build I would have no special score for perpetual checking, as I consider this the worst outcome of the game. If the worst outcome of a non-checking move is to be checkmated in 30, I would prefer to play that rather than lose immediately by violating the perpetual rule. After all, the opponent might not find the mate in 30. So I would set the score of the PCC nodes to mated in 30 in that case
I am aware that some of the positions in the PCC cycle might actually take longer to mate, even if the first repetition is already forbidden, as the defender might stay within the cycle for a few moves before it repeats. But which positions take the longest depends on where you entered the loop, so it cannot be tabulated. Strictly speaking the positions in the loop have a range of DTM. So it could be better to add the number of allowed checks in the longest cycle to the DTM of all of them. Disadvantage is that you get very large DTM, not fitting in 1 byte.
From a given UNSET position and a side:
1) check if that side is checking or chasing (some opposite pieces), ignore if not. That side is suspected of violating the PCC rules. We need to check further with the following step
2) we need to create a kind of search/minimax tree. The tree has multiple levels. Each level is one side, changed alternatively.
- The node of the given side is called the attack. If a node does not attack any more (stop checking or chasing) or if it has any better children moves, say, to be a draw (it cannot be a win) the node can avoid losing. The tree stops at that branch - no PCC. Otherwise, expand all its children with UNSET values, if there is any child node not PCC, the node is not PCC
- The node on the opposite side is called the evasion. On one hand, it must evasion/rescue its King (from checking) or pieces (from chasing). However, on the other hand, that is also a good way to win. Unless it has a clear move to win (in that case, the tree stops at that branch - no PCC), any evasion-UNSET move is good to expand. The node is not PCC only when all children are not PCC '.
You can indeed do the 'verification' of 'potential losses' through a multi-ply forward search. This is a logical extension of the traditional generation, where 1-ply forward searches are used to see if any moves to an UNSET position exist. (In which case the current position is not yet lost.) This was how I wanted to do it before I read the Fan et al. paper. The problem is that the seach could be very deep. Not so much for checking, due to poor King mobility and confinement, but for chasing. A Rook can chase a Horse or a Cannon all over the board before you get a repetition.
The problem is that this will often lead to nothing, with the conclusion that the winning side cannot yet force a repetition no matter which of the zillion of paths it Cannon traverses the board, because at any stage of the sequence the losing side can escape to an UNSET position. So the suspect position will in the end stay UNSET, so that in the next iterations you will have to repeat the search over and over again, until finally the traditional retrograde propagation has turned all the non-chased positions to which the chasing player could leave the tree to 'regular' losses for him.
I think this also touches on an issue we still need to resolve, that generation in the presence of perpetual ban needs PCC identification and traditional retrograde propagation to alternate, many times. After traditional propagation runs out of new wins/losses, you can run the PCC identification algorithm, which turns some of the left UNSET positions to wins/losses. But these new wins and losses will act as new seeds for traditional propagation, which will then resolve some more non-PCC positions before that runs out of steam. And this will cause some PCC candidate positions that were previously declared to be false alarms and remained UNSET to now become wins/losses. Etcetera.
We expand and travel down/up the tree until getting a repetition. We check all moves/states in that repetition if it is PCC and if yes, set the score (-PERPERTUAL_CHECK/-PERPERTUAL_CHASE) for the root. Evasion nodes will have positive scores (+PERPERTUAL_CHECK/+PERPERTUAL_CHASE - they are winning perpetual scores). Depending on the methods we may stop now or continue exhausting all cases.
3) After all, we must propagate PCC positions to find all leading ones and set scores, say, +/-(PERPERTUAL_FORWARD - n)...
That is the basic algorithm. Pushing Pawns, capturing, winning, and losing moves... are all counted when we examine children's moves. There are still so many other details. The code for detecting perpetual chases is so complicated and slow.