Improve SAN of Pawn moves and allow Betza e.p. definition
[xboard.git] / backend.c
index 084c42c..6fac052 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -148,6 +148,7 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 #endif
 #include "backendz.h"
 #include "evalgraph.h"
+#include "engineoutput.h"
 #include "gettext.h"
 
 #ifdef ENABLE_NLS
@@ -250,7 +251,6 @@ static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
 #endif
 
 ChessProgramState *WhitePlayer();
-void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
 int VerifyDisplayMode P(());
 
 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
@@ -289,11 +289,13 @@ int chattingPartner;
 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
 char lastMsg[MSG_SIZ];
+char lastTalker[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
 int promoDefaultAltered;
 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
 static int initPing = -1;
+static Boolean pieceDefs;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -412,9 +414,15 @@ PosFlags (index)
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
+  case VariantChu:
+  case VariantChuChess:
+  case VariantLion:
+    flags |= F_NULL_MOVE;
+    break;
   default:
     break;
   }
+  if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer
   return flags;
 }
 
@@ -777,8 +785,7 @@ UnloadEngine (ChessProgramState *cps)
            ExitAnalyzeMode();
             DoSleep( appData.delayBeforeQuit );
            SendToProgram("quit\n", cps);
-            DoSleep( appData.delayAfterQuit );
-           DestroyChildProcess(cps->pr, cps->useSigterm);
+           DestroyChildProcess(cps->pr, 4 + cps->useSigterm);
        }
        cps->pr = NoProc;
        if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
@@ -846,6 +853,7 @@ InitEngine (ChessProgramState *cps, int n)
     cps->analyzing = FALSE;
     cps->initDone = FALSE;
     cps->reload = FALSE;
+    cps->pseudo = appData.pseudo[n];
 
     /* New features added by Tord: */
     cps->useFEN960 = FALSE;
@@ -947,7 +955,7 @@ extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
 static char resetOptions[] =
        "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
        "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
-       "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
+       "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 -fd \".\" "
        "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
 
 void
@@ -2829,7 +2837,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
     int backup;    /* [DM] For zippy color lines */
     char *p;
     char talker[MSG_SIZ]; // [HGM] chat
-    int channel;
+    int channel, collective=0;
 
     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
 
@@ -3071,8 +3079,18 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                        char mess[MSG_SIZ];
                        snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
                        OutputChatMessage(chattingPartner, mess);
+                       if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
+                           int p;
+                           talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
+                           for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
+                               snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
+                               OutputChatMessage(p, mess);
+                               break;
+                           }
+                       }
                        chattingPartner = -1;
-                       next_out = i+1; // [HGM] suppress printing in ICS window
+                       if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
+                       collective = 0;
                    } else
                    if(!suppressKibitz) // [HGM] kibitz
                        AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
@@ -3288,18 +3306,20 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                                           looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
                int p;
                sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
-               chattingPartner = -1;
+               chattingPartner = -1; collective = 0;
 
                if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
                for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
                    talker[0] = '['; strcat(talker, "] ");
-                   Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
+                   Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
                    chattingPartner = p; break;
                    }
                } else
                if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
                for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(!strcmp("kibitzes", chatPartner[p])) {
                        talker[0] = '['; strcat(talker, "] ");
                        chattingPartner = p; break;
@@ -3307,6 +3327,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                } else
                if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
                for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(!strcmp("whispers", chatPartner[p])) {
                        talker[0] = '['; strcat(talker, "] ");
                        chattingPartner = p; break;
@@ -3315,6 +3336,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
                  if(buf[i-8] == '-' && buf[i-3] == 't')
                  for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
+                   collective = 1;
                    if(!strcmp("c-shouts", chatPartner[p])) {
                        talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
                        chattingPartner = p; break;
@@ -3322,6 +3344,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                  }
                  if(chattingPartner < 0)
                  for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(!strcmp("shouts", chatPartner[p])) {
                        if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
                        else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
@@ -3332,18 +3355,23 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                }
                if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
                for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
-                   talker[0] = 0; Colorize(ColorTell, FALSE);
+                   talker[0] = 0;
+                   Colorize(ColorTell, FALSE);
+                   if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
+                   collective |= 2;
                    chattingPartner = p; break;
                }
-               if(chattingPartner<0) i = oldi; else {
+               if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
                    Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
-                   if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
-                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
                    started = STARTED_COMMENT;
                    parse_pos = 0; parse[0] = NULLCHAR;
                    savingComment = 3 + chattingPartner; // counts as TRUE
-                   suppressKibitz = TRUE;
-                   continue;
+                   if(collective == 3) i = oldi; else {
+                       suppressKibitz = TRUE;
+                       if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       continue;
+                   }
                }
            } // [HGM] chat: end of patch
 
@@ -3427,7 +3455,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                      parse[parse_pos] = NULLCHAR;
                      started = STARTED_COMMENT;
                      savingComment = TRUE;
-                   } else {
+                   } else if(collective != 3) {
                      started = STARTED_CHATTER;
                      savingComment = FALSE;
                    }
@@ -5094,8 +5122,7 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
        * the engine. It would be nice to have a better way to identify castle
        * moves here. */
-      if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
-                                                                        && cps->useOOCastle) {
+      if(appData.fischerCastling && cps->useOOCastle) {
         int fromX = moveList[moveNum][0] - AAA;
         int fromY = moveList[moveNum][1] - ONE;
         int toX = moveList[moveNum][2] - AAA;
@@ -5648,10 +5675,13 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
        } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
                ExcludeClick(origIndex - lineStart);
                return FALSE;
+       } else if(!strncmp(buf+lineStart, "dep\t", 4)) {                // column headers clicked
+               Collapse(origIndex - lineStart);
+               return FALSE;
        }
        ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
-       extendGame = (gameMode == AnalyzeMode && appData.autoExtend);
+       extendGame = (gameMode == AnalyzeMode && appData.autoExtend && origIndex - startPV < 5);
        return TRUE;
 }
 
@@ -5969,7 +5999,7 @@ InitPosition (int redraw)
     oldh = gameInfo.holdingsWidth;
     static int oldv;
 
-    if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
+    if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
 
     /* [AS] Initialize pv info list [HGM] and game status */
     {
@@ -6010,6 +6040,7 @@ InitPosition (int redraw)
     switch (gameInfo.variant) {
     case VariantFischeRandom:
       shuffleOpenings = TRUE;
+      appData.fischerCastling = TRUE;
     default:
       break;
     case VariantShatranj:
@@ -6040,6 +6071,7 @@ InitPosition (int redraw)
       break;
     case VariantCapaRandom:
       shuffleOpenings = TRUE;
+      appData.fischerCastling = TRUE;
     case VariantCapablanca:
       pieces = CapablancaArray;
       gameInfo.boardWidth = 10;
@@ -6520,8 +6552,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
         promotionZoneSize = 3;
     }
 
-    // Treat Lance as Pawn when it is not representing Amazon
-    if(gameInfo.variant != VariantSuper) {
+    // Treat Lance as Pawn when it is not representing Amazon or Lance
+    if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) {
         if(piece == WhiteLance) piece = WhitePawn; else
         if(piece == BlackLance) piece = BlackPawn;
     }
@@ -6791,6 +6823,7 @@ int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = EndOfFile;
 int doubleClick;
+Boolean addToBookFlag;
 
 void
 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
@@ -6913,6 +6946,13 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
            DrawPosition(FALSE, boards[currentMove]);
            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;
+           }
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
                if(boards[0][fromY][0] != EmptySquare) {
@@ -6933,7 +6973,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
         return;
     }
 
-    if(toX < 0 || toY < 0) return;
+    if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
     pup = boards[currentMove][toY][toX];
 
     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
@@ -6953,7 +6993,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                                          fromY, fromX, toY, toX, promoChar);
 
-    if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
+    if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
 
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
@@ -6970,6 +7010,16 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
        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);
+       snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
+       AddBookMove(buf);
+       addToBookFlag = FALSE;
+       ClearHighlights();
+       return;
+    }
+
     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
@@ -7211,8 +7261,8 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
     if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
        (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
                         || kind == WhiteCapturesEnPassant
-                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0);
-    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
+                        || 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;
 }
 
 static int hoverSavedValid;
@@ -7228,7 +7278,7 @@ MarkTargetSquares (int clear)
   } else {
     int capt = 0;
     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
-       !appData.testLegality || gameMode == EditPosition) return;
+       !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
@@ -7304,7 +7354,7 @@ void ReportClick(char *action, int x, int y)
        char buf[MSG_SIZ]; // Inform engine of what user does
        int r, f;
        if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
-         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = !pieceDefs, marker[r][f] = 0;
        if(!first.highlight || gameMode == EditPosition) return;
        snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
        SendToProgram(buf, &first);
@@ -7442,7 +7492,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        /* Check if clicking again on the same color piece */
        fromP = boards[currentMove][fromY][fromX];
        toP = boards[currentMove][y][x];
-       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
+       frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
        if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
           ((WhitePawn <= fromP && fromP <= WhiteKing &&
             WhitePawn <= toP && toP <= WhiteKing &&
@@ -7525,7 +7575,8 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     clearFlag = 0;
 
-    if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
+    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;
        DisplayMessage(_("only marked squares are legal"),"");
        DrawPosition(TRUE, NULL);
@@ -8322,10 +8373,10 @@ BitbaseProbe ()
     // probe EGBB
     if(loaded == 2) return 13; // loading failed before
     if(loaded == 0) {
-       loaded = 2; // prepare for failure
        char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
        HMODULE lib;
        PLOAD_EGBB loadBB;
+       loaded = 2; // prepare for failure
        if(!path) return 13; // no egbb installed
        strncpy(buf, path + 8, MSG_SIZ);
        if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
@@ -8625,7 +8676,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 GameEnds(machineWhite ? BlackWins : WhiteWins,
                            buf1, GE_XBOARD);
                return;
-           } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
+           } else if(!appData.fischerCastling)
            /* [HGM] Kludge to handle engines that send FRC-style castling
               when they shouldn't (like TSCP-Gothic) */
            switch(moveType) {
@@ -8669,7 +8720,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         }
 
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
-        if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
+        if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
             int count = 0;
 
             while( count < adjudicateLossPlies ) {
@@ -8679,7 +8730,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                     score = -score; /* Flip score for winning side */
                 }
 
-                if( score > adjudicateLossThreshold ) {
+                if( score > appData.adjudicateLossThreshold ) {
                     break;
                 }
 
@@ -8723,7 +8774,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                        (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
                        programStats.movelist);
                SendToICS(buf);
-if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
          }
        }
 #endif
@@ -8817,7 +8867,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
       *buf = NULLCHAR;
-      if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+      if(sscanf(message, "setup (%s", buf) == 1) {
+        s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+        ASSIGN(appData.pieceToCharTable, buf);
+      }
       if(startedFromSetupPosition) return;
       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
       if(dummy >= 3) {
@@ -8835,6 +8888,19 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       startedFromSetupPosition = TRUE;
       return;
     }
+    if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
+      ChessSquare piece = WhitePawn;
+      char *p=buf2;
+      if(cps != &first || appData.testLegality) return;
+      if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
+      piece += CharToPiece(*p) - WhitePawn;
+      if(piece < EmptySquare) {
+        pieceDefs = TRUE;
+        ASSIGN(pieceDesc[piece], buf1);
+        if(isupper(*p) && p[1] == '&') { ASSIGN(pieceDesc[WHITE_TO_BLACK piece], buf1); }
+      }
+      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.
      */
@@ -9098,6 +9164,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
           Don't use it. */
        cps->sendTime = 0;
     }
+    if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
+       if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
+           sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
+    }
 
     /*
      * If chess program startup fails, exit with an error message.
@@ -9366,6 +9436,9 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
 
+               if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
+                   nodes += u64Const(0x100000000);
+
                if (plyext != ' ' && plyext != '\t') {
                    time *= 100;
                }
@@ -9809,6 +9882,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
       oldEP = (signed char)board[EP_STATUS];
       board[EP_STATUS] = EP_NONE;
+      board[EP_FILE] = board[EP_RANK] = 100;
 
   if (fromY == DROP_RANK) {
        /* must be first */
@@ -9818,11 +9892,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        }
         piece = board[toY][toX] = (ChessSquare) fromX;
   } else {
-      ChessSquare victim;
+//      ChessSquare victim;
       int i;
 
       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
-           victim = board[killY][killX],
+//           victim = board[killY][killX],
            board[killY][killX] = EmptySquare,
            board[EP_STATUS] = EP_CAPTURE;
 
@@ -9843,6 +9917,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
            if( toY-fromY==2) {
+               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
@@ -9855,6 +9930,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
            if( toY-fromY== -2) {
+               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = (fromY + toY)/2;
                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
@@ -10389,6 +10465,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        SendToProgram(buf, cps);
     }
 
+    setboardSpoiledMachineBlack = FALSE;
     SendToProgram(cps->initString, cps);
     if (gameInfo.variant != VariantNormal &&
        gameInfo.variant != VariantLoadable
@@ -10802,6 +10879,7 @@ SwapEngines (int n)
     SWAP(accumulateTC, h)
     SWAP(drawDepth, h)
     SWAP(host, p)
+    SWAP(pseudo, h)
 }
 
 int
@@ -11352,8 +11430,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            ExitAnalyzeMode();
             DoSleep( appData.delayBeforeQuit );
            SendToProgram("quit\n", &first);
-            DoSleep( appData.delayAfterQuit );
-           DestroyChildProcess(first.pr, first.useSigterm);
+           DestroyChildProcess(first.pr, 4 + first.useSigterm);
            first.reload = TRUE;
        }
        first.pr = NoProc;
@@ -11378,8 +11455,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
        if (second.pr != NoProc) {
             DoSleep( appData.delayBeforeQuit );
            SendToProgram("quit\n", &second);
-            DoSleep( appData.delayAfterQuit );
-           DestroyChildProcess(second.pr, second.useSigterm);
+           DestroyChildProcess(second.pr, 4 + second.useSigterm);
            second.reload = TRUE;
        }
        second.pr = NoProc;
@@ -11549,6 +11625,8 @@ Reset (int redraw, int init)
        fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
                redraw, init, gameMode);
     }
+    pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
+    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
     pausing = pauseExamInvalid = FALSE;
@@ -11697,11 +11775,20 @@ 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
        AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
 
        if (appData.highlightLastMove) {
@@ -12309,12 +12396,26 @@ QuickCompare (Board board, int *minCounts, int *maxCounts)
 int
 QuickScan (Board board, Move *move)
 {   // reconstruct game,and compare all positions in it
-    int cnt=0, stretch=0, total = MakePieceList(board, counts);
+    int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
     do {
        int piece = move->piece;
        int to = move->to, from = pieceList[piece];
+       if(found < 0) { // if already found just scan to game end for final piece count
+         if(QuickCompare(soughtBoard, minSought, maxSought) ||
+          appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
+          flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
+                               appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
+           ) {
+           static int lastCounts[EmptySquare+1];
+           int i;
+           if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
+           if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
+         } else stretch = 0;
+         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
+         if(found >= 0 && !appData.minPieces) return found;
+       }
        if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
-         if(!piece) return -1;
+         if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
          if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
            piece = (++move)->piece;
            from = pieceList[piece];
@@ -12339,23 +12440,12 @@ QuickScan (Board board, Move *move)
          }
        }
        if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
-       if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
+       if((total -= (quickBoard[to] != 0)) < soughtTotal && found < 0) return -1; // piece count dropped below what we search for
        quickBoard[from] = 0;
       aftercastle:
        quickBoard[to] = piece;
        pieceList[piece] = to;
        cnt++; turn ^= 3;
-       if(QuickCompare(soughtBoard, minSought, maxSought) ||
-          appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
-          flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
-                               appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
-         ) {
-           static int lastCounts[EmptySquare+1];
-           int i;
-           if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
-           if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
-       } else stretch = 0;
-       if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
        move++;
     } while(1);
 }
@@ -13308,9 +13398,9 @@ GetOutOfBookInfo (char * buf)
     }
 }
 
-/* Save game in PGN style and close the file */
-int
-SaveGamePGN (FILE *f)
+/* Save game in PGN style */
+static void
+SaveGamePGN2 (FILE *f)
 {
     int i, offset, linelen, newblock;
 //    char *movetext;
@@ -13470,7 +13560,13 @@ SaveGamePGN (FILE *f)
     } else {
        fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
     }
+}
 
+/* Save game in PGN style and close the file */
+int
+SaveGamePGN (FILE *f)
+{
+    SaveGamePGN2(f);
     fclose(f);
     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
     return TRUE;
@@ -13949,6 +14045,7 @@ ExitEvent (int status)
       return;
     }
 
+    if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
 
     if (telnetISR != NULL) {
@@ -13975,14 +14072,12 @@ ExitEvent (int status)
 
         DoSleep( appData.delayBeforeQuit );
        SendToProgram("quit\n", &first);
-        DoSleep( appData.delayAfterQuit );
-       DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
+       DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ );
     }
     if (second.pr != NoProc) {
         DoSleep( appData.delayBeforeQuit );
        SendToProgram("quit\n", &second);
-        DoSleep( appData.delayAfterQuit );
-       DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
+       DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ );
     }
     if (first.isr != NULL) {
        RemoveInputSource(first.isr);
@@ -14213,7 +14308,10 @@ AnalyzeModeEvent ()
        first.maybeThinking = FALSE; /* avoid killing GNU Chess */
        EngineOutputPopUp();
     }
-    if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
+    if (!appData.icsEngineAnalyze) {
+       gameMode = AnalyzeMode;
+       ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header
+    }
     pausing = FALSE;
     ModeHighlight();
     SetGameInfo();
@@ -14968,14 +15066,14 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                        boards[0][y][x] = p;
                    }
                }
-               menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
            }
            if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
-               for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
-                   ChessSquare p = menuBoard[0][x];
-                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
-                   p = menuBoard[BOARD_HEIGHT-1][x];
-                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
+               int r;
+               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;
+                 }
                }
                DisplayMessage("Clicking clock again restores position", "");
                if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
@@ -15237,7 +15335,8 @@ ClockClick (int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetBlackToPlayEvent();
-         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+                     gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
          } else if (shiftKey) {
            AdjustClock(which, -1);
@@ -15249,7 +15348,8 @@ ClockClick (int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetWhiteToPlayEvent();
-         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+                     gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
          } else if (shiftKey) {
            AdjustClock(which, -1);
@@ -15413,9 +15513,18 @@ 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
                AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
            }
            if (appData.highlightLastMove) {
@@ -15761,6 +15870,36 @@ HintEvent ()
     hintRequested = TRUE;
 }
 
+int
+SaveSelected (FILE *g, int dummy, char *dummy2)
+{
+    ListGame * lg = (ListGame *) gameList.head;
+    int nItem, cnt=0;
+    FILE *f;
+
+    if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+        DisplayError(_("Game list not loaded or empty"), 0);
+        return 0;
+    }
+
+    creatingBook = TRUE; // suppresses stuff during load game
+
+    /* Get list size */
+    for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
+       if(lg->position >= 0) { // selected?
+           LoadGame(f, nItem, "", TRUE);
+           SaveGamePGN2(g); // leaves g open
+           cnt++; DoEvents();
+       }
+        lg = (ListGame *) lg->node.succ;
+    }
+
+    fclose(g);
+    creatingBook = FALSE;
+
+    return cnt;
+}
+
 void
 CreateBookEvent ()
 {
@@ -15786,8 +15925,11 @@ CreateBookEvent ()
 
     /* Get list size */
     for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
-       LoadGame(f, nItem, "", TRUE);
-       AddGameToBook(TRUE);
+       if(lg->position >= 0) {
+           LoadGame(f, nItem, "", TRUE);
+           AddGameToBook(TRUE);
+           DoEvents();
+       }
         lg = (ListGame *) lg->node.succ;
     }
 
@@ -16486,6 +16628,10 @@ EngineDefinedVariant (ChessProgramState *cps, int n)
       v = StringToVariant(s);
       if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
        if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
+           if(!strcmp(s, "tenjiku") || !strcmp(s, "dai") || !strcmp(s, "dada") || // ignore Alien-Edition variants
+              !strcmp(s, "maka") || !strcmp(s, "tai") || !strcmp(s, "kyoku") ||
+              !strcmp(s, "checkers") || !strcmp(s, "go") || !strcmp(s, "reversi") ||
+              !strcmp(s, "dark") || !strcmp(s, "alien") || !strcmp(s, "multi") || !strcmp(s, "amazons") ) n++;
            if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
        }
        if(p) *p++ = ',';
@@ -17594,12 +17740,12 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                 if(PieceToChar(piece) == '+') {
                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
                     *p++ = '+';
-                    piece = (ChessSquare)(DEMOTED piece);
+                    piece = (ChessSquare)(CHUDEMOTED piece);
                 }
                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
-                    p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
+                    p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
                     *p++ = '~';
                 }
            }
@@ -17645,7 +17791,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
   } else {
   if(nrCastlingRights) {
      q = p;
-     if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
+     if(appData.fischerCastling) {
        /* [HGM] write directly from rights */
            if(boards[move][CASTLING][2] != NoRights &&
               boards[move][CASTLING][0] != NoRights   )
@@ -17753,7 +17899,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
 Boolean
 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 {
-    int i, j, k, w=0;
+    int i, j, k, w=0, subst=0, shuffle=0;
     char *p, c;
     int emptycount, virgin[BOARD_FILES];
     ChessSquare piece;
@@ -17777,7 +17923,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                    appData.NrRanks = gameInfo.boardHeight - i; i=0;
                 }
                break;
-#if(BOARD_FILES >= 10)
+#if(BOARD_FILES >= 10)*0
             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
                 p++; emptycount=10;
                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
@@ -17792,6 +17938,16 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+            } else if (*p == '<') {
+                if(i == BOARD_HEIGHT-1) shuffle = 1;
+                else if (i != 0 || !shuffle) return FALSE;
+                p++;
+            } else if (shuffle && *p == '>') {
+                p++; // for now ignore closing shuffle range, and assume rank-end
+            } else if (*p == '?') {
+                if (j >= gameInfo.boardWidth) return FALSE;
+                if (i != 0  && i != BOARD_HEIGHT-1) return FALSE; // only on back-rank
+               board[i][(j++)+gameInfo.holdingsWidth] = ClearBoard; p++; subst++; // placeHolder
             } else if (*p == '+' || isalpha(*p)) {
                 if (j >= gameInfo.boardWidth) return FALSE;
                 if(*p=='+') {
@@ -17830,7 +17986,9 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     /* [HGM] look for Crazyhouse holdings here */
     while(*p==' ') p++;
     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
+        int swap=0, wcnt=0, bcnt=0;
         if(*p == '[') p++;
+        if(*p == '<') swap++, p++;
         if(*p == '-' ) p++; /* empty holdings */ else {
             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
             /* if we would allow FEN reading to set board size, we would   */
@@ -17843,18 +18001,46 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                     if( i >= gameInfo.holdingsSize ) return FALSE;
                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
+                    bcnt++;
                 } else {
                     i = (int)piece - (int)WhitePawn;
                    i = PieceToNumber((ChessSquare)i);
                     if( i >= gameInfo.holdingsSize ) return FALSE;
                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
+                    wcnt++;
+                }
+            }
+            if(subst) { // substitute back-rank question marks by holdings pieces
+                for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
+                    int k, m, n = bcnt + 1;
+                    if(board[0][j] == ClearBoard) {
+                        if(!wcnt) return FALSE;
+                        n = rand() % wcnt;
+                        for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((m -= board[k][BOARD_WIDTH-2]) < 0) {
+                            board[0][j] = board[k][BOARD_WIDTH-1]; wcnt--;
+                            if(--board[k][BOARD_WIDTH-2] == 0) board[k][BOARD_WIDTH-1] = EmptySquare;
+                            break;
+                        }
+                    }
+                    if(board[BOARD_HEIGHT-1][j] == ClearBoard) {
+                        if(!bcnt) return FALSE;
+                        if(n >= bcnt) n = rand() % bcnt; // use same randomization for black and white if possible
+                        for(k=0, m=n; k<gameInfo.holdingsSize; k++) if((n -= board[BOARD_HEIGHT-1-k][1]) < 0) {
+                            board[BOARD_HEIGHT-1][j] = board[BOARD_HEIGHT-1-k][0]; bcnt--;
+                            if(--board[BOARD_HEIGHT-1-k][1] == 0) board[BOARD_HEIGHT-1-k][0] = EmptySquare;
+                            break;
+                        }
+                    }
                 }
+                subst = 0;
             }
         }
         if(*p == ']') p++;
     }
 
+    if(subst) return FALSE; // substitution requested, but no holdings
+
     while(*p == ' ') p++;
 
     /* Active color */
@@ -17881,7 +18067,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     board[EP_STATUS] = EP_UNKNOWN;
     for(i=0; i<nrCastlingRights; i++ ) {
         board[CASTLING][i] =
-            gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
+            appData.fischerCastling ? NoRights : initialRights[i];
     }   /* assume possible unless obviously impossible */
     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
@@ -17895,6 +18081,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
     while(*p==' ') p++;
     if(nrCastlingRights) {
+      int fischer = 0;
       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
       if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
           /* castling indicator present, so default becomes no castlings */
@@ -17903,7 +18090,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
           }
       }
       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
-             (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
+             (appData.fischerCastling || gameInfo.variant == VariantSChess) &&
              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
@@ -17925,6 +18112,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               board[CASTLING][2] = whiteKingFile;
              if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
              if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
+              if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
               break;
           case'Q':
               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
@@ -17932,6 +18120,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               board[CASTLING][2] = whiteKingFile;
              if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
              if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
+              if(whiteKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
               break;
           case'k':
               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
@@ -17939,6 +18128,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               board[CASTLING][5] = blackKingFile;
              if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
              if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
+              if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_RGHT-1) fischer = 1;
               break;
           case'q':
               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
@@ -17946,6 +18136,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               board[CASTLING][5] = blackKingFile;
              if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
              if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
+              if(blackKingFile != BOARD_WIDTH>>1|| i != BOARD_LEFT) fischer = 1;
           case '-':
               break;
           default: /* FRC castlings */
@@ -17979,7 +18170,9 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
       }
       for(i=0; i<nrCastlingRights; i++)
         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
-      if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
+      if(gameInfo.variant == VariantSChess)
+        for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = shuffle ? VIRGIN_W | VIRGIN_B : virgin[i]; // when shuffling assume all virgin
+      if(fischer && shuffle) appData.fischerCastling = TRUE;
     if (appData.debugMode) {
         fprintf(debugFP, "FEN castling rights:");
         for(i=0; i<nrCastlingRights; i++)
@@ -17990,6 +18183,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
       while(*p==' ') p++;
     }
 
+    if(shuffle) SetUpShuffle(board, appData.defaultFrcPosition);
+
     /* read e.p. field in games that know e.p. capture */
     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier &&