Did anyone write a xiangqi chess engine?

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Did anyone write a xiangqi chess engine?

Post by hgm »

maksimKorzh wrote: Sun Jan 24, 2021 1:52 am Question on winboard
So I'm running it via wine on linux, seems to be working nicely so far.
Why not use XBoard instead? Then you would not need wine.
So it supports UCCI protocol right?
It supports UCCI (and other UCI dialects) through an adapter (UCI2WB). But the adapteris invoked automatically when you tick the UCCI/USI checkbox while registering the engine. (Or, when you specify the engine from the command line, through use of the option -fUCCI / -sUCCI; the checkbox just causes adding of those options to the engine's entry in the engine list.)
If I want to run JS engine I need to specify to node.exe and path to JS file as command line argument, right?
Right. But recently a problem surfaced in connection with nodejs engines when their (path) names contains spaces (as would be the case for the normal installation of node.exe in "C:\Program Files" in Windows). To make sure the various execute calls would consider the parts of the name as a single path, it would require the pathname to be surrounded by quotes. But because the pathname has to be passed first to the adapter, which then has to use it in an exec call, there were not enough levels of quoting available to do that. Since you seem to be running on Linux, I hope you don't have that problem.

Question on UCCI move format (seems like UCI):
so let's say we have a move c3c4, let's just assume it exists and it's legal but
what's the layout for file and ranks?
Winboard seems to use somewhat that looks like SAN notation.
Is there a way to see debug like in arena gui where I can track of commands, e.g.
position startpos moves .... ?
You can start WinBoard with an extra option -debug, to cause this information to be written to a file called winboard.debug (unless you specify another name through the -debugfile option). For XBoard the file would be called xboard.debug, but when you specify "stderr" ad -debugfile, it would appear in the terminal window from which you launched XBoard.

EDIT:
Ok, it seems the layout is clear

Files (see board from red perspective from left to right): a b c d e f g h f
Ranks (starting from red perspective from bottom to top): 0 1 2 3 4 5 6 7 8 9

What confuses me is that ranks are starting from 0, not from 1, is that a UCI standard? same for UCCI?
This is standard for UCCI, and also for Chinese Xiangqi engines that use UCI. WinBoard also uses this rank numbering in SAN. Fairy-Stockfish developers insist that in UCI the numbering is 1 to 10, though, so that there are distinct dialects UCI and 'UCI-Cyclone'.
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Did anyone write a xiangqi chess engine?

Post by hgm »

maksimKorzh wrote: Sun Jan 24, 2021 4:16 amThe next confusing thing is mailbox size.
I had a look at some engines - many use 256 array (16x16)
but it seems like 14x11 should be fairly enough (analog of 10x12 for common chess)
With this size pieces already should not jump over ranks.
So why 16x16 used?

To HGM: in maxQi I see b[513] // 16 x 8 + dummy + PST
this is a bit confusing...
So what would be the array size without dummy square and PST? (how many files x ranks)?
There is a mailbox system known as 0x88, which uses a board of twice the width (so 16x8 for orthodox Chess), only using the left half of it as 'playing area'. The advantage of that is that a move step that is possible on the board can never jump across the edge, even if it was allowed to jump: you will always end in the unused area. This makes the difference of two square numbers a unique 'fingerprint' for the move, which again makes it easy to detect whether squares are on the same rank, file or diagonal. For, say, a 10x12 board you would not have that: +4 can be a move from a b3 to f3, but it can also be a move from h3 to b4. Because you cannot distinguish the two, it becomes much more complex to figure out whether two squares are on the same rank; otherwise you could have used the +4 difference between the square numbers as index in a look-up table that then tells you "this is a Rook move".

For 8x8 boards this system also has the advantage that you can test whether a move strayed off the board, by ANDing the number of the target square with 0x88 (hence the name of the system), without having to actually access the board, and need some kind of edge guards there. This is why micro-Max uses it. MaxQi only uses it because it is a micro-Max derivative; it needs to test wether a move leaves the 10x9 playing area in some other way. MaxQi also has the peculiarity that it has rotated the board 90 degrees. This was to make it easier to detect whether the Kings face each other; the board scan encounters those then immediately after each other.

In HaQiKi D I use an 18 x 14 board, to be able to do efficient testing for alignment of two given squares (in an IsSquareAttacked() routine). 16x16 is no good for that in Xiangqi, as a3-i3 would have the same step as i3-a4. The reason many Xiangqi engines nevertheless use it is that they are Fruit derivatives. In principle you would already have the 'unique fingerprint' property for a 17x14 board: the largest horizontal move on a 9x10 board makes only 8 steps, so you only need an 8-wide guard band. But then the unused area would only be 8 wide, which would make it usesless. By using 18x14 the unused area can also hold a board. So that I can for instance use an 18x10 area to hold two piece-square tables, that for the white piece at PST[type][sqr], and that for the black type at PST[type][sqr+9].

An alternative method to prevent you from leaving the board would be to define moves as having a finite range. The range of a leaper would obviously be 1, but when you make the moves of a piece dependent on the location it is in, you could give all slider moves a range that would be exhausted before they step off the board. Like

Code: Select all

int d = firstMove[pieceType][fromSqr];
while((step = moveDescriptors[d++])) { // loop over directions
  int range = moveDescriptors[d++];    // second element of (step, range) pair
  int toSqr = fromSqr;
  do {
    toSqr += step;
    int victim = board[toSqr];
    ...
   } while(--range);
}
You can then completely leave out moves that would leave the board (or the area to which the piece is restricted) already on their first step. I.e., for a Rook on a0 you would only tabulate the moves in the +1 and +WIDTH direction (with range 8 and 9, respectively), and you would not waste time on trying moves in directions that are doomed to step off board. And even the other moves will never step off board; their range won't allow it. So you don't need any edge guards. I would still use a board of double the width (so 18x10) to make alignment testing easy, though.

It would probably be wise to fill the moveDescriptor table with the aid of a program; including it as 'hand-crafted' initialized data could be error-prone. Disadvantage is that the moveDescriptor table would be rather large, each of 90 squares would require its own step list, which for a Rook on most square would contain 4 moves of two bytes each (step and range), plus a terminating zero, so 9x90 = 810 bytes. And Horses would even move in 8 directions.

A trick to save on space would be to tabulate only the directions the piece can move in, as a number. Many squares could then use the same list. E.g. on all non-edge squares a Rook could move in all 4 orthogonal directions, which you could number 1-4. (5-8 could then be Elephant directions, 9-16 Horse directions.) The direction numbers could then be used as index in a table that provides the corresponding board step, (boardStep[direction]) and as first index in a table of rangeTab[direction][sqr]. Only sliders would need that, and Rooks and Cannons could use the same table. For leapers the range would just be set to 1 based on their piece type. So you need 4x90 bytes of rangeTab[][].

Code: Select all

int d = firstMove[pieceType][fromSqr];
while((dirNr = directionTab[d++])) { // loop over directions
  int step = stepTab[dirNr];
  int range = 1;
  int toSqr = fromSqr;
  if(IS_SLIDER[pieceType]) range = rangeTab[dirNr][fromSqr];
  do {
    toSqr += step;
    int victim = board[toSqr];
    ...
   } while(--range);
}
User avatar
gbtami
Posts: 389
Joined: Wed Sep 26, 2012 1:29 pm
Location: Hungary

Re: Did anyone write a xiangqi chess engine?

Post by gbtami »

https://github.com/ianfab/Fairy-Stockfish supports Xiangqi (and countless other variants as well).
You can play it online on https://www.pychess.org/
There is Xiangqi Discord server where you can find resources and players also https://discord.gg/Q6ZCVer3
The best English Xiangqi portal is http://www.xqinenglish.com/index.php/en/
User avatar
maksimKorzh
Posts: 771
Joined: Sat Sep 08, 2018 5:37 pm
Location: Ukraine
Full name: Maksim Korzh

Re: Did anyone write a xiangqi chess engine?

Post by maksimKorzh »

gbtami wrote: Sun Jan 24, 2021 6:08 pm https://github.com/ianfab/Fairy-Stockfish supports Xiangqi (and countless other variants as well).
You can play it online on https://www.pychess.org/
There is Xiangqi Discord server where you can find resources and players also https://discord.gg/Q6ZCVer3
The best English Xiangqi portal is http://www.xqinenglish.com/index.php/en/
Thanks for amazing resource!
Btw I've just finished movegen of my new xiangqi engine!
https://github.com/maksimKorzh/wukong-x ... /wukong.js
User avatar
maksimKorzh
Posts: 771
Joined: Sat Sep 08, 2018 5:37 pm
Location: Ukraine
Full name: Maksim Korzh

Re: Did anyone write a xiangqi chess engine?

Post by maksimKorzh »

hgm wrote: Fri Jan 22, 2021 6:06 pm I wrote two Xiangqi engines: MaxQi and HaQiKi D. The latter is closed source. MaxQi is a micro-Max derivative.

Peculiarity of XiangQi is that perpetual checking doesn't make a draw, but loses for the checker. This rule is crucial, as in almost every game it will at some stage be possible to deliver a perpetual check. And if the engine doesn't know this is suicide, it will throw away every game where it is slightly behind. To treat this properly you cannot just detect repetions in the search and score them as draw; you will have to detect the perpetual by testing the in-check status of all positions since the last occurrence. (And if both have been checking with all their moves, it would still be a draw!)

There is even a similar rule for attacking other pieces ('perpetual chasing'), which is quite complex. E.g. it is not allowed to force repetitions by attacking a Rook with a Horse perpetually, and you would lose by doing it. The rules that specify exactly what can attack what are rather elaborate. I once did a test how important they are: if you play engines that only differ in knowing the chasing rules or thinking such repetitions are draw, 18% of the games end in an illegal perpetual chase, where one engine thinks he salvaged a draw, while the other knows it has won.

In MaxQi I use a course kludge to avoid all this complexity: I score every repetition that is not a check evasion as -300 (about the value of a Horse or Cannon). The idea is that when breaking the repetition loop would score lower than that, the game would be lost anyway, so it might as well take the risk to forfeit, in the hope that the way it was repeating is not illegal. The drawback is that it would be willing to incur minor damage (such as losing a Pawn) for avoiding a repeat that would not be held against it. If you want to do this absolutely correctly, (like I do in HaQiKi D) it is a hassle, though.

Another peculiarity is that some pieces (Advisers and Elephants) cannot be used to attack, because they are confined to your own board half. So their only use is defense, and if there is nothing to defend against, they become completely useless. That means they have no fixed piece values that you can simply add, but that their value becomes dependent on the material imbalance in the attacking pieces in the end-game phase. Defending would in general be more difficult than attacking, without the defensive pieces, though, so in the middle game you badly need the defenders even when the other material is equal. I suppose you could formalize that by giving each piece separate (larger) attacking and (smaller) defending piece values, and top off the (purely defensive) value of the defenders at the difference of the opponent's attack power minus your own defensive power of the other pieces. Pawns have also little defensive power, which is another effect that favors the attacker. (The only defensive use of Pawns is that they can prevent an enemy Pawn from attacking, when they are still on their own half in the same file; such pairs neutralize each other. But the remaining Pawns then only add attack value to their army.)

It is also important that you have to know when to attack and when to defend. Attacking is easier, and the game often is a race to checkmate. Piece-Square tables that strongly draw your Pawns and Horses to the enemy Palace encourage that. But if attacking is futile, the opponent would win that race, and then your best chance is to pull back all your pieces around your own Palace (which you can unfortunately not do for the Pawns), to at least salvage a draw. So where in Chess we often use dual evaluation with opening and end-game scores, which we interpolate depending on game phase, it is more important in Xiangqi to make seperate evals for attacking and defending, and switch between those based on the material balance.

HGM, could you please clarify one little question regarding 3 fold repetitions:
So if say we score it as mate then engine would give all the pieces away just to avoid repetitions, is that correct?
So to avoid that we use around knight/cannon value so it only may drop a pawn, is that correct?
Why exactly the score of knight? Why not just the score of pawn?
Please bring some more light on this topic - I've almost done with movegen and now gonna be switching to search,
Also I guess MaxQi uses the only PST right? could you please post it here?
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Did anyone write a xiangqi chess engine?

Post by hgm »

Indeed, if you score it as mate the engine would avoid it at all cost. The problem is that the rules for determining whether it is a draw or a win (and for which player) are very complex. So at the level of MaxQi is it a blind gamble: whould it go for a repetition, with unknown outcome, or should it avoid the repetition and play on? So I adopted the strategy to play on if it is not yet in badly lost position. (Also because forfeiting because of an illegal perpetual looks more stupid then being checkmated, even if both result in a zero.) Which I equated to being more than a Horse or Cannon behind. Being merely a Pawn behind is not a sure loss. By scoring the repetition slightly more negative than the Horse/Cannon value, it would only take its chances on a repetition when it would have lost anyway. In that case delivering an illegal perpetual can be seen as its way to resign.

The PST is the same as in micro-Max: it contains the square of the distance to the center of the board as a penalty. But this is pretty bad for Xiangqi; you should not mimic that. Pawns and Horses should get the largest bonus for attacking squares in the enemy Palace.
User avatar
maksimKorzh
Posts: 771
Joined: Sat Sep 08, 2018 5:37 pm
Location: Ukraine
Full name: Maksim Korzh

Re: Did anyone write a xiangqi chess engine?

Post by maksimKorzh »

hgm wrote: Thu Jan 28, 2021 11:22 am Indeed, if you score it as mate the engine would avoid it at all cost. The problem is that the rules for determining whether it is a draw or a win (and for which player) are very complex. So at the level of MaxQi is it a blind gamble: whould it go for a repetition, with unknown outcome, or should it avoid the repetition and play on? So I adopted the strategy to play on if it is not yet in badly lost position. (Also because forfeiting because of an illegal perpetual looks more stupid then being checkmated, even if both result in a zero.) Which I equated to being more than a Horse or Cannon behind. Being merely a Pawn behind is not a sure loss. By scoring the repetition slightly more negative than the Horse/Cannon value, it would only take its chances on a repetition when it would have lost anyway. In that case delivering an illegal perpetual can be seen as its way to resign.

The PST is the same as in micro-Max: it contains the square of the distance to the center of the board as a penalty. But this is pretty bad for Xiangqi; you should not mimic that. Pawns and Horses should get the largest bonus for attacking squares in the enemy Palace.
Thank you! Now it's really clear. I would make scoring repetitions slightly more than knight/cannon and for PST I've already found some values:

Code: Select all

// piece tables for Xiangqi are from:
// Li, Cuanqi
// 2008   "Using AdaBoost to Implement Chinese Chess Evaluation Functions", UCLA thesis

var soldierTable = [
    0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0,  0,  0,  0,  0,  0,  0,  0,
    0,  0, -2,  0,  4,  0, -2,  0,  0,
    2,  0,  8,  0,  8,  0,  8,  0,  2,
    6,  12, 18, 18, 20, 18, 18, 12, 6,
    10, 20, 30, 34, 40, 34, 30, 20, 10,
    14, 26, 42, 60, 80, 60, 42, 26, 14,
    18, 36, 56, 80, 120, 80, 56, 36, 18,
    0,  3,  6,  9,  12,  9,  6,  3,  0
];

var horseTable = [
    0, -4, 0, 0, 0, 0, 0, -4, 0,
    0, 2, 4, 4, -2, 4, 4, 2, 0,
    4, 2, 8, 8, 4, 8, 8, 2, 4,
    2, 6, 8, 6, 10, 6, 8, 6, 2,
    4, 12, 16, 14, 12, 14, 16, 12, 4,
    6, 16, 14, 18, 16, 18, 14, 16, 6,
    8, 24, 18, 24, 20, 24, 18, 24, 8,
    12, 14, 16, 20, 18, 20, 16, 14, 12,
    4, 10, 28, 16, 8, 16, 28, 10, 4,
    4, 8, 16, 12, 4, 12, 16, 8, 4
];

var chariotTable = [
    -2, 10, 6, 14, 12, 14, 6, 10, -2,
    8, 4, 8, 16, 8, 16, 8, 4, 8,
    4, 8, 6, 14, 12, 14, 6, 8, 4,
    6, 10, 8, 14, 14, 14, 8, 10, 6,
    12, 16, 14, 20, 20, 20, 14, 16, 12,
    12, 14, 12, 18, 18, 18, 12, 14, 12,
    12, 18, 16, 22, 22, 22, 16, 18, 12,
    12, 12, 12, 18, 18, 18, 12, 12, 12,
    16, 20, 18, 24, 26, 24, 18, 20, 16,
    14, 14, 12, 18, 16, 18, 12, 14, 14
]

var cannonTable = [
    0, 0, 2, 6, 6, 6, 2, 0, 0,
    0, 2, 4, 6, 6, 6, 4, 2, 0,
    4, 0, 8, 6, 10, 6, 8, 0, 4,
    0, 0, 0, 2, 4, 2, 0, 0, 0,
    -2, 0, 4, 2, 6, 2, 4, 0, -2,
    0, 0, 0, 2, 8, 2, 0, 0, 0,
    0, 0, -2, 4, 10, 4, -2, 0, 0,
    2, 2, 0, -10, -8, -10, 0, 2, 2,
    2, 2, 0, -4, -14, -4, 0, 2, 2,
    6, 4, 0, -10, -12, -10, 0, 4, 6 
]
As far as I'm making engine to play versus absolute beginners it should be more than enough.
I'll try a couple of matches versus MaxQi though just because of being curious.
Btw can I use UCI instead of UCCI to run under winboard?
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Did anyone write a xiangqi chess engine?

Post by hgm »

Running under UCI can be a bit tricky. But you can try it.
User avatar
maksimKorzh
Posts: 771
Joined: Sat Sep 08, 2018 5:37 pm
Location: Ukraine
Full name: Maksim Korzh

Re: Did anyone write a xiangqi chess engine?

Post by maksimKorzh »

hgm wrote: Thu Jan 28, 2021 1:17 pm Running under UCI can be a bit tricky. But you can try it.
???
It's not a problem to use UCCI - I just thought it would be easier.
User avatar
hgm
Posts: 27808
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Did anyone write a xiangqi chess engine?

Post by hgm »

I suppose it depends on what you consider UCI for Xiangqi. The Fairy-Stockfish developers insist that rank counting starts at 1 then. Chinese UCI Xiangqi engines count 0-9. But they also omit the 'position' keyword, do not know 'startpos', and expect the color in the FEN to be indicated as r or b. This is sometimes called the UCI-Cyclone dialect. UCI2WB uses the cyclone dialect only when called with the argument -c, but counts ranks from 0 for a 10-rank board even in UCI.