Make deferral default in Shogi promotions
[xboard.git] / moves.c
diff --git a/moves.c b/moves.c
index f4f33b2..a0f7b7e 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -60,7 +60,7 @@
 # include <strings.h>
 #endif /* not HAVE_STRING_H */
 #include "common.h"
-#include "backend.h" 
+#include "backend.h"
 #include "moves.h"
 #include "parser.h"
 
@@ -97,52 +97,11 @@ int SameColor(piece1, piece2)
             (int) piece2 <  (int) EmptySquare);
 }
 
-ChessSquare PromoPiece(moveType)
-     ChessMove moveType;
-{
-    switch (moveType) {
-      default:
-       return EmptySquare;
-      case WhitePromotionQueen:
-       return WhiteQueen;
-      case BlackPromotionQueen:
-       return BlackQueen;
-      case WhitePromotionRook:
-       return WhiteRook;
-      case BlackPromotionRook:
-       return BlackRook;
-      case WhitePromotionBishop:
-       return WhiteBishop;
-      case BlackPromotionBishop:
-       return BlackBishop;
-      case WhitePromotionKnight:
-       return WhiteKnight;
-      case BlackPromotionKnight:
-       return BlackKnight;
-      case WhitePromotionKing:
-       return WhiteKing;
-      case BlackPromotionKing:
-       return BlackKing;
-      case WhitePromotionChancellor:
-        return WhiteMarshall;
-      case BlackPromotionChancellor:
-        return BlackMarshall;
-      case WhitePromotionArchbishop:
-        return WhiteAngel;
-      case BlackPromotionArchbishop:
-        return BlackAngel;
-      case WhitePromotionCentaur:
-        return WhiteSilver;
-      case BlackPromotionCentaur:
-        return BlackSilver;
-    }
-}
-
 char pieceToChar[] = {
-                        'P', 'N', 'B', 'R', 'Q', 'F', 'E', 'A', 'C', 'W', 'M', 
+                        'P', 'N', 'B', 'R', 'Q', 'F', 'E', 'A', 'C', 'W', 'M',
                         'O', 'H', 'I', 'J', 'G', 'D', 'V', 'L', 'S', 'U', 'K',
-                        'p', 'n', 'b', 'r', 'q', 'f', 'e', 'a', 'c', 'w', 'm', 
-                        'o', 'h', 'i', 'j', 'g', 'd', 'v', 'l', 's', 'u', 'k', 
+                        'p', 'n', 'b', 'r', 'q', 'f', 'e', 'a', 'c', 'w', 'm',
+                        'o', 'h', 'i', 'j', 'g', 'd', 'v', 'l', 's', 'u', 'k',
                         'x' };
 char pieceNickName[EmptySquare];
 
@@ -174,59 +133,11 @@ ChessSquare CharToPiece(c)
      return EmptySquare;
 }
 
-ChessMove PromoCharToMoveType(whiteOnMove, promoChar)
-     int whiteOnMove;
-     int promoChar;
-{      /* [HGM] made dependent on CharToPiece to alow alternate piece letters */
-       ChessSquare piece = CharToPiece(whiteOnMove ? ToUpper(promoChar) : ToLower(promoChar) );
-
-       if(promoChar == '=') return whiteOnMove ? WhiteNonPromotion : BlackNonPromotion;
-       if(promoChar == NULLCHAR) return NormalMove;
-
-       switch(piece) {
-               case WhiteQueen:
-                       return WhitePromotionQueen;
-               case WhiteRook:
-                       return WhitePromotionRook;
-               case WhiteBishop:
-                       return WhitePromotionBishop;
-               case WhiteKnight:
-                       return WhitePromotionKnight;
-               case WhiteKing:
-                       return WhitePromotionKing;
-               case WhiteAngel:
-                       return WhitePromotionArchbishop;
-               case WhiteMarshall:
-                       return WhitePromotionChancellor;
-               case WhiteSilver:
-                       return WhitePromotionCentaur;
-               case BlackQueen:
-                       return BlackPromotionQueen;
-               case BlackRook:
-                       return BlackPromotionRook;
-               case BlackBishop:
-                       return BlackPromotionBishop;
-               case BlackKnight:
-                       return BlackPromotionKnight;
-               case BlackKing:
-                       return BlackPromotionKing;
-               case BlackAngel:
-                       return BlackPromotionArchbishop;
-               case BlackMarshall:
-                       return BlackPromotionChancellor;
-               case BlackSilver:
-                       return BlackPromotionCentaur;
-               default:
-                       // not all promotion implemented yet! Take Queen for those we don't know.
-                       return (whiteOnMove ? WhitePromotionQueen : BlackPromotionQueen);
-       }
-}
-
 void CopyBoard(to, from)
      Board to, from;
 {
     int i, j;
-    
+
     for (i = 0; i < BOARD_HEIGHT; i++)
       for (j = 0; j < BOARD_WIDTH; j++)
        to[i][j] = from[i][j];
@@ -239,7 +150,7 @@ int CompareBoards(board1, board2)
      Board board1, board2;
 {
     int i, j;
-    
+
     for (i = 0; i < BOARD_HEIGHT; i++)
       for (j = 0; j < BOARD_WIDTH; j++) {
          if (board1[i][j] != board2[i][j])
@@ -268,7 +179,7 @@ void GenPseudoLegal(board, flags, callback, closure)
     int epfile = (signed char)board[EP_STATUS]; // [HGM] gamestate: extract ep status from board
     int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
 
-    for (rf = 0; rf < BOARD_HEIGHT; rf++) 
+    for (rf = 0; rf < BOARD_HEIGHT; rf++)
       for (ff = BOARD_LEFT; ff < BOARD_RGHT; ff++) {
           ChessSquare piece;
 
@@ -278,7 +189,7 @@ void GenPseudoLegal(board, flags, callback, closure)
              if (!BlackPiece(board[rf][ff])) continue;
          }
           m = 0; piece = board[rf][ff];
-          if(PieceToChar(piece) == '~') 
+          if(PieceToChar(piece) == '~')
                  piece = (ChessSquare) ( DEMOTED piece );
           if(gameInfo.variant == VariantShogi)
                  piece = (ChessSquare) ( SHOGI piece );
@@ -310,7 +221,7 @@ void GenPseudoLegal(board, flags, callback, closure)
               }
               if (rf < BOARD_HEIGHT-1 && board[rf + 1][ff] == EmptySquare) {
                  callback(board, flags,
-                          rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotionQueen : NormalMove,
+                          rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
                           rf, ff, rf + 1, ff, closure);
              }
              if (rf == 1 && board[2][ff] == EmptySquare &&
@@ -324,8 +235,8 @@ void GenPseudoLegal(board, flags, callback, closure)
                   if (rf < BOARD_HEIGHT-1 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                      ((flags & F_KRIEGSPIEL_CAPTURE) ||
                       BlackPiece(board[rf + 1][ff + s]))) {
-                     callback(board, flags, 
-                              rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotionQueen : NormalMove,
+                     callback(board, flags,
+                              rf >= BOARD_HEIGHT-1-promoRank ? WhitePromotion : NormalMove,
                               rf, ff, rf + 1, ff + s, closure);
                  }
                  if (rf == BOARD_HEIGHT-4) {
@@ -337,7 +248,7 @@ void GenPseudoLegal(board, flags, callback, closure)
                                   rf, ff, 5, ff + s, closure);
                      }
                  }
-             }             
+             }
              break;
 
            case BlackPawn:
@@ -359,8 +270,8 @@ void GenPseudoLegal(board, flags, callback, closure)
                   break;
               }
              if (rf > 0 && board[rf - 1][ff] == EmptySquare) {
-                 callback(board, flags, 
-                          rf <= promoRank ? BlackPromotionQueen : NormalMove,
+                 callback(board, flags,
+                          rf <= promoRank ? BlackPromotion : NormalMove,
                           rf, ff, rf - 1, ff, closure);
              }
              if (rf == BOARD_HEIGHT-2 && board[BOARD_HEIGHT-3][ff] == EmptySquare &&
@@ -374,8 +285,8 @@ void GenPseudoLegal(board, flags, callback, closure)
                   if (rf > 0 && ff + s >= BOARD_LEFT && ff + s < BOARD_RGHT &&
                      ((flags & F_KRIEGSPIEL_CAPTURE) ||
                       WhitePiece(board[rf - 1][ff + s]))) {
-                     callback(board, flags, 
-                              rf <= promoRank ? BlackPromotionQueen : NormalMove,
+                     callback(board, flags,
+                              rf <= promoRank ? BlackPromotion : NormalMove,
                               rf, ff, rf - 1, ff + s, closure);
                  }
                  if (rf == 3) {
@@ -387,7 +298,7 @@ void GenPseudoLegal(board, flags, callback, closure)
                                   rf, ff, 2, ff + s, closure);
                      }
                  }
-             }             
+             }
              break;
 
             case WhiteUnicorn:
@@ -425,7 +336,7 @@ void GenPseudoLegal(board, flags, callback, closure)
                       callback(board, flags, NormalMove,
                                rf, ff, rf - 2, ff + s, closure);
                  }
-             }             
+             }
              break;
 
             case WhiteCannon:
@@ -476,7 +387,7 @@ void GenPseudoLegal(board, flags, callback, closure)
                       callback(board, flags, NormalMove,
                               rf, ff, rf - 1, ff + s, closure);
                  }
-             }             
+             }
 
             case WhiteWazir:
             case BlackWazir:
@@ -496,14 +407,14 @@ void GenPseudoLegal(board, flags, callback, closure)
             case WhiteAlfil:
             case BlackAlfil:
                 /* [HGM] support Shatranj pieces */
-                for (rs = -1; rs <= 1; rs += 2) 
+                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 ||
                                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);
@@ -525,8 +436,8 @@ void GenPseudoLegal(board, flags, callback, closure)
             case SHOGI BlackBishop:
            case WhiteBishop:
            case BlackBishop:
-             for (rs = -1; rs <= 1; rs += 2) 
-                for (fs = -1; fs <= 1; fs += 2) 
+             for (rs = -1; rs <= 1; rs += 2)
+                for (fs = -1; fs <= 1; fs += 2)
                  for (i = 1;; i++) {
                      rt = rf + (i * rs);
                      ft = ff + (i * fs);
@@ -598,7 +509,7 @@ void GenPseudoLegal(board, flags, callback, closure)
 
            case WhiteQueen:
            case BlackQueen:
-             for (rs = -1; rs <= 1; rs++) 
+             for (rs = -1; rs <= 1; rs++)
                for (fs = -1; fs <= 1; fs++) {
                    if (rs == 0 && fs == 0) continue;
                    for (i = 1;; i++) {
@@ -620,7 +531,7 @@ void GenPseudoLegal(board, flags, callback, closure)
             case SHOGI WhitePawn:
             case SHOGI WhiteFerz:
                   if (rf < BOARD_HEIGHT-1 &&
-                           !SameColor(board[rf][ff], board[rf + 1][ff]) ) 
+                           !SameColor(board[rf][ff], board[rf + 1][ff]) )
                            callback(board, flags, NormalMove,
                                     rf, ff, rf + 1, ff, closure);
               if(piece != SHOGI WhitePawn) goto finishSilver;
@@ -631,7 +542,7 @@ void GenPseudoLegal(board, flags, callback, closure)
             case SHOGI BlackPawn:
             case SHOGI BlackFerz:
                   if (rf > 0 &&
-                           !SameColor(board[rf][ff], board[rf - 1][ff]) ) 
+                           !SameColor(board[rf][ff], board[rf - 1][ff]) )
                            callback(board, flags, NormalMove,
                                     rf, ff, rf - 1, ff, closure);
               if(piece == SHOGI BlackPawn) break;
@@ -640,7 +551,7 @@ void GenPseudoLegal(board, flags, callback, closure)
             case BlackFerz:
             finishSilver:
                 /* [HGM] support Shatranj pieces */
-                for (rs = -1; rs <= 1; rs += 2) 
+                for (rs = -1; rs <= 1; rs += 2)
                   for (fs = -1; fs <= 1; fs += 2) {
                       rt = rf + rs;
                       ft = ff + fs;
@@ -792,7 +703,7 @@ int GenLegal(board, flags, callback, closure)
             board[0][BOARD_RGHT-1] == WhiteRook &&
             castlingRights[0] != NoRights && /* [HGM] check rights */
             ( castlingRights[2] == ff || castlingRights[6] == ff ) &&
-            (ignoreCheck ||                             
+            (ignoreCheck ||
             (!CheckTest(board, flags, 0, ff, 0, ff + 1, FALSE) &&
               !CheckTest(board, flags, 0, ff, 0, BOARD_RGHT-3, FALSE) &&
               (gameInfo.variant != VariantJanus || !CheckTest(board, flags, 0, ff, 0, BOARD_RGHT-2, FALSE)) &&
@@ -892,7 +803,7 @@ int GenLegal(board, flags, callback, closure)
             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(ff > BOARD_LEFT+2) 
+            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)
@@ -918,7 +829,7 @@ int GenLegal(board, flags, callback, closure)
             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(ff > BOARD_LEFT+2) 
+            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)
@@ -962,7 +873,7 @@ void CheckTestCallback(board, flags, kind, rf, ff, rt, ft, closure)
    e.p. capture.  The possibility of castling out of a check along the
    back rank is not accounted for (i.e., we still return nonzero), as
    this is illegal anyway.  Return value is the number of times the
-   king is in check. */ 
+   king is in check. */
 int CheckTest(board, flags, rf, ff, rt, ft, enPassant)
      Board board;
      int flags;
@@ -1092,7 +1003,7 @@ ChessMove LegalityTest(board, flags, rf, ff, rt, ft, promoChar)
 
     if(rf == DROP_RANK) return LegalDrop(board, flags, ff, rt, ft);
     piece = board[rf][ff];
-    
+
     if (appData.debugMode) {
         int i;
         for(i=0; i<6; i++) fprintf(debugFP, "%d ", castlingRights[i]);
@@ -1116,50 +1027,48 @@ ChessMove LegalityTest(board, flags, rf, ff, rt, ft, promoChar)
                && cl.kind != WhiteCapturesEnPassant && cl.kind != BlackCapturesEnPassant)
        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 == VariantShogi) {
         /* [HGM] Shogi promotions. '=' means defer */
         if(rf != DROP_RANK && cl.kind == NormalMove) {
             ChessSquare piece = board[rf][ff];
 
             if(promoChar == PieceToChar(BlackQueen)) promoChar = NULLCHAR; /* [HGM] Kludge */
-            if(promoChar != NULLCHAR && promoChar != 'x' &&
-               promoChar != '+' && promoChar != '=' &&
-               ToUpper(PieceToChar(PROMOTED piece)) != ToUpper(promoChar) )
-                    cl.kind = IllegalMove;
+            if(promoChar == 'd' && (piece == WhiteRook   || piece == BlackRook)   ||
+               promoChar == 'h' && (piece == WhiteBishop || piece == BlackBishop) ||
+               promoChar == 'g' && (piece <= WhiteFerz || piece <= BlackFerz && piece >= BlackPawn) )
+                  promoChar = '^'; // allowed ICS notations
+if(appData.debugMode)fprintf(debugFP,"SHOGI promoChar = %c\n", promoChar ? promoChar : '-');
+            if(promoChar != NULLCHAR && promoChar != '^' && promoChar != '=')
+                return CharToPiece(promoChar) == EmptySquare ? ImpossibleMove : IllegalMove;
             else if(flags & F_WHITE_ON_MOVE) {
                 if( (int) piece < (int) WhiteWazir &&
-                     (rf > BOARD_HEIGHT-4 || rt > BOARD_HEIGHT-4) ) {
+                     (rf >= BOARD_HEIGHT*2/3 || rt >= BOARD_HEIGHT*2/3) ) {
                     if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 ||
                          piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */
-                             cl.kind = promoChar == '=' ? IllegalMove : WhitePromotionKnight;
-                    else /* promotion optional, default is promote */
-                             cl.kind = promoChar == '=' ? WhiteNonPromotion : WhitePromotionQueen;
-                   
-                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
-                                            NormalMove : IllegalMove;
+                       cl.kind = promoChar == '=' ? IllegalMove : WhitePromotion;
+                    else /* promotion optional, default is defer */
+                       cl.kind = promoChar == '^' ? WhitePromotion : WhiteNonPromotion;
+                } else cl.kind = promoChar == '^' ? IllegalMove : NormalMove;
             } else {
-                if( (int) piece < (int) BlackWazir && (rf < 3 || rt < 3) ) {
+                if( (int) piece < (int) BlackWazir && (rf < BOARD_HEIGHT/3 || rt < BOARD_HEIGHT/3) ) {
                     if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 ||
                          piece == BlackKnight && rt < 2 ) /* promotion obligatory */
-                             cl.kind = promoChar == '=' ? IllegalMove : BlackPromotionKnight;
-                    else /* promotion optional, default is promote */
-                             cl.kind = promoChar == '=' ? BlackNonPromotion : BlackPromotionQueen;
-
-                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
-                                            NormalMove : IllegalMove;
+                       cl.kind = promoChar == '=' ? IllegalMove : BlackPromotion;
+                    else /* promotion optional, default is defer */
+                       cl.kind = promoChar == '^' ? BlackPromotion : BlackNonPromotion;
+                } else cl.kind = promoChar == '^' ? IllegalMove : NormalMove;
             }
         }
     } else
-    if (promoChar != NULLCHAR && promoChar != 'x') {
+    if (promoChar != NULLCHAR) {
        if(promoChar == '=') cl.kind = IllegalMove; else // [HGM] shogi: no deferred promotion outside Shogi
-       if (cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen) {
-           cl.kind = 
-             PromoCharToMoveType((flags & F_WHITE_ON_MOVE) != 0, promoChar);
+       if (cl.kind == WhitePromotion || cl.kind == BlackPromotion) {
+           if(CharToPiece(promoChar) == EmptySquare) cl.kind = ImpossibleMove; // non-existing piece
        } else {
            cl.kind = IllegalMove;
        }
     }
-    /* [HGM] For promotions, 'ToQueen' = optional, 'ToKnight' = mandatory */
     return cl.kind;
 }
 
@@ -1192,7 +1101,7 @@ int MateTest(board, flags)
     int inCheck, r, f, myPieces=0, hisPieces=0, nrKing=0;
     ChessSquare king = flags & F_WHITE_ON_MOVE ? WhiteKing : BlackKing;
 
-    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) { 
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
         // [HGM] losers: Count pieces and kings, to detect other unorthodox winning conditions
        nrKing += (board[r][f] == king);   // stm has king
         if( board[r][f] != EmptySquare ) {
@@ -1219,19 +1128,28 @@ int MateTest(board, flags)
     if (cl.count > 0) {
        return inCheck ? MT_CHECK : MT_NONE;
     } else {
+        if(gameInfo.holdingsWidth && gameInfo.variant != VariantSuper || gameInfo.variant != VariantGreat) { // drop game
+            int r, f, n, holdings = flags & F_WHITE_ON_MOVE ? BOARD_WIDTH-1 : 0;
+            for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) if(board[r][f] == EmptySquare) // all empty squares
+                for(n=0; n<BOARD_HEIGHT; n++) // all pieces in hand
+                    if(board[n][holdings] != EmptySquare) {
+                        int moveType = LegalDrop(board, flags, board[n][holdings], r, f);
+                        if(moveType == WhiteDrop || moveType == BlackDrop) return MT_CHECK; // we can resolve check by legal drop
+                    }
+        }
        if(gameInfo.variant == VariantSuicide) // [HGM] losers: always stalemate, since no check, but result varies
                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
-                                           
-        return inCheck ? MT_CHECKMATE 
-                      : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj) ? 
+
+        return inCheck ? MT_CHECKMATE
+                      : (gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShatranj) ?
                          MT_STAINMATE : MT_STALEMATE;
     }
 }
 
-     
+
 extern void DisambiguateCallback P((Board board, int flags, ChessMove kind,
                                    int rf, int ff, int rt, int ft,
                                    VOIDSTAR closure));
@@ -1294,7 +1212,7 @@ void Disambiguate(board, flags, closure)
     if (closure->count == 0) {
        /* See if it's an illegal move due to check */
         illegal = 1;
-        GenLegal(board, flags|F_IGNORE_CHECK, DisambiguateCallback, (VOIDSTAR) closure);       
+        GenLegal(board, flags|F_IGNORE_CHECK, DisambiguateCallback, (VOIDSTAR) closure);
        if (closure->count == 0) {
            /* No, it's not even that */
     if (appData.debugMode) { int i, j;
@@ -1308,52 +1226,52 @@ void Disambiguate(board, flags, closure)
        }
     }
 
+    if (c == 'x') c = NULLCHAR; // get rid of any 'x' (which should never happen?)
     if(gameInfo.variant == VariantShogi) {
-        /* [HGM] Shogi promotions. '=' means defer */
+        /* [HGM] Shogi promotions. On input, '=' means defer, '^' promote. Afterwards, c is set to '+' for promotions, NULL other */
         if(closure->rfIn != DROP_RANK && closure->kind == NormalMove) {
             ChessSquare piece = closure->piece;
-            if(c != NULLCHAR && c != 'x' && c != '+' && c != '=' &&
-               ToUpper(PieceToChar(PROMOTED piece)) != ToUpper(c) ) 
-                    closure->kind = IllegalMove;
+            if (c == 'd' && (piece == WhiteRook   || piece == BlackRook)   ||
+                c == 'h' && (piece == WhiteBishop || piece == BlackBishop) ||
+                c == 'g' && (piece <= WhiteFerz || piece <= BlackFerz && piece >= BlackPawn) )
+                   c = '^'; // allowed ICS notations
+            if(c != NULLCHAR && c != '^' && c != '=') closure->kind = IllegalMove; // otherwise specifying a piece is illegal
             else if(flags & F_WHITE_ON_MOVE) {
                 if( (int) piece < (int) WhiteWazir &&
-                     (closure->rf > BOARD_HEIGHT-4 || closure->rt > BOARD_HEIGHT-4) ) {
+                     (closure->rf >= BOARD_HEIGHT-(BOARD_HEIGHT/3) || closure->rt >= BOARD_HEIGHT-(BOARD_HEIGHT/3)) ) {
                     if( (piece == WhitePawn || piece == WhiteQueen) && closure->rt > BOARD_HEIGHT-2 ||
                          piece == WhiteKnight && closure->rt > BOARD_HEIGHT-3) /* promotion mandatory */
-                             closure->kind = c == '=' ? IllegalMove : WhitePromotionKnight;
-                    else /* promotion optional, default is promote */
-                             closure->kind = c == '=' ? NormalMove  : WhitePromotionQueen;
-                    if(c != '=') closure->promoCharIn = 'q';
-                } else closure->kind = (c == NULLCHAR || c == 'x' || c == '=') ?
-                                            NormalMove : IllegalMove;
+                       closure->kind = c == '=' ? IllegalMove : WhitePromotion;
+                    else /* promotion optional, default is defer */
+                       closure->kind = c == '^' ? WhitePromotion : WhiteNonPromotion; 
+                } else closure->kind = c == '^' ? IllegalMove : NormalMove;
             } else {
-                if( (int) piece < (int) BlackWazir && (closure->rf < 3 || closure->rt < 3) ) {
+                if( (int) piece < (int) BlackWazir && (closure->rf < BOARD_HEIGHT/3 || closure->rt < BOARD_HEIGHT/3) ) {
                     if( (piece == BlackPawn || piece == BlackQueen) && closure->rt < 1 ||
                          piece == BlackKnight && closure->rt < 2 ) /* promotion obligatory */
-                             closure->kind = c == '=' ? IllegalMove : BlackPromotionKnight;
-                    else /* promotion optional, default is promote */
-                             closure->kind = c == '=' ? NormalMove  : BlackPromotionQueen;
-                    if(c != '=') closure->promoCharIn = 'q';
-                } else closure->kind = (c == NULLCHAR || c == 'x' || c == '=') ?
-                                            NormalMove : IllegalMove;
+                       closure->kind = c == '=' ? IllegalMove : BlackPromotion;
+                    else /* promotion optional, default is defer */
+                       closure->kind = c == '^' ? BlackPromotion : BlackNonPromotion;
+                } else closure->kind = c == '^' ? IllegalMove : NormalMove;
             }
         }
+        if(closure->kind == WhitePromotion || closure->kind == BlackPromotion) c = '^'; else
+        if(closure->kind == WhiteNonPromotion || closure->kind == BlackNonPromotion) c = '=';
     } else
-    if (closure->promoCharIn != NULLCHAR && closure->promoCharIn != 'x') {
-       if (closure->kind == WhitePromotionQueen
-           || closure->kind == BlackPromotionQueen) {
-           closure->kind = 
-             PromoCharToMoveType((flags & F_WHITE_ON_MOVE) != 0,
-                                 closure->promoCharIn);
-       } else {
-           closure->kind = IllegalMove;
-       }
-    }
-    /* [HGM] returns 'q' for optional promotion, 'n' for mandatory */
-    if(closure->promoCharIn != '=')
-        closure->promoChar = ToLower(closure->promoCharIn);
-    else closure->promoChar = '=';
-    if (closure->promoChar == 'x') closure->promoChar = NULLCHAR;
+    if (closure->kind == WhitePromotion || closure->kind == BlackPromotion) {
+        if(c == NULLCHAR) { // missing promoChar on mandatory promotion; use default for variant
+            if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+                c = PieceToChar(BlackFerz);
+            else if(gameInfo.variant == VariantGreat)
+                c = PieceToChar(BlackMan);
+            else
+                c = PieceToChar(BlackQueen);
+        } else if(c == '=') closure->kind = IllegalMove; // no deferral outside Shogi
+    } else if (c != NULLCHAR) closure->kind = IllegalMove;
+
+    closure->promoChar = ToLower(c); // this can be NULLCHAR! Note we keep original promoChar even if illegal.
+    if(c != '^' && c != '=' && c != NULLCHAR && CharToPiece(c) == EmptySquare)
+       closure->kind = ImpossibleMove; // but we cannot handle non-existing piece types!
     if (closure->count > 1) {
        closure->kind = AmbiguousMove;
     }
@@ -1364,9 +1282,6 @@ void Disambiguate(board, flags, closure)
        */
        closure->kind = IllegalMove;
     }
-    if(closure->kind == IllegalMove)
-    /* [HGM] might be a variant we don't understand, pass on promotion info */
-        closure->promoChar = ToLower(closure->promoCharIn);
     if (appData.debugMode) {
         fprintf(debugFP, "Disambiguate out: %d(%d,%d)-(%d,%d) = %d (%c)\n",
         closure->piece,closure->ff,closure->rf,closure->ft,closure->rt,closure->promoChar,
@@ -1417,7 +1332,7 @@ void CoordsToAlgebraicCallback(board, flags, kind, rf, ff, rt, ft, closure)
            } else {
                cl->either++; /* rank or file will rule out this move */
            }
-       }           
+       }
     }
 }
 
@@ -1435,7 +1350,7 @@ ChessMove CoordsToAlgebraic(board, flags, rf, ff, rt, ft, promoChar, out)
     ChessMove kind;
     char *outp = out, c;
     CoordsToAlgebraicClosure cl;
-    
+
     if (rf == DROP_RANK) {
        /* Bughouse piece drop */
        *outp++ = ToUpper(PieceToChar((ChessSquare) ff));
@@ -1497,27 +1412,31 @@ ChessMove CoordsToAlgebraic(board, flags, rf, ff, rt, ft, promoChar, out)
        }
         return kind;
 
-       
+
       case WhiteKing:
       case BlackKing:
         /* Fabien moved code: FRC castling first (if KxR), wild castling second */
        /* Code added by Tord:  FRC castling. */
        if((piece == WhiteKing && board[rt][ft] == WhiteRook) ||
           (piece == BlackKing && board[rt][ft] == BlackRook)) {
-         if(ft > ff) strcpy(out, "O-O"); else strcpy(out, "O-O-O");
-            return LegalityTest(board, flags, rf, ff, rt, ft, promoChar);
+         if(ft > ff)
+           safeStrCpy(out, "O-O", MOVE_LEN);
+         else
+           safeStrCpy(out, "O-O-O", MOVE_LEN);
+         return LegalityTest(board, flags, rf, ff, rt, ft, promoChar);
        }
        /* End of code added by Tord */
        /* 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) &&
+            (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)
-               strcpy(out, "O-O");
+             safeStrCpy(out, "O-O", MOVE_LEN);
             else
-               strcpy(out, "O-O-O");
+             safeStrCpy(out, "O-O-O", MOVE_LEN);
 
            /* This notation is always unambiguous, unless there are
               kings on both the d and e files, with "wild castling"
@@ -1580,46 +1499,14 @@ ChessMove CoordsToAlgebraic(board, flags, rf, ff, rt, ft, promoChar, out)
         if(rt+ONE <= '9')
            *outp++ = rt + ONE;
         else { *outp++ = (rt+ONE-'0')/10 + '0';*outp++ = (rt+ONE-'0')%10 + '0'; }
-       *outp = NULLCHAR;
         if (gameInfo.variant == VariantShogi) {
             /* [HGM] in Shogi non-pawns can promote */
-            if(flags & F_WHITE_ON_MOVE) {
-                if( (int) cl.piece < (int) WhiteWazir &&
-                     (rf > BOARD_HEIGHT-4 || rt > BOARD_HEIGHT-4) ) {
-                    if( (piece == WhitePawn || piece == WhiteQueen) && rt > BOARD_HEIGHT-2 ||
-                         piece == WhiteKnight && rt > BOARD_HEIGHT-3) /* promotion mandatory */
-                             cl.kind = promoChar == '=' ? IllegalMove : WhitePromotionKnight;
-                    else cl.kind =  WhitePromotionQueen; /* promotion optional */
-                   
-                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
-                                            NormalMove : IllegalMove;
-            } else {
-                if( (int) cl.piece < (int) BlackWazir && (rf < 3 || rt < 3) ) {
-                    if( (piece == BlackPawn || piece == BlackQueen) && rt < 1 ||
-                         piece == BlackKnight && rt < 2 ) /* promotion obligatory */
-                             cl.kind = promoChar == '=' ? IllegalMove : BlackPromotionKnight;
-                    else cl.kind =  BlackPromotionQueen; /* promotion optional */
-                } else cl.kind = (promoChar == NULLCHAR || promoChar == 'x' || promoChar == '=') ?
-                                            NormalMove : IllegalMove;
-            }
-            if(cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen) {
-                /* for optional promotions append '+' or '=' */
-                if(promoChar == '=') {
-                    *outp++ = '=';
-                    cl.kind = NormalMove;
-                } else *outp++ = '+';
-                *outp = NULLCHAR;
-            } else if(cl.kind == IllegalMove) {
-                /* Illegal move specifies any given promotion */
-                if(promoChar != NULLCHAR && promoChar != 'x') {
-                    *outp++ = '=';
-                    *outp++ = ToUpper(promoChar);
-                    *outp = NULLCHAR;
-                }
-            }
+            if(promoChar == '^') promoChar = '+';
+            *outp++ = promoChar; // Don't bother to correct move type, return value is never used!
         }
+       *outp = NULLCHAR;
         return cl.kind;
-       
+
       /* [HGM] Always long notation for fairies we don't know */
       case WhiteFalcon:
       case BlackFalcon:
@@ -1631,9 +1518,9 @@ ChessMove CoordsToAlgebraic(board, flags, rf, ff, rt, ft, promoChar, out)
        /* Moving a nonexistent piece */
        break;
     }
-    
+
     /* Not a legal move, even ignoring check.
-       If there was a piece on the from square, 
+       If there was a piece on the from square,
        use style "Ng1g3" or "Ng1xe8";
        if there was a pawn or nothing (!),
        use style "g1g3" or "g1xe8".  Use "x"
@@ -1733,8 +1620,8 @@ void ExistingAttacksCallback(board, flags, kind, rf, ff, rt, ft, closure)
     }
     // search move in chaseStack, and delete it if it occurred there (as we know now it is not a new capture)
     for(i=0; i<chaseStackPointer; i++) {
-       if(chaseStack[i].rf == rf && chaseStack[i].ff == ff && 
-          chaseStack[i].rt == rt && chaseStack[i].ft == ft   ) { 
+       if(chaseStack[i].rf == rf && chaseStack[i].ff == ff &&
+          chaseStack[i].rt == rt && chaseStack[i].ft == ft   ) {
            // move found on chaseStack, delete it by overwriting with move popped from top of chaseStack
            chaseStack[i] = chaseStack[--chaseStackPointer];
            break;
@@ -1774,9 +1661,9 @@ int PerpetualChase(int first, int last)
        chaseStackPointer = 0;   // clear stack that is going to hold possible chases
        // determine all captures possible after the move, and put them on chaseStack
        GenLegal(boards[i+1], PosFlags(i), AttacksCallback, &cl);
-       if(appData.debugMode) { int n; 
-           for(n=0; n<chaseStackPointer; n++) 
-                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE, 
+       if(appData.debugMode) { int n;
+           for(n=0; n<chaseStackPointer; n++)
+                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE,
                                               chaseStack[n].ft+AAA, chaseStack[n].rt+ONE);
             fprintf(debugFP, ": all capts\n");
        }
@@ -1786,9 +1673,9 @@ int PerpetualChase(int first, int last)
        cl.rt = moveList[i][3]-ONE;
        cl.ft = moveList[i][2]-AAA+BOARD_LEFT;
        GenLegal(boards[i],   PosFlags(i), ExistingAttacksCallback, &cl);
-       if(appData.debugMode) { int n; 
-           for(n=0; n<chaseStackPointer; n++) 
-                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE, 
+       if(appData.debugMode) { int n;
+           for(n=0; n<chaseStackPointer; n++)
+                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE,
                                               chaseStack[n].ft+AAA, chaseStack[n].rt+ONE);
             fprintf(debugFP, ": new capts after %c%c%c%c\n", cl.ff+AAA, cl.rf+ONE, cl.ft+AAA, cl.rt+ONE);
        }
@@ -1800,11 +1687,11 @@ int PerpetualChase(int first, int last)
            if(attacker >= (int) BlackPawn) attacker = BLACK_TO_WHITE attacker; // convert to white, as piecee type
            if(victim   >= (int) BlackPawn) victim   = BLACK_TO_WHITE victim;
 
-           if((attacker == WhiteKnight || attacker == WhiteCannon) && victim == WhiteRook) 
+           if((attacker == WhiteKnight || attacker == WhiteCannon) && victim == WhiteRook)
                continue; // C or H attack on R is always chase; leave on chaseStack
 
            if(attacker == victim) {
-                if(LegalityTest(boards[i+1], PosFlags(i+1), chaseStack[j].rt, 
+                if(LegalityTest(boards[i+1], PosFlags(i+1), chaseStack[j].rt,
                    chaseStack[j].ft, chaseStack[j].rf, chaseStack[j].ff, NULLCHAR) == NormalMove) {
                        // we can capture back with equal piece, so this is no chase but a sacrifice
                         chaseStack[j] = chaseStack[--chaseStackPointer]; // delete the capture from the chaseStack
@@ -1832,13 +1719,13 @@ int PerpetualChase(int first, int last)
            // if a recapture was found, piece is protected, and we are not chasing it.
            if(cl.recaptures) { // attacked piece was defended by true protector, no chase
                chaseStack[j] = chaseStack[--chaseStackPointer]; // so delete from chaseStack
-               j--; /* ! */ 
+               j--; /* ! */
            }
        }
        // chaseStack now contains all moves that chased
-       if(appData.debugMode) { int n; 
-           for(n=0; n<chaseStackPointer; n++) 
-                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE, 
+       if(appData.debugMode) { int n;
+           for(n=0; n<chaseStackPointer; n++)
+                fprintf(debugFP, "%c%c%c%c ", chaseStack[n].ff+AAA, chaseStack[n].rf+ONE,
                                               chaseStack[n].ft+AAA, chaseStack[n].rt+ONE);
             fprintf(debugFP, ": chases\n");
        }
@@ -1864,8 +1751,8 @@ int PerpetualChase(int first, int last)
            }
        }
         preyStackPointer = tail; // keep bottom part of preyStack, popping pieces unchased on move i.
-       if(appData.debugMode) { int n; 
-            for(n=0; n<preyStackPointer; n++) 
+       if(appData.debugMode) { int n;
+            for(n=0; n<preyStackPointer; n++)
                 fprintf(debugFP, "%c%c ", preyStack[n].file+AAA, preyStack[n].rank+ONE);
             fprintf(debugFP, "always chased upto ply %d\n", i);
        }