by H.G.Muller » 20 Jan 2021, 20:32
There should be no problem with the coordinates, if they implemented UCI-cyclone correctly. WinBoard (or actually UCI2WB, the protocol adapter) might not have been aware that the engine was UCCI, causing a mismatch.
Making EGT is basically very simple. (But repetitive, and therefore tedious.) The EGT is a table of game results of all possible positions. To which position a tabulated result belongs is purely dependent on the location in the table; to keep the EGT compact no information to identify the position is stored at all.
Details can vary depending on how you represent the EGT, and this again depends on what you store as results. E.g. you could store Win/Draw/Loss information only, and that would require 2 bits per position. Which you could pack into words or bytes. And if you do that in a clever way, you could even pack 5 WDL results per byte, as only 3 of the 4 possibilities are needed. (And 3^5 = 243 < 256.) OTOH if you want to store 'Distance to Mate' (DTM) you would need to store a number, and make the storage for it large enough to hold the worst case. The other extreme is that you only tabulate Won/Not-Won information; that takes only 1 bit per position.
Then you need an 'indexing scheme', which is a mapping from positions to unique numbers. You could make that very simple (like for Chess number the squares 0-63, and use index = sqrNrWhitePawn*64*64 + sqrNrWhiteKing*64 + sqrNrBlackKing to map KP/K positions on a unique number. This wastes a little space ('broken positions', where two pieces are on the same square), but it has the advantage that it is easily invertible. You can try to save space by excluding illegal positions (like Kings on adjacent squares), but that makes the index calculation slower, and would not save that much (i.e. not a large factor). In Janggi you would of course definitely want to explot the fact that K and A are confined to the Palace, e.g. by having index = sqrNrWhitePawn1*9*9*90 + sqrNrWhitePawn2*9*9 + sqrNrWhiteKing*9 + sqrNrBlackKing, for KPP/K, using a separate numbering system 0-8 for the Palace squares. (I suppose you could actually number the Pawn squares more cleverly as well, as Pawns cannot get everywhere either.)
Suppose you want to make a DTM EGT, and reserve 1 byte per position. How many positions you have depends on the number of pieces in the end-game you make the EGT for. (And in Janggi, what part of the board is accessible to those.) In principle each piece constellation occurs twice: with white to move or with black to move. The way I usually do this is pack the info for both of those in one byte: the lowest bit indicates whether the position with white on move is known to be won. The other 7 bits contain the number of moves black can maximally make before he is checkmated (the DTM), when black has the move, plus 2. That +2 is becase the DTM can be 0 (in positions where black is already checkmated), and I want to reserve the code 0 for positions for which the DTM is not (yet) known. Initially of course nothing is known, so the entire EGT contains zeros.
Then I start by 'seeding' the EGT with wins: all positions where white can capture the black King. The most efficient way to do this is for every possible position with the black King missing, (which is only a fraction of the positions in the EGT, 1/9 in the case of Janggi) generate all moves of the white pieces. And for the destination of that move, the total position with the black King on that square is a lost one (as that move would capture it). So you 'mark' it by setting the lowest bit of the byte for that position to 1.
Second intialization step is to detect the checkmates. For each position that is marked as 'won' (i.e. where black is in check, we have no others yet), try all black moves to see if there is one through which he can get out of check. This is done by probing the EGT: each move causes an index change (a multiple of the change of the board square number, depending on that index calculation), and you test whether the target position is marked as 'won' too. If all possible moves bring you to 'won' positions, the current position is a checkmate, and you set the upper 7 bits to 2. No issues with stalemate in Janggi. (In Xiangi you would also have to do this test for positions where you are not in check, as stalemate is also lost there, and should be labeled 2 too. In Chess you must recognize the stalemate, but label then such that the will never be considered lost even though you have no moves that would escape the loss, for which I reserved DTM code 1.)
That completes the initialization. We can the start iterating: generate the mated-in-(N+1) positions from the mated-in-N (where the just calculated checkmates are the mated-in-0). For this we run through the EGT, and for each 2 we encounter in the DTM bits, we generate all white 'retrograde' moves (sometimes called 'unmoves'). For non-captues unmoves are usually the same as normal moves; running a movie of a chess game backwards in time shows nothing strange until something gets 'uncaptured'; in the latter case it would look like a move to an empty square, but leave behind an opponent piece on the square it came from! In KPP/K there would be nothing to uncapture, though; all the mates must already have all pieces on the board.
From the mated-in-N positions, you do all white unmoves. The positions you reach that way are then marked as 'won', again by setting the lowest bit. If that bit was already set, nothing changes, and you go on with the next white unmove. If it was not marked yet it is 'newly won'; you then mark it, and actually make the white unmove on the board. From this newly-won position you then try all black unmoves. This reaches positions that now no longer can move safely (i.e. without getting mated in N+1) to the newly-won position, so they are 'potentially lost'. But you tentatively 'label' them as mated-in-(N+1) (which would be code N+3 in the DTM field, in my way of encoding DTM).
Then you still have to 'verify' these 'potentially lost' positions. You run again through the entire EGT, and for each of those you generate all black normal moves (i.e. not retrograde). To see if he still can 'escape' to a position that was not (yet) 'won' (only the mate-in-N or faster positions are marked 'won' at this stage). If he can escape, you set the DTM back to 'undecided' (0). If he hasn't, you leave the code for mated-in-(N+1) there, as it is now a verified mated-in-(N+1) position, from which he can only reach mate-in-N-or-faster positions. While running through the EGT, you count how many positions will keep the mated-in-(N+1) DTM.
You keep doing this for increasing N, until you reach the point where no new (longer) checkmates were added. You have then found all forced wins. Positions that did not receive a DTM are then draws.
When black has pieces that can check (or can BikJang), things get more complex: the algorithm as described would consider perpetual checking legal, and the checks would always remain 'escapes' for black, and the positions from which he can check would thus always remain draws. There exists an algorithm that solves that. But, if you still feel up to it, I can explain that later.