Fix deferral on sweep promotions
[xboard.git] / backend.c
index bc9aa62..0c12671 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,8 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
+ * Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -192,7 +193,6 @@ void GameEnds P((ChessMove result, char *resultDetails, int whosays));
 void EditPositionDone P((Boolean fakeRights));
 void PrintOpponents P((FILE *fp));
 void PrintPosition P((FILE *fp, int move));
-void StartChessProgram P((ChessProgramState *cps));
 void SendToProgram P((char *message, ChessProgramState *cps));
 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
@@ -261,6 +261,7 @@ void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 VariantClass startVariant; /* [HGM] nicks: initial variant */
 Boolean abortMatch;
+int deadRanks;
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -296,7 +297,7 @@ int promoDefaultAltered;
 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
 static int initPing = -1;
 int border;       /* [HGM] width of board rim, needed to size seek graph  */
-char bestMove[MSG_SIZ];
+char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
 int solvingTime, totalTime;
 
 /* States for ics_getting_history */
@@ -387,7 +388,7 @@ u64ToDouble (u64 value)
    by this function.
  */
 int
-PosFlags (index)
+PosFlags (int index)
 {
   int flags = F_ALL_CASTLE_OK;
   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
@@ -443,6 +444,7 @@ char *currentDebugFile; // [HGM] debug split: to remember name
 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
 char thinkOutput1[MSG_SIZ*10];
+char promoRestrict[MSG_SIZ];
 
 ChessProgramState first, second, pairing;
 
@@ -515,7 +517,7 @@ AppData appData;
 Board boards[MAX_MOVES];
 /* [HGM] Following 7 needed for accurate legality tests: */
 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
-signed char  initialRights[BOARD_FILES];
+unsigned char initialRights[BOARD_FILES];
 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
 int   initialRulePlies, FENrulePlies;
 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
@@ -563,8 +565,8 @@ ChessSquare  KnightmateArray[2][BOARD_FILES] = {
 ChessSquare SpartanArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
-        BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+    { BlackAlfil, BlackDragon, BlackKing, BlackTower,
+        BlackTower, BlackKing, BlackAngel, BlackAlfil }
 };
 
 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
@@ -690,18 +692,18 @@ ChessSquare CourierArray[2][BOARD_FILES] = {
         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
 };
 ChessSquare ChuArray[6][BOARD_FILES] = {
-    { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
-      WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
-    { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
-      BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
-    { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
-      WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
-    { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
-      BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
-    { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
-      WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
-    { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
-      BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
+    { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
+      WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
+    { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
+      BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
+    { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
+      WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
+    { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
+      BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
+    { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
+      WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
+    { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
+      BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
 };
 #else // !(BOARD_FILES>=12)
 #define CourierArray CapablancaArray
@@ -995,6 +997,7 @@ Load (ChessProgramState *cps, int i)
        SwapEngines(i);
        ReplaceEngine(cps, i);
        FloatToFront(&appData.recentEngineList, engineLine);
+       if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
        return;
     }
     p = engineName;
@@ -1584,7 +1587,7 @@ MatchEvent (int mode)
        }
        matchMode = mode;
        matchGame = roundNr = 1;
-       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
+       first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
        NextMatchGame();
 }
 
@@ -1740,7 +1743,13 @@ InitBackEnd3 P((void))
             if(!blackPlaysFirst) {
                 startedFromPositionFile = TRUE;
                 CopyBoard(filePosition, boards[0]);
+                CopyBoard(initialPosition, boards[0]);
             }
+       } else if(*appData.fen != NULLCHAR) {
+           if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
+                startedFromPositionFile = TRUE;
+               Reset(TRUE, TRUE);
+           }
        }
        if (initialMode == AnalyzeMode) {
          if (appData.noChessProgram) {
@@ -1980,7 +1989,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
        DisplayFatalError(_("Error reading from keyboard"), error, 1);
     } else if (gotEof++ > 0) {
        RemoveInputSource(isr);
-       DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
+       DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
     }
 }
 
@@ -2127,7 +2136,7 @@ StringToVariant (char *e)
     } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (p = StrCaseStr(e, variantNames[i])) {
-       if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
+       if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
        v = (VariantClass) i;
        found = TRUE;
        break;
@@ -3387,7 +3396,8 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
 #if ZIPPY
                if (loggedOn == TRUE)
                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
-                          (appData.zippyPlay && ZippyMatch(buf, &backup)));
+                          (appData.zippyPlay && ZippyMatch(buf, &backup)))
+                       ;
 #endif
            } // [DM] 'else { ' deleted
                if (
@@ -4161,6 +4171,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
                      } else if (gotPremove) {
+                       int oldFMM = forwardMostMove;
                        gotPremove = 0;
                        ClearPremoveHighlights();
                        if (appData.debugMode)
@@ -4168,6 +4179,13 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                           UserMoveEvent(premoveFromX, premoveFromY,
                                        premoveToX, premoveToY,
                                         premovePromoChar);
+                       if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
+                         if(moveList[oldFMM-1][1] != '@')
+                           SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
+                                         moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+                         else // (drop)
+                           SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+                       }
                      }
                    }
 
@@ -4888,13 +4906,13 @@ ParseBoard12 (char *string)
                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
                   if(old == new) continue;
-                  if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
+                  if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
                   else if(new == WhiteWazir || new == BlackWazir) {
                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
-                           boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
+                           boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
                       else boards[moveNum][k][j] = old; // preserve type of Gold
                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
-                      boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
+                      boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
               }
          } else {
            /* Move from ICS was illegal!?  Punt. */
@@ -5144,16 +5162,26 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
       } else
       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
        char *m = moveList[moveNum];
+       static char c[2];
+       *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
        if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
          snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
                                               m[2], m[3] - '0',
                                               m[5], m[6] - '0',
                                               m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
-       else
-         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
+       else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
+         *c = m[9]; if(*c == '\n') *c = NULLCHAR;
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
+                                              m[7], m[8] - '0',
+                                              m[7], m[8] - '0',
+                                              m[5], m[6] - '0',
+                                              m[5], m[6] - '0',
+                                              m[2], m[3] - '0', c);
+       } else
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
                                               m[5], m[6] - '0',
                                               m[5], m[6] - '0',
-                                              m[2], m[3] - '0');
+                                              m[2], m[3] - '0', c);
          SendToProgram(buf, cps);
       } else
       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
@@ -5352,11 +5380,15 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
          if(killX >= 0 && killY >= 0) {
            sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
-           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
          }
        } else {
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
+         if(killX >= 0 && killY >= 0) {
+           sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
+         }
        }
     }
 }
@@ -5379,12 +5411,22 @@ int dragging;
 static ClickType lastClickType;
 
 int
+PieceInString (char *s, ChessSquare piece)
+{
+  char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
+  while((p = strchr(s, ID))) {
+    if(!suffix || p[1] == suffix) return TRUE;
+    s = p;
+  }
+  return FALSE;
+}
+
+int
 Partner (ChessSquare *p)
 { // change piece into promotion partner if one shogi-promotes to the other
-  int stride = gameInfo.variant == VariantChu ? 22 : 11;
-  ChessSquare partner;
-  partner = (*p/stride & 1 ? *p - stride : *p + stride);
+  ChessSquare partner = promoPartner[*p];
   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
+  if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
   *p = partner;
   return 1;
 }
@@ -5407,8 +5449,10 @@ Sweep (int step)
        else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
        else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
        if(!step) step = -1;
-    } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
+    } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
            !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
+           promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
+           promoSweep == pawn ||
            appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
             (promoSweep == WhiteLion || promoSweep == BlackLion)));
     if(toX >= 0) {
@@ -5543,6 +5587,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
         *toX = currentMoveString[2] - AAA;
         *toY = currentMoveString[3] - ONE;
        *promoChar = currentMoveString[4];
+       if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
     if (appData.debugMode) {
@@ -5660,21 +5705,26 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
 }
 
 int
-MultiPV (ChessProgramState *cps)
+MultiPV (ChessProgramState *cps, int kind)
 {      // check if engine supports MultiPV, and if so, return the number of the option that sets it
        int i;
-       for(i=0; i<cps->nrOptions; i++)
-           if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
-               return i;
+       for(i=0; i<cps->nrOptions; i++) {
+           char *s = cps->option[i].name;
+           if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
+           if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
+                         && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
+       }
        return -1;
 }
 
 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+static int multi, pv_margin;
+static ChessProgramState *activeCps;
 
 Boolean
 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
 {
-       int startPV, multi, lineStart, origIndex = index;
+       int startPV, lineStart, origIndex = index;
        char *p, buf2[MSG_SIZ];
        ChessProgramState *cps = (pane ? &second : &first);
 
@@ -5688,14 +5738,22 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
        do{ while(buf[index] && buf[index] != '\n') index++;
        } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
-               int n = cps->option[multi].value;
-               if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+       if(lineStart == 0 && gameMode == AnalyzeMode) {
+           int n = 0;
+           if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
+           if(n == 0) { // click not on "fewer" or "more"
+               if((multi = -2 - MultiPV(cps, 2)) >= 0) {
+                   pv_margin = cps->option[multi].value;
+                   activeCps = cps; // non-null signals margin adjustment
+               }
+           } else if((multi = MultiPV(cps, 1)) >= 0) {
+               n += cps->option[multi].value; if(n < 1) n = 1;
                snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
                if(cps->option[multi].value != n) SendToProgram(buf2, cps);
                cps->option[multi].value = n;
                *start = *end = 0;
                return FALSE;
+           }
        } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
                ExcludeClick(origIndex - lineStart);
                return FALSE;
@@ -5743,6 +5801,16 @@ void
 UnLoadPV ()
 {
   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
+  if(activeCps) {
+    if(pv_margin != activeCps->option[multi].value) {
+      char buf[MSG_SIZ];
+      snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
+      SendToProgram(buf, activeCps);
+      activeCps->option[multi].value = pv_margin;
+    }
+    activeCps = NULL;
+    return;
+  }
   if(endPV < 0) return;
   if(appData.autoCopyPV) CopyFENToClipboard();
   endPV = -1;
@@ -5771,6 +5839,17 @@ MovePV (int x, int y, int h)
 { // step through PV based on mouse coordinates (called on mouse move)
   int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15);
 
+  if(activeCps) { // adjusting engine's multi-pv margin
+    if(x > lastX) pv_margin++; else
+    if(x < lastX) pv_margin -= (pv_margin > 0);
+    if(x != lastX) {
+      char buf[MSG_SIZ];
+      snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
+      DisplayMessage(buf, "");
+    }
+    lastX = x;
+    return;
+  }
   // we must somehow check if right button is still down (might be released off board!)
   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
@@ -5956,7 +6035,7 @@ ptclen (const char *s, char *escapes)
 {
     int n = 0;
     if(!*escapes) return strlen(s);
-    while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
+    while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
     return n;
 }
 
@@ -5965,28 +6044,58 @@ SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 /* [HGM] moved here from winboard.c because of its general usefulness */
 /*       Basically a safe strcpy that uses the last character as King */
 {
-    int result = FALSE; int NrPieces, offs;
+    int result = FALSE; int NrPieces;
+    unsigned char partner[EmptySquare];
 
     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
                     && NrPieces >= 12 && !(NrPieces&1)) {
-        int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
+        int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
 
         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
         for( i=offs=0; i<NrPieces/2-1; i++ ) {
-            char *p;
-            if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
-            table[i + offs] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
+            char *p, c=0;
+            if(map[j] == '/') offs = WhitePBishop - i, j++;
+            if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
+            table[i+offs] = map[j++];
+            if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
+            if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
+            if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
         }
         table[(int) WhiteKing]  = map[j++];
-        for( i=offs=0; i<NrPieces/2-1; i++ ) {
-            char *p;
-            if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
-            table[WHITE_TO_BLACK i + offs] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
+        for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
+            char *p, c=0;
+            if(map[j] == '/') offs = WhitePBishop - ii, j++;
+            i = WHITE_TO_BLACK ii;
+            if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
+            table[i+offs] = map[j++];
+            if(p = strchr(escapes, map[j])) j++, table[i+offs] += 64*(p - escapes + 1);
+            if(c) partner[i+offs] = table[i+offs], table[i+offs] = c;
+            if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
         }
         table[(int) BlackKing]  = map[j++];
 
+
+        if(*escapes) { // set up promotion pairing
+            for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
+            // pieceToChar entirely filled, so we can look up specified partners
+            for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
+                int c = table[i];
+                if(c == '^' || c == '-') { // has specified partner
+                    int p;
+                    for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
+                    if(c == '^') table[i] = '+';
+                    if(p < EmptySquare) {
+                        if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
+                        if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
+                        promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
+                    }
+                } else if(c == '*') {
+                    table[i] = partner[i];
+                    promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
+                }
+            }
+        }
+
         result = TRUE;
     }
 
@@ -6079,7 +6188,7 @@ InitPosition (int redraw)
       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
     initialPosition[EP_STATUS] = EP_NONE;
     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
-    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+    SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
          SetCharTable(pieceNickName, appData.pieceNickNames);
     else SetCharTable(pieceNickName, "............");
@@ -6172,8 +6281,10 @@ InitPosition (int redraw)
       gameInfo.boardWidth  = 12;
       gameInfo.boardHeight = 12;
       nrCastlingRights = 0;
-      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
-                                   "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
+//      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
+  //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
+      SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
+                                   "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6187,7 +6298,7 @@ InitPosition (int redraw)
       break;
     case VariantSpartan:
       pieces = SpartanArray;
-      SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
+      SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
       break;
     case VariantLion:
       pieces = lionArray;
@@ -6353,6 +6464,7 @@ InitPosition (int redraw)
           initialRights[i] = filePosition[CASTLING][i];
       startedFromSetupPosition = TRUE;
     }
+    if(*appData.men) LoadPieceDesc(appData.men);
 
     CopyBoard(boards[0], initialPosition);
 
@@ -6399,11 +6511,11 @@ SendBoard (ChessProgramState *cps, int moveNum)
          if ((int) *bp < (int) BlackPawn) {
            if(j == BOARD_RGHT+1)
                 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
-           else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
+           else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
-             snprintf(message, MSG_SIZ,"%c%c%c+\n",
-                        PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
+                        PieceToChar((ChessSquare)(DEMOTED(*bp))),
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6423,12 +6535,12 @@ SendBoard (ChessProgramState *cps, int moveNum)
              && ((int) *bp >= (int) BlackPawn)) {
            if(j == BOARD_LEFT-2)
                 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
-           else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
-                    AAA + j, ONE + i);
+           else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
+                    AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
-             snprintf(message, MSG_SIZ,"%c%c%c+\n",
-                        PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
+                        PieceToChar((ChessSquare)(DEMOTED(*bp))),
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6592,9 +6704,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
 
     piece = boards[currentMove][fromY][fromX];
     if(gameInfo.variant == VariantChu) {
-        int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
         promotionZoneSize = BOARD_HEIGHT/3;
-        highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
+        highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
         highestPromotingPiece = (int)WhiteAlfil;
@@ -6659,7 +6770,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
            *promoChoice = PieceToChar(p++);
            if(*promoChoice != '.') break;
        }
-       return FALSE;
+       if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
     }
     // no sense asking what we must promote to if it is going to explode...
     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
@@ -6758,6 +6869,7 @@ OKToStartUserMove (int x, int y)
       case PlayFromGameFile:
            if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
       case EditGame:
+      case AnalyzeMode:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
@@ -6874,9 +6986,10 @@ char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = EndOfFile;
 int doubleClick;
 Boolean addToBookFlag;
+static Board rightsBoard, nullBoard;
 
 void
-UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
+UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
 {
     ChessMove moveType;
     ChessSquare pup;
@@ -6960,6 +7073,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
+            DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
             return;
        }
        break;
@@ -6981,6 +7095,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
+            DrawPosition(TRUE, boards[currentMove]);
             return;
        }
        break;
@@ -6997,12 +7112,15 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
            return;
        } else if (toX >= 0 && toY >= 0) {
            if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
-               ChessSquare q, p = boards[0][rf][ff];
-               if(p >= BlackPawn) p = BLACK_TO_WHITE p;
-               if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
-               else p = CHUDEMOTED (q = boards[0][rf][ff]);
-               if(PieceToChar(q) == '+') gatingPiece = p;
-           }
+               ChessSquare p = boards[0][rf][ff];
+               if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
+               if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
+               if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
+                   int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
+                   DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
+                   gatingPiece = p;
+               }
+           } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
                if(boards[0][fromY][0] != EmptySquare) {
@@ -7017,6 +7135,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                }
            } else
            boards[0][fromY][fromX] = gatingPiece;
+           ClearHighlights();
            DrawPosition(FALSE, boards[currentMove]);
            return;
        }
@@ -7045,6 +7164,8 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
 
     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
 
+    if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
+
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
@@ -7057,13 +7178,15 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
             ClearPremoveHighlights(); // was included
        else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
+       DrawPosition(FALSE, NULL);
        return;
     }
 
     if(addToBookFlag) { // adding moves to book
        char buf[MSG_SIZ], move[MSG_SIZ];
         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
-       if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
+       if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
+                                                                  killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
        snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
        AddBookMove(buf);
        addToBookFlag = FALSE;
@@ -7275,12 +7398,12 @@ MarkByFEN(char *fen)
        int r, f;
        if(!appData.markers || !appData.highlightDragging) return;
        for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
-       r=BOARD_HEIGHT-1; f=BOARD_LEFT;
+       r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
        while(*fen) {
            int s = 0;
            marker[r][f] = 0;
            if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
-           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
+           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
            if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
            if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
            if(*fen == 'T') marker[r][f++] = 0; else
@@ -7309,11 +7432,12 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
 {
     typedef char Markers[BOARD_RANKS][BOARD_FILES];
     Markers *m = (Markers *) closure;
-    if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
+    if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
+                                     kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
        (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
                         || kind == WhiteCapturesEnPassant
-                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
-    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
+                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
+    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
 }
 
 static int hoverSavedValid;
@@ -7325,7 +7449,7 @@ MarkTargetSquares (int clear)
   if(clear) { // no reason to ever suppress clearing
     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
     hoverSavedValid = 0;
-    if(!sum) return; // nothing was cleared,no redraw needed
+    if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
   } else {
     int capt = 0;
     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
@@ -7364,8 +7488,8 @@ CanPromote (ChessSquare piece, int y)
        // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
        if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
-          gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-         gameInfo.variant == VariantMakruk) return FALSE;
+         (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+           gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
        return (piece == BlackPawn && y <= zone ||
                piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
                piece == BlackLance && y <= zone ||
@@ -7419,11 +7543,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
     static TimeMark lastClickTime, prevClickTime;
 
+    if(flashing) return;
+
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
     if (!flipView && y >= 0) {
@@ -7514,7 +7640,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
        doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
       }
-      fromX = x; fromY = y; toX = toY = killX = killY = -1;
+      fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
         appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
@@ -7527,7 +7653,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
                    promoSweep = defaultPromoChoice;
-                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
                    Sweep(0); // Pawn that is going to promote: preview promotion piece
                    DisplayMessage("", _("Pull pawn backwards to under-promote"));
                }
@@ -7540,7 +7666,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            return;
        }
     }
-printf("to click %d,%d\n",x,y);
+
     /* fromX != -1 */
     if (clickType == Press && gameMode != EditPosition) {
        ChessSquare fromP;
@@ -7566,13 +7692,13 @@ printf("to click %d,%d\n",x,y);
             !(fromP == BlackKing && toP == BlackRook && frc)))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
-           killX = killY = -1;
+           killX = killY = kill2X = kill2Y = -1;
            if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
                second = FALSE; // first double-click rather than scond click
                doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
            }
            promoDefaultAltered = FALSE;
-           MarkTargetSquares(1);
+          if(!second) MarkTargetSquares(1);
           if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
@@ -7592,7 +7718,7 @@ printf("to click %d,%d\n",x,y);
                DragPieceBegin(xPix, yPix, FALSE);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
                    promoSweep = defaultPromoChoice;
-                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
                    Sweep(0); // Pawn that is going to promote: preview promotion piece
                }
            }
@@ -7603,10 +7729,10 @@ printf("to click %d,%d\n",x,y);
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
     }
-printf("A type=%d\n",clickType);
 
-    if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
+    if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
        gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
+       DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
        return;
     }
 
@@ -7629,10 +7755,11 @@ printf("A type=%d\n",clickType);
            second = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
-           MarkTargetSquares(1);
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
+           MarkTargetSquares(-1);
+           DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
        } else {
            /* First upclick in same square; start click-click mode */
            SetHighlights(x, y, -1, -1);
@@ -7641,7 +7768,7 @@ printf("A type=%d\n",clickType);
     }
 
     clearFlag = 0;
-printf("B\n");
+
     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
        if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
@@ -7649,7 +7776,7 @@ printf("B\n");
        DrawPosition(TRUE, NULL);
        return; // ignore to-click
     }
-printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
+
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
     if(!sweepSelecting) {
@@ -7672,14 +7799,16 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
            return;
        }
        if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
-           killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
+           killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
        } else
        if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
        if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
          if(appData.sweepSelect) {
            promoSweep = defaultPromoChoice;
-           if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
+           if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
            selectFlag = 0; lastX = xPix; lastY = yPix;
+           ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
+           saveFlash = appData.flashCount; appData.flashCount = 0;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
            sweepSelecting = 1;
            DisplayMessage("", _("Pull pawn backwards to under-promote"));
@@ -7693,13 +7822,16 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
        } else {
            ClearHighlights();
        }
+       MarkTargetSquares(1);
     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
        sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
+        *promoRestrict = 0; appData.flashCount = saveFlash;
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
            ClearHighlights();
        }
+       MarkTargetSquares(1);
     } else {
 #if 0
 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
@@ -7710,6 +7842,8 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
            ClearHighlights();
        }
 #endif
+       if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
+         defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
        if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
        if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
          dragging *= 2;            // flag button-less dragging if we are dragging
@@ -7717,7 +7851,7 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
          if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
          else {
            kill2X = killX; kill2Y = killY;
-           killX = x; killY = y;     //remeber this square as intermediate
+           killX = x; killY = y;     // remember this square as intermediate
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
            MarkTargetSquares(0);
@@ -7727,6 +7861,7 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
+        MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
     }
 
     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
@@ -7763,7 +7898,10 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
 
     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
 
-    if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
+    if(legal[toY][toX] == 2) { // highlight-induced promotion
+       if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
+       else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+    }
 
     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -7781,22 +7919,24 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
            DisplayMessage("Click in holdings to choose piece", "");
            return;
        }
+       DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
        PromotionPopUp(promoChoice);
     } else {
        int oldMove = currentMove;
+       flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
        if (!appData.highlightLastMove || gotPremove) ClearHighlights();
-       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
        if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
           Explode(boards[currentMove-1], fromX, fromY, toX, toY))
            DrawPosition(TRUE, boards[currentMove]);
-        MarkTargetSquares(1);
        fromX = fromY = -1;
+       flashing = 0;
     }
     appData.animate = saveAnimate;
     if (appData.animate || appData.animateDragging) {
        /* Undo animation damage if needed */
-       DrawPosition(FALSE, NULL);
+//     DrawPosition(FALSE, NULL);
     }
 }
 
@@ -7892,6 +8032,23 @@ RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
 }
 
 void
+Wheel (int dir, int x, int y)
+{
+    if(gameMode == EditPosition) {
+       int xSqr = EventToSquare(x, BOARD_WIDTH);
+       int ySqr = EventToSquare(y, BOARD_HEIGHT);
+       if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
+       if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
+       do {
+           boards[currentMove][ySqr][xSqr] += dir;
+           if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
+           if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
+       } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
+       DrawPosition(FALSE, boards[currentMove]);
+    } else if(dir > 0) ForwardEvent(); else BackwardEvent();
+}
+
+void
 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
 {
 //    char * hint = lastHint;
@@ -7915,6 +8072,9 @@ SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats
 
     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
 
+    if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
+        && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
+
     SetProgramStats( &stats );
 }
 
@@ -8551,19 +8711,19 @@ DeferredBookMove (void)
 
 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
 static ChessProgramState *stalledEngine;
-static char stashedInputMove[MSG_SIZ];
+static char stashedInputMove[MSG_SIZ], abortEngineThink;
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
 {
-    static char firstLeg[20];
+    static char firstLeg[20], legs;
     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
     char realname[MSG_SIZ];
     int fromX, fromY, toX, toY;
     ChessMove moveType;
     char promoChar, roar;
     char *p, *pv=buf1;
-    int machineWhite, oldError;
+    int oldError;
     char *bookHit;
 
     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
@@ -8634,24 +8794,29 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            return;
        }
 
+      if(cps->usePing) {
+
         /* This method is only useful on engines that support ping */
+        if(abortEngineThink) {
+           if (appData.debugMode) {
+               fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
+           }
+            SendToProgram("undo\n", cps);
+           return;
+       }
+
         if (cps->lastPing != cps->lastPong) {
-         if (gameMode == BeginningOfGame) {
            /* Extra move from before last new; ignore */
            if (appData.debugMode) {
                fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
            }
-         } else {
-           if (appData.debugMode) {
-               fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
-                       cps->which, gameMode);
-           }
-
-            SendToProgram("undo\n", cps);
-         }
          return;
        }
 
+      } else {
+
+       int machineWhite = FALSE;
+
        switch (gameMode) {
          case BeginningOfGame:
            /* Extra move from before last reset; ignore */
@@ -8697,17 +8862,21 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            }
            return;
        }
+      }
 
         if(cps->alphaRank) AlphaRank(machineMove, 4);
 
        // [HGM] lion: (some very limited) support for Alien protocol
        killX = killY = kill2X = kill2Y = -1;
        if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
+           if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
            safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
            return;
        }
        if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
+           char *q = strchr(p+1, ',');            // second comma?
            safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
+           if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
            safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
        }
        if(firstLeg[0]) { // there was a previous leg;
@@ -8717,10 +8886,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            while(isdigit(*q)) q++; // find start of to-square
            safeStrCpy(machineMove, firstLeg, 20);
            while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
-           if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
+           if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
+           else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
            safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
            sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
-           firstLeg[0] = NULLCHAR;
+           firstLeg[0] = NULLCHAR; legs = 0;
        }
 
         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
@@ -8732,7 +8902,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
-             GameEnds(machineWhite ? BlackWins : WhiteWins,
+             GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
                        buf1, GE_XBOARD);
            }
            return;
@@ -8750,7 +8920,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             if(moveType == IllegalMove) {
              snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
-                GameEnds(machineWhite ? BlackWins : WhiteWins,
+                GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
                            buf1, GE_XBOARD);
                return;
            } else if(!appData.fischerCastling)
@@ -8797,11 +8967,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         }
         if(appData.epd) {
            if(solvingTime >= 0) {
-              snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
-              totalTime += solvingTime; first.matchWins++;
+              snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
+              totalTime += solvingTime; first.matchWins++; solvingTime = -1;
            } else {
-              snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
-              second.matchWins++;
+              snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
+              if(solvingTime == -2) second.matchWins++;
            }
            OutputKibitz(2, buf1);
            GameEnds(GameUnfinished, NULL, GE_XBOARD);
@@ -8964,6 +9134,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         while(message[s] && message[s++] != ' ');
         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
+           if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
            appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
            if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
@@ -8980,10 +9151,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
     }
     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
       ChessSquare piece = WhitePawn;
-      char *p=message+6, *q, *s = SUFFIXES, ID = *p;
-      if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
+      char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
+      if(*p == '+') promoted++, ID = *++p;
       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
-      piece += CharToPiece(ID & 255) - WhitePawn;
+      piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
@@ -8999,6 +9170,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       }
       return;
     }
+    if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
+      promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
+      Sweep(0);
+      return;
+    }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
      * want this, I was asked to put it in, and obliged.
      */
@@ -9110,11 +9286,16 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if(initPing == cps->lastPong) {
            if(gameInfo.variant == VariantUnknown) {
                DisplayError(_("Engine did not send setup for non-standard variant"), 0);
-               *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
+               *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
                GameEnds(GameUnfinished, NULL, GE_XBOARD);
            }
            initPing = -1;
         }
+       if(cps->lastPing == cps->lastPong && abortEngineThink) {
+           abortEngineThink = FALSE;
+           DisplayMessage("", "");
+           ThawUI();
+       }
        return;
     }
     if(!strncmp(message, "highlight ", 10)) {
@@ -9530,6 +9711,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
        if (!ignore) {
            ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
+           int solved = 0;
            buf1[0] = NULLCHAR;
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
@@ -9558,10 +9740,33 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
                if(*bestMove) { // rememer time best EPD move was first found
                    int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
-                   ChessMove mt;
-                   int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
-                   ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
-                   solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
+                   ChessMove mt; char *p = bestMove;
+                   int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+                   solved = 0;
+                   while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
+                       if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
+                           solvingTime = (solvingTime < 0 ? time : solvingTime);
+                           solved = 1;
+                           break;
+                       }
+                       while(*p && *p != ' ') p++;
+                       while(*p == ' ') p++;
+                   }
+                   if(!solved) solvingTime = -1;
+               }
+               if(*avoidMove && !solved) {
+                   int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
+                   ChessMove mt; char *p = avoidMove, solved = 1;
+                   int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+                   while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
+                       if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
+                           solved = 0; solvingTime = -2;
+                           break;
+                       }
+                       while(*p && *p != ' ') p++;
+                       while(*p == ' ') p++;
+                   }
+                   if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
                }
 
                if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
@@ -9634,6 +9839,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                     [AS] Protect the thinkOutput buffer from overflow... this
                     is only useful if buf1 hasn't overflowed first!
                 */
+               if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
                if(curscore >= MATE_SCORE) 
                    snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
                else if(curscore <= -MATE_SCORE) 
@@ -10078,7 +10284,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
        }
 
-     if (fromX == toX && fromY == toY) return;
+     if (fromX == toX && fromY == toY && killX < 0) return;
 
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
@@ -10091,6 +10297,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
         board[EP_STATUS] = EP_NONE; // capture was fake!
     } else
+    if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
+        board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
+        board[toY][toX] = piece;
+        board[EP_STATUS] = EP_NONE; // capture was fake!
+    } else
     /* Code added by Tord: */
     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
@@ -10115,10 +10326,14 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
       }
     /* End of code added by Tord */
 
+    } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
+       board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
+       board[toY][toX] = piece;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
         && toY == fromY && toX > fromX+1) {
-       for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
+       for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
+                                                                                            ; // castle with nearest piece
         board[fromY][toX-1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10126,7 +10341,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
-       for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
+       for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
+                                                                                 ; // castle with nearest piece
         board[fromY][toX+1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10137,8 +10353,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
-        if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
-            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+        if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
+            board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY >= BOARD_HEIGHT>>1)
               && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
@@ -10169,7 +10385,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX > fromX+1) {
-       for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
+       for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
+                                                                                            ;
         board[fromY][toX-1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10177,7 +10394,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
-       for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
+       for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
+                                                                               ;
         board[fromY][toX+1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10202,8 +10420,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
-        if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
-            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+        if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
+            board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY < BOARD_HEIGHT>>1)
               && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
@@ -10277,10 +10495,10 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
         p = (int) captured;
         if (p >= (int) BlackPawn) {
           p -= (int)BlackPawn;
-          if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
+          if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
                   /* Restore shogi-promoted piece to its original  first */
-                  captured = (ChessSquare) (DEMOTED captured);
-                  p = DEMOTED p;
+                  captured = (ChessSquare) (DEMOTED(captured));
+                  p = DEMOTED(p);
           }
           p = PieceToNumber((ChessSquare)p);
           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
@@ -10288,9 +10506,9 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
        } else {
           p -= (int)WhitePawn;
-          if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
-                  captured = (ChessSquare) (DEMOTED captured);
-                  p = DEMOTED p;
+          if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
+                  captured = (ChessSquare) (DEMOTED(captured));
+                  p = DEMOTED(p);
           }
           p = PieceToNumber((ChessSquare)p);
           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
@@ -10318,13 +10536,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
-        board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
+        board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
-       if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
-          && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
+       if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
+          && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
         board[toY][toX] = newPiece;
     }
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
@@ -10350,13 +10568,17 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
     ChessSquare p = boards[forwardMostMove][toY][toX];
 //    forwardMostMove++; // [HGM] bare: moved downstream
 
+    if(kill2X >= 0) x = kill2X, y = kill2Y; else
     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
     (void) CoordsToAlgebraic(boards[forwardMostMove],
                             PosFlags(forwardMostMove),
-                            fromY, fromX, y, x, promoChar,
+                            fromY, fromX, y, x, (killX < 0)*promoChar,
                             s);
+    if(kill2X >= 0 && kill2Y >= 0)
+        sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
     if(killX >= 0 && killY >= 0)
-        sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
+        sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
+                                           toX + AAA, toY + ONE - '0', promoChar);
 
     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
         int timeLeft; static int lastLoadFlag=0; int king, piece;
@@ -10470,7 +10692,7 @@ ShowMove (int fromX, int fromY, int toX, int toY)
        currentMove = forwardMostMove;
     }
 
-    killX = killY = -1; // [HGM] lion: used up
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
 
     if (instant) return;
 
@@ -10615,6 +10837,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
 
       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
+
       if (b == NULL) {
        VariantClass v;
        char c, *q = cps->variants, *p = strchr(q, ',');
@@ -10867,8 +11090,10 @@ Substitute (char *participants, int expunge)
        p++; q++;
     }
     if(*p) { // difference
-       while(*p && *p++ != '\n');
-       while(*q && *q++ != '\n');
+       while(*p && *p++ != '\n')
+                                ;
+       while(*q && *q++ != '\n')
+                                ;
       changed = nPlayers;
        changes = 1 + (strcmp(p, q) != 0);
     }
@@ -11250,6 +11475,11 @@ NextMatchGame ()
     res = LoadGameOrPosition(matchGame); // setup game
     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
     if(!res) return; // abort when bad game/pos file
+    if(appData.epd) {// in EPD mode we make sure first engine is to move
+       firstWhite = !(forwardMostMove & 1);
+       first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
+       second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
+    }
     TwoMachinesEvent();
 }
 
@@ -11313,7 +11543,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
 
     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
 
@@ -11657,6 +11887,16 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            return;
        } else {
            gameMode = nextGameMode;
+           if(appData.epd) {
+               snprintf(buf, MSG_SIZ, "-------------------------------------- ");
+               OutputKibitz(2, buf);
+               snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
+               OutputKibitz(2, buf);
+               snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
+               if(second.matchWins) OutputKibitz(2, buf);
+               snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
+               OutputKibitz(2, buf);
+           }
            snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
                     first.tidy, second.tidy,
                     first.matchWins, second.matchWins,
@@ -11780,6 +12020,7 @@ Reset (int redraw, int init)
                redraw, init, gameMode);
     }
     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
+    deadRanks = 0; // assume entire board is used
     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
     CleanupTail(); // [HGM] vari: delete any stored variations
     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
@@ -11797,7 +12038,10 @@ Reset (int redraw, int init)
     lastHint[0] = NULLCHAR;
     ClearGameInfo(&gameInfo);
     gameInfo.variant = StringToVariant(appData.variant);
-    if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
+    if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
+       gameInfo.variant = VariantUnknown;
+       strncpy(engineVariant, appData.variant, MSG_SIZ);
+    }
     ics_user_moved = ics_clock_paused = FALSE;
     ics_getting_history = H_FALSE;
     ics_gamenum = -1;
@@ -11811,7 +12055,7 @@ Reset (int redraw, int init)
     ClearPremoveHighlights();
     gotPremove = FALSE;
     alarmSounded = FALSE;
-    killX = killY = -1; // [HGM] lion
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion
 
     GameEnds(EndOfFile, NULL, GE_PLAYER);
     if(appData.serverMovesName != NULL) {
@@ -11929,21 +12173,17 @@ AutoPlayOneMove ()
            SetHighlights(-1, -1, toX, toY);
        }
     } else {
-        int viaX = moveList[currentMove][5] - AAA;
-        int viaY = moveList[currentMove][6] - ONE;
         fromX = moveList[currentMove][0] - AAA;
         fromY = moveList[currentMove][1] - ONE;
 
         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
 
         if(moveList[currentMove][4] == ';') { // multi-leg
-            ChessSquare piece = boards[currentMove][viaY][viaX];
-           AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
-            boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
-            AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
-            boards[currentMove][viaY][viaX] = piece;
-        } else
+            killX = moveList[currentMove][5] - AAA;
+            killY = moveList[currentMove][6] - ONE;
+        }
        AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+       killX = killY = -1;
 
        if (appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
@@ -12017,13 +12257,13 @@ LoadGameOneMove (ChessMove readAhead)
       case BlackASideCastleFR:
       /* POP Fabien */
        if (appData.debugMode)
-         fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
+         fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
         fromX = currentMoveString[0] - AAA;
         fromY = currentMoveString[1] - ONE;
         toX = currentMoveString[2] - AAA;
         toY = currentMoveString[3] - ONE;
        promoChar = currentMoveString[4];
-       if(promoChar == ';') promoChar = NULLCHAR;
+       if(promoChar == ';') promoChar = currentMoveString[7];
        break;
 
       case WhiteDrop:
@@ -12195,7 +12435,7 @@ LoadGameOneMove (ChessMove readAhead)
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
-       killX = killY = -1; // [HGM] lion: used up
+       killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
        currentMove = forwardMostMove;
        return TRUE;
     }
@@ -12764,7 +13004,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     char buf[MSG_SIZ];
     int gn = gameNumber;
     ListGame *lg = NULL;
-    int numPGNTags = 0;
+    int numPGNTags = 0, i;
     int err, pos = -1;
     GameMode oldGameMode;
     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
@@ -12782,7 +13022,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (gameMode != BeginningOfGame) {
       Reset(FALSE, TRUE);
     }
-    killX = killY = -1; // [HGM] lion: in case we did not Reset
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
 
     gameFileFP = f;
     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
@@ -12953,6 +13193,8 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
+    for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
+
     if (cm == XBoardGame) {
        /* Skip any header junk before position diagram and/or move 1 */
        for (;;) {
@@ -12977,7 +13219,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        gameInfo.event = StrSave(yy_text);
     }
 
-    startedFromSetupPosition = FALSE;
+    startedFromSetupPosition = startedFromPositionFile; // [HGM]
     while (cm == PGNTag) {
        if (appData.debugMode)
          fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
@@ -13004,7 +13246,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            return FALSE;
          }
          CopyBoard(boards[0], initial_position);
-         if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
+         if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
            CopyBoard(initialPosition, initial_position);
          if (blackPlaysFirst) {
            currentMove = forwardMostMove = backwardMostMove = 1;
@@ -13346,9 +13588,12 @@ LoadPosition (FILE *f, int positionNumber, char *title)
            DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
        }
-       if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
-           sscanf(p+3, "%s", bestMove);
+       if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
+           sscanf(p+4, "%[^;]", bestMove);
        } else *bestMove = NULLCHAR;
+       if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
+           sscanf(p+4, "%[^;]", avoidMove);
+       } else *avoidMove = NULLCHAR;
     } else {
        (void) fgets(line, MSG_SIZ, f);
        (void) fgets(line, MSG_SIZ, f);
@@ -13687,6 +13932,10 @@ SaveGamePGN2 (FILE *f)
                    snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
                }
 
+           if(appData.cumulativeTimePGN) {
+               snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
+           }
+
             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
                      pvInfoList[i].score >= 0 ? "+" : "",
                      pvInfoList[i].score / 100.0,
@@ -14802,6 +15051,7 @@ TwoMachinesEvent P((void))
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+  if(!appData.epd) {
     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
 
     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
@@ -14820,6 +15070,7 @@ TwoMachinesEvent P((void))
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+  }
     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
     if(appData.matchPause>10000 || appData.matchPause<10)
                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
@@ -14831,6 +15082,7 @@ TwoMachinesEvent P((void))
     // we are now committed to starting the game
     stalling = 0;
     DisplayMessage("", "");
+  if(!appData.epd) {
     if (startedFromSetupPosition) {
        SendBoard(&second, backwardMostMove);
     if (appData.debugMode) {
@@ -14840,6 +15092,7 @@ TwoMachinesEvent P((void))
     for (i = backwardMostMove; i < forwardMostMove; i++) {
        SendMoveToProgram(i, &second);
     }
+  }
 
     gameMode = TwoMachinesPlay;
     pausing = startingEngine = FALSE;
@@ -14858,11 +15111,13 @@ TwoMachinesEvent P((void))
       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
       SendToProgram(buf, &first);
     }
+  if(!appData.epd) {
     SendToProgram(second.computerString, &second);
     if (second.sendName) {
       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
       SendToProgram(buf, &second);
     }
+  }
 
     ResetClocks();
     if (!first.sendTime || !second.sendTime) {
@@ -14971,6 +15226,16 @@ EditGameEvent ()
       case MachinePlaysBlack:
       case BeginningOfGame:
        SendToProgram("force\n", &first);
+       if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
+           if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
+               char buf[MSG_SIZ];
+               abortEngineThink = TRUE;
+               snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
+               SendToProgram(buf, &first);
+               DisplayMessage("Aborting engine think", "");
+               FreezeUI();
+           }
+       }
        SetUserThinkingEnables();
        break;
       case PlayFromGameFile:
@@ -15047,10 +15312,10 @@ EditGameEvent ()
     SetGameInfo();
 }
 
-
 void
 EditPositionEvent ()
 {
+    int i;
     if (gameMode == EditPosition) {
        EditGameEvent();
        return;
@@ -15062,8 +15327,11 @@ EditPositionEvent ()
     gameMode = EditPosition;
     ModeHighlight();
     SetGameInfo();
+    CopyBoard(rightsBoard, nullBoard);
     if (currentMove > 0)
       CopyBoard(boards[0], boards[currentMove]);
+    for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
+      rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
 
     blackPlaysFirst = !WhiteOnMove(currentMove);
     ResetClocks();
@@ -15097,22 +15365,23 @@ EditPositionDone (Boolean fakeRights)
     startedFromSetupPosition = TRUE;
     InitChessProgram(&first, FALSE);
     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
+      int r, f;
       boards[0][EP_STATUS] = EP_NONE;
-      boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
-      if(boards[0][0][BOARD_WIDTH>>1] == king) {
-       boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
-       boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
-      } else boards[0][CASTLING][2] = NoRights;
-      if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
-       boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
-       boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
-      } else boards[0][CASTLING][5] = NoRights;
-      if(gameInfo.variant == VariantSChess) {
-       int i;
-       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
-         boards[0][VIRGIN][i] = 0;
-         if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
-         if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
+      for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
+      for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
+       if(rightsBoard[r][f]) {
+         ChessSquare p = boards[0][r][f];
+         if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
+         else if(p == king) boards[0][CASTLING][2] = f;
+         else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
+         else rightsBoard[r][f] = 2; // mark for second pass
+       }
+      }
+      for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
+       if(rightsBoard[r][f] == 2) {
+         ChessSquare p = boards[0][r][f];
+         if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
+         if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
        }
       }
     }
@@ -15211,12 +15480,13 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
     ChessSquare piece = boards[0][y][x];
     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
     static int lastVariant;
+    int baseRank = BOARD_HEIGHT-1, hasRights = 0;
 
     if (gameMode != EditPosition && gameMode != IcsExamining) return;
 
     switch (selection) {
       case ClearBoard:
-       fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+       fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
        MarkTargetSquares(1);
        CopyBoard(currentBoard, boards[0]);
        CopyBoard(menuBoard, initialPosition);
@@ -15243,14 +15513,16 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                    }
                }
            }
+           CopyBoard(rightsBoard, nullBoard);
            if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
-               int r;
+               int r, i;
                for(r = 0; r < BOARD_HEIGHT; r++) {
                  for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
                    ChessSquare p = menuBoard[r][x];
                    for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
                  }
                }
+               menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
                DisplayMessage("Clicking clock again restores position", "");
                if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
                if(!nonEmpty) { // asked to clear an empty board
@@ -15265,6 +15537,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                } else
                    CopyBoard(erasedBoard, currentBoard);
 
+               for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
+                   rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
            }
        }
        if (gameMode == EditPosition) {
@@ -15304,7 +15578,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
       case PromotePiece:
         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
-            selection = (ChessSquare) (PROMOTED piece);
+            selection = (ChessSquare) (PROMOTED(piece));
         } else if(piece == EmptySquare) selection = WhiteSilver;
         else selection = (ChessSquare)((int)piece - 1);
         goto defaultlabel;
@@ -15312,7 +15586,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
       case DemotePiece:
         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
-            selection = (ChessSquare) (DEMOTED piece);
+            selection = (ChessSquare) (DEMOTED(piece));
         } else if(piece == EmptySquare) selection = BlackSilver;
         else selection = (ChessSquare)((int)piece + 1);
         goto defaultlabel;
@@ -15327,12 +15601,21 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
         goto defaultlabel;
 
+      case WhiteRook:
+        baseRank = 0;
+      case BlackRook:
+        if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
+        if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
+        goto defaultlabel;
+
       case WhiteKing:
+        baseRank = 0;
       case BlackKing:
         if(gameInfo.variant == VariantXiangqi)
             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
         if(gameInfo.variant == VariantKnightmate)
             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
+        if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
       default:
         defaultlabel:
        if (gameMode == IcsExamining) {
@@ -15341,6 +15624,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                     PieceToChar(selection), AAA + x, ONE + y);
            SendToICS(buf);
        } else {
+            rightsBoard[y][x] = hasRights;
             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
                 int n;
                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
@@ -15669,7 +15953,7 @@ ForwardInner (int target)
 
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
 
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
@@ -15690,19 +15974,15 @@ ForwardInner (int target)
                SetHighlights(-1, -1, toX, toY);
            }
        } else {
-            int viaX = moveList[target - 1][5] - AAA;
-            int viaY = moveList[target - 1][6] - ONE;
             fromX = moveList[target - 1][0] - AAA;
             fromY = moveList[target - 1][1] - ONE;
            if (target == currentMove + 1) {
                if(moveList[target - 1][4] == ';') { // multi-leg
-                   ChessSquare piece = boards[currentMove][viaY][viaX];
-                   AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
-                   boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
-                   AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
-                   boards[currentMove][viaY][viaX] = piece;
-               } else
+                   killX = moveList[target - 1][5] - AAA;
+                   killY = moveList[target - 1][6] - ONE;
+               }
                AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+               killX = killY = -1;
            }
            if (appData.highlightLastMove) {
                SetHighlights(fromX, fromY, toX, toY);
@@ -15788,7 +16068,7 @@ BackwardInner (int target)
     if (gameMode == EditPosition) return;
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
     if (currentMove <= backwardMostMove) {
        ClearHighlights();
        DrawPosition(full_redraw, boards[currentMove]);
@@ -16164,7 +16444,7 @@ PrintPosition (FILE *fp, int move)
     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
            char c = PieceToChar(boards[move][i][j]);
-           fputc(c == 'x' ? '.' : c, fp);
+           fputc(c == '?' ? '.' : c, fp);
             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
        }
     }
@@ -16544,7 +16824,11 @@ GetInfoFromComment (int index, char * text)
         pvInfoList[index-1].score = score;
         pvInfoList[index-1].time  = 10*time; // centi-sec
         if(*sep == '}') *sep = 0; else *--sep = '{';
-        if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
+        if(p != text) {
+            while(*p++ = *sep++)
+                                ;
+            sep = text;
+        } // squeeze out space between PV and comment, and return both
     }
     return sep;
 }
@@ -16881,6 +17165,7 @@ ParseOption (Option *opt, ChessProgramState *cps)
        char *p, *q, buf[MSG_SIZ];
        int n, min = (-1)<<31, max = 1<<31, def;
 
+       opt->target = &opt->value;   // OK for spin/slider and checkbox
        if(p = strstr(opt->name, " -spin ")) {
            if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
            if(max < min) max = min; // enforce consistency
@@ -16903,14 +17188,17 @@ ParseOption (Option *opt, ChessProgramState *cps)
        } else if((p = strstr(opt->name, " -string "))) {
            opt->textValue = p+9;
            opt->type = TextBox;
+           opt->target = &opt->textValue;
        } else if((p = strstr(opt->name, " -file "))) {
            // for now -file is a synonym for -string, to already provide compatibility with future polyglots
-           opt->textValue = p+7;
+           opt->target = opt->textValue = p+7;
            opt->type = FileName; // FileName;
+           opt->target = &opt->textValue;
        } else if((p = strstr(opt->name, " -path "))) {
            // for now -file is a synonym for -string, to already provide compatibility with future polyglots
-           opt->textValue = p+7;
+           opt->target = opt->textValue = p+7;
            opt->type = PathName; // PathName;
+           opt->target = &opt->textValue;
        } else if(p = strstr(opt->name, " -check ")) {
            if(sscanf(p, " -check %d", &def) < 1) return FALSE;
            opt->value = (def != 0);
@@ -16974,9 +17262,9 @@ FeatureDone (ChessProgramState *cps, int val)
       (cb == TwoMachinesEventIfReady)) {
     CancelDelayedEvent();
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
-  }
+  } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
   cps->initDone = val;
-  if(val) cps->reload = FALSE;
+  if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
 }
 
 /* Parse feature command from engine */
@@ -17528,11 +17816,13 @@ ResetClocks ()
 
 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
 
+static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
+
 /* Decrement running clock by amount of time that has passed */
 void
 DecrementClocks ()
 {
-    long timeRemaining;
+    long tRemaining;
     long lastTickLength, fudge;
     TimeMark now;
 
@@ -17549,28 +17839,32 @@ DecrementClocks ()
 
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
-       timeRemaining = whiteTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0 && !appData.icsActive) {
+        tRemaining = whiteTimeRemaining -= lastTickLength;
+        if( tRemaining < 0 && !appData.icsActive) {
             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
-                lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+                lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
             }
         }
+       if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
        DisplayWhiteClock(whiteTimeRemaining - fudge,
                          WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+       timeSuffix = 0;
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
-       timeRemaining = blackTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
+        tRemaining = blackTimeRemaining -= lastTickLength;
+        if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
             if(suddenDeath) {
                 blackStartMove = forwardMostMove;
-                lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+                lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
             }
         }
+       if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
        DisplayBlackClock(blackTimeRemaining - fudge,
                          !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+       timeSuffix = 0;
     }
     if (CheckFlags()) return;
 
@@ -17585,7 +17879,7 @@ DecrementClocks ()
     }
 
     tickStartTM = now;
-    intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
+    intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);
 
     /* if the time remaining has fallen below the alarm threshold, sound the
@@ -17601,9 +17895,9 @@ DecrementClocks ()
               ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
           )) return;
 
-       if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
+       if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+       } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -17744,7 +18038,7 @@ TimeString (long ms)
 {
     long second, minute, hour, day;
     char *sign = "";
-    static char buf[32];
+    static char buf[40], moveTime[8];
 
     if (ms > 0 && ms <= 9900) {
       /* convert milliseconds to tenths, rounding up */
@@ -17771,13 +18065,16 @@ TimeString (long ms)
     minute = second / 60;
     second = second % 60;
 
+    if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
+    else *moveTime = NULLCHAR;
+
     if (day > 0)
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
-             sign, day, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
+             sign, day, hour, minute, second, moveTime);
     else if (hour > 0)
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
     else
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
 
     return buf;
 }
@@ -17891,7 +18188,7 @@ char *
 PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
-    int whiteToPlay;
+    int whiteToPlay, haveRights = nrCastlingRights;
     char buf[MSG_SIZ];
     char *p, *q;
     int emptycount;
@@ -17902,7 +18199,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     p = buf;
 
     /* Piece placement data */
-    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
        if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
        emptycount = 0;
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
@@ -17918,13 +18215,13 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                 if(PieceToChar(piece) == '+') {
                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
                     *p++ = '+';
-                    piece = (ChessSquare)(CHUDEMOTED piece);
+                    piece = (ChessSquare)(CHUDEMOTED(piece));
                 }
                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
                 if(*p = PieceSuffix(piece)) p++;
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
-                    p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
+                    p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
                     *p++ = '~';
                 }
            }
@@ -17965,10 +18262,30 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     *p++ = whiteToPlay ? 'w' : 'b';
     *p++ = ' ';
 
+  if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+    haveRights = 0; q = p;
+    for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+      piece = boards[move][0][i];
+      if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+        if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
+      }
+    }
+    for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+      piece = boards[move][BOARD_HEIGHT-1][i];
+      if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+        if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
+      }
+    }
+    if(p == q) *p++ = '-';
+    *p++ = ' ';
+  }
+
   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
-    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
+    while(*p++ = *q++)
+                      ;
+    if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
-  if(nrCastlingRights) {
+  if(haveRights) {
      int handW=0, handB=0;
      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
        for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
@@ -18004,9 +18321,9 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
         /* [HGM] write true castling rights */
         if( nrCastlingRights == 6 ) {
             int q, k=0;
-            if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
+            if(boards[move][CASTLING][0] != NoRights &&
                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
-            q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+            q = (boards[move][CASTLING][1] != NoRights &&
                  boards[move][CASTLING][2] != NoRights  );
             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
@@ -18015,9 +18332,9 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
             }
            if(q) *p++ = 'Q';
             k = 0;
-            if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
+            if(boards[move][CASTLING][3] != NoRights &&
                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
-            q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+            q = (boards[move][CASTLING][4] != NoRights &&
                  boards[move][CASTLING][5] != NoRights  );
             if(handB) {
                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
@@ -18094,12 +18411,14 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
     char *p, c;
     int emptycount, virgin[BOARD_FILES];
-    ChessSquare piece;
+    ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
 
     p = fen;
 
+    for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
+
     /* Piece placement data */
-    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
        j = 0;
        for (;;) {
             if (*p == '/' || *p == ' ' || *p == '[' ) {
@@ -18108,7 +18427,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
                 if (*p == '/') p++;
-               else if(autoSize) { // we stumbled unexpectedly into end of board
+               else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
                        for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
                     }
@@ -18148,7 +18467,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                     if(q = strchr(s, p[1])) p++;
                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
                     if(piece == EmptySquare) return FALSE; /* unknown piece */
-                    piece = (ChessSquare) (CHUPROMOTED piece ); p++;
+                    piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
                 } else {
                     char c = *p++;
@@ -18158,13 +18477,13 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
                 if(piece==EmptySquare) return FALSE; /* unknown piece */
                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
-                    piece = (ChessSquare) (PROMOTED piece);
+                    piece = (ChessSquare) (PROMOTED(piece));
                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
                     p++;
                 }
                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
-                if(piece == WhiteKing) wKingRank = i;
-                if(piece == BlackKing) bKingRank = i;
+                if(piece == king) wKingRank = i;
+                if(piece == WHITE_TO_BLACK king) bKingRank = i;
            } else {
                return FALSE;
            }
@@ -18172,7 +18491,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     }
     while (*p == '/' || *p == ' ') p++;
 
-    if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
+    if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
 
     /* [HGM] by default clear Crazyhouse holdings, if present */
     if(gameInfo.holdingsWidth) {
@@ -18273,6 +18592,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
     /* set defaults in case FEN is incomplete */
     board[EP_STATUS] = EP_UNKNOWN;
+    board[TOUCHED_W] = board[TOUCHED_B] = 0;
     for(i=0; i<nrCastlingRights; i++ ) {
         board[CASTLING][i] =
             appData.fischerCastling ? NoRights : initialRights[i];
@@ -18287,6 +18607,21 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                                  && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
     FENrulePlies = 0;
 
+    if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+      char *q = p;
+      int w=0, b=0;
+      while(isalpha(*p)) {
+        if(isupper(*p)) w |= 1 << (*p++ - 'A');
+        if(islower(*p)) b |= 1 << (*p++ - 'a');
+      }
+      if(*p == '-') p++;
+      if(p != q) {
+        board[TOUCHED_W] = ~w;
+        board[TOUCHED_B] = ~b;
+        while(*p == ' ') p++;
+      }
+    } else
+
     if(nrCastlingRights) {
       int fischer = 0;
       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
@@ -18308,10 +18643,10 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
         }
         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
-        if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
-                                     && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
-        if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
-                                     && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
+        if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
+                                     && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
+        if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
+                                     && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
         switch(c) {
           case'K':
               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);