Improve SAN of Pawn moves and allow Betza e.p. definition
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index 4d45196..9700dbf 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -54,6 +54,8 @@
 #include "config.h"
 
 #include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
 #if HAVE_STRING_H
 # include <string.h>
 #else /* not HAVE_STRING_H */
@@ -71,6 +73,7 @@ int PosFlags(int index);
 
 extern signed char initialRights[BOARD_FILES]; /* [HGM] all rights enabled, set in InitPosition */
 int quickFlag;
+char *pieceDesc[EmptySquare];
 
 int
 WhitePiece (ChessSquare piece)
@@ -165,6 +168,121 @@ CompareBoards (Board board1, Board board2)
     return TRUE;
 }
 
+// [HGM] gen: configurable move generation from Betza notation sent by engine.
+
+//  alphabet      "abcdefghijklmnopqrstuvwxyz"
+char symmetry[] = "FBNW.FFW.NKN.NW.QR....W..N";
+char xStep[]    = "2110.130.102.10.00....0..2";
+char yStep[]    = "2132.133.313.20.11....1..3";
+char dirType[]  = "01000104000200000260050000";
+//  alphabet   "a b    c d e f    g h    i j k l    m n o p q r    s    t u v    w x y z "
+int dirs1[] = { 0,0x3C,0,0,0,0xC3,0,0,   0,0,0,0xF0,0,0,0,0,0,0x0F,0   ,0,0,0   ,0,0,0,0 };
+int dirs2[] = { 0,0x18,0,0,0,0x81,0,0xFF,0,0,0,0x60,0,0,0,0,0,0x06,0x66,0,0,0x99,0,0,0,0 };
+
+int rot[][4] = { // rotation matrices for each direction
+  { 1, 0, 0, 1 },
+  { 0, 1, 1, 0 },
+  { 0, 1,-1, 0 },
+  { 1, 0, 0,-1 },
+  {-1, 0, 0,-1 },
+  { 0,-1,-1, 0 },
+  { 0,-1, 1, 0 },
+  {-1, 0, 0, 1 }
+};
+
+void
+MovesFromString (Board board, int flags, int f, int r, char *desc, MoveCallback cb, VOIDSTAR cl)
+{
+    char *p = desc;
+    int mine, his, dir, bit, occup, i;
+    if(flags & F_WHITE_ON_MOVE) his = 2, mine = 1; else his = 1, mine = 2;
+    while(*p) {                  // more moves to go
+       int expo = 1, dx, dy, x, y, mode, dirSet, retry=0, initial=0, jump=1;
+       if(*p == 'i') initial = 1, desc = ++p;
+       while(islower(*p)) p++;  // skip prefixes
+       if(!isupper(*p)) return; // syntax error: no atom
+       dirSet = 0;              // build direction set based on atom symmetry
+       switch(symmetry[*p-'A']) {
+         case 'B': expo = 0;    // bishop, slide
+         case 'F':              // diagonal atom (degenerate 4-fold)
+                   while(islower(*desc) && (i = dirType[*desc-'a']) != '0') {
+                       int b = dirs1[*desc-'a']; // use wide version
+                       if( islower(desc[1]) &&
+                                ((i | dirType[desc[1]-'a']) & 3) == 3) {   // combinable (perpendicular dim)
+                           b = dirs1[*desc-'a'] & dirs1[desc[1]-'a'];      // intersect wide & perp wide
+                           desc += 2;
+                       } else desc++;
+                       dirSet |= b;
+                   }
+                   dirSet &= 0x99; if(!dirSet) dirSet = 0x99;
+                   break;
+         case 'R': expo = 0;    // rook, slide
+         case 'W':              // orthogonal atom (non-deg 4-fold)
+                   while(islower(*desc) && (dirType[*desc-'a'] & ~4) != '0') dirSet |= dirs2[*desc++-'a'];
+                   dirSet &= 0x55; if(!dirSet) dirSet = 0x55;
+                   break;
+         case 'N':              // oblique atom (degenerate 8-fold)
+                   while(islower(*desc) && (i = dirType[*desc-'a']) != '0') {
+                       int b = dirs2[*desc-'a']; // when alone, use narrow version
+                       if(desc[1] == 'h') b = dirs1[*desc-'a'], desc += 2; // dirs1 is wide version
+                       else if(islower(desc[1]) && i < '4'
+                               && ((i | dirType[desc[1]-'a']) & 3) == 3) { // combinable (perpendicular dim)
+                           b = dirs1[*desc-'a'] & dirs2[desc[1]-'a'];      // intersect wide & perp narrow
+                           desc += 2;
+                       } else desc++;
+                       dirSet |= b;
+                   }
+                   if(!dirSet) dirSet = 0xFF;
+                   break;
+         case 'Q': expo = 0;    // queen, slide
+         case 'K':              // non-deg (pseudo) 8-fold
+                   dirSet=0x55; // start with orthogonal moves
+                   retry = 1;   // and schedule the diagonal moves for later
+                   break;       // should not have direction indicators
+         default:  return;      // syntax error: invalid atom
+       }
+       if(mine == 2) dirSet = dirSet >> 4 | dirSet << 4 & 255; // invert black moves
+       mode = 0;                // build mode mask
+       if(*desc == 'm') mode |= 4, desc++;
+       if(*desc == 'c') mode |= his, desc++;
+       if(*desc == 'd') mode |= mine, desc++;
+       if(*desc == 'e') mode |= 8, desc++;
+       if(*desc == 'n') jump = 0, desc++;
+       while(*desc == 'j') jump++, desc++;
+       if(!mode) mode = his + 4;// no mode spec, use default = mc
+       dx = xStep[*p-'A'] - '0';                     // step vector of atom
+       dy = yStep[*p-'A'] - '0';
+       if(isdigit(*++p)) expo = atoi(p++);           // read exponent
+       if(expo > 9) p++;                             // allow double-digit
+       desc = p;                                     // this is start of next move
+       if(initial && board[r][f] != initialPosition[r][f]) continue;
+        do {
+         for(dir=0, bit=1; dir<8; dir++, bit += bit) { // loop over directions
+           int i = expo, vx, vy;
+           if(!(bit & dirSet)) continue;             // does not move in this direction
+           vx = dx*rot[dir][0] + dy*rot[dir][1];     // rotate step vector
+           vy = dx*rot[dir][2] + dy*rot[dir][3];
+           x = f; y = r;                             // start square
+           do {
+               x += vx; y += vy;                     // step to next square
+               if(y < 0 || y >= BOARD_HEIGHT || x < BOARD_LEFT || x >= BOARD_RGHT) break;
+               if(!jump    && board[y - vy + vy/2][x - vx + vx/2] != EmptySquare) break; // blocked
+               if(jump > 1 && board[y - vy + vy/2][x - vx + vx/2] == EmptySquare) break; // no hop
+               if(board[y][x] < BlackPawn)   occup = 1; else
+               if(board[y][x] < EmptySquare) occup = 2; else
+                                             occup = 4;
+               if(mode & 8 && y == board[EP_RANK] && occup == 4 && board[EP_FILE] == x) { // to e.p. square
+                   cb(board, flags, mine == 1 ? WhiteCapturesEnPassant : BlackCapturesEnPassant, r, f, y, x, cl);
+               }
+               if(occup & mode) cb(board, flags, NormalMove, r, f, y, x, cl); // allowed, generate
+               if(occup != 4) break; // not valid transit square
+           } while(--i);
+         }
+         dx = dy = 1; dirSet = 0x99; // prepare for diagonal moves of K,Q
+       } while(retry--);             // and start doing them
+    }
+} // next atom
+
 // [HGM] move generation now based on hierarchy of subroutines for rays and combinations of rays
 
 void
@@ -382,20 +500,19 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
     int rf, ff;
     int i, j, d, s, fs, rs, rt, ft, m;
     int epfile = (signed char)board[EP_STATUS]; // [HGM] gamestate: extract ep status from board
-    int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
+    int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
 
     for (rf = 0; rf < BOARD_HEIGHT; rf++)
       for (ff = BOARD_LEFT; ff < BOARD_RGHT; ff++) {
           ChessSquare piece;
-          int rookRange;
 
          if(board[rf][ff] == EmptySquare) continue;
          if ((flags & F_WHITE_ON_MOVE) != (board[rf][ff] < BlackPawn)) continue; // [HGM] speed: wrong color
-         rookRange = 1000;
           m = 0; piece = board[rf][ff];
           if(PieceToChar(piece) == '~')
                  piece = (ChessSquare) ( DEMOTED piece );
           if(filter != EmptySquare && piece != filter) continue;
+          if(pieceDesc[piece]) { MovesFromString(board, flags, ff, rf, pieceDesc[piece], callback, closure); continue; } // [HGM] gen
           if(IS_SHOGI(gameInfo.variant))
                  piece = (ChessSquare) ( SHOGI piece );
 
@@ -672,6 +789,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
             /* Make Dragon-Horse also do Dababba moves outside Shogi, for better disambiguation in variant Fairy */
            case WhiteCardinal:
            case BlackCardinal:
+              if(gameInfo.variant == VariantChuChess) goto DragonHorse;
               for (d = 0; d <= 1; d++) // Dababba moves that Rook cannot do
                 for (s = -2; s <= 2; s += 4) {
                      rt = rf + s * d;
@@ -686,6 +804,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
             case SHOGI BlackCardinal:
             case SHOGI WhitePCardinal:
             case SHOGI BlackPCardinal:
+            DragonHorse:
                Bishop(board, flags, rf, ff, callback, closure);
                Wazir(board, flags, rf, ff, callback, closure);
                break;
@@ -735,22 +854,28 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
             /* Make Dragon-King Dababba & Rook-like outside Shogi, for better disambiguation in variant Fairy */
            case WhiteDragon:
            case BlackDragon:
+              if(gameInfo.variant == VariantChuChess) goto DragonKing;
               for (d = 0; d <= 1; d++) // Dababba moves that Rook cannot do
                 for (s = -2; s <= 2; s += 4) {
                      rt = rf + s * d;
                      ft = ff + s * (1 - d);
-                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT || board[rf+rt>>1][ff+ft>>1] == EmptySquare) continue;
+                      if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) continue;
+                      if (board[rf+rt>>1][ff+ft>>1] == EmptySquare && gameInfo.variant != VariantSpartan) continue;
                      if (SameColor(board[rf][ff], board[rt][ft])) continue;
                      callback(board, flags, NormalMove, rf, ff, rt, ft, closure);
                  }
-              if(gameInfo.variant == VariantSpartan) rookRange = 2; // in Spartan Chess restrict range to modern Dababba
-              goto doRook;
+              if(gameInfo.variant == VariantSpartan) // in Spartan Chess restrict range to modern Dababba
+               Wazir(board, flags, rf, ff, callback, closure);
+             else
+               Rook(board, flags, rf, ff, callback, closure);
+              break;
 
             /* Shogi Dragon King has to continue as Ferz after Rook moves */
             case SHOGI WhiteDragon:
             case SHOGI BlackDragon:
             case SHOGI WhitePDragon:
             case SHOGI BlackPDragon:
+            DragonKing:
                Rook(board, flags, rf, ff, callback, closure);
                Ferz(board, flags, rf, ff, callback, closure);
                break;
@@ -773,7 +898,6 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
             case SHOGI BlackPRook:
            case WhiteRook:
            case BlackRook:
-          doRook:
                Rook(board, flags, rf, ff, callback, closure);
                break;
 
@@ -1211,8 +1335,10 @@ GenLegal (Board board, int  flags, MoveCallback callback, VOIDSTAR closure, Ches
                 if(k != ft && board[0][k] != EmptySquare) ft = NoRights;
             for(k=left; k<right && ft != NoRights; k++) /* then if not checked */
                 if(!ignoreCheck && CheckTest(board, flags, 0, ff, 0, k, FALSE)) ft = NoRights;
-            if(ft != NoRights && board[0][ft] == WhiteRook)
-                callback(board, flags, WhiteHSideCastleFR, 0, swap ? ft : ff, 0, swap ? ff : ft, closure);
+            if(ft != NoRights && board[0][ft] == WhiteRook) {
+                if(flags & F_FRC_TYPE_CASTLING) callback(board, flags, WhiteHSideCastleFR, 0, ff, 0, ft, closure);
+                if(swap)                        callback(board, flags, WhiteHSideCastleFR, 0, ft, 0, ff, closure);
+            }
 
             ft = castlingRights[1]; /* Rook file if we have A-side rights */
             left  = BOARD_LEFT+2;
@@ -1220,11 +1346,14 @@ GenLegal (Board board, int  flags, MoveCallback callback, VOIDSTAR closure, Ches
             if(ff <= BOARD_LEFT+2) { left = ff+1; right = BOARD_LEFT+3; }
             for(k=left; k<=right && ft != NoRights; k++) /* first test if blocked */
                 if(k != ft && board[0][k] != EmptySquare) ft = NoRights;
+            if(ft == 0 && ff != 1 && board[0][1] != EmptySquare) ft = NoRights; /* Rook can be blocked on b1 */
             if(ff > BOARD_LEFT+2)
             for(k=left+1; k<=right && ft != NoRights; k++) /* then if not checked */
                 if(!ignoreCheck && CheckTest(board, flags, 0, ff, 0, k, FALSE)) ft = NoRights;
-            if(ft != NoRights && board[0][ft] == WhiteRook)
-                callback(board, flags, WhiteASideCastleFR, 0, swap ? ft : ff, 0, swap ? ff : ft, closure);
+            if(ft != NoRights && board[0][ft] == WhiteRook) {
+                if(flags & F_FRC_TYPE_CASTLING) callback(board, flags, WhiteASideCastleFR, 0, ff, 0, ft, closure);
+                if(swap)                        callback(board, flags, WhiteASideCastleFR, 0, ft, 0, ff, closure);
+            }
         }
     } else {
         ff = castlingRights[5]; /* King file if we have any rights */
@@ -1237,8 +1366,10 @@ GenLegal (Board board, int  flags, MoveCallback callback, VOIDSTAR closure, Ches
                 if(k != ft && board[BOARD_HEIGHT-1][k] != EmptySquare) ft = NoRights;
             for(k=left; k<right && ft != NoRights; k++) /* then if not checked */
                 if(!ignoreCheck && CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, k, FALSE)) ft = NoRights;
-            if(ft != NoRights && board[BOARD_HEIGHT-1][ft] == BlackRook)
-                callback(board, flags, BlackHSideCastleFR, BOARD_HEIGHT-1, swap ? ft : ff, BOARD_HEIGHT-1, swap ? ff : ft, closure);
+            if(ft != NoRights && board[BOARD_HEIGHT-1][ft] == BlackRook) {
+                if(flags & F_FRC_TYPE_CASTLING) callback(board, flags, BlackHSideCastleFR, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ft, closure);
+                if(swap)                        callback(board, flags, BlackHSideCastleFR, BOARD_HEIGHT-1, ft, BOARD_HEIGHT-1, ff, closure);
+            }
 
             ft = castlingRights[4]; /* Rook file if we have A-side rights */
             left  = BOARD_LEFT+2;
@@ -1246,11 +1377,14 @@ GenLegal (Board board, int  flags, MoveCallback callback, VOIDSTAR closure, Ches
             if(ff <= BOARD_LEFT+2) { left = ff+1; right = BOARD_LEFT+3; }
             for(k=left; k<=right && ft != NoRights; k++) /* first test if blocked */
                 if(k != ft && board[BOARD_HEIGHT-1][k] != EmptySquare) ft = NoRights;
+            if(ft == 0 && ff != 1 && board[BOARD_HEIGHT-1][1] != EmptySquare) ft = NoRights; /* Rook can be blocked on b8 */
             if(ff > BOARD_LEFT+2)
             for(k=left+1; k<=right && ft != NoRights; k++) /* then if not checked */
                 if(!ignoreCheck && CheckTest(board, flags, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, k, FALSE)) ft = NoRights;
-            if(ft != NoRights && board[BOARD_HEIGHT-1][ft] == BlackRook)
-                callback(board, flags, BlackASideCastleFR, BOARD_HEIGHT-1, swap ? ft : ff, BOARD_HEIGHT-1, swap ? ff : ft, closure);
+            if(ft != NoRights && board[BOARD_HEIGHT-1][ft] == BlackRook) {
+                if(flags & F_FRC_TYPE_CASTLING) callback(board, flags, BlackASideCastleFR, BOARD_HEIGHT-1, ff, BOARD_HEIGHT-1, ft, closure);
+                if(swap)                        callback(board, flags, BlackASideCastleFR, BOARD_HEIGHT-1, ft, BOARD_HEIGHT-1, ff, closure);
+            }
         }
     }
 
@@ -1299,13 +1433,22 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
 {
     CheckTestClosure cl;
     ChessSquare king = flags & F_WHITE_ON_MOVE ? WhiteKing : BlackKing;
-    ChessSquare captured = EmptySquare, ep, trampled;
+    ChessSquare captured = EmptySquare, ep=0, trampled=0;
     /*  Suppress warnings on uninitialized variables    */
 
     if(gameInfo.variant == VariantXiangqi)
         king = flags & F_WHITE_ON_MOVE ? WhiteWazir : BlackWazir;
     if(gameInfo.variant == VariantKnightmate)
         king = flags & F_WHITE_ON_MOVE ? WhiteUnicorn : BlackUnicorn;
+    if(gameInfo.variant == VariantChu) { // strictly speaking this is not needed, as Chu officially has no check
+       int r, f, k = king, royals=0, prince = flags & F_WHITE_ON_MOVE ? WhiteMonarch : BlackMonarch;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+           if(board[r][f] == k || board[r][f] == prince) {
+               if(++royals > 1) return FALSE; // no check if we have two royals (ignores double captureby Lion!)
+               king = board[r][f]; // remember hich one we had
+           }
+       }
+    }
 
     if (rt >= 0) {
        if (enPassant) {
@@ -1370,6 +1513,16 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
     return cl.fking < BOARD_RGHT ? cl.check : 1000; // [HGM] atomic: return 1000 if we have no king
 }
 
+int
+HasLion (Board board, int flags)
+{
+    int lion = F_WHITE_ON_MOVE & flags ? WhiteLion : BlackLion;
+    int r, f;
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+        if(board[r][f] == lion) return 1;
+    return 0;
+}
+
 ChessMove
 LegalDrop (Board board, int flags, ChessSquare piece, int rt, int ft)
 {   // [HGM] put drop legality testing in separate routine for clarity
@@ -1386,9 +1539,9 @@ if(appData.debugMode) fprintf(debugFP, "LegalDrop: %d @ %d,%d)\n", piece, ft, rt
             piece == WhiteKnight && rt > BOARD_HEIGHT-3 ||
             piece == BlackKnight && rt < 2 ) return IllegalMove; // e.g. where dropped piece has no moves
         if(piece == WhitePawn || piece == BlackPawn) {
-            int r;
+            int r, max = 1 + (BOARD_HEIGHT == 7); // two Pawns per file in Tori!
             for(r=1; r<BOARD_HEIGHT-1; r++)
-                if(board[r][ft] == piece) return IllegalMove; // or there already is a Pawn in file
+                if(!(max -= (board[r][ft] == piece))) return IllegalMove; // or there already is a Pawn in file
             // should still test if we mate with this Pawn
         }
     } else if(gameInfo.variant == VariantSChess) { // only back-rank drops
@@ -1431,8 +1584,8 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC
 
     /* [HGM] Cobra and Falcon are wildcard pieces; consider all their moves legal */
     /* (perhaps we should disallow moves that obviously leave us in check?)              */
-    if(piece == WhiteFalcon || piece == BlackFalcon ||
-       piece == WhiteCobra  || piece == BlackCobra)
+    if((piece == WhiteFalcon || piece == BlackFalcon ||
+        piece == WhiteCobra  || piece == BlackCobra) && gameInfo.variant != VariantChu)
         return CheckTest(board, flags, rf, ff, rt, ft, FALSE) ? IllegalMove : NormalMove;
 
     cl.rf = rf;
@@ -1453,10 +1606,14 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC
             if(rf != 0) return IllegalMove; // must be on back rank
             if(!(board[VIRGIN][ff] & VIRGIN_W)) return IllegalMove; // non-virgin
             if(board[PieceToNumber(CharToPiece(ToUpper(promoChar)))][BOARD_WIDTH-2] == 0) return ImpossibleMove;// must be in stock
+            if(cl.kind == WhiteHSideCastleFR && (ff == BOARD_RGHT-2 || ff == BOARD_RGHT-3)) return ImpossibleMove;
+            if(cl.kind == WhiteASideCastleFR && (ff == BOARD_LEFT+2 || ff == BOARD_LEFT+3)) return ImpossibleMove;
         } else {
             if(rf != BOARD_HEIGHT-1) return IllegalMove;
             if(!(board[VIRGIN][ff] & VIRGIN_B)) return IllegalMove; // non-virgin
             if(board[BOARD_HEIGHT-1-PieceToNumber(CharToPiece(ToLower(promoChar)))][1] == 0) return ImpossibleMove;
+            if(cl.kind == BlackHSideCastleFR && (ff == BOARD_RGHT-2 || ff == BOARD_RGHT-3)) return ImpossibleMove;
+            if(cl.kind == BlackASideCastleFR && (ff == BOARD_LEFT+2 || ff == BOARD_LEFT+3)) return ImpossibleMove;
         }
     } else
     if(gameInfo.variant == VariantChu) {
@@ -1500,16 +1657,26 @@ if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promo
         }
     } else
     if (promoChar != NULLCHAR) {
+       if(cl.kind == NormalMove && promoChar == '+') { // allow shogi-style promotion is pieceToChar specifies them
+            ChessSquare piece = board[rf][ff];
+            if(piece < BlackPawn ? piece > WhiteMan : piece > BlackMan) return ImpossibleMove; // already promoted
+            // should test if in zone, really
+            if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight) && HasLion(board, flags))
+                return IllegalMove;
+            if(PieceToChar(PROMOTED piece) == '+') return flags & F_WHITE_ON_MOVE ? WhitePromotion : BlackPromotion;
+        } else
        if(promoChar == '=') cl.kind = IllegalMove; else // [HGM] shogi: no deferred promotion outside Shogi
        if (cl.kind == WhitePromotion || cl.kind == BlackPromotion) {
            ChessSquare piece = CharToPiece(flags & F_WHITE_ON_MOVE ? ToUpper(promoChar) : ToLower(promoChar));
            if(piece == EmptySquare)
                 cl.kind = ImpossibleMove; // non-existing piece
-           if(gameInfo.variant == VariantSpartan && cl.kind == BlackPromotion ) {
+           if(gameInfo.variant == VariantChuChess && promoChar == 'l' && HasLion(board, flags)) {
+                cl.kind = IllegalMove; // no two Lions
+           } else if(gameInfo.variant == VariantSpartan && cl.kind == BlackPromotion ) {
                if(promoChar != PieceToChar(BlackKing)) {
                    if(CheckTest(board, flags, rf, ff, rt, ft, FALSE)) cl.kind = IllegalMove; // [HGM] spartan: only promotion to King was possible
                    if(piece == BlackLance) cl.kind = ImpossibleMove;
-               } else { // promotion to King allowed only if we do not haave two yet
+               } else { // promotion to King allowed only if we do not have two yet
                    int r, f, kings = 0;
                    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) kings += (board[r][f] == BlackKing);
                    if(kings == 2) cl.kind = IllegalMove;
@@ -1594,7 +1761,7 @@ MateTest (Board board, int flags)
        else if(gameInfo.variant == VariantGiveaway) return MT_STEALMATE; // no check exists, stalemated = win
 
         return inCheck ? MT_CHECKMATE
-                      : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj || gameInfo.variant == VariantShogi) ?
+                      : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj || IS_SHOGI(gameInfo.variant)) ?
                          MT_STAINMATE : MT_STALEMATE;
     }
 }
@@ -1691,10 +1858,14 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure)
             if(closure->rf != 0) closure->kind = IllegalMove; // must be on back rank
             if(!(board[VIRGIN][closure->ff] & VIRGIN_W)) closure->kind = IllegalMove; // non-virgin
             if(board[PieceToNumber(CharToPiece(ToUpper(c)))][BOARD_WIDTH-2] == 0) closure->kind = ImpossibleMove;// must be in stock
+            if(closure->kind == WhiteHSideCastleFR && (closure->ff == BOARD_RGHT-2 || closure->ff == BOARD_RGHT-3)) closure->kind = ImpossibleMove;
+            if(closure->kind == WhiteASideCastleFR && (closure->ff == BOARD_LEFT+2 || closure->ff == BOARD_LEFT+3)) closure->kind = ImpossibleMove;
         } else {
             if(closure->rf != BOARD_HEIGHT-1) closure->kind = IllegalMove;
             if(!(board[VIRGIN][closure->ff] & VIRGIN_B)) closure->kind = IllegalMove; // non-virgin
             if(board[BOARD_HEIGHT-1-PieceToNumber(CharToPiece(ToLower(c)))][1] == 0) closure->kind = ImpossibleMove;
+            if(closure->kind == BlackHSideCastleFR && (closure->ff == BOARD_RGHT-2 || closure->ff == BOARD_RGHT-3)) closure->kind = ImpossibleMove;
+            if(closure->kind == BlackASideCastleFR && (closure->ff == BOARD_LEFT+2 || closure->ff == BOARD_LEFT+3)) closure->kind = ImpossibleMove;
         }
     } else
     if(gameInfo.variant == VariantChu) {
@@ -1743,6 +1914,12 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure)
             else
                 c = PieceToChar(BlackQueen);
         } else if(c == '=') closure->kind = IllegalMove; // no deferral outside Shogi
+        else if(c == 'l' && gameInfo.variant == VariantChuChess && HasLion(board, flags)) closure->kind = IllegalMove;
+    } else if (c == '+') { // '+' outside shogi, check if pieceToCharTable enabled it
+        ChessSquare p = closure->piece;
+        if(p > WhiteMan && p < BlackPawn || p > BlackMan || PieceToChar(PROMOTED p) != '+')
+            closure->kind = ImpossibleMove; // used on non-promotable piece
+        else if(gameInfo.variant == VariantChuChess && HasLion(board, flags)) closure->kind = IllegalMove;
     } else if (c != NULLCHAR) closure->kind = IllegalMove;
 
     closure->promoChar = ToLower(c); // this can be NULLCHAR! Note we keep original promoChar even if illegal.
@@ -1811,7 +1988,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
 {
     ChessSquare piece;
     ChessMove kind;
-    char *outp = out, c;
+    char *outp = out, c, capture;
     CoordsToAlgebraicClosure cl;
 
     if (rf == DROP_RANK) {
@@ -1844,14 +2021,15 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
        }
        /* Pawn move */
         *outp++ = ff + AAA;
-        if (ff == ft && board[rt][ft] == EmptySquare) { /* [HGM] Xiangqi has straight noncapts! */
+       capture = board[rt][ft] != EmptySquare || kind == WhiteCapturesEnPassant || kind == BlackCapturesEnPassant;
+        if (ff == ft && !capture) { /* [HGM] Xiangqi has straight noncapts! */
            /* Non-capture; use style "e5" */
             if(rt+ONE <= '9')
                *outp++ = rt + ONE;
             else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
        } else {
            /* Capture; use style "exd5" */
-            if(gameInfo.variant != VariantXiangqi || board[rt][ft] != EmptySquare )
+            if(capture)
             *outp++ = 'x';  /* [HGM] Xiangqi has sideway noncaptures across river! */
             *outp++ = ft + AAA;
             if(rt+ONE <= '9')
@@ -1963,7 +2141,8 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
             /* [HGM] in Shogi non-pawns can promote */
             *outp++ = promoChar; // Don't bother to correct move type, return value is never used!
         }
-        else if (gameInfo.variant != VariantSuper && promoChar &&
+        else if (gameInfo.variant == VariantChuChess && promoChar ||
+                 gameInfo.variant != VariantSuper && promoChar &&
                  (piece == WhiteLance || piece == BlackLance) ) { // Lance sometimes represents Pawn
             *outp++ = '=';
             *outp++ = ToUpper(promoChar);