Fix deferral on sweep promotions
[xboard.git] / backend.c
index 36131f6..0c12671 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,8 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
+ * Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -260,6 +261,7 @@ void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 VariantClass startVariant; /* [HGM] nicks: initial variant */
 Boolean abortMatch;
+int deadRanks;
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -295,7 +297,7 @@ int promoDefaultAltered;
 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
 static int initPing = -1;
 int border;       /* [HGM] width of board rim, needed to size seek graph  */
-char bestMove[MSG_SIZ];
+char bestMove[MSG_SIZ], avoidMove[MSG_SIZ];
 int solvingTime, totalTime;
 
 /* States for ics_getting_history */
@@ -386,7 +388,7 @@ u64ToDouble (u64 value)
    by this function.
  */
 int
-PosFlags (index)
+PosFlags (int index)
 {
   int flags = F_ALL_CASTLE_OK;
   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
@@ -563,8 +565,8 @@ ChessSquare  KnightmateArray[2][BOARD_FILES] = {
 ChessSquare SpartanArray[2][BOARD_FILES] = {
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
-        BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+    { BlackAlfil, BlackDragon, BlackKing, BlackTower,
+        BlackTower, BlackKing, BlackAngel, BlackAlfil }
 };
 
 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
@@ -690,18 +692,18 @@ ChessSquare CourierArray[2][BOARD_FILES] = {
         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
 };
 ChessSquare ChuArray[6][BOARD_FILES] = {
-    { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
-      WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
-    { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
-      BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
-    { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
-      WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
-    { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
-      BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
-    { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
-      WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
-    { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
-      BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
+    { WhiteLance, WhiteCat, WhiteCopper, WhiteFerz, WhiteWazir, WhiteKing,
+      WhiteAlfil, WhiteWazir, WhiteFerz, WhiteCopper, WhiteCat, WhiteLance },
+    { BlackLance, BlackCat, BlackCopper, BlackFerz, BlackWazir, BlackAlfil,
+      BlackKing, BlackWazir, BlackFerz, BlackCopper, BlackCat, BlackLance },
+    { WhiteAxe, EmptySquare, WhiteBishop, EmptySquare, WhiteClaw, WhiteMarshall,
+      WhiteAngel, WhiteClaw, EmptySquare, WhiteBishop, EmptySquare, WhiteAxe },
+    { BlackAxe, EmptySquare, BlackBishop, EmptySquare, BlackClaw, BlackAngel,
+      BlackMarshall, BlackClaw, EmptySquare, BlackBishop, EmptySquare, BlackAxe },
+    { WhiteDagger, WhiteSword, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
+      WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSword, WhiteDagger },
+    { BlackDagger, BlackSword, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
+      BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSword, BlackDagger }
 };
 #else // !(BOARD_FILES>=12)
 #define CourierArray CapablancaArray
@@ -995,6 +997,7 @@ Load (ChessProgramState *cps, int i)
        SwapEngines(i);
        ReplaceEngine(cps, i);
        FloatToFront(&appData.recentEngineList, engineLine);
+       if(gameMode == BeginningOfGame) Reset(TRUE, TRUE);
        return;
     }
     p = engineName;
@@ -1584,7 +1587,7 @@ MatchEvent (int mode)
        }
        matchMode = mode;
        matchGame = roundNr = 1;
-       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
+       first.matchWins = second.matchWins = totalTime = 0; // [HGM] match: needed in later matches
        NextMatchGame();
 }
 
@@ -1742,6 +1745,11 @@ InitBackEnd3 P((void))
                 CopyBoard(filePosition, boards[0]);
                 CopyBoard(initialPosition, boards[0]);
             }
+       } else if(*appData.fen != NULLCHAR) {
+           if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
+                startedFromPositionFile = TRUE;
+               Reset(TRUE, TRUE);
+           }
        }
        if (initialMode == AnalyzeMode) {
          if (appData.noChessProgram) {
@@ -1981,7 +1989,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
        DisplayFatalError(_("Error reading from keyboard"), error, 1);
     } else if (gotEof++ > 0) {
        RemoveInputSource(isr);
-       DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
+       DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
     }
 }
 
@@ -3388,7 +3396,8 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
 #if ZIPPY
                if (loggedOn == TRUE)
                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
-                          (appData.zippyPlay && ZippyMatch(buf, &backup)));
+                          (appData.zippyPlay && ZippyMatch(buf, &backup)))
+                       ;
 #endif
            } // [DM] 'else { ' deleted
                if (
@@ -4162,6 +4171,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
                      } else if (gotPremove) {
+                       int oldFMM = forwardMostMove;
                        gotPremove = 0;
                        ClearPremoveHighlights();
                        if (appData.debugMode)
@@ -4169,6 +4179,13 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                           UserMoveEvent(premoveFromX, premoveFromY,
                                        premoveToX, premoveToY,
                                         premovePromoChar);
+                       if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
+                         if(moveList[oldFMM-1][1] != '@')
+                           SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
+                                         moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+                         else // (drop)
+                           SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+                       }
                      }
                    }
 
@@ -5146,13 +5163,21 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
        char *m = moveList[moveNum];
        static char c[2];
-       *c = m[7]; // promoChar
+       *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
        if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
          snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
                                               m[2], m[3] - '0',
                                               m[5], m[6] - '0',
                                               m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
-       else
+       else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
+         *c = m[9]; if(*c == '\n') *c = NULLCHAR;
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
+                                              m[7], m[8] - '0',
+                                              m[7], m[8] - '0',
+                                              m[5], m[6] - '0',
+                                              m[5], m[6] - '0',
+                                              m[2], m[3] - '0', c);
+       } else
          snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
                                               m[5], m[6] - '0',
                                               m[5], m[6] - '0',
@@ -5355,14 +5380,14 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
          if(killX >= 0 && killY >= 0) {
            sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
-           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + killX, ONE + killY);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
          }
        } else {
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
          if(killX >= 0 && killY >= 0) {
            sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
-           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
          }
        }
     }
@@ -5562,7 +5587,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
         *toX = currentMoveString[2] - AAA;
         *toY = currentMoveString[3] - ONE;
        *promoChar = currentMoveString[4];
-       if(*promoChar == ';') *promoChar = currentMoveString[7];
+       if(*promoChar == ';') *promoChar = currentMoveString[7 + 2*(currentMoveString[8] != 0)];
         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
     if (appData.debugMode) {
@@ -6256,8 +6281,10 @@ InitPosition (int redraw)
       gameInfo.boardWidth  = 12;
       gameInfo.boardHeight = 12;
       nrCastlingRights = 0;
-      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
-                                   "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
+//      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
+  //                                 "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
+      SetCharTableEsc(pieceToChar, "P.BRQSEXOG...HD..^DLI^HNV........^T..^L.C...A^AFT/^F^G^M.^E^X^O^I.^P.^B^R..M^S^C^VK"
+                                   "p.brqsexog...hd..^dli^hnv........^t..^l.c...a^aft/^f^g^m.^e^x^o^i.^p.^b^r..m^s^c^vk", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6271,7 +6298,7 @@ InitPosition (int redraw)
       break;
     case VariantSpartan:
       pieces = SpartanArray;
-      SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
+      SetCharTable(pieceToChar, "PNBRQ.....................K......lw......g...h......ck");
       break;
     case VariantLion:
       pieces = lionArray;
@@ -6437,6 +6464,7 @@ InitPosition (int redraw)
           initialRights[i] = filePosition[CASTLING][i];
       startedFromSetupPosition = TRUE;
     }
+    if(*appData.men) LoadPieceDesc(appData.men);
 
     CopyBoard(boards[0], initialPosition);
 
@@ -6841,6 +6869,7 @@ OKToStartUserMove (int x, int y)
       case PlayFromGameFile:
            if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
       case EditGame:
+      case AnalyzeMode:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
@@ -6957,9 +6986,10 @@ char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = EndOfFile;
 int doubleClick;
 Boolean addToBookFlag;
+static Board rightsBoard, nullBoard;
 
 void
-UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
+UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
 {
     ChessMove moveType;
     ChessSquare pup;
@@ -7043,6 +7073,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
+            DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
             return;
        }
        break;
@@ -7064,6 +7095,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
+            DrawPosition(TRUE, boards[currentMove]);
             return;
        }
        break;
@@ -7082,8 +7114,13 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
            if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
                ChessSquare p = boards[0][rf][ff];
                if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
-               if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
-           }
+               if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); else
+               if(p == WhiteKing || p == BlackKing || p == WhiteRook || p == BlackRook || p == WhitePawn || p == BlackPawn) {
+                   int n = rightsBoard[toY][toX] ^= 1; // toggle virginity of K or R
+                   DisplayMessage("", n ? _("rights granted") : _("rights revoked"));
+                   gatingPiece = p;
+               }
+           } else  rightsBoard[toY][toX] = 0;  // revoke rights on moving
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
                if(boards[0][fromY][0] != EmptySquare) {
@@ -7141,6 +7178,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
             ClearPremoveHighlights(); // was included
        else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
+       DrawPosition(FALSE, NULL);
        return;
     }
 
@@ -7360,7 +7398,7 @@ MarkByFEN(char *fen)
        int r, f;
        if(!appData.markers || !appData.highlightDragging) return;
        for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
-       r=BOARD_HEIGHT-1; f=BOARD_LEFT;
+       r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
        while(*fen) {
            int s = 0;
            marker[r][f] = 0;
@@ -7394,10 +7432,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) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
+    if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
+                                     kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
        (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
                         || kind == WhiteCapturesEnPassant
-                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
+                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
 }
 
@@ -7410,7 +7449,7 @@ MarkTargetSquares (int clear)
   if(clear) { // no reason to ever suppress clearing
     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
     hoverSavedValid = 0;
-    if(!sum) return; // nothing was cleared,no redraw needed
+    if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
   } else {
     int capt = 0;
     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
@@ -7504,11 +7543,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
     static TimeMark lastClickTime, prevClickTime;
 
+    if(flashing) return;
+
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
     if (!flipView && y >= 0) {
@@ -7599,7 +7640,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
        doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
       }
-      fromX = x; fromY = y; toX = toY = killX = killY = -1;
+      fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
         appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
@@ -7651,13 +7692,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
             !(fromP == BlackKing && toP == BlackRook && frc)))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
-           killX = killY = -1;
+           killX = killY = kill2X = kill2Y = -1;
            if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
                second = FALSE; // first double-click rather than scond click
                doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
            }
            promoDefaultAltered = FALSE;
-           MarkTargetSquares(1);
+          if(!second) MarkTargetSquares(1);
           if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
@@ -7714,10 +7755,11 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            second = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
-           MarkTargetSquares(1);
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
+           MarkTargetSquares(-1);
+           DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
        } else {
            /* First upclick in same square; start click-click mode */
            SetHighlights(x, y, -1, -1);
@@ -7757,7 +7799,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            return;
        }
        if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
-           killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
+           killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
        } else
        if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
        if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
@@ -7766,6 +7808,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED(piece)) == '+') promoSweep = CHUPROMOTED(piece);
            selectFlag = 0; lastX = xPix; lastY = yPix;
            ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
+           saveFlash = appData.flashCount; appData.flashCount = 0;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
            sweepSelecting = 1;
            DisplayMessage("", _("Pull pawn backwards to under-promote"));
@@ -7779,14 +7822,16 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        } else {
            ClearHighlights();
        }
+       MarkTargetSquares(1);
     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
        sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
-        *promoRestrict = 0;
+        *promoRestrict = 0; appData.flashCount = saveFlash;
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
            ClearHighlights();
        }
+       MarkTargetSquares(1);
     } else {
 #if 0
 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
@@ -7806,7 +7851,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
          if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
          else {
            kill2X = killX; kill2Y = killY;
-           killX = x; killY = y;     //remeber this square as intermediate
+           killX = x; killY = y;     // remember this square as intermediate
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
            MarkTargetSquares(0);
@@ -7816,6 +7861,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
+        MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
     }
 
     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
@@ -7852,7 +7898,10 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
 
-    if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
+    if(legal[toY][toX] == 2) { // highlight-induced promotion
+       if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
+       else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+    }
 
     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -7870,22 +7919,24 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            DisplayMessage("Click in holdings to choose piece", "");
            return;
        }
+       DrawPosition(FALSE, NULL); // shows piece on from-square during promo popup
        PromotionPopUp(promoChoice);
     } else {
        int oldMove = currentMove;
+       flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
        if (!appData.highlightLastMove || gotPremove) ClearHighlights();
-       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
+       if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY), DrawPosition(FALSE, NULL);
        if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
           Explode(boards[currentMove-1], fromX, fromY, toX, toY))
            DrawPosition(TRUE, boards[currentMove]);
-        MarkTargetSquares(1);
        fromX = fromY = -1;
+       flashing = 0;
     }
     appData.animate = saveAnimate;
     if (appData.animate || appData.animateDragging) {
        /* Undo animation damage if needed */
-       DrawPosition(FALSE, NULL);
+//     DrawPosition(FALSE, NULL);
     }
 }
 
@@ -8021,6 +8072,9 @@ SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats
 
     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
 
+    if( gameMode == AnalyzeMode && stats.pv && stats.pv[0]
+        && appData.analysisBell && stats.time >= 100*appData.analysisBell ) RingBell();
+
     SetProgramStats( &stats );
 }
 
@@ -8662,7 +8716,7 @@ static char stashedInputMove[MSG_SIZ], abortEngineThink;
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
 {
-    static char firstLeg[20];
+    static char firstLeg[20], legs;
     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
     char realname[MSG_SIZ];
     int fromX, fromY, toX, toY;
@@ -8815,11 +8869,14 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        // [HGM] lion: (some very limited) support for Alien protocol
        killX = killY = kill2X = kill2Y = -1;
        if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
+           if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
            safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
            return;
        }
        if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
+           char *q = strchr(p+1, ',');            // second comma?
            safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
+           if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
            safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
        }
        if(firstLeg[0]) { // there was a previous leg;
@@ -8829,10 +8886,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            while(isdigit(*q)) q++; // find start of to-square
            safeStrCpy(machineMove, firstLeg, 20);
            while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
-           if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
+           if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
+           else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
            safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
            sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
-           firstLeg[0] = NULLCHAR;
+           firstLeg[0] = NULLCHAR; legs = 0;
        }
 
         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
@@ -8909,11 +8967,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         }
         if(appData.epd) {
            if(solvingTime >= 0) {
-              snprintf(buf1, MSG_SIZ, "%d. %4.2fs\n", matchGame, solvingTime/100.);
-              totalTime += solvingTime; first.matchWins++;
+              snprintf(buf1, MSG_SIZ, "%d. %4.2fs: %s ", matchGame, solvingTime/100., parseList[backwardMostMove]);
+              totalTime += solvingTime; first.matchWins++; solvingTime = -1;
            } else {
-              snprintf(buf1, MSG_SIZ, "%d. wrong (%s)\n", matchGame, parseList[backwardMostMove]);
-              second.matchWins++;
+              snprintf(buf1, MSG_SIZ, "%d. %s?%s ", matchGame, parseList[backwardMostMove], solvingTime == -2 ? " ???" : "");
+              if(solvingTime == -2) second.matchWins++;
            }
            OutputKibitz(2, buf1);
            GameEnds(GameUnfinished, NULL, GE_XBOARD);
@@ -9076,6 +9134,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         while(message[s] && message[s++] != ' ');
         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
+           if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
            appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
            if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
@@ -9092,10 +9151,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
     }
     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
       ChessSquare piece = WhitePawn;
-      char *p=message+6, *q, *s = SUFFIXES, ID = *p;
-      if(*p == '+') piece = CHUPROMOTED(WhitePawn), ID = *++p;
+      char *p=message+6, *q, *s = SUFFIXES, ID = *p, promoted = 0;
+      if(*p == '+') promoted++, ID = *++p;
       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
-      piece += CharToPiece(ID & 255) - WhitePawn;
+      piece = CharToPiece(ID & 255); if(promoted) piece = CHUPROMOTED(piece);
       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
       /* always accept definition of  */       && piece != WhiteFalcon && piece != BlackFalcon
       /* wild-card pieces.            */       && piece != WhiteCobra  && piece != BlackCobra
@@ -9227,7 +9286,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if(initPing == cps->lastPong) {
            if(gameInfo.variant == VariantUnknown) {
                DisplayError(_("Engine did not send setup for non-standard variant"), 0);
-               *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery?
+               *engineVariant = NULLCHAR; ASSIGN(appData.variant, "normal"); // back to normal as error recovery?
                GameEnds(GameUnfinished, NULL, GE_XBOARD);
            }
            initPing = -1;
@@ -9652,6 +9711,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
        if (!ignore) {
            ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
+           int solved = 0;
            buf1[0] = NULLCHAR;
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
@@ -9680,10 +9740,33 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
                if(*bestMove) { // rememer time best EPD move was first found
                    int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
-                   ChessMove mt;
-                   int ok = ParseOneMove(bestMove, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1);
-                   ok    &= ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
-                   solvingTime = (ok && ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2 ? time : -1);
+                   ChessMove mt; char *p = bestMove;
+                   int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+                   solved = 0;
+                   while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
+                       if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
+                           solvingTime = (solvingTime < 0 ? time : solvingTime);
+                           solved = 1;
+                           break;
+                       }
+                       while(*p && *p != ' ') p++;
+                       while(*p == ' ') p++;
+                   }
+                   if(!solved) solvingTime = -1;
+               }
+               if(*avoidMove && !solved) {
+                   int ff1, tf1, fr1, tr1, ff2, tf2, fr2, tr2; char pp1, pp2;
+                   ChessMove mt; char *p = avoidMove, solved = 1;
+                   int ok = ParseOneMove(pv, forwardMostMove, &mt, &ff2, &fr2, &tf2, &tr2, &pp2);
+                   while(ok && *p && ParseOneMove(p, forwardMostMove, &mt, &ff1, &fr1, &tf1, &tr1, &pp1)) {
+                       if(ff1==ff2 && fr1==fr2 && tf1==tf2 && tr1==tr2 && pp1==pp2) {
+                           solved = 0; solvingTime = -2;
+                           break;
+                       }
+                       while(*p && *p != ' ') p++;
+                       while(*p == ' ') p++;
+                   }
+                   if(solved && !*bestMove) solvingTime = (solvingTime < 0 ? time : solvingTime);
                }
 
                if(serverMoves && (time > 100 || time == 0 && plylev > 7)) {
@@ -9756,6 +9839,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                     [AS] Protect the thinkOutput buffer from overflow... this
                     is only useful if buf1 hasn't overflowed first!
                 */
+               if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
                if(curscore >= MATE_SCORE) 
                    snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
                else if(curscore <= -MATE_SCORE) 
@@ -10200,7 +10284,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
        }
 
-     if (fromX == toX && fromY == toY) return;
+     if (fromX == toX && fromY == toY && killX < 0) return;
 
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
@@ -10248,7 +10332,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
         && toY == fromY && toX > fromX+1) {
-       for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++); // castle with nearest piece
+       for(rookX=fromX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT-1; rookX++)
+                                                                                            ; // castle with nearest piece
         board[fromY][toX-1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10256,7 +10341,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
-       for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--); // castle with nearest piece
+       for(rookX=fromX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
+                                                                                 ; // castle with nearest piece
         board[fromY][toX+1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10299,7 +10385,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX > fromX+1) {
-       for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++);
+       for(rookX=toX+1; board[toY][rookX] == EmptySquare && rookX < BOARD_RGHT - 1; rookX++)
+                                                                                            ;
         board[fromY][toX-1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10307,7 +10394,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
                && toY == fromY && toX < fromX-1) {
-       for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--);
+       for(rookX=toX-1; board[toY][rookX] == EmptySquare && rookX > 0; rookX--)
+                                                                               ;
         board[fromY][toX+1] = board[fromY][rookX];
         board[fromY][rookX] = EmptySquare;
        board[fromY][fromX] = EmptySquare;
@@ -10480,13 +10568,17 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
     ChessSquare p = boards[forwardMostMove][toY][toX];
 //    forwardMostMove++; // [HGM] bare: moved downstream
 
+    if(kill2X >= 0) x = kill2X, y = kill2Y; else
     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
     (void) CoordsToAlgebraic(boards[forwardMostMove],
                             PosFlags(forwardMostMove),
                             fromY, fromX, y, x, (killX < 0)*promoChar,
                             s);
+    if(kill2X >= 0 && kill2Y >= 0)
+        sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
     if(killX >= 0 && killY >= 0)
-        sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
+        sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY || toX== kill2X && toY == kill2Y ? '-' : 'x',
+                                           toX + AAA, toY + ONE - '0', promoChar);
 
     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
         int timeLeft; static int lastLoadFlag=0; int king, piece;
@@ -10600,7 +10692,7 @@ ShowMove (int fromX, int fromY, int toX, int toY)
        currentMove = forwardMostMove;
     }
 
-    killX = killY = -1; // [HGM] lion: used up
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
 
     if (instant) return;
 
@@ -10745,6 +10837,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
 
       b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth,
                            gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy);
+
       if (b == NULL) {
        VariantClass v;
        char c, *q = cps->variants, *p = strchr(q, ',');
@@ -10997,8 +11090,10 @@ Substitute (char *participants, int expunge)
        p++; q++;
     }
     if(*p) { // difference
-       while(*p && *p++ != '\n');
-       while(*q && *q++ != '\n');
+       while(*p && *p++ != '\n')
+                                ;
+       while(*q && *q++ != '\n')
+                                ;
       changed = nPlayers;
        changes = 1 + (strcmp(p, q) != 0);
     }
@@ -11380,6 +11475,11 @@ NextMatchGame ()
     res = LoadGameOrPosition(matchGame); // setup game
     appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too!
     if(!res) return; // abort when bad game/pos file
+    if(appData.epd) {// in EPD mode we make sure first engine is to move
+       firstWhite = !(forwardMostMove & 1);
+       first.twoMachinesColor =  firstWhite ? "white\n" : "black\n";   // perform actual color assignement
+       second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
+    }
     TwoMachinesEvent();
 }
 
@@ -11443,7 +11543,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
 
     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
 
@@ -11787,6 +11887,16 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            return;
        } else {
            gameMode = nextGameMode;
+           if(appData.epd) {
+               snprintf(buf, MSG_SIZ, "-------------------------------------- ");
+               OutputKibitz(2, buf);
+               snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec (total time %4.2f sec) "), totalTime/(100.*first.matchWins), totalTime/100.);
+               OutputKibitz(2, buf);
+               snprintf(buf, MSG_SIZ, _("%d avoid-moves played "), second.matchWins);
+               if(second.matchWins) OutputKibitz(2, buf);
+               snprintf(buf, MSG_SIZ, _("Solved %d out of %d (%3.1f%%) "), first.matchWins, nextGame-1, first.matchWins*100./(nextGame-1));
+               OutputKibitz(2, buf);
+           }
            snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
                     first.tidy, second.tidy,
                     first.matchWins, second.matchWins,
@@ -11910,6 +12020,7 @@ Reset (int redraw, int init)
                redraw, init, gameMode);
     }
     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
+    deadRanks = 0; // assume entire board is used
     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
     CleanupTail(); // [HGM] vari: delete any stored variations
     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
@@ -11927,7 +12038,10 @@ Reset (int redraw, int init)
     lastHint[0] = NULLCHAR;
     ClearGameInfo(&gameInfo);
     gameInfo.variant = StringToVariant(appData.variant);
-    if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
+    if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) {
+       gameInfo.variant = VariantUnknown;
+       strncpy(engineVariant, appData.variant, MSG_SIZ);
+    }
     ics_user_moved = ics_clock_paused = FALSE;
     ics_getting_history = H_FALSE;
     ics_gamenum = -1;
@@ -11941,7 +12055,7 @@ Reset (int redraw, int init)
     ClearPremoveHighlights();
     gotPremove = FALSE;
     alarmSounded = FALSE;
-    killX = killY = -1; // [HGM] lion
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion
 
     GameEnds(EndOfFile, NULL, GE_PLAYER);
     if(appData.serverMovesName != NULL) {
@@ -12059,21 +12173,17 @@ AutoPlayOneMove ()
            SetHighlights(-1, -1, toX, toY);
        }
     } else {
-        int viaX = moveList[currentMove][5] - AAA;
-        int viaY = moveList[currentMove][6] - ONE;
         fromX = moveList[currentMove][0] - AAA;
         fromY = moveList[currentMove][1] - ONE;
 
         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
 
         if(moveList[currentMove][4] == ';') { // multi-leg
-            ChessSquare piece = boards[currentMove][viaY][viaX];
-           AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
-            boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
-            AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
-            boards[currentMove][viaY][viaX] = piece;
-        } else
+            killX = moveList[currentMove][5] - AAA;
+            killY = moveList[currentMove][6] - ONE;
+        }
        AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+       killX = killY = -1;
 
        if (appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
@@ -12325,7 +12435,7 @@ LoadGameOneMove (ChessMove readAhead)
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
-       killX = killY = -1; // [HGM] lion: used up
+       killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
        currentMove = forwardMostMove;
        return TRUE;
     }
@@ -12894,7 +13004,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     char buf[MSG_SIZ];
     int gn = gameNumber;
     ListGame *lg = NULL;
-    int numPGNTags = 0;
+    int numPGNTags = 0, i;
     int err, pos = -1;
     GameMode oldGameMode;
     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
@@ -12912,7 +13022,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (gameMode != BeginningOfGame) {
       Reset(FALSE, TRUE);
     }
-    killX = killY = -1; // [HGM] lion: in case we did not Reset
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
 
     gameFileFP = f;
     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
@@ -13083,6 +13193,8 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
+    for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
+
     if (cm == XBoardGame) {
        /* Skip any header junk before position diagram and/or move 1 */
        for (;;) {
@@ -13476,9 +13588,12 @@ LoadPosition (FILE *f, int positionNumber, char *title)
            DisplayError(_("Bad FEN position in file"), 0);
            return FALSE;
        }
-       if((p = strstr(line, ";")) && (p = strstr(p+1, "bm "))) { // EPD with best move
-           sscanf(p+3, "%s", bestMove);
+       if((strchr(line, ';')) && (p = strstr(line, " bm "))) { // EPD with best move
+           sscanf(p+4, "%[^;]", bestMove);
        } else *bestMove = NULLCHAR;
+       if((strchr(line, ';')) && (p = strstr(line, " am "))) { // EPD with avoid move
+           sscanf(p+4, "%[^;]", avoidMove);
+       } else *avoidMove = NULLCHAR;
     } else {
        (void) fgets(line, MSG_SIZ, f);
        (void) fgets(line, MSG_SIZ, f);
@@ -13817,6 +13932,10 @@ SaveGamePGN2 (FILE *f)
                    snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
                }
 
+           if(appData.cumulativeTimePGN) {
+               snprintf(buf, MSG_SIZ, " %+ld", timeRemaining[i & 1][i+1]/1000);
+           }
+
             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
                      pvInfoList[i].score >= 0 ? "+" : "",
                      pvInfoList[i].score / 100.0,
@@ -14932,6 +15051,7 @@ TwoMachinesEvent P((void))
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+  if(!appData.epd) {
     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
 
     if(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth,
@@ -14950,6 +15070,7 @@ TwoMachinesEvent P((void))
       ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
       return;
     }
+  }
     GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
     if(appData.matchPause>10000 || appData.matchPause<10)
                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
@@ -14961,6 +15082,7 @@ TwoMachinesEvent P((void))
     // we are now committed to starting the game
     stalling = 0;
     DisplayMessage("", "");
+  if(!appData.epd) {
     if (startedFromSetupPosition) {
        SendBoard(&second, backwardMostMove);
     if (appData.debugMode) {
@@ -14970,6 +15092,7 @@ TwoMachinesEvent P((void))
     for (i = backwardMostMove; i < forwardMostMove; i++) {
        SendMoveToProgram(i, &second);
     }
+  }
 
     gameMode = TwoMachinesPlay;
     pausing = startingEngine = FALSE;
@@ -14988,11 +15111,13 @@ TwoMachinesEvent P((void))
       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
       SendToProgram(buf, &first);
     }
+  if(!appData.epd) {
     SendToProgram(second.computerString, &second);
     if (second.sendName) {
       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
       SendToProgram(buf, &second);
     }
+  }
 
     ResetClocks();
     if (!first.sendTime || !second.sendTime) {
@@ -15187,10 +15312,10 @@ EditGameEvent ()
     SetGameInfo();
 }
 
-
 void
 EditPositionEvent ()
 {
+    int i;
     if (gameMode == EditPosition) {
        EditGameEvent();
        return;
@@ -15202,8 +15327,11 @@ EditPositionEvent ()
     gameMode = EditPosition;
     ModeHighlight();
     SetGameInfo();
+    CopyBoard(rightsBoard, nullBoard);
     if (currentMove > 0)
       CopyBoard(boards[0], boards[currentMove]);
+    for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
+      rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
 
     blackPlaysFirst = !WhiteOnMove(currentMove);
     ResetClocks();
@@ -15237,22 +15365,23 @@ EditPositionDone (Boolean fakeRights)
     startedFromSetupPosition = TRUE;
     InitChessProgram(&first, FALSE);
     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
+      int r, f;
       boards[0][EP_STATUS] = EP_NONE;
-      boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
-      if(boards[0][0][BOARD_WIDTH>>1] == king) {
-       boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
-       boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
-      } else boards[0][CASTLING][2] = NoRights;
-      if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
-       boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
-       boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
-      } else boards[0][CASTLING][5] = NoRights;
-      if(gameInfo.variant == VariantSChess) {
-       int i;
-       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
-         boards[0][VIRGIN][i] = 0;
-         if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
-         if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
+      for(f=0; f<=nrCastlingRights; f++) boards[0][CASTLING][f] = NoRights;
+      for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // first pass: Kings & e.p.
+       if(rightsBoard[r][f]) {
+         ChessSquare p = boards[0][r][f];
+         if(p == (blackPlaysFirst ? WhitePawn : BlackPawn)) boards[0][EP_STATUS] = f;
+         else if(p == king) boards[0][CASTLING][2] = f;
+         else if(p == WHITE_TO_BLACK king) boards[0][CASTLING][5] = f;
+         else rightsBoard[r][f] = 2; // mark for second pass
+       }
+      }
+      for(r=BOARD_HEIGHT-1; r>=0; r--) for(f=BOARD_RGHT-1; f>=BOARD_LEFT; f--) { // second pass: Rooks
+       if(rightsBoard[r][f] == 2) {
+         ChessSquare p = boards[0][r][f];
+         if(p == WhiteRook) boards[0][CASTLING][(f < boards[0][CASTLING][2])] = f; else
+         if(p == BlackRook) boards[0][CASTLING][(f < boards[0][CASTLING][5])+3] = f;
        }
       }
     }
@@ -15351,12 +15480,13 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
     ChessSquare piece = boards[0][y][x];
     static Board erasedBoard, currentBoard, menuBoard, nullBoard;
     static int lastVariant;
+    int baseRank = BOARD_HEIGHT-1, hasRights = 0;
 
     if (gameMode != EditPosition && gameMode != IcsExamining) return;
 
     switch (selection) {
       case ClearBoard:
-       fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+       fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
        MarkTargetSquares(1);
        CopyBoard(currentBoard, boards[0]);
        CopyBoard(menuBoard, initialPosition);
@@ -15383,14 +15513,16 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                    }
                }
            }
+           CopyBoard(rightsBoard, nullBoard);
            if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
-               int r;
+               int r, i;
                for(r = 0; r < BOARD_HEIGHT; r++) {
                  for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
                    ChessSquare p = menuBoard[r][x];
                    for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
                  }
                }
+               menuBoard[CASTLING][0] = menuBoard[CASTLING][3] = NoRights; // h-side Rook was deleted
                DisplayMessage("Clicking clock again restores position", "");
                if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
                if(!nonEmpty) { // asked to clear an empty board
@@ -15405,6 +15537,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                } else
                    CopyBoard(erasedBoard, currentBoard);
 
+               for(i=0; i<nrCastlingRights; i++) if(boards[0][CASTLING][i] != NoRights)
+                   rightsBoard[castlingRank[i]][boards[0][CASTLING][i]] = 1; // copy remaining rights
            }
        }
        if (gameMode == EditPosition) {
@@ -15467,12 +15601,21 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
         goto defaultlabel;
 
+      case WhiteRook:
+        baseRank = 0;
+      case BlackRook:
+        if(y == baseRank && (x == BOARD_LEFT || x == BOARD_RGHT-1 || appData.fischerCastling)) hasRights = 1;
+        if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
+        goto defaultlabel;
+
       case WhiteKing:
+        baseRank = 0;
       case BlackKing:
         if(gameInfo.variant == VariantXiangqi)
             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
         if(gameInfo.variant == VariantKnightmate)
             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
+        if(y == baseRank && (x == BOARD_WIDTH>>1 || appData.fischerCastling)) hasRights = 1;
       default:
         defaultlabel:
        if (gameMode == IcsExamining) {
@@ -15481,6 +15624,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                     PieceToChar(selection), AAA + x, ONE + y);
            SendToICS(buf);
        } else {
+            rightsBoard[y][x] = hasRights;
             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
                 int n;
                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
@@ -15809,7 +15953,7 @@ ForwardInner (int target)
 
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
 
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
@@ -15830,19 +15974,15 @@ ForwardInner (int target)
                SetHighlights(-1, -1, toX, toY);
            }
        } else {
-            int viaX = moveList[target - 1][5] - AAA;
-            int viaY = moveList[target - 1][6] - ONE;
             fromX = moveList[target - 1][0] - AAA;
             fromY = moveList[target - 1][1] - ONE;
            if (target == currentMove + 1) {
                if(moveList[target - 1][4] == ';') { // multi-leg
-                   ChessSquare piece = boards[currentMove][viaY][viaX];
-                   AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
-                   boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
-                   AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
-                   boards[currentMove][viaY][viaX] = piece;
-               } else
+                   killX = moveList[target - 1][5] - AAA;
+                   killY = moveList[target - 1][6] - ONE;
+               }
                AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+               killX = killY = -1;
            }
            if (appData.highlightLastMove) {
                SetHighlights(fromX, fromY, toX, toY);
@@ -15928,7 +16068,7 @@ BackwardInner (int target)
     if (gameMode == EditPosition) return;
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
     if (currentMove <= backwardMostMove) {
        ClearHighlights();
        DrawPosition(full_redraw, boards[currentMove]);
@@ -16684,7 +16824,11 @@ GetInfoFromComment (int index, char * text)
         pvInfoList[index-1].score = score;
         pvInfoList[index-1].time  = 10*time; // centi-sec
         if(*sep == '}') *sep = 0; else *--sep = '{';
-        if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
+        if(p != text) {
+            while(*p++ = *sep++)
+                                ;
+            sep = text;
+        } // squeeze out space between PV and comment, and return both
     }
     return sep;
 }
@@ -17672,11 +17816,13 @@ ResetClocks ()
 
 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
 
+static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
+
 /* Decrement running clock by amount of time that has passed */
 void
 DecrementClocks ()
 {
-    long timeRemaining;
+    long tRemaining;
     long lastTickLength, fudge;
     TimeMark now;
 
@@ -17693,28 +17839,32 @@ DecrementClocks ()
 
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
-       timeRemaining = whiteTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0 && !appData.icsActive) {
+        tRemaining = whiteTimeRemaining -= lastTickLength;
+        if( tRemaining < 0 && !appData.icsActive) {
             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
-                lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+                lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
             }
         }
+       if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
        DisplayWhiteClock(whiteTimeRemaining - fudge,
                          WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+       timeSuffix = 0;
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
-       timeRemaining = blackTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
+        tRemaining = blackTimeRemaining -= lastTickLength;
+        if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
             if(suddenDeath) {
                 blackStartMove = forwardMostMove;
-                lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+                lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
             }
         }
+       if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
        DisplayBlackClock(blackTimeRemaining - fudge,
                          !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+       timeSuffix = 0;
     }
     if (CheckFlags()) return;
 
@@ -17729,7 +17879,7 @@ DecrementClocks ()
     }
 
     tickStartTM = now;
-    intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
+    intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);
 
     /* if the time remaining has fallen below the alarm threshold, sound the
@@ -17745,9 +17895,9 @@ DecrementClocks ()
               ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
           )) return;
 
-       if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
+       if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+       } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -17888,7 +18038,7 @@ TimeString (long ms)
 {
     long second, minute, hour, day;
     char *sign = "";
-    static char buf[32];
+    static char buf[40], moveTime[8];
 
     if (ms > 0 && ms <= 9900) {
       /* convert milliseconds to tenths, rounding up */
@@ -17915,13 +18065,16 @@ TimeString (long ms)
     minute = second / 60;
     second = second % 60;
 
+    if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
+    else *moveTime = NULLCHAR;
+
     if (day > 0)
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
-             sign, day, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
+             sign, day, hour, minute, second, moveTime);
     else if (hour > 0)
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
     else
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
 
     return buf;
 }
@@ -18046,7 +18199,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     p = buf;
 
     /* Piece placement data */
-    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
        if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
        emptycount = 0;
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
@@ -18128,7 +18281,9 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
   }
 
   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
-    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
+    while(*p++ = *q++)
+                      ;
+    if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
   if(haveRights) {
      int handW=0, handB=0;
@@ -18260,8 +18415,10 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
     p = fen;
 
+    for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
+
     /* Piece placement data */
-    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
        j = 0;
        for (;;) {
             if (*p == '/' || *p == ' ' || *p == '[' ) {