Implement -monoMouse option (XB)
[xboard.git] / backend.c
index 71e7099..c4c5bf3 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 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
 #ifdef WIN32
 #include <windows.h>
 
-int flock(int f, int code);
-#define LOCK_EX 2
-#define SLASH '\\'
+    int flock(int f, int code);
+#   define LOCK_EX 2
+#   define SLASH '\\'
+
+#   ifdef ARC_64BIT
+#       define EGBB_NAME "egbbdll64.dll"
+#   else
+#       define EGBB_NAME "egbbdll.dll"
+#   endif
 
 #else
 
-#include <sys/file.h>
-#define SLASH '/'
+#   include <sys/file.h>
+#   define SLASH '/'
+
+#   include <dlfcn.h>
+#   ifdef ARC_64BIT
+#       define EGBB_NAME "egbbso64.so"
+#   else
+#       define EGBB_NAME "egbbso.so"
+#   endif
+    // kludge to allow Windows code in back-end by converting it to corresponding Linux code 
+#   define CDECL
+#   define HMODULE void *
+#   define LoadLibrary(x) dlopen(x, RTLD_LAZY)
+#   define GetProcAddress dlsym
 
 #endif
 
@@ -130,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
@@ -225,14 +244,13 @@ void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
 void ToggleSecond P((void));
 void PauseEngine P((ChessProgramState *cps));
-static int NonStandardBoardSize P((void));
+static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
 
 #ifdef WIN32
        extern void ConsoleCreate();
 #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
@@ -271,10 +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;
+int border;       /* [HGM] width of board rim, needed to size seek graph  */
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -393,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;
 }
 
@@ -617,6 +644,13 @@ ChessSquare GrandArray[2][BOARD_FILES] = {
         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
 };
 
+ChessSquare ChuChessArray[2][BOARD_FILES] = {
+    { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
+        WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
+    { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
+        BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
+};
+
 #ifdef GOTHIC
 ChessSquare GothicArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
@@ -751,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);
@@ -815,11 +848,12 @@ InitEngine (ChessProgramState *cps, int n)
     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
     TidyProgramName(cps->program, cps->host, cps->tidy);
     cps->matchWins = 0;
-    ASSIGN(cps->variants, appData.variant);
+    ASSIGN(cps->variants, appData.noChessProgram ? "" : appData.variant);
     cps->analysisSupport = 2; /* detect */
     cps->analyzing = FALSE;
     cps->initDone = FALSE;
     cps->reload = FALSE;
+    cps->pseudo = appData.pseudo[n];
 
     /* New features added by Tord: */
     cps->useFEN960 = FALSE;
@@ -837,6 +871,7 @@ InitEngine (ChessProgramState *cps, int n)
     /* [HGM] debug */
     cps->debug = FALSE;
 
+    cps->drawDepth = appData.drawDepth[n];
     cps->supportsNPS = UNKNOWN;
     cps->memSize = FALSE;
     cps->maxCores = FALSE;
@@ -920,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
@@ -1166,6 +1201,11 @@ InitBackEnd1 ()
        DisplayFatalError(buf, 0, 2);
        return;
 
+      case VariantNormal:     /* definitely works! */
+       if(strcmp(appData.variant, "normal") && !appData.noChessProgram) { // [HGM] hope this is an engine-defined variant
+         safeStrCpy(engineVariant, appData.variant, MSG_SIZ);
+         return;
+       }
       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
       case VariantFairy:      /* [HGM] TestLegality definitely off! */
       case VariantGothic:     /* [HGM] should work */
@@ -1178,7 +1218,6 @@ InitBackEnd1 ()
       case VariantFalcon:     /* [HGM] untested */
       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
                                 offboard interposition not understood */
-      case VariantNormal:     /* definitely works! */
       case VariantWildCastle: /* pieces not automatically shuffled */
       case VariantNoCastle:   /* pieces not automatically shuffled */
       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
@@ -1203,6 +1242,7 @@ InitBackEnd1 ()
       case VariantGrand:      /* should work */
       case VariantSpartan:    /* should work */
       case VariantLion:       /* should work */
+      case VariantChuChess:   /* should work */
        break;
       }
     }
@@ -1555,6 +1595,23 @@ InitBackEnd3 P((void))
     char buf[MSG_SIZ];
     int err, len;
 
+    if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
+       !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
+        appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
+       !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
+       !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
+       char c, *q = first.variants, *p = strchr(q, ',');
+       if(p) *p = NULLCHAR;
+       if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
+           int w, h, s;
+           if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
+               appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
+           ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
+           Reset(TRUE, FALSE);         // and re-initialize
+       }
+       if(p) *p = ',';
+    }
+
     InitChessProgram(&first, startedFromSetupPosition);
 
     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
@@ -2051,14 +2108,14 @@ StringToVariant (char *e)
     int wnum = -1;
     VariantClass v = VariantNormal;
     int i, found = FALSE;
-    char buf[MSG_SIZ];
+    char buf[MSG_SIZ], c;
     int len;
 
     if (!e) return v;
 
     /* [HGM] skip over optional board-size prefixes */
-    if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
-        sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
+    if( sscanf(e, "%dx%d_%c", &i, &i, &c) == 3 ||
+        sscanf(e, "%dx%d+%d_%c", &i, &i, &i, &c) == 4 ) {
         while( *e++ != '_');
     }
 
@@ -2067,7 +2124,8 @@ StringToVariant (char *e)
        found = TRUE;
     } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
-      if (StrCaseStr(e, variantNames[i])) {
+      if (p = StrCaseStr(e, variantNames[i])) {
+       if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
        v = (VariantClass) i;
        found = TRUE;
        break;
@@ -2663,8 +2721,8 @@ DrawSeekGraph ()
 {
     int i;
     if(!seekGraphUp) return FALSE;
-    h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
-    w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
+    h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + 2*border;
+    w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap + 2*border;
 
     DrawSeekBackground(0, 0, w, h);
     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
@@ -2783,7 +2841,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...
 
@@ -3025,8 +3083,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);
@@ -3242,18 +3310,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;
@@ -3261,6 +3331,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;
@@ -3269,6 +3340,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;
@@ -3276,6 +3348,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); }
@@ -3286,18 +3359,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
 
@@ -3381,7 +3459,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;
                    }
@@ -4786,7 +4864,7 @@ ParseBoard12 (char *string)
              default:
                break;
              case MT_CHECK:
-                if(gameInfo.variant != VariantShogi)
+                if(!IS_SHOGI(gameInfo.variant))
                     strcat(parseList[moveNum - 1], "+");
                break;
              case MT_CHECKMATE:
@@ -5016,6 +5094,11 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
     char buf[MSG_SIZ];
 
     if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') {
+       if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) {
+           sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : "");
+           SendToProgram(buf, cps);
+           return;
+       }
        // null move in variant where engine does not understand it (for analysis purposes)
        SendBoard(cps, moveNum + 1); // send position after move in stead.
        return;
@@ -5043,8 +5126,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;
@@ -5058,16 +5140,18 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
        }
        else SendToProgram(moveList[moveNum], cps);
       } else
+      if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
+                                              moveList[moveNum][5], moveList[moveNum][6] - '0',
+                                              moveList[moveNum][5], moveList[moveNum][6] - '0',
+                                              moveList[moveNum][2], moveList[moveNum][3] - '0');
+         SendToProgram(buf, cps);
+      } else
       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
        if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
          if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else
          snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
                                              moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
-       } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
-         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves
-                                              moveList[moveNum][5], moveList[moveNum][6] - '0',
-                                              moveList[moveNum][5], moveList[moveNum][6] - '0',
-                                              moveList[moveNum][2], moveList[moveNum][3] - '0');
        } else
          snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
                                               moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
@@ -5244,6 +5328,7 @@ UploadGameEvent ()
 }
 
 int killX = -1, killY = -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])
@@ -5277,28 +5362,43 @@ ProcessICSInitScript (FILE *f)
 }
 
 
-static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
+static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
+int dragging;
 static ClickType lastClickType;
 
+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);
+  if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
+  *p = partner;
+  return 1;
+}
+
 void
 Sweep (int step)
 {
     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
+    static int toggleFlag;
     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
-    if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
+    if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare;
+    if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion
     do {
-       promoSweep -= step;
+       if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step;
        if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
        else if((int)promoSweep == -1) promoSweep = WhiteKing;
        else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
        else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
        if(!step) step = -1;
     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
-           appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) ||
-           IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep);
+           !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
+           appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
+            (promoSweep == WhiteLion || promoSweep == BlackLion)));
     if(toX >= 0) {
        int victim = boards[currentMove][toY][toX];
        boards[currentMove][toY][toX] = promoSweep;
@@ -5436,7 +5536,8 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
        if (appData.testLegality) {
          return (*moveType != IllegalMove);
        } else {
-         return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
+         return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
+                        // [HGM] lion: if this is a double move we are less critical
                        WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
        }
 
@@ -5579,10 +5680,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;
 }
 
@@ -5900,7 +6004,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 */
     {
@@ -5929,9 +6033,10 @@ InitPosition (int redraw)
     gameInfo.boardHeight   = 8;
     gameInfo.holdingsSize  = 0;
     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
-    for(i=0; i<BOARD_FILES-2; i++)
+    for(i=0; i<BOARD_FILES-6; i++)
       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");
     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
          SetCharTable(pieceNickName, appData.pieceNickNames);
@@ -5941,6 +6046,7 @@ InitPosition (int redraw)
     switch (gameInfo.variant) {
     case VariantFischeRandom:
       shuffleOpenings = TRUE;
+      appData.fischerCastling = TRUE;
     default:
       break;
     case VariantShatranj:
@@ -5971,6 +6077,7 @@ InitPosition (int redraw)
       break;
     case VariantCapaRandom:
       shuffleOpenings = TRUE;
+      appData.fischerCastling = TRUE;
     case VariantCapablanca:
       pieces = CapablancaArray;
       gameInfo.boardWidth = 10;
@@ -6001,7 +6108,7 @@ InitPosition (int redraw)
     case VariantFalcon:
       pieces = FalconArray;
       gameInfo.boardWidth = 10;
-      SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
+      SetCharTable(pieceToChar, "PNBRQ............FKpnbrq............fk");
       break;
     case VariantXiangqi:
       pieces = XiangqiArray;
@@ -6044,6 +6151,12 @@ InitPosition (int redraw)
       pieces = lionArray;
       SetCharTable(pieceToChar, "PNBRQ................LKpnbrq................lk");
       break;
+    case VariantChuChess:
+      pieces = ChuChessArray;
+      gameInfo.boardWidth = 10;
+      gameInfo.boardHeight = 10;
+      SetCharTable(pieceToChar, "PNBRQ.....M.+++......LKpnbrq.....m.+++......lk");
+      break;
     case VariantFairy:
       pieces = fairyArray;
       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
@@ -6098,7 +6211,8 @@ InitPosition (int redraw)
 
     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
     if(pawnRow < 1) pawnRow = 1;
-    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2;
+    if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN ||
+       gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) pawnRow = 2;
     if(gameInfo.variant == VariantChu) pawnRow = 3;
 
     /* User pieceToChar list overrules defaults */
@@ -6113,7 +6227,7 @@ InitPosition (int redraw)
             initialPosition[i][j] = s;
 
         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
-        initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
+        initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth];
         initialPosition[pawnRow][j] = WhitePawn;
         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
         if(gameInfo.variant == VariantXiangqi) {
@@ -6135,14 +6249,15 @@ InitPosition (int redraw)
                initialPosition[BOARD_HEIGHT-1-i][j] =  pieces[2*i+1][j-gameInfo.holdingsWidth];
              }
         }
-        if(gameInfo.variant == VariantGrand) {
+        if(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
             if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
                initialPosition[0][j] = WhiteRook;
                initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
             }
         }
-        initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] =  pieces[1][j-gameInfo.holdingsWidth];
+        initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] =  pieces[1][j-gameInfo.holdingsWidth];
     }
+    if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing;
     if( (gameInfo.variant == VariantShogi) && !overrule ) {
 
             j=BOARD_LEFT+1;
@@ -6420,7 +6535,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
     /* [HGM] add Shogi promotions */
     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
-    ChessSquare piece;
+    ChessSquare piece, partner;
     ChessMove moveType;
     Boolean premove;
 
@@ -6439,12 +6554,12 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3;
         highestPromotingPiece = (int)WhiteAlfil;
-    } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
+    } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
         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;
     }
@@ -6453,10 +6568,13 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     if((int)piece >= BlackPawn) {
         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
              return FALSE;
+        if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE;
         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
     } else {
         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
+        if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess)
+             return FALSE;
     }
 
     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
@@ -6492,7 +6610,11 @@ 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) {
-       *promoChoice = PieceToChar(BlackFerz);  // no choice
+       ChessSquare p=BlackFerz;  // no choice
+       while(p < EmptySquare) {  //but make sure we use piece that exists
+           *promoChoice = PieceToChar(p++);
+           if(*promoChoice != '.') break;
+       }
        return FALSE;
     }
     // no sense asking what we must promote to if it is going to explode...
@@ -6502,7 +6624,9 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     }
     // give caller the default choice even if we will not make it
     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
-    if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
+    partner = piece; // pieces can promote if the pieceToCharTable says so
+    if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete?
+    else if(Partner(&partner))     *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+');
     if(        sweepSelect && gameInfo.variant != VariantGreat
                           && gameInfo.variant != VariantGrand
                           && gameInfo.variant != VariantSuper) return FALSE;
@@ -6513,7 +6637,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
              gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
     if(appData.testLegality && !premove) {
        moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
-                       fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR);
+                       fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR);
+        if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal
        if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
@@ -6704,6 +6829,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)
@@ -6822,10 +6948,17 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
        /* EditPosition, empty square, or different color piece;
           click-click move is possible */
        if (toX == -2 || toY == -2) {
-           boards[0][fromY][fromX] = EmptySquare;
+           boards[0][fromY][fromX] = (boards[0][fromY][fromX] == EmptySquare ? DarkSquare : EmptySquare);
            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) {
@@ -6846,7 +6979,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 */
@@ -6858,7 +6991,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
           // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
           if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
           fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
-          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
+          while(PieceToChar(fromX) == '.' || PieceToChar(fromX) == '+' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
          fromY = DROP_RANK;
     }
 
@@ -6866,7 +6999,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) {
@@ -6883,6 +7016,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);
 }
 
@@ -7114,29 +7257,34 @@ MarkByFEN(char *fen)
        DrawPosition(TRUE, NULL);
 }
 
+static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
+
 void
 Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure)
 {
     typedef char Markers[BOARD_RANKS][BOARD_FILES];
     Markers *m = (Markers *) closure;
-    if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2))
+    if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 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;
+
 void
 MarkTargetSquares (int clear)
 {
   int x, y, sum=0;
   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
   } 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++;
@@ -7166,34 +7314,35 @@ ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
 int
 CanPromote (ChessSquare piece, int y)
 {
+        int zone = (gameInfo.variant == VariantChuChess ? 3 : 1);
        if(gameMode == EditPosition) return FALSE; // no promotions when editing position
        // 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   || gameInfo.variant == VariantASEAN) return FALSE;
-       return (piece == BlackPawn && y == 1 ||
-               piece == WhitePawn && y == BOARD_HEIGHT-2 ||
-               piece == BlackLance && y == 1 ||
-               piece == WhiteLance && y == BOARD_HEIGHT-2 );
+       return (piece == BlackPawn && y <= zone ||
+               piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
+               piece == BlackLance && y <= zone ||
+               piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
 }
 
 void
 HoverEvent (int xPix, int yPix, int x, int y)
 {
-       static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
        static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1;
        int r, f;
-       if(dragging == 2) DragPieceMove(xPix, yPix); // [HGM] lion: drag without button for second leg
        if(!first.highlight) return;
        if(fromX != oldFromX || fromY != oldFromY)  oldX = oldY = -1; // kludge to fake entry on from-click
        if(x == oldX && y == oldY) return; // only do something if we enter new square
        oldFromX = fromX; oldFromY = fromY;
-       if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) // record markings after from-change
+       if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change
          for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
            baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
-       else if(oldX != x || oldY != y) {
+         hoverSavedValid = 1;
+       } else if(oldX != x || oldY != y) {
          // [HGM] lift: entered new to-square; redraw arrow, and inform engine
+         if(hoverSavedValid) // don't restore markers that are supposed to be cleared
          for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
            marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
          if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
@@ -7211,12 +7360,15 @@ 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 || !appData.markers, 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);
 }
 
+Boolean right; // instructs front-end to use button-1 events as if they were button 3
+
 void
 LeftClick (ClickType clickType, int xPix, int yPix)
 {
@@ -7227,13 +7379,6 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     ChessSquare piece;
     static TimeMark lastClickTime, prevClickTime;
 
-    if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
-
-    prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
-
-    if (clickType == Press) ErrorPopDown();
-    lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
-
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
     if (!flipView && y >= 0) {
@@ -7243,6 +7388,20 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        x = BOARD_WIDTH - 1 - x;
     }
 
+    if(appData.monoMouse && gameMode == EditPosition && clickType == Press && boards[currentMove][y][x] == EmptySquare) {
+       static int dummy;
+       RightClick(clickType, xPix, yPix, &dummy, &dummy);
+       right = TRUE;
+       return;
+    }
+
+    if(SeekGraphClick(clickType, xPix, yPix, 0)) return;
+
+    prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
+
+    if (clickType == Press) ErrorPopDown();
+    lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
+
     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
        defaultPromoChoice = promoSweep;
        promoSweep = EmptySquare;   // terminate sweep
@@ -7349,8 +7508,9 @@ 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!
+           legal[y][x] == 0 && // if engine told we can move to here, do it even if own piece
           ((WhitePawn <= fromP && fromP <= WhiteKing &&
             WhitePawn <= toP && toP <= WhiteKing &&
             !(fromP == WhiteKing && toP == WhiteRook && frc) &&
@@ -7432,7 +7592,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);
@@ -7446,8 +7607,11 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        toY = y;
     }
 
+    piece = boards[currentMove][fromY][fromX];
+
     saveAnimate = appData.animate;
     if (clickType == Press) {
+       if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
        if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
            // must be Edit Position mode with empty-square selected
            fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
@@ -7455,7 +7619,6 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            return;
        }
        if(dragging == 2) {  // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job
-           dragging = 1;
            return;
        }
        if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
@@ -7464,9 +7627,8 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        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) {
-           ChessSquare piece = boards[currentMove][fromY][fromX];
            promoSweep = defaultPromoChoice;
-           if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
+           if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
            selectFlag = 0; lastX = xPix; lastY = yPix;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
            sweepSelecting = 1;
@@ -7482,7 +7644,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
-       sweepSelecting = 0;
+       sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
@@ -7498,14 +7660,15 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
 #endif
+       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
          MarkTargetSquares(1);
          if(x == killX && y == killY) killX = killY = -1; else {
            killX = x; killY = y;     //remeber this square as intermediate
-           MarkTargetSquares(0);
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
+           MarkTargetSquares(0);
            return;
          }
        }
@@ -7564,7 +7727,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            DisplayMessage("Click in holdings to choose piece", "");
            return;
        }
-       PromotionPopUp();
+       PromotionPopUp(promoChoice);
     } else {
        int oldMove = currentMove;
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
@@ -7958,6 +8121,7 @@ Adjudicate (ChessProgramState *cps)
              case MT_NONE:
              default:
                break;
+             case MT_STEALMATE:
              case MT_STALEMATE:
              case MT_STAINMATE:
                reason = "Xboard adjudication: Stalemate";
@@ -8196,11 +8360,66 @@ Adjudicate (ChessProgramState *cps)
        return 0;
 }
 
+typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square);
+typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options);
+static int egbbCode[] = { 6, 5, 4, 3, 2, 1 };
+
+static int
+BitbaseProbe ()
+{
+    int pieces[10], squares[10], cnt=0, r, f, res;
+    static int loaded;
+    static PPROBE_EGBB probeBB;
+    if(!appData.testLegality) return 10;
+    if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12;
+    if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12;
+    if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game
+    for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+       ChessSquare piece = boards[forwardMostMove][r][f];
+       int black = (piece >= BlackPawn);
+       int type = piece - black*BlackPawn;
+       if(piece == EmptySquare) continue;
+       if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece
+       if(type == WhiteKing) type = WhiteQueen + 1;
+       type = egbbCode[type];
+       squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT;
+        pieces[cnt] = type + black*6;
+       if(++cnt > 5) return 11;
+    }
+    pieces[cnt] = squares[cnt] = 0;
+    // probe EGBB
+    if(loaded == 2) return 13; // loading failed before
+    if(loaded == 0) {
+       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);
+       snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME);
+       lib = LoadLibrary(buf);
+       if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; }
+       loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen");
+       probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen");
+       if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; }
+       p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD
+       loaded = 1; // success!
+    }
+    res = probeBB(forwardMostMove & 1, pieces, squares);
+    return res > 0 ? 1 : res < 0 ? -1 : 0;
+}
+
 char *
 SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
 {   // [HGM] book: this routine intercepts moves to simulate book replies
     char *bookHit = NULL;
 
+    if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position
+       char buf[MSG_SIZ];
+       snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth);
+       SendToProgram(buf, cps);
+    }
     //first determine if the incoming move brings opponent into his book
     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
        bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
@@ -8288,7 +8507,7 @@ HandleMachineMove (char *message, ChessProgramState *cps)
     char realname[MSG_SIZ];
     int fromX, fromY, toX, toY;
     ChessMove moveType;
-    char promoChar;
+    char promoChar, roar;
     char *p, *pv=buf1;
     int machineWhite, oldError;
     char *bookHit;
@@ -8450,8 +8669,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) res=%d",
-                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
+            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);
            if (gameMode == TwoMachinesPlay) {
              GameEnds(machineWhite ? BlackWins : WhiteWins,
                        buf1, GE_XBOARD);
@@ -8474,7 +8693,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) {
@@ -8518,7 +8737,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 ) {
@@ -8528,7 +8747,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;
                 }
 
@@ -8572,7 +8791,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
@@ -8604,10 +8822,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            cps->other->maybeThinking = TRUE;
        }
 
+       roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX]));
+
        ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
 
         if (!pausing && appData.ringBellAfterMoves) {
-           RingBell();
+           if(!roar) RingBell();
        }
 
        /*
@@ -8658,13 +8878,16 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     }
 
     if (!strncmp(message, "setup ", 6) && 
-       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
+       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown ||
+          NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize))
                                        ) { // [HGM] allow first engine to define opening position
       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(startedFromSetupPosition) return;
+      if(sscanf(message, "setup (%s", buf) == 1) {
+        s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+        ASSIGN(appData.pieceToCharTable, buf);
+      }
       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
       if(dummy >= 3) {
         while(message[s] && message[s++] != ' ');
@@ -8674,13 +8897,36 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
           if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
+          startedFromSetupPosition = FALSE;
         }
       }
+      if(startedFromSetupPosition) return;
       ParseFEN(boards[0], &dummy, message+s, FALSE);
       DrawPosition(TRUE, boards[0]);
+      CopyBoard(initialPosition, boards[0]);
       startedFromSetupPosition = TRUE;
       return;
     }
+    if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
+      ChessSquare piece = WhitePawn;
+      char *p=buf2;
+      if(*p == '+') piece = CHUPROMOTED WhitePawn, p++;
+      piece += CharToPiece(*p) - WhitePawn;
+      if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
+      /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
+      /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
+      /* For variants we don't have   */       && gameInfo.variant != VariantBerolina
+      /* correct rules for, we cannot */       && gameInfo.variant != VariantCylinder
+      /* enforce legality on our own! */       && gameInfo.variant != VariantUnknown
+                                               && gameInfo.variant != VariantGreat
+                                               && gameInfo.variant != VariantFairy    ) return;
+      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.
      */
@@ -8789,6 +9035,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
     }
     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
+       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?
+               GameEnds(GameUnfinished, NULL, GE_XBOARD);
+           }
+           initPing = -1;
+        }
        return;
     }
     if(!strncmp(message, "highlight ", 10)) {
@@ -8936,6 +9190,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.
@@ -9204,6 +9462,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;
                }
@@ -9622,7 +9883,7 @@ ParseGameHistory (char *game)
          default:
            break;
          case MT_CHECK:
-            if(gameInfo.variant != VariantShogi)
+            if(!IS_SHOGI(gameInfo.variant))
                 strcat(parseList[boardIndex - 1], "+");
            break;
          case MT_CHECKMATE:
@@ -9638,15 +9899,16 @@ ParseGameHistory (char *game)
 void
 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 {
-  ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
-  int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
+  ChessSquare captured = board[toY][toX], piece, pawn, king, killed; 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 */
     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
 
       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
-      oldEP = (signed char)board[EP_STATUS];
+      oldEP = (signed char)board[EP_FILE]; epRank = board[EP_RANK];
       board[EP_STATUS] = EP_NONE;
+      board[EP_FILE] = board[EP_RANK] = 100;
 
   if (fromY == DROP_RANK) {
        /* must be first */
@@ -9656,11 +9918,12 @@ 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],
+           killed = board[killY][killX],
            board[killY][killX] = EmptySquare,
            board[EP_STATUS] = EP_CAPTURE;
 
@@ -9673,14 +9936,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
            }
       }
 
-      if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
-           if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
-               board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
-      } else
-      if( board[fromY][fromX] == WhitePawn ) {
+      pawn = board[fromY][fromX];
+      if( pawn == WhiteLance || pawn == BlackLance ) {
+           if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu ) {
+               if(gameInfo.variant == VariantSpartan) board[EP_STATUS] = EP_PAWN_MOVE; // in Spartan no e.p. rights must be set
+               else pawn += WhitePawn - WhiteLance; // Lance is Pawn-like in most variants, so let Pawn code treat it by this kludge
+           }
+      }
+      if( pawn == WhitePawn ) {
            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) {
+           if( toY-fromY>=2) {
+               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY - 1 | 128*(toY - fromY > 2);
                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
@@ -9689,10 +9956,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                      board[EP_STATUS] = toX;
           }
       } else
-      if( board[fromY][fromX] == BlackPawn ) {
+      if( pawn == BlackPawn ) {
            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) {
+           if( toY-fromY<= -2) {
+               board[EP_FILE] = (fromX + toX)/2; board[EP_RANK] = toY + 1 | 128*(fromY - toY > 2);
                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX < fromX)
                      board[EP_STATUS] = toX | berolina;
@@ -9702,6 +9970,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           }
        }
 
+       if(fromY == 0) board[TOUCHED_W] |= 1<<fromX; else // new way to keep track of virginity
+       if(fromY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<fromX;
+       if(toY == 0) board[TOUCHED_W] |= 1<<toX; else
+       if(toY == BOARD_HEIGHT-1) board[TOUCHED_B] |= 1<<toX;
+
        for(i=0; i<nrCastlingRights; i++) {
            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
               board[CASTLING][i] == toX   && castlingRank[i] == toY
@@ -9722,10 +9995,17 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
      if(gameInfo.variant == VariantKnightmate)
          king += (int) WhiteUnicorn - (int) WhiteKing;
 
+    if(piece != WhiteKing && piece != BlackKing && pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
+       && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and captures own
+       board[toY][toX] = piece; board[fromY][fromX] = EmptySquare;
+       board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
+        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 ||
         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
+      board[EP_STATUS] = EP_NONE; // capture was fake!
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
       if((toX > fromX) != (piece == WhiteRook)) {
@@ -9735,6 +10015,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
       }
     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
+      board[EP_STATUS] = EP_NONE;
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
       if((toX > fromX) != (piece == BlackRook)) {
@@ -9749,17 +10030,19 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
         && toY == fromY && toX > fromX+1) {
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
-        board[fromY][BOARD_RGHT-1] = EmptySquare;
+       for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
+        board[toY][toX-1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX+1] = board[fromY][BOARD_LEFT];
-        board[fromY][BOARD_LEFT] = EmptySquare;
+       for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
+        board[toY][toX+1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
-                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
+                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
                ) {
        /* white pawn promotion */
@@ -9768,16 +10051,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
             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)
+              && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
-              && (board[fromY][fromX] == WhitePawn)
+              && (pawn == WhitePawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = WhitePawn;
-       captured = board[toY - 1][toX];
-       board[toY - 1][toX] = EmptySquare;
+       board[toY][toX] = piece;
+       if(toY == epRank - 128 + 1)
+           captured = board[toY - 2][toX], board[toY - 2][toX] = EmptySquare;
+       else
+           captured = board[toY - 1][toX], board[toY - 1][toX] = EmptySquare;
     } else if ((fromY == BOARD_HEIGHT-4)
               && (toX == fromX)
                && gameInfo.variant == VariantBerolina
@@ -9796,15 +10081,17 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                && toY == fromY && toX > fromX+1) {
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
-        board[fromY][BOARD_RGHT-1] = EmptySquare;
+       for(rookX=BOARD_RGHT-1; board[toY][rookX] == DarkSquare && rookX > toX + 1; rookX--);
+        board[toY][toX-1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX+1] = board[fromY][BOARD_LEFT];
-        board[fromY][BOARD_LEFT] = EmptySquare;
+       for(rookX=BOARD_LEFT; board[toY][rookX] == DarkSquare && rookX < toX - 1; rookX++);
+        board[toY][toX+1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
     } else if (fromY == 7 && fromX == 3
               && board[fromY][fromX] == BlackKing
               && toY == 7 && toX == 5) {
@@ -9820,7 +10107,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
-                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
+                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
               && toY < promoRank && promoChar
                ) {
        /* black pawn promotion */
@@ -9829,16 +10116,18 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
             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)
+              && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
-              && (board[fromY][fromX] == BlackPawn)
+              && (pawn == BlackPawn)
               && (board[toY][toX] == EmptySquare)) {
        board[fromY][fromX] = EmptySquare;
-       board[toY][toX] = BlackPawn;
-       captured = board[toY + 1][toX];
-       board[toY + 1][toX] = EmptySquare;
+       board[toY][toX] = piece;
+       if(toY == epRank - 128 - 1)
+           captured = board[toY + 2][toX], board[toY + 2][toX] = EmptySquare;
+       else
+           captured = board[toY + 1][toX], board[toY + 1][toX] = EmptySquare;
     } else if ((fromY == 3)
               && (toX == fromX)
                && gameInfo.variant == VariantBerolina
@@ -9898,8 +10187,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
         p = (int) captured;
         if (p >= (int) BlackPawn) {
           p -= (int)BlackPawn;
-          if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
-                  /* in Shogi restore piece to its original  first */
+          if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
+                  /* Restore shogi-promoted piece to its original  first */
                   captured = (ChessSquare) (DEMOTED captured);
                   p = DEMOTED p;
           }
@@ -9909,7 +10198,7 @@ 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(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
+          if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
                   captured = (ChessSquare) (DEMOTED captured);
                   p = DEMOTED p;
           }
@@ -9933,12 +10222,15 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        board[toY][toX] = EmptySquare;
       }
     }
+
     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
     } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
         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
@@ -10061,7 +10353,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
       default:
        break;
       case MT_CHECK:
-        if(gameInfo.variant != VariantShogi)
+        if(!IS_SHOGI(gameInfo.variant))
             strcat(parseList[forwardMostMove - 1], "+");
        break;
       case MT_CHECKMATE:
@@ -10069,8 +10361,6 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
        strcat(parseList[forwardMostMove - 1], "#");
        break;
     }
-
-    killX = killY = -1; // [HGM] lion: used up
 }
 
 /* Updates currentMove if not pausing */
@@ -10090,6 +10380,8 @@ ShowMove (int fromX, int fromY, int toX, int toY)
        currentMove = forwardMostMove;
     }
 
+    killX = killY = -1; // [HGM] lion: used up
+
     if (instant) return;
 
     DisplayMove(currentMove - 1);
@@ -10138,40 +10430,75 @@ SendEgtPath (ChessProgramState *cps)
 }
 
 static int
-NonStandardBoardSize ()
-{
-      /* [HGM] Awkward testing. Should really be a table */
-      int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
-      if( gameInfo.variant == VariantXiangqi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantShogi )
-           overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
-      if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
-          gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantCourier )
-           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
-      if( gameInfo.variant == VariantSuper )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
-      if( gameInfo.variant == VariantGreat )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
-      if( gameInfo.variant == VariantSChess )
-           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantGrand )
-           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
-      if( gameInfo.variant == VariantChu )
-           overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0;
-      return overruled;
+NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize)
+{
+      int width = 8, height = 8, holdings = 0;             // most common sizes
+      if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
+      // correct the deviations default for each variant
+      if( v == VariantXiangqi ) width = 9,  height = 10;
+      if( v == VariantShogi )   width = 9,  height = 9,  holdings = 7;
+      if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5;
+      if( v == VariantCapablanca || v == VariantCapaRandom ||
+          v == VariantGothic || v == VariantFalcon || v == VariantJanus )
+                                width = 10;
+      if( v == VariantCourier ) width = 12;
+      if( v == VariantSuper )                            holdings = 8;
+      if( v == VariantGreat )   width = 10,              holdings = 8;
+      if( v == VariantSChess )                           holdings = 7;
+      if( v == VariantGrand )   width = 10, height = 10, holdings = 7;
+      if( v == VariantChuChess) width = 10, height = 10;
+      if( v == VariantChu )     width = 12, height = 12;
+      return boardWidth >= 0   && boardWidth   != width  || // -1 is default,
+             boardHeight >= 0  && boardHeight  != height || // and thus by definition OK
+             holdingsSize >= 0 && holdingsSize != holdings;
+}
+
+char variantError[MSG_SIZ];
+
+char *
+SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine)
+{     // returns error message (recognizable by upper-case) if engine does not support the variant
+      char *p, *variant = VariantName(v);
+      static char b[MSG_SIZ];
+      if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */
+          snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight,
+                                               holdingsSize, variant); // cook up sized variant name
+           /* [HGM] varsize: try first if this deviant size variant is specifically known */
+           if(StrStr(list, b) == NULL) {
+               // specific sized variant not known, check if general sizing allowed
+               if(proto != 1 && StrStr(list, "boardsize") == NULL) {
+                   snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
+                            boardWidth, boardHeight, holdingsSize, engine);
+                   return NULL;
+               }
+               /* [HGM] here we really should compare with the maximum supported board size */
+           }
+      } else snprintf(b, MSG_SIZ,"%s", variant);
+      if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best
+      p = StrStr(list, b);
+      while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b);
+      if(p == NULL) {
+          // occurs not at all in list, or only as sub-string
+          snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine);
+          if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported
+              int l = strlen(variantError);
+              char *q;
+              while(p != list && p[-1] != ',') p--;
+              q = strchr(p, ',');
+              if(q) *q = NULLCHAR;
+              snprintf(variantError + l, MSG_SIZ - l,  _(", but %s is"), p);
+              if(q) *q= ',';
+          }
+          return NULL;
+      }
+      return b;
 }
 
 void
 InitChessProgram (ChessProgramState *cps, int setup)
 /* setup needed to setup FRC opening position */
 {
-    char buf[MSG_SIZ], b[MSG_SIZ];
+    char buf[MSG_SIZ], *b;
     if (appData.noChessProgram) return;
     hintRequested = FALSE;
     bookRequested = FALSE;
@@ -10189,37 +10516,32 @@ InitChessProgram (ChessProgramState *cps, int setup)
        SendToProgram(buf, cps);
     }
 
+    setboardSpoiledMachineBlack = FALSE;
     SendToProgram(cps->initString, cps);
     if (gameInfo.variant != VariantNormal &&
        gameInfo.variant != VariantLoadable
         /* [HGM] also send variant if board size non-standard */
-        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
-                                            ) {
-      char *v = VariantName(gameInfo.variant);
-      if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
-        /* [HGM] in protocol 1 we have to assume all variants valid */
-       snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
-       DisplayFatalError(buf, 0, 1);
+        || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) {
+
+      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, ',');
+       if(p) *p = NULLCHAR;
+       v = StringToVariant(q);
+       DisplayError(variantError, 0);
+       if(v != VariantUnknown && cps == &first) {
+           int w, h, s;
+           if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
+               appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
+           ASSIGN(appData.variant, q);
+           Reset(TRUE, FALSE);
+       }
+       if(p) *p = ',';
        return;
       }
 
-      if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */
-       snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
-                gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
-           /* [HGM] varsize: try first if this defiant size variant is specifically known */
-           if(StrStr(cps->variants, b) == NULL) {
-               // specific sized variant not known, check if general sizing allowed
-               if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
-                   if(StrStr(cps->variants, "boardsize") == NULL) {
-                    snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
-                            gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
-                       DisplayFatalError(buf, 0, 1);
-                       return;
-                   }
-                   /* [HGM] here we really should compare with the maximum supported board size */
-               }
-           }
-      } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
       snprintf(buf, MSG_SIZ, "variant %s\n", b);
       SendToProgram(buf, cps);
     }
@@ -10259,7 +10581,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        SendToProgram("easy\n", cps);
     }
     if (cps->usePing) {
-      snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
+      snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing);
       SendToProgram(buf, cps);
     }
     cps->initDone = TRUE;
@@ -10618,7 +10940,9 @@ SwapEngines (int n)
     SWAP(fenOverride, p)
     SWAP(NPS, h)
     SWAP(accumulateTC, h)
+    SWAP(drawDepth, h)
     SWAP(host, p)
+    SWAP(pseudo, h)
 }
 
 int
@@ -11169,8 +11493,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;
@@ -11195,8 +11518,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;
@@ -11298,7 +11620,8 @@ FeedMovesToProgram (ChessProgramState *cps, int upto)
     if(currentlyInitializedVariant != gameInfo.variant) {
       char buf[MSG_SIZ];
         // [HGM] variantswitch: make engine aware of new variant
-       if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
+       if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
+                             gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, ""))
                return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
        snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
        SendToProgram(buf, cps);
@@ -11365,6 +11688,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;
@@ -11513,11 +11838,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) {
@@ -11765,6 +12099,7 @@ LoadGameOneMove (ChessMove readAhead)
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
+       killX = killY = -1; // [HGM] lion: used up
        currentMove = forwardMostMove;
        return TRUE;
     }
@@ -12011,22 +12346,40 @@ void
 PackMove (int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
 {
     int sq = fromX + (fromY<<4);
-    int piece = quickBoard[sq];
+    int piece = quickBoard[sq], rook;
     quickBoard[sq] = 0;
     moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
-    if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+    if(piece == pieceList[1] && fromY == toY) {
+      if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
        int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
        moveDatabase[movePtr++].piece = Q_WCASTL;
        quickBoard[sq] = piece;
        piece = quickBoard[from]; quickBoard[from] = 0;
        moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+      } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling
+       quickBoard[sq] = 0; // remove Rook
+       moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square
+       moveDatabase[movePtr++].piece = Q_WCASTL;
+       quickBoard[sq] = pieceList[1]; // put King
+       piece = rook;
+       moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
+      }
     } else
-    if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+    if(piece == pieceList[2] && fromY == toY) {
+      if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
        int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
        moveDatabase[movePtr++].piece = Q_BCASTL;
        quickBoard[sq] = piece;
        piece = quickBoard[from]; quickBoard[from] = 0;
        moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+      } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling
+       quickBoard[sq] = 0; // remove Rook
+       moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2);
+       moveDatabase[movePtr++].piece = Q_BCASTL;
+       quickBoard[sq] = pieceList[2]; // put King
+       piece = rook;
+       moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1;
+      }
     } else
     if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
        quickBoard[(fromY<<4)+toX] = 0;
@@ -12106,12 +12459,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];
@@ -12136,23 +12503,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);
 }
@@ -12530,7 +12886,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        if (!err) numPGNTags++;
 
         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
-        if(gameInfo.variant != oldVariant) {
+        if(gameInfo.variant != oldVariant && (gameInfo.variant != VariantNormal || gameInfo.variantName == NULL || *gameInfo.variantName == NULLCHAR)) {
             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
            ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
            InitPosition(TRUE);
@@ -12549,6 +12905,8 @@ 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
+           CopyBoard(initialPosition, initial_position);
          if (blackPlaysFirst) {
            currentMove = forwardMostMove = backwardMostMove = 1;
            CopyBoard(boards[1], initial_position);
@@ -13105,9 +13463,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;
@@ -13267,7 +13625,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;
@@ -13746,6 +14110,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) {
@@ -13772,14 +14137,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);
@@ -14010,7 +14373,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();
@@ -14331,9 +14697,12 @@ TwoMachinesEvent P((void))
     }
     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
 
-    if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) {
-       startingEngine = FALSE;
+    if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
+                         gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
+       startingEngine = matchMode = FALSE;
        DisplayError("second engine does not play this", 0);
+       gameMode = TwoMachinesPlay; ModeHighlight(); // Needed to make sure menu item is unchecked
+       EditGameEvent(); // switch back to EditGame mode
        return;
     }
 
@@ -14740,6 +15109,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
 
     switch (selection) {
       case ClearBoard:
+       fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+       MarkTargetSquares(1);
        CopyBoard(currentBoard, boards[0]);
        CopyBoard(menuBoard, initialPosition);
        if (gameMode == IcsExamining && ics_type == ICS_FICS) {
@@ -14759,19 +15130,19 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                                     AAA + x, ONE + y);
                            SendToICS(buf);
                        }
-                   } else {
+                   } else if(boards[0][y][x] != DarkSquare) {
                        if(boards[0][y][x] != p) nonEmpty++;
                        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]);
@@ -15033,7 +15404,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);
@@ -15045,7 +15417,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);
@@ -15189,6 +15562,7 @@ ForwardInner (int target)
 
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
+    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
 
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
@@ -15209,9 +15583,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) {
@@ -15298,6 +15681,7 @@ BackwardInner (int target)
     if (gameMode == EditPosition) return;
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
+    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
     if (currentMove <= backwardMostMove) {
        ClearHighlights();
        DrawPosition(full_redraw, boards[currentMove]);
@@ -15557,6 +15941,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 ()
 {
@@ -15582,8 +15996,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;
     }
 
@@ -16282,6 +16699,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++ = ',';
@@ -17390,12 +17811,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++ = '~';
                 }
            }
@@ -17440,8 +17861,17 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
   if(nrCastlingRights) {
+     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
+       for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+     }
      q = p;
-     if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
+     if(appData.fischerCastling) {
+       if(handW) { // in shuffle S-Chess simply dump all virgin pieces
+           for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
+               if(boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
+       } else {
        /* [HGM] write directly from rights */
            if(boards[move][CASTLING][2] != NoRights &&
               boards[move][CASTLING][0] != NoRights   )
@@ -17449,12 +17879,18 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
            if(boards[move][CASTLING][2] != NoRights &&
               boards[move][CASTLING][1] != NoRights   )
                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
+       }
+       if(handB) {
+           for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--)
+               if(boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
+       } else {
            if(boards[move][CASTLING][5] != NoRights &&
               boards[move][CASTLING][3] != NoRights   )
                 *p++ = boards[move][CASTLING][3] + AAA;
            if(boards[move][CASTLING][5] != NoRights &&
               boards[move][CASTLING][4] != NoRights   )
                 *p++ = boards[move][CASTLING][4] + AAA;
+       }
      } else {
 
         /* [HGM] write true castling rights */
@@ -17464,9 +17900,8 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
             q = (boards[move][CASTLING][1] == BOARD_LEFT &&
                  boards[move][CASTLING][2] != NoRights  );
-            if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
-               for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
-                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+            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--)
                     if((boards[move][0][i] != WhiteKing || k+q == 0) &&
                         boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
             }
@@ -17476,9 +17911,8 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
             q = (boards[move][CASTLING][4] == BOARD_LEFT &&
                  boards[move][CASTLING][5] != NoRights  );
-            if(gameInfo.variant == VariantSChess) {
-               for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
-                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+            if(handB) {
+                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
                     if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
                         boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
             }
@@ -17549,7 +17983,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;
@@ -17573,7 +18007,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;
@@ -17588,6 +18022,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=='+') {
@@ -17626,7 +18070,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   */
@@ -17639,18 +18085,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 */
@@ -17677,7 +18151,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;
@@ -17691,6 +18165,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 */
@@ -17699,7 +18174,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;
@@ -17721,6 +18196,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++);
@@ -17728,6 +18204,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--);
@@ -17735,6 +18212,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++);
@@ -17742,6 +18220,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 */
@@ -17775,7 +18254,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++)
@@ -17786,6 +18267,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 &&