Fix variant recognition in ICS mode
[xboard.git] / backend.c
index b05e795..a3a81ef 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -295,6 +295,9 @@ 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  */
+char bestMove[MSG_SIZ];
+int solvingTime, totalTime;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -413,6 +416,11 @@ PosFlags (index)
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
+  case VariantChu:
+  case VariantChuChess:
+  case VariantLion:
+    flags |= F_NULL_MOVE;
+    break;
   default:
     break;
   }
@@ -842,7 +850,7 @@ 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;
@@ -949,7 +957,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
@@ -1195,6 +1203,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 */
@@ -1207,7 +1220,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 */
@@ -2098,14 +2110,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++ != '_');
     }
 
@@ -2115,7 +2127,7 @@ StringToVariant (char *e)
     } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (p = StrCaseStr(e, variantNames[i])) {
-       if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
+       if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
        v = (VariantClass) i;
        found = TRUE;
        break;
@@ -2711,8 +2723,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);
@@ -5131,10 +5143,17 @@ 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');
+       char *m = moveList[moveNum];
+       if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
+                                              m[2], m[3] - '0',
+                                              m[5], m[6] - '0',
+                                              m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
+       else
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
+                                              m[5], m[6] - '0',
+                                              m[5], m[6] - '0',
+                                              m[2], m[3] - '0');
          SendToProgram(buf, cps);
       } else
       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
@@ -5211,7 +5230,7 @@ SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char
       case WhitePromotion:
       case BlackPromotion:
         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-           gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+           gameInfo.variant == VariantMakruk)
          snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
                PieceToChar(WhiteFerz));
@@ -5317,10 +5336,11 @@ UploadGameEvent ()
     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
 }
 
-int killX = -1, killY = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
+int killX = -1, killY = -1, kill2X = -1, kill2Y = -1; // [HGM] lion: used for passing e.p. capture square to MakeMove
+int legNr = 1;
 
 void
-CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7])
+CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[9])
 {
     if (rf == DROP_RANK) {
       if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass
@@ -5330,7 +5350,10 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
        if (promoChar == 'x' || promoChar == NULLCHAR) {
          sprintf(move, "%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
-         if(killX >= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+         if(killX >= 0 && killY >= 0) {
+           sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
+         }
        } else {
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
@@ -5381,8 +5404,8 @@ Sweep (int 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;
+       else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
+       else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
        if(!step) step = -1;
     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
            !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
@@ -5509,6 +5532,12 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
       case BlackASideCastleFR:
       /* End of code added by Tord */
       case IllegalMove:                /* bug or odd chess variant */
+       if(currentMoveString[1] == '@') { // illegal drop
+         *fromX = WhiteOnMove(moveNum) ?
+           (int) CharToPiece(ToUpper(currentMoveString[0])) :
+           (int) CharToPiece(ToLower(currentMoveString[0]));
+         goto drop;
+       }
         *fromX = currentMoveString[0] - AAA;
         *fromY = currentMoveString[1] - ONE;
         *toX = currentMoveString[2] - AAA;
@@ -5535,6 +5564,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
        *fromX = *moveType == WhiteDrop ?
          (int) CharToPiece(ToUpper(currentMoveString[0])) :
          (int) CharToPiece(ToLower(currentMoveString[0]));
+      drop:
        *fromY = DROP_RANK;
         *toX = currentMoveString[2] - AAA;
         *toY = currentMoveString[3] - ONE;
@@ -5675,7 +5705,7 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
        }
        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;
 }
 
@@ -5922,23 +5952,40 @@ SetUpShuffle (Board board, int number)
 }
 
 int
-SetCharTable (char *table, const char * map)
+ptclen (const char *s, char *escapes)
+{
+    int n = 0;
+    if(!*escapes) return strlen(s);
+    while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
+    return n;
+}
+
+int
+SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 /* [HGM] moved here from winboard.c because of its general usefulness */
 /*       Basically a safe strcpy that uses the last character as King */
 {
-    int result = FALSE; int NrPieces;
+    int result = FALSE; int NrPieces, offs;
 
-    if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
+    if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
                     && NrPieces >= 12 && !(NrPieces&1)) {
-        int i; /* [HGM] Accept even length from 12 to 34 */
+        int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
 
         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
-        for( i=0; i<NrPieces/2-1; i++ ) {
-            table[i] = map[i];
-            table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
+            char *p;
+            if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
+            table[i + offs] = map[j++];
+            if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
         }
-        table[(int) WhiteKing]  = map[NrPieces/2-1];
-        table[(int) BlackKing]  = map[NrPieces-1];
+        table[(int) WhiteKing]  = map[j++];
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
+            char *p;
+            if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
+            table[WHITE_TO_BLACK i + offs] = map[j++];
+            if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
+        }
+        table[(int) BlackKing]  = map[j++];
 
         result = TRUE;
     }
@@ -5946,6 +5993,12 @@ SetCharTable (char *table, const char * map)
     return result;
 }
 
+int
+SetCharTable (unsigned char *table, const char * map)
+{
+    return SetCharTableEsc(table, map, "");
+}
+
 void
 Prelude (Board board)
 {      // [HGM] superchess: random selection of exo-pieces
@@ -6022,9 +6075,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);
@@ -6096,7 +6150,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;
@@ -6118,8 +6172,8 @@ InitPosition (int redraw)
       gameInfo.boardWidth  = 12;
       gameInfo.boardHeight = 12;
       nrCastlingRights = 0;
-      SetCharTable(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN+.++.++++++++++.+++++K"
-                                "p.brqsexogcathd.vmlifn+.++.++++++++++.+++++k");
+      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
+                                   "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6205,7 +6259,7 @@ InitPosition (int redraw)
 
     /* User pieceToChar list overrules defaults */
     if(appData.pieceToCharTable != NULL)
-        SetCharTable(pieceToChar, appData.pieceToCharTable);
+        SetCharTableEsc(pieceToChar, appData.pieceToCharTable, SUFFIXES);
 
     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
 
@@ -6345,11 +6399,11 @@ SendBoard (ChessProgramState *cps, int moveNum)
          if ((int) *bp < (int) BlackPawn) {
            if(j == BOARD_RGHT+1)
                 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
-           else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
+           else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
-             snprintf(message, MSG_SIZ,"%c%c%c+\n",
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6369,12 +6423,12 @@ SendBoard (ChessProgramState *cps, int moveNum)
              && ((int) *bp >= (int) BlackPawn)) {
            if(j == BOARD_LEFT-2)
                 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
-           else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
-                    AAA + j, ONE + i);
+           else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
+                    AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
-             snprintf(message, MSG_SIZ,"%c%c%c+\n",
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6504,8 +6558,10 @@ DefaultPromoChoice (int white)
 {
     ChessSquare result;
     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN)
+       gameInfo.variant == VariantMakruk)
        result = WhiteFerz; // no choice
+    else if(gameInfo.variant == VariantASEAN)
+       result = WhiteRook; // no choice
     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
        result= WhiteKing; // in Suicide Q is the last thing we want
     else if(gameInfo.variant == VariantSpartan)
@@ -6539,8 +6595,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
         promotionZoneSize = BOARD_HEIGHT/3;
         highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
-    } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) {
-        promotionZoneSize = BOARD_HEIGHT/3;
+    } else if(gameInfo.variant == VariantShogi) {
+        promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
         highestPromotingPiece = (int)WhiteAlfil;
     } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) {
         promotionZoneSize = 3;
@@ -6597,7 +6653,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
 
     // we either have a choice what to promote to, or (in Shogi) whether to promote
     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-       gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) {
+       gameInfo.variant == VariantMakruk) {
        ChessSquare p=BlackFerz;  // no choice
        while(p < EmptySquare) {  //but make sure we use piece that exists
            *promoChoice = PieceToChar(p++);
@@ -6936,10 +6992,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) {
@@ -6960,7 +7023,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 */
@@ -6972,7 +7035,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;
     }
 
@@ -6980,7 +7043,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) {
@@ -7000,6 +7063,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     if(addToBookFlag) { // adding moves to book
        char buf[MSG_SIZ], move[MSG_SIZ];
         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
+       if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
        snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
        AddBookMove(buf);
        addToBookFlag = FALSE;
@@ -7245,11 +7309,11 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
 {
     typedef char Markers[BOARD_RANKS][BOARD_FILES];
     Markers *m = (Markers *) closure;
-    if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || 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;
@@ -7265,7 +7329,7 @@ MarkTargetSquares (int clear)
   } else {
     int capt = 0;
     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
-       !appData.testLegality || gameMode == EditPosition) return;
+       !appData.testLegality && !pieceDefs || gameMode == EditPosition) return;
     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
@@ -7301,11 +7365,11 @@ CanPromote (ChessSquare piece, int y)
        if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
           gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-         gameInfo.variant == VariantMakruk   || gameInfo.variant == VariantASEAN) return FALSE;
+         gameInfo.variant == VariantMakruk) return FALSE;
        return (piece == BlackPawn && y <= zone ||
                piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
-               piece == BlackLance && y == 1 ||
-               piece == WhiteLance && y == BOARD_HEIGHT-2 );
+               piece == BlackLance && y <= zone ||
+               piece == WhiteLance && y >= BOARD_HEIGHT-1-zone );
 }
 
 void
@@ -7341,12 +7405,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)
 {
@@ -7357,13 +7424,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) {
@@ -7373,6 +7433,20 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        x = BOARD_WIDTH - 1 - x;
     }
 
+    if(appData.monoMouse && gameMode == EditPosition && fromX < 0 && 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
@@ -7466,7 +7540,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            return;
        }
     }
-
+printf("to click %d,%d\n",x,y);
     /* fromX != -1 */
     if (clickType == Press && gameMode != EditPosition) {
        ChessSquare fromP;
@@ -7481,6 +7555,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        toP = boards[currentMove][y][x];
        frc = appData.fischerCastling || gameInfo.variant == VariantSChess;
        if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect!
+           marker[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) &&
@@ -7512,7 +7587,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                else gatingPiece = doubleClick ? fromP : EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
-               ReportClick("lift", x, y);
+               if(!second) ReportClick("lift", x, y);
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix, FALSE);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
@@ -7528,8 +7603,14 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
     }
+printf("A type=%d\n",clickType);
+
+    if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
+       gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
+       return;
+    }
 
-    if (clickType == Release && x == fromX && y == fromY && killX < 0) {
+    if (clickType == Release && x == fromX && y == fromY && killX < 0 && !sweepSelecting) {
        DragPieceEnd(xPix, yPix); dragging = 0;
        if(clearFlag) {
            // a deferred attempt to click-click move an empty square on top of a piece
@@ -7543,10 +7624,9 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
        }
-       if (second || sweepSelecting) {
+       if (second) {
            /* Second up/down in same square; just abort move */
-           if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
-           second = sweepSelecting = 0;
+           second = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
            MarkTargetSquares(1);
@@ -7561,14 +7641,15 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     }
 
     clearFlag = 0;
-
-    if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) {
+printf("B\n");
+    if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
+       fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
        if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
        DisplayMessage(_("only marked squares are legal"),"");
        DrawPosition(TRUE, NULL);
        return; // ignore to-click
     }
-
+printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
     if(!sweepSelecting) {
@@ -7633,7 +7714,9 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
          dragging *= 2;            // flag button-less dragging if we are dragging
          MarkTargetSquares(1);
-         if(x == killX && y == killY) killX = killY = -1; else {
+         if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
+         else {
+           kill2X = killX; kill2Y = killY;
            killX = x; killY = y;     //remeber this square as intermediate
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
@@ -7680,7 +7763,9 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
 
-    if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
+    if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
+
+    if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
         MarkTargetSquares(1);
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
@@ -8024,7 +8109,7 @@ Adjudicate (ChessProgramState *cps)
        // most tests only when we understand the game, i.e. legality-checking on
            if( appData.testLegality )
            {   /* [HGM] Some more adjudications for obstinate engines */
-               int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
+               int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+2], i;
                static int moveCount = 6;
                ChessMove result;
                char *reason = NULL;
@@ -8359,10 +8444,10 @@ BitbaseProbe ()
     // probe EGBB
     if(loaded == 2) return 13; // loading failed before
     if(loaded == 0) {
-       loaded = 2; // prepare for failure
        char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ];
        HMODULE lib;
        PLOAD_EGBB loadBB;
+       loaded = 2; // prepare for failure
        if(!path) return 13; // no egbb installed
        strncpy(buf, path + 8, MSG_SIZ);
        if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf);
@@ -8616,17 +8701,23 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         if(cps->alphaRank) AlphaRank(machineMove, 4);
 
        // [HGM] lion: (some very limited) support for Alien protocol
-       killX = killY = -1;
+       killX = killY = kill2X = kill2Y = -1;
        if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
            safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
            return;
-       } else if(firstLeg[0]) { // there was a previous leg;
-           // only support case where same piece makes two step (and don't even test that!)
+       }
+       if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
+           safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
+           safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
+       }
+       if(firstLeg[0]) { // there was a previous leg;
+           // only support case where same piece makes two step
            char buf[20], *p = machineMove+1, *q = buf+1, f;
            safeStrCpy(buf, machineMove, 20);
            while(isdigit(*q)) q++; // find start of to-square
            safeStrCpy(machineMove, firstLeg, 20);
-           while(isdigit(*p)) p++;
+           while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
+           if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
            safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
            sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
            firstLeg[0] = NULLCHAR;
@@ -8638,8 +8729,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
          snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
                    machineMove, _(cps->which));
            DisplayMoveError(buf1);
-            snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d",
-                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType);
+            snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
+                    machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
              GameEnds(machineWhite ? BlackWins : WhiteWins,
                        buf1, GE_XBOARD);
@@ -8704,6 +8795,17 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            free(fen);
            GameEnds(GameUnfinished, NULL, GE_XBOARD);
         }
+        if(appData.epd) {
+           if(solvingTime >= 0) {
+              snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
+              totalTime += solvingTime; first.matchWins++;
+           } else {
+              snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
+              second.matchWins++;
+           }
+           OutputKibitz(2, buf1);
+           GameEnds(GameUnfinished, NULL, GE_XBOARD);
+        }
 
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
         if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
@@ -8715,7 +8817,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 if( count & 1 ) {
                     score = -score; /* Flip score for winning side */
                 }
-printf("score=%d count=%d\n",score,count);
+
                 if( score > appData.adjudicateLossThreshold ) {
                     break;
                 }
@@ -8854,10 +8956,9 @@ printf("score=%d count=%d\n",score,count);
       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);
+        s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTableEsc(pieceToChar, buf, SUFFIXES);
         ASSIGN(appData.pieceToCharTable, buf);
       }
-      if(startedFromSetupPosition) return;
       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
       if(dummy >= 3) {
         while(message[s] && message[s++] != ' ');
@@ -8866,14 +8967,38 @@ printf("score=%d count=%d\n",score,count);
            appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
            if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
-          if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
+          if(*buf) SetCharTableEsc(pieceToChar, buf, SUFFIXES); // 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=message+6, *q, *s = SUFFIXES, ID = *p;
+      if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
+      if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
+      piece += CharToPiece(ID & 255) - 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((ID & 32) == 0 && 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.
      */
@@ -8993,7 +9118,7 @@ printf("score=%d count=%d\n",score,count);
        return;
     }
     if(!strncmp(message, "highlight ", 10)) {
-       if(appData.testLegality && appData.markers) return;
+       if(appData.testLegality && !*engineVariant && appData.markers) return;
        MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
        return;
     }
@@ -9408,6 +9533,10 @@ printf("score=%d count=%d\n",score,count);
            buf1[0] = NULLCHAR;
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
+               char score_buf[MSG_SIZ];
+
+               if(nodes>>32 == u64Const(0xFFFFFFFF))   // [HGM] negative node count read
+                   nodes += u64Const(0x100000000);
 
                if (plyext != ' ' && plyext != '\t') {
                    time *= 100;
@@ -9427,6 +9556,14 @@ printf("score=%d count=%d\n",score,count);
 
                if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
 
+               if(*bestMove) { // rememer time best EPD move was first found
+                   int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
+                   ChessMove mt;
+                   int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
+                   ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+                   solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
+               }
+
                if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
                        char buf[MSG_SIZ];
                        FILE *f;
@@ -9497,11 +9634,17 @@ printf("score=%d count=%d\n",score,count);
                     [AS] Protect the thinkOutput buffer from overflow... this
                     is only useful if buf1 hasn't overflowed first!
                 */
-               snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
+               if(curscore >= MATE_SCORE) 
+                   snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
+               else if(curscore <= -MATE_SCORE) 
+                   snprintf(score_buf, MSG_SIZ, "#%d", curscore + MATE_SCORE);
+               else
+                   snprintf(score_buf, MSG_SIZ, "%+.2f", ((double) curscore) / 100.0);
+               snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%s %s%s",
                         plylev,
                         (gameMode == TwoMachinesPlay ?
                          ToUpper(cps->twoMachinesColor[0]) : ' '),
-                        ((double) curscore) / 100.0,
+                        score_buf,
                         prefixHint ? lastHint : "",
                         prefixHint ? " " : "" );
 
@@ -9843,15 +9986,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;
+  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
   int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1;
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
     /* 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 */
@@ -9864,10 +10008,14 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 //      ChessSquare victim;
       int i;
 
-      if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
+      if( killX >= 0 && killY >= 0 ) { // [HGM] lion: Lion trampled over something
 //           victim = board[killY][killX],
+           killed = board[killY][killX],
            board[killY][killX] = EmptySquare,
            board[EP_STATUS] = EP_CAPTURE;
+           if( kill2X >= 0 && kill2Y >= 0)
+             killed2 = board[kill2Y][kill2X], board[kill2Y][kill2X] = EmptySquare;
+      }
 
       if( board[toY][toX] != EmptySquare ) {
            board[EP_STATUS] = EP_CAPTURE;
@@ -9878,14 +10026,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;
@@ -9894,10 +10046,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;
@@ -9907,6 +10060,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
@@ -9927,10 +10085,17 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
      if(gameInfo.variant == VariantKnightmate)
          king += (int) WhiteUnicorn - (int) WhiteKing;
 
+    if(pieceDesc[piece] && killX >= 0 && strchr(pieceDesc[piece], 'O') // Betza castling-enabled
+       && (piece < BlackPawn ? killed < BlackPawn : killed >= BlackPawn)) {    // and tramples 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)) {
@@ -9940,6 +10105,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)) {
@@ -9949,20 +10115,25 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
       }
     /* End of code added by Tord */
 
+    } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
+       board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
+       board[toY][toX] = piece;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
         && toY == fromY && toX > fromX+1) {
+       for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
+        board[fromY][toX-1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
-        board[fromY][BOARD_RGHT-1] = EmptySquare;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
+       for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
+        board[fromY][toX+1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX+1] = board[fromY][BOARD_LEFT];
-        board[fromY][BOARD_LEFT] = EmptySquare;
     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu)
                && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
@@ -9973,16 +10144,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
@@ -9999,17 +10172,19 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX > fromX+1) {
+       for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
+        board[fromY][toX-1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
-        board[fromY][BOARD_RGHT-1] = EmptySquare;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
+       for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
+        board[fromY][toX+1] = board[fromY][rookX];
+        board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
         board[toY][toX] = king;
-        board[toY][toX+1] = board[fromY][BOARD_LEFT];
-        board[fromY][BOARD_LEFT] = EmptySquare;
     } else if (fromY == 7 && fromX == 3
               && board[fromY][fromX] == BlackKing
               && toY == 7 && toX == 5) {
@@ -10034,16 +10209,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
@@ -10103,8 +10280,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;
           }
@@ -10114,7 +10291,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;
           }
@@ -10442,7 +10619,19 @@ InitChessProgram (ChessProgramState *cps, int setup)
       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
       if (b == NULL) {
-       DisplayFatalError(variantError, 0, 1);
+       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;
       }
 
@@ -11234,16 +11423,17 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
               && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
               && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
               && result != GameIsDrawn)
-           {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
+           {   int i, j, k=0, oppoKings = 0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
                for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
                        int p = (signed char)boards[forwardMostMove][i][j] - color;
                        if(p >= 0 && p <= (int)WhiteKing) k++;
+                       oppoKings += (p + color == WhiteKing + BlackPawn - color);
                }
                if (appData.debugMode) {
                     fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
                        result, resultDetails ? resultDetails : "(null)", whosays, k, color);
                }
-               if(k <= 1) {
+               if(k <= 1 && oppoKings > 0) { // the latter needed in Atomic, where bare K wins if opponent King already destroyed
                        result = GameIsDrawn;
                        snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
                        resultDetails = buf;
@@ -11592,6 +11782,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;
@@ -11740,11 +11932,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) {
@@ -11819,7 +12020,7 @@ LoadGameOneMove (ChessMove readAhead)
       case BlackASideCastleFR:
       /* POP Fabien */
        if (appData.debugMode)
-         fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
+         fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
         fromX = currentMoveString[0] - AAA;
         fromY = currentMoveString[1] - ONE;
         toX = currentMoveString[2] - AAA;
@@ -11946,8 +12147,13 @@ LoadGameOneMove (ChessMove readAhead)
            if (appData.debugMode)
              fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
                      yy_text, currentMoveString);
-            fromX = currentMoveString[0] - AAA;
-            fromY = currentMoveString[1] - ONE;
+            if(currentMoveString[1] == '@') {
+                fromX = CharToPiece(WhiteOnMove(currentMove) ? ToUpper(currentMoveString[0]) : ToLower(currentMoveString[0]));
+                fromY = DROP_RANK;
+            } else {
+                fromX = currentMoveString[0] - AAA;
+                fromY = currentMoveString[1] - ONE;
+            }
             toX = currentMoveString[2] - AAA;
             toY = currentMoveString[3] - ONE;
            promoChar = currentMoveString[4];
@@ -12352,12 +12558,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];
@@ -12382,23 +12602,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);
 }
@@ -12561,7 +12770,10 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     int numPGNTags = 0;
     int err, pos = -1;
     GameMode oldGameMode;
-    VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
+    VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
+    char oldName[MSG_SIZ];
+
+    safeStrCpy(oldName, engineVariant, MSG_SIZ); v = oldVariant;
 
     if (appData.debugMode)
        fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
@@ -12776,7 +12988,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);
@@ -12795,6 +13007,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);
@@ -12917,6 +13131,10 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        StartChessProgram(&first);
     }
     InitChessProgram(&first, FALSE);
+    if(gameInfo.variant == VariantUnknown && *oldName) {
+       safeStrCpy(engineVariant, oldName, MSG_SIZ);
+       gameInfo.variant = v;
+    }
     SendToProgram("force\n", &first);
     if (startedFromSetupPosition) {
        SendBoard(&first, forwardMostMove);
@@ -13109,8 +13327,8 @@ LoadPosition (FILE *f, int positionNumber, char *title)
        DisplayError(_("Position not found in file"), 0);
        return FALSE;
     }
-    // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
-    fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
+    // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces (or * for blackout)
+    fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || line[0] == '*' || CharToPiece(line[0]) != EmptySquare;
 
     if (pn >= 2) {
        if (fenMode || line[0] == '#') pn--;
@@ -13126,10 +13344,14 @@ LoadPosition (FILE *f, int positionNumber, char *title)
     }
 
     if (fenMode) {
+       char *p;
        if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) {
            DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
        }
+       if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
+           sscanf(p+3, "%s", bestMove);
+       } else *bestMove = NULLCHAR;
     } else {
        (void) fgets(line, MSG_SIZ, f);
        (void) fgets(line, MSG_SIZ, f);
@@ -13351,9 +13573,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;
@@ -13513,7 +13735,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;
@@ -14581,8 +14809,10 @@ TwoMachinesEvent P((void))
 
     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
                          gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) {
-       startingEngine = FALSE;
+       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;
     }
 
@@ -14989,6 +15219,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) {
@@ -15008,7 +15240,7 @@ 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;
                    }
@@ -15282,7 +15514,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);
@@ -15294,7 +15527,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);
@@ -15438,6 +15672,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();
@@ -15458,9 +15693,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) {
@@ -15547,6 +15791,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]);
@@ -15806,6 +16051,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 ()
 {
@@ -15831,8 +16106,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;
     }
 
@@ -16531,6 +16809,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++ = ',';
@@ -17612,7 +17894,7 @@ char *
 PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
-    int whiteToPlay;
+    int whiteToPlay, haveRights = nrCastlingRights;
     char buf[MSG_SIZ];
     char *p, *q;
     int emptycount;
@@ -17642,6 +17924,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                     piece = (ChessSquare)(CHUDEMOTED piece);
                 }
                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
+                if(*p = PieceSuffix(piece)) p++;
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
                     p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
@@ -17685,12 +17968,39 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     *p++ = whiteToPlay ? 'w' : 'b';
     *p++ = ' ';
 
+  if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+    haveRights = 0; q = p;
+    for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+      piece = boards[move][0][i];
+      if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+        if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
+      }
+    }
+    for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+      piece = boards[move][BOARD_HEIGHT-1][i];
+      if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+        if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
+      }
+    }
+    if(p == q) *p++ = '-';
+    *p++ = ' ';
+  }
+
   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
-  if(nrCastlingRights) {
+  if(haveRights) {
+     int handW=0, handB=0;
+     if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
+       for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
+       for(i=0; i<BOARD_HEIGHT; i++) handB += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+     }
      q = p;
      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   )
@@ -17698,36 +18008,40 @@ 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 */
         if( nrCastlingRights == 6 ) {
             int q, k=0;
-            if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
+            if(boards[move][CASTLING][0] != NoRights &&
                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
-            q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+            q = (boards[move][CASTLING][1] != NoRights &&
                  boards[move][CASTLING][2] != NoRights  );
-            if(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';
             }
            if(q) *p++ = 'Q';
             k = 0;
-            if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
+            if(boards[move][CASTLING][3] != NoRights &&
                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
-            q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+            q = (boards[move][CASTLING][4] != NoRights &&
                  boards[move][CASTLING][5] != NoRights  );
-            if(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;
             }
@@ -17798,10 +18112,10 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
 Boolean
 ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 {
-    int i, j, k, w=0, subst=0, shuffle=0;
+    int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
     char *p, c;
     int emptycount, virgin[BOARD_FILES];
-    ChessSquare piece;
+    ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
 
     p = fen;
 
@@ -17848,13 +18162,20 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                 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)) {
+               char *q, *s = SUFFIXES;
                 if (j >= gameInfo.boardWidth) return FALSE;
                 if(*p=='+') {
-                    piece = CharToPiece(*++p);
+                    char c = *++p;
+                    if(q = strchr(s, p[1])) p++;
+                    piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
                     if(piece == EmptySquare) return FALSE; /* unknown piece */
                     piece = (ChessSquare) (CHUPROMOTED piece ); p++;
                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
-                } else piece = CharToPiece(*p++);
+                } else {
+                    char c = *p++;
+                   if(q = strchr(s, *p)) p++;
+                   piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
+               }
 
                 if(piece==EmptySquare) return FALSE; /* unknown piece */
                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
@@ -17863,6 +18184,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                     p++;
                 }
                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
+                if(piece == king) wKingRank = i;
+                if(piece == WHITE_TO_BLACK king) bKingRank = i;
            } else {
                return FALSE;
            }
@@ -17962,8 +18285,16 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
     /* return the extra info in global variiables             */
 
+    while(*p==' ') p++;
+
+    if(!isdigit(*p) && *p != '-') { // we seem to have castling rights. Make sure they are on the rank the King actually is.
+        if(wKingRank >= 0) for(i=0; i<3; i++) castlingRank[i] = wKingRank;
+        if(bKingRank >= 0) for(i=3; i<6; i++) castlingRank[i] = bKingRank;
+    }
+
     /* set defaults in case FEN is incomplete */
     board[EP_STATUS] = EP_UNKNOWN;
+    board[TOUCHED_W] = board[TOUCHED_B] = 0;
     for(i=0; i<nrCastlingRights; i++ ) {
         board[CASTLING][i] =
             appData.fischerCastling ? NoRights : initialRights[i];
@@ -17978,7 +18309,21 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                                  && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
     FENrulePlies = 0;
 
-    while(*p==' ') p++;
+    if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+      char *q = p;
+      int w=0, b=0;
+      while(isalpha(*p)) {
+        if(isupper(*p)) w |= 1 << (*p++ - 'A');
+        if(islower(*p)) b |= 1 << (*p++ - 'a');
+      }
+      if(*p == '-') p++;
+      if(p != q) {
+        board[TOUCHED_W] = ~w;
+        board[TOUCHED_B] = ~b;
+        while(*p == ' ') p++;
+      }
+    } else
+
     if(nrCastlingRights) {
       int fischer = 0;
       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
@@ -17995,18 +18340,18 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
 
         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
-            if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
-            if(board[0             ][i] == WhiteKing) whiteKingFile = i;
+            if(board[castlingRank[5]][i] == BlackKing) blackKingFile = i;
+            if(board[castlingRank[2]][i] == WhiteKing) whiteKingFile = i;
         }
         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
-        if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
-                                     && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
-        if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
-                                     && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
+        if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
+                                     && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
+        if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
+                                     && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
         switch(c) {
           case'K':
-              for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
+              for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);
               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
              if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
@@ -18014,7 +18359,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               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++);
+              for(i=BOARD_LEFT;  i<BOARD_RGHT && board[castlingRank[2]][i]!=WhiteRook && i<whiteKingFile; i++);
               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
              if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
@@ -18022,7 +18367,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               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--);
+              for(i=BOARD_RGHT-1; board[castlingRank[5]][i]!=BlackRook && i>blackKingFile; i--);
               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
              if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
@@ -18030,7 +18375,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
               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++);
+              for(i=BOARD_LEFT; i<BOARD_RGHT && board[castlingRank[5]][i]!=BlackRook && i<blackKingFile; i++);
               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
              if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;