Send ping in EditGameEvent
[xboard.git] / backend.c
index 125fd3d..233fb8d 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -296,6 +296,8 @@ 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];
+int solvingTime, totalTime;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -2125,7 +2127,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;
@@ -5228,7 +5230,7 @@ SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char
       case WhitePromotion:
       case BlackPromotion:
         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-           gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+           gameInfo.variant == VariantMakruk)
          snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteFerz));
@@ -5334,11 +5336,11 @@ UploadGameEvent ()
     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
 }
 
-int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
+int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
 int legNr = 1;
 
 void
-CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
+CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
 {
     if (rf == DROP_RANK) {
       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
@@ -5348,7 +5350,10 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
        if (promoChar == 'x' || promoChar == NULLCHAR) {
          sprintf(move, "%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
-         if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+         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);
+         }
        } else {
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
@@ -5951,7 +5956,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 != '/' && !strchr(escapes, *s)), s++;
     return n;
 }
 
@@ -5960,25 +5965,25 @@ 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;
+    int result = FALSE; int NrPieces, offs;
 
     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 */
 
         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
-        for( i=0; i<NrPieces/2-1; i++ ) {
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
             char *p;
-            if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
-            table[i] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
+            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);
         }
         table[(int) WhiteKing]  = map[j++];
-        for( i=0; i<NrPieces/2-1; i++ ) {
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
             char *p;
-            if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
-            table[WHITE_TO_BLACK i] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
+            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);
         }
         table[(int) BlackKing]  = map[j++];
 
@@ -6167,8 +6172,8 @@ 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/+.++.++++++++++.+++++K"
+                                   "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6394,11 +6399,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",
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6418,12 +6423,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",
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6553,8 +6558,10 @@ DefaultPromoChoice (int white)
 {
     ChessSquare result;
     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+       gameInfo.variant == VariantMakruk)
        result = WhiteFerz; // no choice
+    else if(gameInfo.variant == VariantASEAN)
+       result = WhiteRook; // no choice
     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
        result= WhiteKing; // in Suicide Q is the last thing we want
     else if(gameInfo.variant == VariantSpartan)
@@ -6646,7 +6653,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
 
     // we either have a choice what to promote to, or (in Shogi) whether to promote
     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
+       gameInfo.variant == VariantMakruk) {
        ChessSquare p=BlackFerz;  // no choice
        while(p < EmptySquare) {  //but make sure we use piece that exists
            *promoChoice = PieceToChar(p++);
@@ -7358,7 +7365,7 @@ CanPromote (ChessSquare piece, int y)
        if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
           gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-         gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
+         gameInfo.variant == VariantMakruk) return FALSE;
        return (piece == BlackPawn && y <= zone ||
                piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
                piece == BlackLance && y <= zone ||
@@ -7707,7 +7714,9 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
        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
          MarkTargetSquares(1);
-         if(x == killX && y == killY) killX = killY = -1; else {
+         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
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
@@ -8100,7 +8109,7 @@ Adjudicate (ChessProgramState *cps)
        // most tests only when we understand the game, i.e. legality-checking on
            if( appData.testLegality )
            {   /* [HGM] Some more adjudications for obstinate engines */
-               int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
+               int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
                static int moveCount = 6;
                ChessMove result;
                char *reason = NULL;
@@ -8692,7 +8701,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         if(cps->alphaRank) AlphaRank(machineMove, 4);
 
        // [HGM] lion: (some very limited) support for Alien protocol
-       killX = killY = -1;
+       killX = killY = kill2X = kill2Y = -1;
        if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
            safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
            return;
@@ -8720,8 +8729,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
          snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
                    machineMove, _(cps->which));
            DisplayMoveError(buf1);
-            snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
-                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
+            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,
                        buf1, GE_XBOARD);
@@ -8786,6 +8795,17 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            free(fen);
            GameEnds(GameUnfinished, NULL, GE_XBOARD);
         }
+        if(appData.epd) {
+           if(solvingTime >= 0) {
+              snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
+              totalTime += solvingTime; first.matchWins++;
+           } else {
+              snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
+              second.matchWins++;
+           }
+           OutputKibitz(2, buf1);
+           GameEnds(GameUnfinished, NULL, GE_XBOARD);
+        }
 
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
@@ -8960,10 +8980,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=buf2, *q, *s = SUFFIXES, ID = *p;
+      char *p=message+6, *q, *s = SUFFIXES, ID = *p;
       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
-      piece += CharToPiece(ID) - WhitePawn;
+      piece += CharToPiece(ID & 255) - WhitePawn;
       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
@@ -8975,7 +8995,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       if(piece < EmptySquare) {
         pieceDefs = TRUE;
         ASSIGN(pieceDesc[piece], buf1);
-        if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
+        if((ID & 32) == 0 && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
       }
       return;
     }
@@ -9098,7 +9118,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        return;
     }
     if(!strncmp(message, "highlight ", 10)) {
-       if((appData.testLegality || *engineVariant) && appData.markers) return;
+       if(appData.testLegality && !*engineVariant && appData.markers) return;
        MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
        return;
     }
@@ -9513,6 +9533,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            buf1[0] = NULLCHAR;
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
+               char score_buf[MSG_SIZ];
 
                if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
                    nodes += u64Const(0x100000000);
@@ -9535,6 +9556,14 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
                if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
 
+               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);
+               }
+
                if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
                        char buf[MSG_SIZ];
                        FILE *f;
@@ -9605,11 +9634,17 @@ 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!
                 */
-               snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
+               if(curscore >= MATE_SCORE) 
+                   snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
+               else if(curscore <= -MATE_SCORE) 
+                   snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
+               else
+                   snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
+               snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
                         plylev,
                         (gameMode == TwoMachinesPlay ?
                          ToUpper(cps->twoMachinesColor[0]) : ' '),
-                        ((double) curscore) / 100.0,
+                        score_buf,
                         prefixHint ? lastHint : "",
                         prefixHint ? " " : "" );
 
@@ -9951,7 +9986,7 @@ ParseGameHistory (char *game)
 void
 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 {
-  ChessSquare captured = board[toY][toX], piece, pawn, king, killed; int p, rookX, oldEP, epRank, berolina = 0;
+  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
@@ -9973,11 +10008,14 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 //      ChessSquare victim;
       int i;
 
-      if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
+      if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
 //           victim = board[killY][killX],
            killed = board[killY][killX],
            board[killY][killX] = EmptySquare,
            board[EP_STATUS] = EP_CAPTURE;
+           if( kill2X >= 0 && kill2Y >= 0)
+             killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
+      }
 
       if( board[toY][toX] != EmptySquare ) {
            board[EP_STATUS] = EP_CAPTURE;
@@ -10077,6 +10115,9 @@ 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) {
@@ -11382,16 +11423,17 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
               && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
               && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
               && result != GameIsDrawn)
-           {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
+           {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
                for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
                        int p = (signed char)boards[forwardMostMove][i][j] - color;
                        if(p >= 0 && p <= (int)WhiteKing) k++;
+                       oppoKings += (p + color == WhiteKing + BlackPawn - color);
                }
                if (appData.debugMode) {
                     fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
                        result, resultDetails ? resultDetails : "(null)", whosays, k, color);
                }
-               if(k <= 1) {
+               if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
                        result = GameIsDrawn;
                        snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
                        resultDetails = buf;
@@ -11978,7 +12020,7 @@ 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;
@@ -13302,10 +13344,14 @@ LoadPosition (FILE *f, int positionNumber, char *title)
     }
 
     if (fenMode) {
+       char *p;
        if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
            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);
+       } else *bestMove = NULLCHAR;
     } else {
        (void) fgets(line, MSG_SIZ, f);
        (void) fgets(line, MSG_SIZ, f);
@@ -14928,6 +14974,11 @@ EditGameEvent ()
       case MachinePlaysBlack:
       case BeginningOfGame:
        SendToProgram("force\n", &first);
+       if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
+         char buf[MSG_SIZ];
+         snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
+         SendToProgram(buf, &first);
+       }
        SetUserThinkingEnables();
        break;
       case PlayFromGameFile:
@@ -17848,7 +17899,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;
@@ -17922,10 +17973,28 @@ 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;
   } 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
@@ -17961,9 +18030,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--)
@@ -17972,9 +18041,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--)
@@ -18051,7 +18120,7 @@ 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;
 
@@ -18120,8 +18189,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                     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;
            }
@@ -18230,6 +18299,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];
@@ -18244,6 +18314,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;
@@ -18265,10 +18350,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--);