Fix Duck Chess
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index ef2f504..3a07137 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -122,6 +122,7 @@ unsigned char pieceToChar[EmptySquare+1] = {
                         'x' };
 unsigned char pieceNickName[EmptySquare];
 int promoPartner[EmptySquare];
+unsigned char autoProm[EmptySquare];
 
 char
 PieceToChar (ChessSquare p)
@@ -170,7 +171,7 @@ CopyBoard (Board to, Board from)
 {
     int i, j;
 
-    for (i = 0; i < BOARD_HEIGHT; i++)
+    for (i = 0; i < handSize; i++)
       for (j = 0; j < BOARD_WIDTH; j++)
        to[i][j] = from[i][j];
     for (j = 0; j < BOARD_FILES; j++) // [HGM] gamestate: copy castling rights and ep status
@@ -212,7 +213,7 @@ CollectPieceDescriptors ()
     if(!pieceDefs) return "";
     if(gameInfo.variant == VariantChu) return ""; // for now don't do this for Chu Shogi
     if(gameInfo.variant == VariantShogi) pieceName = shogiName;
-    if(gameInfo.variant == VariantXiangqi) pieceName = xqName;
+    if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) pieceName = xqName;
     for(p=WhitePawn; p<EmptySquare; p++) {
        if((c = pieceToChar[p]) == '.' || c == '~') continue;  // does not participate
        m = pieceDesc[p]; d = (c == '+' ? pieceToChar[DEMOTED(p)] : c);
@@ -310,6 +311,8 @@ OK (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOID
     (*(int*)cl)++;
 }
 
+static int viaX = 100, viaY = 100, epFlag;
+
 void
 MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle, int range, char *desc, MoveCallback cb, VOIDSTAR cl)
 {
@@ -318,11 +321,13 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
     ChessMove promo= NormalMove; ChessSquare pc = board[r][f];
     if(pc == DarkSquare) return; // this is not a piece, but a 'hole' in the board
     if(flags & F_WHITE_ON_MOVE) his = 2, mine = 1; else his = 1, mine = 2;
-    if(pc == WhitePawn || pc == WhiteLance) promo = WhitePromotion, promoRank = BOARD_HEIGHT-1; else
-    if(pc == BlackPawn || pc == BlackLance) promo = BlackPromotion, promoRank = 0;
+    if(gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantJanggi) {
+       if(pc == WhitePawn || pc == WhiteLance) promo = WhitePromotion, promoRank = BOARD_HEIGHT-1; else
+       if(pc == BlackPawn || pc == BlackLance) promo = BlackPromotion, promoRank = 0;
+    }
     while(*p) {                  // more moves to go
-       int expo = -1, dx, dy, x, y, mode, dirSet, ds2=0, retry=0, initial=0, jump=1, skip = 0, all = 0;
-       char *cont = NULL;
+       int expo = -1, dx, dy, x, y, mode, dirSet, ds2=0, retry=0, initial=0, jump=1, skip = 0, all = 0, put = 0, u = 0;
+       char *cont = NULL, *q;
        while(*p == 'i') initial++, desc = ++p;
        while(islower(*p)) p++;  // skip prefixes
        if(!isupper(*p)) return; // syntax error: no atom
@@ -384,6 +389,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                    if(!dirSet) dirSet = (tx < 0 ? 0xFF                     // default is all directions, but in continuation leg
                                          : all == 0xFF ? 0xEF : 0x45);     // omits backward, and for 4-fold atoms also diags
                    dirSet = (dirSet << angle | dirSet >> 8-angle) & 255;   // re-orient direction system
+                   if(dx && dx != dy) break;     // oblique continuation
                    ds2 = dirSet & 0xAA;          // extract diagonal directions
                    if(dirSet &= 0x55)            // start with orthogonal moves, if present
                         retry = 1, dx = 0;       // and schedule the diagonal moves for later
@@ -393,10 +399,12 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
        }
        if(mine == 2 && tx < 0) dirSet = dirSet >> 4 | dirSet << 4 & 255;   // invert black moves
        mode = 0;                // build mode mask
+       if(*desc == 'u') put++, desc++;               // unload stuff at start of leg
        if(*desc == 'm') mode |= 4, desc++;           // move to empty
        if(*desc == 'c') mode |= his, desc++;         // capture foe
        if(*desc == 'd') mode |= mine, desc++;        // destroy (capture friend)
        if(*desc == 'e') mode |= 8, desc++;           // e.p. capture last mover
+       if(*desc == 'x') mode  = mine|1<<12, desc++;  // induction step
        if(*desc == 't') mode |= 16, desc++;          // exclude enemies as hop platform ('test')
        if(*desc == 'p') mode |= 32, desc++;          // hop over occupied
        if(*desc == 'g') mode |= 64, desc++;          // hop and toggle range
@@ -408,6 +416,9 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
        if(isdigit(*++p)) expo = atoi(p++);           // read exponent
        if(expo > 9) p++;                             // allow double-digit
        desc = p;                                     // this is start of next move
+       if(tx == -2 && mode & 1<<12) continue;        // prevent recursive move borrowing (flaky)
+       epFlag = // flags initial orthogonal and diagonal pawn non-capture multi-pushes (which have legacy meaning)
+               (initial && promo != NormalMove && !cont && mode == 4 && (!dx || dx == dy) && (dy > 1 ? !jump : expo > 1));
        if(initial == 2) { if(board[r][f] != initialPosition[r-2*his+3][f]) continue; initial = 0; } else
        if(initial && !range) {
                if(   (board[r][f] != initialPosition[r][f] ||
@@ -424,6 +435,8 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
        } else {
            strncpy(buf, cont, 80); cont = buf;       // copy next leg(s), so we can modify
            atom = buf; while(islower(*atom)) atom++; // skip to atom
+           for(q=buf; q!=atom && *q != 'a'; q++)     // test whether next leg unloads
+              if(*q == 'u') u = 1;
            if(mode & 32) mode ^= 256 + 32;           // in non-final legs 'p' means 'pass through'
            if(mode & 64 + 512) {
                mode |= 256;                          // and 'g' too, but converts leaper <-> slider
@@ -449,7 +462,7 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                if(y < 0 || y >= BOARD_HEIGHT) break; // vertically off-board: always done
                if(x <  BOARD_LEFT) { if(mode & 128) x += BOARD_RGHT - BOARD_LEFT, loop++; else break; }
                if(x >= BOARD_RGHT) { if(mode & 128) x -= BOARD_RGHT - BOARD_LEFT, loop++; else break; }
-               if(j) { j--; continue; }              // skip irrespective of occupation
+               if(j > 0) { j--; continue; }          // skip irrespective of occupation
                if(board[y][x] == DarkSquare) break;  // black squares are supposed to be off board
                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
@@ -460,13 +473,23 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                if(initial && expo - i + 1 != range) { if(occup == 4) continue; else break; }
                if(cont) {                            // non-final leg
                  if(mode&16 && his&occup) occup &= 3;// suppress hopping foe in t-mode
+                 if(skip < 0) mode |= 4;             // 'n' = 'm' + rights creation in non-final step leg
                  if(occup & mode) {                  // valid intermediate square, do continuation
                    char origAtom = *atom;
-                   int rg = (expo != 1 ? expo - i + 1 : range);   // pass length of last *slider* leg
+                   int rg = (expo != 1 ? expo - i + 1 : range);  // pass length of last *slider* leg
+                   int transp = (occup | 1<<12) & mode & 0x1104; // no side effect on intermediate square
                    if(!(bit & all)) *atom = rotate[*atom - 'A']; // orth-diag interconversion to make direction valid
-                   if(occup & mode & 0x104)          // no side effects, merge legs to one move
+                   if(transp && !u) {                // no side effects, merge legs to one move
+                       if(skip < 0 && occup == 4) {  // create e.p. rights on this square
+                           if(viaX != 100) {         // second e.p. square!
+                               if(viaX == x && viaY == y - vy) viaY = y | 128; // flag it when we can handle it
+                           } else viaX = x, viaY = y;
+                       }
+                       if(mode & 1<<12) MovesFromString(board, flags,  x, y, x, y, dir, rg, cont, cb, cl), occup = 0; else
                        MovesFromString(board, flags, f, r, x, y, dir, rg, cont, cb, cl);
-                   if(occup & mode & 3 && (killX < 0 || kill2X < 0 && (legNr > 1 || killX == x && killY == y) ||
+                       if(viaY & 128) viaY = y - vy; else viaX = viaY = 100;
+                   }
+                   if((occup & mode & 3 || transp && u) && (killX < 0 || kill2X < 0 && (legNr > 1 || killX == x && killY == y) ||
                                            (legNr == 1 ? kill2X == x && kill2Y == y : killX == x && killY == y))) {     // destructive first leg
                        int cnt = 0;
                        legNr <<= 1;
@@ -511,7 +534,13 @@ MovesFromString (Board board, int flags, int f, int r, int tx, int ty, int angle
                    break;
                }
                if(mode & 16 && (board[y][x] == WhiteKing || board[y][x] == BlackKing)) break; // tame piece, cannot capture royal
-               if(occup & mode) cb(board, flags, y == promoRank ? promo : NormalMove, r, f, y, x, cl); // allowed, generate
+               if(occup & mode) {
+                 if(mode & 1<<12) {
+                   ChessSquare neighbor = board[y][x];
+                   char *borrow = (neighbor == pc ? NULL : pieceDesc[neighbor]); // do not borrow from equal type
+                   if(borrow) MovesFromString(board, flags, f, r, -2, -2, dir, range, borrow, cb, cl);      // borrow moves from neighbor
+                 } else cb(board, flags, y == promoRank ? promo : put ? Swap : NormalMove, r, f, y, x, cl); // allowed, generate
+               }
                if(occup != 4) break; // not valid transit square
            } while(--i);
          }
@@ -729,6 +758,42 @@ Knight (Board board, int flags, int rf, int ff, MoveCallback callback, VOIDSTAR
            }
 }
 
+void
+Zebra (Board board, int flags, int rf, int ff, MoveCallback callback, VOIDSTAR closure)
+{
+    int i, j, s, rt, ft;
+    for (i = -1; i <= 1; i += 2)
+       for (j = -1; j <= 1; j += 2)
+           for (s = 2; s <= 3; s++) {
+               rt = rf + i*s;
+               ft = ff + j*(5-s);
+               if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
+                   && ( gameInfo.variant != VariantJanggi || board[rf+i*(s-2)][ff+j*(3-s)] == EmptySquare && board[rf+i*(s-1)][ff+j*(4-s)] == EmptySquare)
+                   && !SameColor(board[rf][ff], board[rt][ft]))
+                   callback(board, flags, NormalMove, rf, ff, rt, ft, closure);
+           }
+}
+
+void
+PalaceDiags (Board board, int flags, int rf, int ff, int isRook, MoveCallback callback, VOIDSTAR closure)
+{   // Janggi diagonal palace moves
+    int piece = board[rf][ff];
+    int middle = BOARD_WIDTH/2;
+    int palace = (rf < 3 ? 1 : BOARD_HEIGHT-2);
+    if(ff == middle) {
+       if(rf == palace && isRook) Ferz(board, flags, rf, ff, callback, closure);
+    } else if((ff == middle+1 || ff == middle-1) && (rf == palace+1 || rf == palace-1)) { // Palace corner
+       int rt = 2*palace - rf, ft = 2*middle - ff; // reflect
+       ChessSquare center = board[palace][middle];
+       if(isRook && !SameColor(piece, center))
+           callback(board, flags, NormalMove, rf, ff, palace, middle, closure);
+       if(center == WhiteCannon || center == BlackCannon) return;
+       if((center == EmptySquare) == isRook && !SameColor(piece, board[rt][ft])
+          && piece + board[rt][ft] != WhiteCannon + BlackCannon)
+           callback(board, flags, NormalMove, rf, ff, rt, ft, closure);
+    }
+}
+
 /* Call callback once for each pseudo-legal move in the given
    position, except castling moves. A move is pseudo-legal if it is
    legal, or if it would be legal except that it leaves the king in
@@ -743,10 +808,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 {
     int rf, ff;
     int i, j, d, s, fs, rs, rt, ft, m;
+    int vari = gameInfo.variant;
     int epfile = (signed char)board[EP_STATUS]; // [HGM] gamestate: extract ep status from board
-    int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
+    int dead = (vari == VariantSChess && !gameInfo.holdingsSize ? 1 : 0);
+    int promoRank = vari == VariantMakruk || vari == VariantGrand || vari == VariantChuChess ? 3 : 1;
 
-    for (rf = 0; rf < BOARD_HEIGHT; rf++)
+    if(vari == VariantSChess && !gameInfo.holdingsSize) promoRank = 2;
+    for (rf = dead; rf < BOARD_HEIGHT - dead; rf++)
       for (ff = BOARD_LEFT; ff < BOARD_RGHT; ff++) {
           ChessSquare piece;
 
@@ -760,7 +828,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
               MovesFromString(board, flags, ff, rf, -1, -1, 0, 0, pieceDesc[piece], callback, closure);
               continue;
           }
-          if(IS_SHOGI(gameInfo.variant))
+          if(IS_SHOGI(vari))
                  piece = (ChessSquare) ( SHOGI piece );
 
           switch ((int)piece) {
@@ -770,7 +838,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
              break;
 
              case WhitePawn:
-              if(gameInfo.variant == VariantXiangqi) {
+              if(vari != VariantNormal && (vari == VariantXiangqi || vari == VariantJanggi)) {
                   /* [HGM] capture and move straight ahead in Xiangqi */
                   if (rf < BOARD_HEIGHT-1 &&
                            !SameColor(board[rf][ff], board[rf + 1][ff]) ) {
@@ -779,23 +847,32 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                   }
                   /* and move sideways when across the river */
                   for (s = -1; s <= 1; s += 2) {
-                      if (rf >= BOARD_HEIGHT>>1 &&
+                      if ((rf >= BOARD_HEIGHT>>1 || vari == VariantJanggi) &&
                           ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                           !WhitePiece(board[rf][ff+s]) ) {
                            callback(board, flags, NormalMove,
                                     rf, ff, rf, ff+s, closure);
                       }
                   }
+                 if(vari == VariantJanggi) { // diagonal moves in palace
+                     int d = BOARD_HEIGHT - rf;
+                     if(d == 3 || d == 2) {
+                         if(ff == BOARD_WIDTH/2 - d + 2 && !WhitePiece(board[rf+1][ff+1]))
+                             callback(board, flags, NormalMove, rf, ff, rf+1, ff+1, closure);
+                         if(ff == BOARD_WIDTH/2 + d - 2 && !WhitePiece(board[rf+1][ff-1]))
+                             callback(board, flags, NormalMove, rf, ff, rf+1, ff-1, closure);
+                     }
+                 }
                   break;
               }
               if (rf < BOARD_HEIGHT-1 && board[rf + 1][ff] == EmptySquare) {
                  callback(board, flags,
-                          rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
+                          rf >= BOARD_HEIGHT-1-promoRank && !autoProm[WhitePawn] ? WhitePromotion : NormalMove,
                           rf, ff, rf + 1, ff, closure);
              }
              if (rf <= (BOARD_HEIGHT>>1)-3 && board[rf+1][ff] == EmptySquare && // [HGM] grand: also on 3rd rank on 10-board
-                  gameInfo.variant != VariantShatranj && /* [HGM] */
-                  gameInfo.variant != VariantCourier  && /* [HGM] */
+                  vari != VariantShatranj && /* [HGM] */
+                  vari != VariantCourier  && /* [HGM] */
                   board[rf+2][ff] == EmptySquare ) {
                       callback(board, flags, NormalMove,
                                rf, ff, rf+2, ff, closure);
@@ -805,7 +882,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                      ((flags & F_KRIEGSPIEL_CAPTURE) ||
                       BlackPiece(board[rf + 1][ff + s]))) {
                      callback(board, flags,
-                              rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
+                              rf >= BOARD_HEIGHT-1-promoRank && !autoProm[WhitePawn] ? WhitePromotion : NormalMove,
                               rf, ff, rf + 1, ff + s, closure);
                  }
                  if (rf >= BOARD_HEIGHT+1>>1) {// [HGM] grand: 4th & 5th rank on 10-board
@@ -822,7 +899,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
              break;
 
            case BlackPawn:
-              if(gameInfo.variant == VariantXiangqi) {
+              if(vari != VariantNormal && (vari == VariantXiangqi || vari == VariantJanggi)) {
                   /* [HGM] capture straight ahead in Xiangqi */
                   if (rf > 0 && !SameColor(board[rf][ff], board[rf - 1][ff]) ) {
                            callback(board, flags, NormalMove,
@@ -830,23 +907,31 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                   }
                   /* and move sideways when across the river */
                   for (s = -1; s <= 1; s += 2) {
-                      if (rf < BOARD_HEIGHT>>1 &&
+                      if ((rf < BOARD_HEIGHT>>1 || vari == VariantJanggi) &&
                           ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                           !BlackPiece(board[rf][ff+s]) ) {
                            callback(board, flags, NormalMove,
                                     rf, ff, rf, ff+s, closure);
                       }
                   }
+                 if(vari == VariantJanggi) { // diagonal moves in palace
+                     if(rf == 1 || rf == 2) {
+                         if(ff == BOARD_WIDTH/2 - rf + 1 && !BlackPiece(board[rf-1][ff+1]))
+                             callback(board, flags, NormalMove, rf, ff, rf-1, ff+1, closure);
+                         if(ff == BOARD_WIDTH/2 + rf - 1 && !BlackPiece(board[rf-1][ff-1]))
+                             callback(board, flags, NormalMove, rf, ff, rf-1, ff-1, closure);
+                     }
+                 }
                   break;
               }
              if (rf > 0 && board[rf - 1][ff] == EmptySquare) {
                  callback(board, flags,
-                          rf <= promoRank ? BlackPromotion : NormalMove,
+                          rf <= promoRank && !autoProm[BlackPawn] ? BlackPromotion : NormalMove,
                           rf, ff, rf - 1, ff, closure);
              }
              if (rf >= (BOARD_HEIGHT+1>>1)+2 && board[rf-1][ff] == EmptySquare && // [HGM] grand
-                  gameInfo.variant != VariantShatranj && /* [HGM] */
-                  gameInfo.variant != VariantCourier  && /* [HGM] */
+                  vari != VariantShatranj && /* [HGM] */
+                  vari != VariantCourier  && /* [HGM] */
                  board[rf-2][ff] == EmptySquare) {
                  callback(board, flags, NormalMove,
                           rf, ff, rf-2, ff, closure);
@@ -856,7 +941,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                      ((flags & F_KRIEGSPIEL_CAPTURE) ||
                       WhitePiece(board[rf - 1][ff + s]))) {
                      callback(board, flags,
-                              rf <= promoRank ? BlackPromotion : NormalMove,
+                              rf <= promoRank && !autoProm[BlackPawn] ? BlackPromotion : NormalMove,
                               rf, ff, rf - 1, ff + s, closure);
                  }
                  if (rf < BOARD_HEIGHT>>1) {
@@ -882,7 +967,8 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                      rt = rf + i*s;
                      ft = ff + j*(3-s);
                       if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
-                          && ( gameInfo.variant != VariantXiangqi || board[rf+i*(s-1)][ff+j*(2-s)] == EmptySquare)
+                          && ( vari == VariantNormal ||
+                               vari != VariantJanggi && vari != VariantXiangqi || board[rf+i*(s-1)][ff+j*(2-s)] == EmptySquare)
                           && !SameColor(board[rf][ff], board[rt][ft]))
                      callback(board, flags, NormalMove,
                               rf, ff, rt, ft, closure);
@@ -911,6 +997,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
             case WhiteCannon:
             case BlackCannon:
+             if(vari == VariantJanggi) PalaceDiags(board, flags, rf, ff, FALSE, callback, closure);
               for (d = 0; d <= 1; d++)
                 for (s = -1; s <= 1; s += 2) {
                   m = 0;
@@ -918,10 +1005,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                      rt = rf + (i * s) * d;
                      ft = ff + (i * s) * (1 - d);
                       if (rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT) break;
+                     if(vari == VariantJanggi) {
+                         if(board[rt][ft] == WhiteCannon || board[rt][ft] == BlackCannon) break;
+                      } else
                       if (m == 0 && board[rt][ft] == EmptySquare)
                                  callback(board, flags, NormalMove,
                                           rf, ff, rt, ft, closure);
-                      if (m == 1 && board[rt][ft] != EmptySquare &&
+                      if (m == 1 && (board[rt][ft] != EmptySquare || vari == VariantJanggi) &&
                           !SameColor(board[rf][ff], board[rt][ft]) )
                                  callback(board, flags, NormalMove,
                                           rf, ff, rt, ft, closure);
@@ -933,16 +1023,16 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
             /* Gold General (and all its promoted versions) . First do the */
             /* diagonal forward steps, then proceed as normal Wazir        */
             case SHOGI (PROMO WhitePawn):
-               if(gameInfo.variant == VariantShogi) goto WhiteGold;
+               if(vari == VariantShogi) goto WhiteGold;
             case SHOGI (PROMO BlackPawn):
-               if(gameInfo.variant == VariantShogi) goto BlackGold;
+               if(vari == VariantShogi) goto BlackGold;
             case SHOGI WhiteAxe:
             case SHOGI BlackAxe:
                SlideVertical(board, flags, rf, ff, callback, closure);
                break;
 
             case SHOGI (PROMO WhiteKnight):
-               if(gameInfo.variant == VariantShogi) goto WhiteGold;
+               if(vari == VariantShogi) goto WhiteGold;
             case SHOGI WhiteClaw:
             case SHOGI BlackDrunk:
             case SHOGI BlackAlfil:
@@ -952,7 +1042,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                break;
 
             case SHOGI (PROMO BlackKnight):
-               if(gameInfo.variant == VariantShogi) goto BlackGold;
+               if(vari == VariantShogi) goto BlackGold;
             case SHOGI BlackClaw:
             case SHOGI WhiteDrunk:
             case SHOGI WhiteAlfil:
@@ -964,7 +1054,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
             case SHOGI WhiteGnu:
             case SHOGI BlackGnu:
-               if(gameInfo.variant == VariantShogi) goto BlackGold;
+               if(vari == VariantShogi) goto BlackGold;
                SlideVertical(board, flags, rf, ff, callback, closure);
                Ferz(board, flags, rf, ff, callback, closure);
                StepSideways(board, flags, rf, ff, callback, closure);
@@ -988,12 +1078,20 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
             case WhiteWazir:
             case BlackWazir:
-               if(gameInfo.variant == VariantXiangqi) {
-                   int palace = (piece == WhiteWazir ? 1 : BOARD_HEIGHT-2); // Palace center
-                   if(ff <= BOARD_WIDTH/2 && !SameColor(board[rf][ff+1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff+1, closure);
-                   if(ff >= BOARD_WIDTH/2 && !SameColor(board[rf][ff-1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff-1, closure);
+           janggi:
+               if(vari == VariantXiangqi || vari == VariantJanggi) {
+                   int palace = (piece < BlackPawn ? 1 : BOARD_HEIGHT-2); // Palace center
+                   int middle = BOARD_WIDTH/2;
+                   if(ff <= middle && !SameColor(board[rf][ff+1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff+1, closure);
+                   if(ff >= middle && !SameColor(board[rf][ff-1], piece)) callback(board, flags, NormalMove, rf, ff, rf, ff-1, closure);
                    if(rf >= palace && !SameColor(board[rf-1][ff], piece)) callback(board, flags, NormalMove, rf, ff, rf-1, ff, closure);
                    if(rf <= palace && !SameColor(board[rf+1][ff], piece)) callback(board, flags, NormalMove, rf, ff, rf+1, ff, closure);
+                   if(vari == VariantJanggi) {
+                       if(ff == middle) {
+                           if(rf == palace) Ferz(board, flags, rf, ff, callback, closure);
+                       } else if(rf != palace && !SameColor(board[palace][middle], piece))
+                           callback(board, flags, NormalMove, rf, ff, palace, middle, closure);
+                   }
                    break;
                }
                Wazir(board, flags, rf, ff, callback, closure);
@@ -1018,20 +1116,21 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
             case WhiteAlfil:
             case BlackAlfil:
+               if(vari == VariantJanggi) { Zebra(board, flags, rf, ff, callback, closure); break; }
                 /* [HGM] support Shatranj pieces */
                 for (rs = -1; rs <= 1; rs += 2)
                   for (fs = -1; fs <= 1; fs += 2) {
                       rt = rf + 2 * rs;
                       ft = ff + 2 * fs;
                       if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
-                          && ( gameInfo.variant != VariantXiangqi ||
+                          && ( vari != VariantXiangqi ||
                                board[rf+rs][ff+fs] == EmptySquare && (2*rf < BOARD_HEIGHT) == (2*rt < BOARD_HEIGHT) )
 
                           && !SameColor(board[rf][ff], board[rt][ft]))
                                callback(board, flags, NormalMove,
                                         rf, ff, rt, ft, closure);
-                      if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-                         gameInfo.variant == VariantChu      || gameInfo.variant == VariantXiangqi) continue; // classical Alfil
+                      if(vari == VariantShatranj || vari == VariantCourier ||
+                         vari == VariantChu      || vari == VariantXiangqi) continue; // classical Alfil
                       rt = rf + rs; // in unknown variant we assume Modern Elephant, which can also do one step
                       ft = ff + fs;
                       if (!(rt < 0 || rt >= BOARD_HEIGHT || ft < BOARD_LEFT || ft >= BOARD_RGHT)
@@ -1039,7 +1138,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                                callback(board, flags, NormalMove,
                                         rf, ff, rt, ft, closure);
                  }
-                if(gameInfo.variant == VariantSpartan)
+                if(vari == VariantSpartan)
                    for(fs = -1; fs <= 1; fs += 2) {
                       ft = ff + fs;
                       if (!(ft < BOARD_LEFT || ft >= BOARD_RGHT) && board[rf][ft] == EmptySquare)
@@ -1050,7 +1149,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;
+              if(vari == 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;
@@ -1087,7 +1186,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
             /* Shogi Lance is unlike anything, and asymmetric at that */
             case SHOGI WhiteQueen:
-              if(gameInfo.variant == VariantChu) goto doQueen;
+              if(vari == VariantChu) goto doQueen;
               for(i = 1;; i++) {
                       rt = rf + i;
                       ft = ff;
@@ -1100,7 +1199,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
               break;
 
             case SHOGI BlackQueen:
-              if(gameInfo.variant == VariantChu) goto doQueen;
+              if(vari == VariantChu) goto doQueen;
               for(i = 1;; i++) {
                       rt = rf - i;
                       ft = ff;
@@ -1115,7 +1214,7 @@ 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 || gameInfo.variant == VariantSpartan) goto DragonKing;
+              if(vari == VariantChuChess || vari == VariantSpartan) 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;
@@ -1143,7 +1242,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
             case WhiteMarshall:
             case BlackMarshall:
                Rook(board, flags, rf, ff, callback, closure);
-               if(gameInfo.variant == VariantSpartan) // in Spartan Chess Chancellor is used for Dragon King.
+               if(vari == VariantSpartan) // in Spartan Chess Chancellor is used for Dragon King.
                    Ferz(board, flags, rf, ff, callback, closure);
                else
                    Knight(board, flags, rf, ff, callback, closure);
@@ -1163,12 +1262,13 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                break;
 
             /* Shogi Rooks are ordinary Rooks */
+           case WhiteRook:
+           case BlackRook:
+               if(vari == VariantJanggi) PalaceDiags(board, flags, rf, ff, TRUE, callback, closure);
             case SHOGI WhiteRook:
             case SHOGI BlackRook:
             case SHOGI WhitePRook:
             case SHOGI BlackPRook:
-           case WhiteRook:
-           case BlackRook:
                Rook(board, flags, rf, ff, callback, closure);
                break;
 
@@ -1190,20 +1290,21 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                break;
 
             case WhiteMan:
-                if(gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN) goto commoner;
+                if(vari != VariantMakruk && vari != VariantASEAN) goto commoner;
             case SHOGI WhiteFerz:
                Ferz(board, flags, rf, ff, callback, closure);
                StepForward(board, flags, rf, ff, callback, closure);
                break;
 
             case BlackMan:
-                if(gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN) goto commoner;
+                if(vari != VariantMakruk && vari != VariantASEAN) goto commoner;
             case SHOGI BlackFerz:
                StepBackward(board, flags, rf, ff, callback, closure);
 
             case WhiteFerz:
             case BlackFerz:
-               if(gameInfo.variant == VariantXiangqi && ff != BOARD_WIDTH>>1) {
+               if(vari == VariantJanggi) goto janggi;
+               if(vari == VariantXiangqi && ff != BOARD_WIDTH>>1) {
                    int rt = (piece == BlackFerz ? BOARD_HEIGHT-2 : 1);
                    int ft = BOARD_WIDTH>>1;
                    if(!SameColor(board[rf][ff], board[rt][ft]))
@@ -1253,7 +1354,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
 
            // Use Lance as Berolina / Spartan Pawn.
            case WhiteLance:
-             if(gameInfo.variant == VariantSuper) goto Amazon;
+             if(vari == VariantSuper) goto Amazon;
              if (rf < BOARD_HEIGHT-1 && BlackPiece(board[rf + 1][ff]))
                  callback(board, flags,
                           rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
@@ -1269,7 +1370,7 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
              break;
 
            case BlackLance:
-             if(gameInfo.variant == VariantSuper) goto Amazon;
+             if(vari == VariantSuper) goto Amazon;
              if (rf > 0 && WhitePiece(board[rf - 1][ff]))
                  callback(board, flags,
                           rf <= promoRank ? BlackPromotion : NormalMove,
@@ -1314,9 +1415,9 @@ GenPseudoLegal (Board board, int flags, MoveCallback callback, VOIDSTAR closure,
                break;
 
             case SHOGI (PROMO WhiteFerz):
-               if(gameInfo.variant == VariantShogi) goto WhiteGold;
+               if(vari == VariantShogi) goto WhiteGold;
             case SHOGI (PROMO BlackFerz):
-               if(gameInfo.variant == VariantShogi) goto BlackGold;
+               if(vari == VariantShogi) goto BlackGold;
             case SHOGI WhiteSword:
             case SHOGI BlackSword:
             case SHOGI WhitePSword:
@@ -1722,7 +1823,7 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
     int saveKill = killX;
     /*  Suppress warnings on uninitialized variables    */
 
-    if(gameInfo.variant == VariantXiangqi)
+    if(gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi)
         king = flags & F_WHITE_ON_MOVE ? WhiteWazir : BlackWazir;
     if(gameInfo.variant == VariantKnightmate)
         king = flags & F_WHITE_ON_MOVE ? WhiteUnicorn : BlackUnicorn;
@@ -1809,6 +1910,29 @@ CheckTest (Board board, int flags, int rf, int ff, int rt, int ft, int enPassant
     return cl.fking < BOARD_RGHT ? cl.check : (gameInfo.variant == VariantAtomic)*1000; // [HGM] atomic: return 1000 if we have no king
 }
 
+typedef struct { int rt, ft, ep; } EnPassantClosure;
+
+void
+EnPassantCallback (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
+{
+    EnPassantClosure *cl = (EnPassantClosure *) closure;
+    if(rt == cl->rt && ft == cl->ft) {
+        if(kind = WhiteCapturesEnPassant || kind == BlackCapturesEnPassant) cl->ep = 2; // is e.p. capture
+       if(epFlag) cl->ep = 0; // for backward compatibility lets any imn pawn multi-push handle by old code
+       if(viaX != 100) board[EP_FILE] = viaX, board[EP_RANK] = viaY, cl->ep = 1; // generates e.p. rights
+    }
+}
+
+int
+EnPassantTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoChar)
+{   // sets e.p. square in board if move generated rights, returns whether move is an e.p. capture
+    EnPassantClosure cl;
+    ChessSquare piece = board[rf][ff];
+    cl.rt = rt; cl.ft = ft; cl.ep = 0;
+    MovesFromString(board, flags, ff, rf, -1, -1, 0, 0, pieceDesc[piece], EnPassantCallback, (void *) &cl);
+    return cl.ep;
+}
+
 int
 HasLion (Board board, int flags)
 {
@@ -1822,14 +1946,15 @@ HasLion (Board board, int flags)
 ChessMove
 LegalDrop (Board board, int flags, ChessSquare piece, int rt, int ft)
 {   // [HGM] put drop legality testing in separate routine for clarity
-    int n;
+    int n, p = piece;
 if(appData.debugMode) fprintf(debugFP, "LegalDrop: %d @ %d,%d)\n", piece, ft, rt);
     if(board[rt][ft] != EmptySquare) return ImpossibleMove; // must drop to empty square
-    n = PieceToNumber(piece);
-    if((gameInfo.holdingsWidth == 0 || (flags & F_WHITE_ON_MOVE ? board[n][BOARD_WIDTH-1] : board[BOARD_HEIGHT-1-n][0]) != piece)
+    if(PieceToChar(piece) == '+') p = CHUDEMOTED(p);
+    n = PieceToNumber(p);
+    if((gameInfo.holdingsWidth == 0 || (flags & F_WHITE_ON_MOVE ? board[n][BOARD_WIDTH-1] : board[handSize-1-n][0]) != p)
        && gameInfo.variant != VariantBughouse) // in bughouse we don't check for availability, because ICS doesn't always tell us
         return ImpossibleMove; // piece not available
-    if(gameInfo.variant == VariantShogi) { // in Shogi lots of drops are forbidden!
+    if(gameInfo.variant == VariantShogi && !autoProm[piece]) { // in Shogi lots of drops are forbidden! (but not in Kyoto/micro-)
         if((piece == WhitePawn || piece == WhiteQueen) && rt == BOARD_HEIGHT-1 ||
            (piece == BlackPawn || piece == BlackQueen) && rt == 0 ||
             piece == WhiteKnight && rt > BOARD_HEIGHT-3 ||
@@ -1897,7 +2022,25 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC
        return(IllegalMove); // [HGM] losers: if there are legal captures, non-capts are illegal
 
     if(promoChar == 'x') promoChar = NULLCHAR; // [HGM] is this ever the case?
-    if(gameInfo.variant == VariantSChess && promoChar && promoChar != '=' && board[rf][ff] != WhitePawn && board[rf][ff] != BlackPawn) {
+    if(autoProm[piece]) promoChar = NULLCHAR;  // ignore promotion characters on auto-promoting pieces
+    if(gameInfo.variant == VariantSChess) {
+      if(!gameInfo.holdingsSize) {  // holdingless Seirawan
+        if(promoChar == NULLCHAR) { // gating indicator could be missing
+          if(flags & F_WHITE_ON_MOVE) {
+            if(rf == 1) {
+              if(board[0][ff] != DarkSquare) return WhitePromotion; // mandatory gating, report it as promotion
+              if(cl.kind == WhiteQueenSideCastle && board[0][0] != DarkSquare) return WhitePromotion;
+              if(cl.kind == WhiteKingSideCastle && board[0][BOARD_RGHT-1] != DarkSquare) return WhitePromotion;
+            }
+          } else {
+            if(rf == BOARD_HEIGHT-2) {
+              if(board[BOARD_HEIGHT-1][ff] != DarkSquare) return BlackPromotion;
+              if(cl.kind == BlackQueenSideCastle && board[BOARD_HEIGHT-1][0] != DarkSquare) return BlackPromotion;
+              if(cl.kind == BlackKingSideCastle && board[BOARD_HEIGHT-1][BOARD_RGHT-1] != DarkSquare) return BlackPromotion;
+            }
+          }
+        }
+      } else if(promoChar && promoChar != '=' && board[rf][ff] != WhitePawn && board[rf][ff] != BlackPawn) {
         if(board[rf][ff] < BlackPawn) { // white
             if(rf != 0) return IllegalMove; // must be on back rank
             if(!(board[VIRGIN][ff] & VIRGIN_W)) return IllegalMove; // non-virgin
@@ -1911,6 +2054,7 @@ LegalityTest (Board board, int flags, int rf, int ff, int rt, int ft, int promoC
             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) {
         if(cl.kind != NormalMove || promoChar == NULLCHAR || promoChar == '=') return cl.kind;
@@ -2058,7 +2202,8 @@ MateTest (Board board, int flags)
                return myPieces == hisPieces ? MT_STALEMATE :
                                        myPieces > hisPieces ? MT_STAINMATE : MT_STEALMATE;
        else if(gameInfo.variant == VariantLosers) return inCheck ? MT_TRICKMATE : MT_STEALMATE;
-       else if(gameInfo.variant == VariantGiveaway) return MT_STEALMATE; // no check exists, stalemated = win
+       else if(gameInfo.variant == VariantGiveaway || gameInfo.variant == VariantDuck) return MT_STEALMATE; // no check exists, stalemated = win
+       else if(gameInfo.variant == VariantJanggi && !inCheck) return MT_NONE; // in Janggi turn passing is always an option
 
         return inCheck ? MT_CHECKMATE
                       : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj || IS_SHOGI(gameInfo.variant)) ?
@@ -2165,33 +2310,40 @@ Disambiguate (Board board, int flags, DisambiguateClosure *closure)
            return;
          }
        }
-    } else if(pieceDefs && closure->count > 1 && closure->rtIn >=0) { // [HGM] gen: move is ambiguous under engine-defined rules (and not one-click)
-       DisambiguateClosure spare = *closure;
-        if(gameInfo.variant == VariantXiangqi && closure->pieceIn == EmptySquare && closure->ffIn < 0) {
+    } else if(closure->count > 1 && closure->rtIn >=0) { // [HGM] gen: move is ambiguous under engine-defined rules (and not one-click)
+        if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantJanggi) &&
+           (closure->pieceIn == WhitePawn || closure->pieceIn == BlackPawn) && closure->ffIn < 0) {
             closure->ffIn = closure->ftIn; //closure->pieceIn = (flags & 1 ? BlackPawn : WhitePawn); // forward Pawn push has priority
             Disambiguate(board, flags, closure);
             return;
         }
+      if(pieceDefs) {
+       DisambiguateClosure spare = *closure;
        pieceDefs = FALSE; spare.count = 0;     // See if the (erroneous) built-in rules would resolve that
         GenLegal(board, flags, DisambiguateCallback, (VOIDSTAR) &spare, closure->pieceIn);
        if(spare.count == 1) *closure = spare;  // It does, so use those in stead (game from file saved before gen patch?)
        pieceDefs = TRUE;
+      }
     }
 
     if (c == 'x') c = NULLCHAR; // get rid of any 'x' (which should never happen?)
     if(gameInfo.variant == VariantSChess && c && c != '=' && closure->piece != WhitePawn && closure->piece != BlackPawn) {
         if(closure->piece < BlackPawn) { // white
-            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(closure->rf != !gameInfo.holdingsSize) closure->kind = IllegalMove; // must be on back rank
+          if(!(board[VIRGIN][closure->ff] & VIRGIN_W)) closure->kind = IllegalMove; // non-virgin
+          if(gameInfo.holdingsSize) {
             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(closure->rf != BOARD_HEIGHT-1-!gameInfo.holdingsSize) closure->kind = IllegalMove;
+          if(!(board[VIRGIN][closure->ff] & VIRGIN_B)) closure->kind = IllegalMove; // non-virgin
+          if(gameInfo.holdingsSize) {
             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) {
@@ -2319,11 +2471,14 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
     ChessMove kind;
     char *outp = out, c, capture;
     CoordsToAlgebraicClosure cl;
+    int d;
 
     if (rf == DROP_RANK) {
        if(ff == EmptySquare) { strncpy(outp, "--",3); return NormalMove; } // [HGM] pass
        /* Bughouse piece drop */
-       *outp++ = ToUpper(PieceToChar((ChessSquare) ff));
+       c = PieceToChar((ChessSquare) ff);
+       if(c == '+') c = pieceNickName[ff]; // must have nickname for promote drop
+       *outp++ = ToUpper(c);
        *outp++ = '@';
         *outp++ = ft + AAA;
         if(rt+ONE <= '9')
@@ -2358,6 +2513,9 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
             else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
        } else {
            /* Capture; use style "exd5" */
+            if(gameInfo.variant == VariantJanggi && (rt < 2 || rt > BOARD_HEIGHT-3) && ft > BOARD_WIDTH/2 - 2 && ft < BOARD_WIDTH/2 + 2) {
+              *outp++ = rf + ONE;
+            }
             if(capture)
             *outp++ = 'x';  /* [HGM] Xiangqi has sideway noncaptures across river! */
             *outp++ = ft + AAA;
@@ -2382,6 +2540,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
 
       case WhiteKing:
       case BlackKing:
+        d = (gameInfo.variant == VariantSChess && !gameInfo.holdingsSize);
         /* Fabien moved code: FRC castling first (if KxR), wild castling second */
        /* Code added by Tord:  FRC castling. */
        if((piece == WhiteKing && board[rt][ft] == WhiteRook) ||
@@ -2396,14 +2555,14 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
        /* Test for castling or ICS wild castling */
        /* Use style "O-O" (oh-oh) for PGN compatibility */
        else if (rf == rt &&
-           rf == ((piece == WhiteKing) ? 0 : BOARD_HEIGHT-1) &&
+           rf == ((piece == WhiteKing) ? d : BOARD_HEIGHT-1-d) &&
             (ft - ff > 1 || ff - ft > 1) &&  // No castling if legal King move (on narrow boards!)
             ((ff == BOARD_WIDTH>>1 && (ft == BOARD_LEFT+2 || ft == BOARD_RGHT-2)) ||
              (ff == (BOARD_WIDTH-1)>>1 && (ft == BOARD_LEFT+1 || ft == BOARD_RGHT-3)))) {
             if(ft==BOARD_LEFT+1 || ft==BOARD_RGHT-2)
-             snprintf(out, MOVE_LEN, "O-O%c%c", promoChar ? '/' : 0, ToUpper(promoChar));
+             snprintf(out, MOVE_LEN, "O-O%c%c%c", promoChar ? '/' : 0, ToUpper(promoChar), ff + AAA);
             else
-             snprintf(out, MOVE_LEN, "O-O-O%c%c", promoChar ? '/' : 0, ToUpper(promoChar));
+             snprintf(out, MOVE_LEN, "O-O-O%c%c%c", promoChar ? '/' : 0, ToUpper(promoChar), ff + AAA);
 
            /* This notation is always unambiguous, unless there are
               kings on both the d and e files, with "wild castling"
@@ -2443,12 +2602,15 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
           else "N1f3" or "N5xf7",
           else "Ng1f3" or "Ng5xf7".
        */
+        if(c=='+') {
+            c = pieceNickName[piece]; // prefer any nick over +X notation
+            if(c < 'A') *outp++ = c = '+';
+        }
         if( c == '~' || c == '+') {
            /* [HGM] print nonexistent piece as its demoted version */
-           piece = (ChessSquare) (CHUDEMOTED(piece));
+           c = PieceToChar((ChessSquare) (CHUDEMOTED(piece)));
         }
-        if(c=='+') *outp++ = c;
-        *outp++ = ToUpper(PieceToChar(piece));
+        *outp++ = ToUpper(c);
         if(*outp = PieceSuffix(piece)) outp++;
 
        if (cl.file || (cl.either && !cl.rank)) {
@@ -2467,6 +2629,7 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
         if(rt+ONE <= '9')
            *outp++ = rt + ONE;
         else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
+        if(autoProm[piece]) promoChar = 0; // no promotion suffix for implied promotions
         if (IS_SHOGI(gameInfo.variant)) {
             /* [HGM] in Shogi non-pawns can promote */
             *outp++ = promoChar; // Don't bother to correct move type, return value is never used!
@@ -2478,8 +2641,15 @@ CoordsToAlgebraic (Board board, int flags, int rf, int ff, int rt, int ft, int p
             *outp++ = ToUpper(promoChar);
         }
         else if (gameInfo.variant == VariantSChess && promoChar) { // and in S-Chess we have gating
+            ChessSquare victim = board[rt][ft];
+            if(piece == WhiteRook && victim == WhiteKing ||
+               piece == BlackRook && victim == BlackKing) {
+                strncpy(out, "O-O-O", MOVE_LEN);
+                outp = out + 3 + 2*(ft > ff);
+            }
             *outp++ = '/';
             *outp++ = ToUpper(promoChar);
+            if(out[0] == 'O') *outp++ = ff + AAA;
         }
        *outp = NULLCHAR;
         return cl.kind;