Allow WinBoard to use bitmaps with alpha channel
[xboard.git] / backend.c
index 325888f..faeccf5 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -261,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;
@@ -296,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 */
@@ -564,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 */
@@ -691,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
@@ -980,13 +981,14 @@ FloatToFront(char **list, char *engineLine)
     ASSIGN(*list, tidy+1);
 }
 
-char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
+char *insert, *wbOptions, *currentEngine[2]; // point in ChessProgramNames were we should insert new engine
 
 void
 Load (ChessProgramState *cps, int i)
 {
     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
+       ASSIGN(currentEngine[i], engineLine);
        snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
        SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
        ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
@@ -1029,7 +1031,7 @@ Load (ChessProgramState *cps, int i)
        q = firstChessProgramNames;
        if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
        quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s",
                        quote, p, quote, appData.directory[i],
                        useNick ? " -fn \"" : "",
                        useNick ? nickName : "",
@@ -1039,12 +1041,13 @@ Load (ChessProgramState *cps, int i)
                        isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
                        storeVariant ? " -variant " : "",
                        storeVariant ? VariantName(gameInfo.variant) : "");
-       if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
-       firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
+       if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " %s", wbOptions);
+       firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 2);
        if(insert != q) insert[-1] = NULLCHAR;
-       snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
+       snprintf(firstChessProgramNames, len, "%s\n%s\n%s", q, buf, insert);
        if(q)   free(q);
        FloatToFront(&appData.recentEngineList, buf);
+       ASSIGN(currentEngine[i], buf);
     }
     ReplaceEngine(cps, i);
 }
@@ -1586,7 +1589,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();
 }
 
@@ -1599,6 +1602,7 @@ InitBackEnd3 P((void))
     char buf[MSG_SIZ];
     int err, len;
 
+    ParseFeatures(appData.features[0], &first);
     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
@@ -3395,7 +3399,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 (
@@ -4299,7 +4304,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
 
     } else if (count == 0) {
        RemoveInputSource(isr);
-        DisplayFatalError(_("Connection closed by ICS"), 0, 0);
+        DisplayFatalError(_("Connection closed by ICS"), 0, 6666);
     } else {
        DisplayFatalError(_("Error reading from ICS"), error, 1);
     }
@@ -5161,14 +5166,14 @@ 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 if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
-         *c = m[9];
+       else if(*c && m[8] != '\n') { // 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',
@@ -5384,7 +5389,7 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
            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);
+           sprintf(move+4, ";%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);
          }
        }
@@ -6279,8 +6284,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;
@@ -6294,7 +6301,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;
@@ -6494,7 +6501,7 @@ SendBoard (ChessProgramState *cps, int moveNum)
        * deprecated "black" command.
        */
       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
-        SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
+        SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn && !pieceDesc[WhitePawn] ? "a2a3\n" : "black\nforce\n", cps);
 
       if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper
 
@@ -6698,9 +6705,12 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
        return FALSE;
 
+    if(legal[toY][toX] == 4) return FALSE;
+
     piece = boards[currentMove][fromY][fromX];
     if(gameInfo.variant == VariantChu) {
         promotionZoneSize = BOARD_HEIGHT/3;
+        if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
@@ -6982,6 +6992,7 @@ 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)
@@ -7109,8 +7120,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) {
@@ -7168,6 +7184,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;
     }
 
@@ -7386,13 +7403,13 @@ 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;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
+       r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
        while(*fen) {
            int s = 0;
-           marker[r][f] = 0;
            if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
-           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
+           if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
+           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
            if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
            if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
            if(*fen == 'T') marker[r][f++] = 0; else
@@ -7526,12 +7543,13 @@ void ReportClick(char *action, int x, int y)
 }
 
 Boolean right; // instructs front-end to use button-1 events as if they were button 3
+Boolean deferChoice;
 
 void
 LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
-    Boolean saveAnimate;
+    static Boolean saveAnimate;
     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
@@ -7539,6 +7557,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     if(flashing) return;
 
+  if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
     if (!flipView && y >= 0) {
@@ -7629,7 +7648,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 = kill2X = kill2Y = -1;
+      fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
       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) {
@@ -7681,7 +7700,7 @@ 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 = kill2X = kill2Y = -1;
+           killX = killY = kill2X = kill2Y = -1; *promoRestrict = NULLCHAR;
            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
@@ -7882,12 +7901,25 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     }
 
     // off-board moves should not be highlighted
-    if(x < 0 || y < 0) ClearHighlights();
-    else ReportClick("put", x, y);
+    if(x < 0 || y < 0) {
+       ClearHighlights();
+       DrawPosition(FALSE, NULL);
+    } else ReportClick("put", x, y);
 
     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));
+    } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
+      if(!*promoRestrict) {           // but has not done that yet
+       deferChoice = TRUE;           // set up retry for when it does
+       return;                       // and wait for that
+      }
+      promoChoice = ToLower(*promoRestrict); // force engine's choice
+      deferChoice = FALSE;
+    }
 
     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -7905,16 +7937,18 @@ 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]);
+       else DrawPosition(FALSE, NULL);
        fromX = fromY = -1;
        flashing = 0;
     }
@@ -8952,11 +8986,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. solved %4.2fs\n"), matchGame, solvingTime/100.);
+              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);
@@ -9119,6 +9153,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
@@ -9135,10 +9170,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
@@ -9154,9 +9189,13 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       }
       return;
     }
-    if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
-      promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
-      Sweep(0);
+    if(!appData.testLegality && sscanf(message, "choice %s", promoRestrict) == 1) {
+      if(deferChoice) {
+        LeftClick(Press, 0, 0); // finish the click that was interrupted
+      } else if(promoSweep != EmptySquare) {
+        promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
+        if(strlen(promoRestrict) > 1) Sweep(0);
+      }
       return;
     }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
@@ -9270,7 +9309,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;
@@ -9695,6 +9734,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) {
@@ -9723,8 +9763,9 @@ 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; char *p = bestMove, solved = 0;
+                   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);
@@ -9736,6 +9777,20 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                    }
                    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)) {
                        char buf[MSG_SIZ];
@@ -10160,16 +10215,16 @@ ParseGameHistory (char *game)
 void
 ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 {
-  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, berolina = 0;
+  ChessSquare captured = board[toY][toX], piece, pawn, king, killed, killed2; int p, rookX, oldEP, epRank, epFile, lastFile, lastRank, 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_FILE]; epRank = board[EP_RANK];
+      oldEP = (signed char)board[EP_STATUS]; epRank = board[EP_RANK]; epFile = board[EP_FILE]; lastFile = board[LAST_FILE],lastRank = board[LAST_RANK];
       board[EP_STATUS] = EP_NONE;
-      board[EP_FILE] = board[EP_RANK] = 100;
+      board[EP_FILE] = board[EP_RANK] = board[LAST_FILE] = board[LAST_RANK] = 100;
 
   if (fromY == DROP_RANK) {
        /* must be first */
@@ -10201,6 +10256,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
       }
 
       pawn = board[fromY][fromX];
+      if(pieceDesc[pawn] && strchr(pieceDesc[pawn], 'e')) { // piece with user-defined e.p. capture
+       if(captured == EmptySquare && toX == epFile && (toY == (epRank & 127) || toY + (pawn < BlackPawn ? -1 : 1) == epRank - 128)) {
+           captured = board[lastRank][lastFile]; // remove victim
+           board[lastRank][lastFile] = EmptySquare;
+           pawn = EmptySquare; // kludge to suppress old e.p. code
+       }
+      }
       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
@@ -10218,6 +10280,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
                        gameInfo.variant != VariantBerolina || toX > fromX)
                      board[EP_STATUS] = toX;
+              board[LAST_FILE] = toX; board[LAST_RANK] = toY;
           }
       } else
       if( pawn == BlackPawn ) {
@@ -10231,6 +10294,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
                        gameInfo.variant != VariantBerolina || toX > fromX)
                      board[EP_STATUS] = toX;
+              board[LAST_FILE] = toX; board[LAST_RANK] = toY;
           }
        }
 
@@ -10300,7 +10364,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;
@@ -10308,7 +10373,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;
@@ -10323,18 +10389,16 @@ 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 || abs(toX - fromX) > 4)
+              && (epFile == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
               && (pawn == WhitePawn)
               && (board[toY][toX] == EmptySquare)) {
+       if(lastFile == 100) lastFile = (board[fromY][toX] == BlackPawn ? toX : fromX), lastRank = fromY; // assume FIDE e.p. if victim present
        board[fromY][fromX] = 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;
+       captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
     } else if ((fromY == BOARD_HEIGHT-4)
               && (toX == fromX)
                && gameInfo.variant == VariantBerolina
@@ -10351,7 +10415,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;
@@ -10359,7 +10424,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;
@@ -10388,18 +10454,16 @@ 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 || abs(toX - fromX) > 4)
+              && (epFile == toX && epRank == toY || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
               && (toX != fromX)
                && gameInfo.variant != VariantXiangqi
                && gameInfo.variant != VariantBerolina
               && (pawn == BlackPawn)
               && (board[toY][toX] == EmptySquare)) {
+       if(lastFile == 100) lastFile = (board[fromY][toX] == WhitePawn ? toX : fromX), lastRank = fromY;
        board[fromY][fromX] = 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;
+       captured = board[lastRank][lastFile], board[lastRank][lastFile] = EmptySquare;
     } else if ((fromY == 3)
               && (toX == fromX)
                && gameInfo.variant == VariantBerolina
@@ -10866,31 +10930,47 @@ InitChessProgram (ChessProgramState *cps, int setup)
 }
 
 
-void
-ResendOptions (ChessProgramState *cps)
+char *
+ResendOptions (ChessProgramState *cps, int toEngine)
 { // send the stored value of the options
   int i;
-  char buf[MSG_SIZ];
+  static char buf2[MSG_SIZ*10];
+  char buf[MSG_SIZ], *p = buf2;
   Option *opt = cps->option;
+  *p = NULLCHAR;
   for(i=0; i<cps->nrOptions; i++, opt++) {
+      *buf = NULLCHAR;
       switch(opt->type) {
         case Spin:
         case Slider:
         case CheckBox:
-           snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value);
+           if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
+           snprintf(buf, MSG_SIZ, "%s=%d", opt->name, opt->value);
           break;
         case ComboBox:
-          snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]);
+           if(opt->value != *(int*) (opt->name + MSG_SIZ - 104))
+            snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->choice[opt->value]);
           break;
         default:
-           snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue);
+           if(strcmp(opt->textValue, opt->name + MSG_SIZ - 100))
+           snprintf(buf, MSG_SIZ, "%s=%s", opt->name, opt->textValue);
           break;
         case Button:
         case SaveButton:
           continue;
       }
-      SendToProgram(buf, cps);
+      if(*buf) {
+        if(toEngine) {
+         snprintf(buf2, MSG_SIZ, "option %s\n", buf);
+          SendToProgram(buf2, cps);
+        } else {
+          if(p != buf2) *p++ = ',';
+         strncpy(p, buf, 10*MSG_SIZ-1 - (p - buf2));
+         while(*p) p++;
+        }
+      }
   }
+  return buf2;
 }
 
 void
@@ -10938,7 +11018,7 @@ StartChessProgram (ChessProgramState *cps)
         cps->comboCnt = 0;  //                and values of combo boxes
       }
       SendToProgram(buf, cps);
-      if(cps->reload) ResendOptions(cps);
+      if(cps->reload) ResendOptions(cps, TRUE);
     } else {
       SendToProgram("xboard\n", cps);
     }
@@ -11054,8 +11134,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);
     }
@@ -11157,7 +11239,7 @@ CreateTourney (char *name)
 int
 NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
 {
-    char buf[MSG_SIZ], *p, *q;
+    char buf[2*MSG_SIZ], *p, *q;
     int i=1, header, skip, all = !strcmp(group, "all"), depth = 0;
     insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names'
     skip = !all && group[0]; // if group requested, we start in skip mode
@@ -11190,6 +11272,32 @@ NamesToList (char *names, char **engineList, char **engineMnemonic, char *group)
     return i;
 }
 
+void
+SaveEngineSettings (int n)
+{
+    int len; char *p, *q, *s, buf[MSG_SIZ], *optionSettings;
+    if(!currentEngine[n] || !currentEngine[n][0]) return; // no engine from list is loaded
+    p = strstr(firstChessProgramNames, currentEngine[n]);
+    if(!p) return; // sanity check; engine could be deleted from list after loading
+    optionSettings = ResendOptions(n ? &second : &first, FALSE);
+    len = strlen(currentEngine[n]);
+    q = p + len; *p = 0; // cut list into head and tail piece
+    s = strstr(currentEngine[n], "firstOptions");
+    if(s && (s[-1] == '-' || s[-1] == '/') && (s[12] == ' ' || s[12] == '=') && (s[13] == '"' || s[13] == '\'')) {
+       char *r = s + 14;
+       while(*r && *r != s[13]) r++;
+       s[14] = 0; // cut currentEngine into head and tail part, removing old settings
+       snprintf(buf, MSG_SIZ, "%s%s%s", currentEngine[n], optionSettings, *r ? r : "\""); // synthesize new engine line
+    } else if(*optionSettings) {
+       snprintf(buf, MSG_SIZ, "%s -firstOptions \"%s\"", currentEngine[n], optionSettings);
+    }
+    ASSIGN(currentEngine[n], buf); // updated engine line
+    len = p - firstChessProgramNames + strlen(q) + strlen(currentEngine[n]) + 1;
+    s = malloc(len);
+    snprintf(s, len, "%s%s%s", firstChessProgramNames, currentEngine[n], q);
+    FREE(firstChessProgramNames); firstChessProgramNames = s; // new list
+}
+
 // following implemented as macro to avoid type limitations
 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
 
@@ -11614,7 +11722,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
               && result != GameIsDrawn)
            {   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;
+                       int p = (int)boards[forwardMostMove][i][j] - color;
                        if(p >= 0 && p <= (int)WhiteKing) k++;
                        oppoKings += (p + color == WhiteKing + BlackPawn - color);
                }
@@ -11852,9 +11960,11 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
            if(appData.epd) {
                snprintf(buf, MSG_SIZ, "-------------------------------------- ");
                OutputKibitz(2, buf);
-               snprintf(buf, MSG_SIZ, _("Average solving time %4.2f sec "), totalTime/(100.*first.matchWins));
+               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, _("Solved %d of %d (%3.1f%%) "), first.matchWins, nextGame, first.matchWins*100./nextGame);
+               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"),
@@ -11980,6 +12090,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
@@ -13550,6 +13661,9 @@ LoadPosition (FILE *f, int positionNumber, char *title)
        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);
@@ -13888,6 +14002,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,
@@ -14809,7 +14927,9 @@ MachineWhiteEvent ()
 
        safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
-       HandleMachineMove(bookMove, &first);
+       savedMessage = bookMove; // args for deferred call
+       savedState = &first;
+       ScheduleDelayedEvent(DeferredBookMove, 1);
     }
 }
 
@@ -14884,7 +15004,9 @@ MachineBlackEvent ()
 
        safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
        strcat(bookMove, bookHit);
-       HandleMachineMove(bookMove, &first);
+       savedMessage = bookMove; // args for deferred call
+       savedState = &first;
+       ScheduleDelayedEvent(DeferredBookMove, 1);
     }
 }
 
@@ -14954,7 +15076,7 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry)
 void
 TwoMachinesEvent P((void))
 {
-    int i;
+    int i, move = forwardMostMove;
     char buf[MSG_SIZ];
     ChessProgramState *onmove;
     char *bookHit = NULL;
@@ -15003,6 +15125,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,
@@ -15021,6 +15144,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 */
@@ -15032,6 +15156,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) {
@@ -15041,6 +15166,7 @@ TwoMachinesEvent P((void))
     for (i = backwardMostMove; i < forwardMostMove; i++) {
        SendMoveToProgram(i, &second);
     }
+  }
 
     gameMode = TwoMachinesPlay;
     pausing = startingEngine = FALSE;
@@ -15059,14 +15185,16 @@ 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) {
+    if (!first.sendTime || !second.sendTime || move == 0) { // [HGM] first engine changed sides from Reset, so recalc time odds
+       ResetClocks();
        timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
        timeRemaining[1][forwardMostMove] = blackTimeRemaining;
     }
@@ -15258,10 +15386,10 @@ EditGameEvent ()
     SetGameInfo();
 }
 
-
 void
 EditPositionEvent ()
 {
+    int i;
     if (gameMode == EditPosition) {
        EditGameEvent();
        return;
@@ -15273,8 +15401,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();
@@ -15308,22 +15439,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;
        }
       }
     }
@@ -15422,6 +15554,7 @@ 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;
 
@@ -15454,14 +15587,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
@@ -15476,6 +15611,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) {
@@ -15538,12 +15675,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) {
@@ -15552,6 +15698,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) {
@@ -16751,7 +16898,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;
 }
@@ -17069,8 +17220,10 @@ StringFeature (char **p, char *name, char **loc, ChessProgramState *cps)
   if (strncmp((*p), name, len) == 0
       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
     (*p) += len + 2;
-    ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits
-    sscanf(*p, "%[^\"]", *loc);
+    len = strlen(*p) + 1; if(len < MSG_SIZ && !strcmp(name, "option")) len = MSG_SIZ; // make sure string options have enough space to change their value
+    FREE(*loc); *loc = malloc(len);
+    strncpy(*loc, *p, len);
+    sscanf(*p, "%[^\"]", *loc); // should always fit, because we allocated at least strlen(*p)
     while (**p && **p != '\"') (*p)++;
     if (**p == '\"') (*p)++;
     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
@@ -17172,6 +17325,8 @@ ParseOption (Option *opt, ChessProgramState *cps)
                strcat(buf, "\n");
                SendToProgram(buf, cps);
        }
+       *(int*) (opt->name + MSG_SIZ - 104) = opt->value; // hide default values somewhere
+       if(opt->target == &opt->textValue) strncpy(opt->name + MSG_SIZ - 100, opt->textValue, 99);
        return TRUE;
 }
 
@@ -17252,6 +17407,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
     if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first
        if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse
+       if(cps->nrOptions == 0) { ASSIGN(cps->option[0].name, _("Make Persistent -save")); ParseOption(&(cps->option[cps->nrOptions++]), cps); }
        FREE(cps->option[cps->nrOptions].name);
        cps->option[cps->nrOptions].name = q; q = NULL;
        if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
@@ -17739,11 +17895,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;
 
@@ -17760,28 +17918,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;
 
@@ -17796,7 +17958,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
@@ -17812,9 +17974,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;
        }
@@ -17955,7 +18117,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 */
@@ -17982,13 +18144,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;
 }
@@ -18113,7 +18278,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++) {
@@ -18195,7 +18360,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;
@@ -18327,8 +18494,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 == '[' ) {
@@ -18951,12 +19120,15 @@ LoadVariation (int index, char *text)
        ToNrEvent(currentMove+1);
 }
 
+int transparency[2];
+
 void
 LoadTheme ()
 {
-    char *p, *q, buf[MSG_SIZ];
+#define BUF_SIZ (2*MSG_SIZ)
+    char *p, *q, buf[BUF_SIZ];
     if(engineLine && engineLine[0]) { // a theme was selected from the listbox
-       snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
+       snprintf(buf, BUF_SIZ, "-theme %s", engineLine);
        ParseArgsFromString(buf);
        ActivateTheme(TRUE); // also redo colors
        return;
@@ -18966,38 +19138,48 @@ LoadTheme ()
     {
        int len;
        q = appData.themeNames;
-       snprintf(buf, MSG_SIZ, "\"%s\"", nickName);
+       snprintf(buf, BUF_SIZ, "\"%s\"", nickName);
       if(appData.useBitmaps) {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d",
-               appData.liteBackTextureFile, appData.darkBackTextureFile,
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt true -lbtf \"%s\"",
+               Shorten(appData.liteBackTextureFile));
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dbtf \"%s\" -lbtm %d -dbtm %d",
+               Shorten(appData.darkBackTextureFile),
                appData.liteBackTextureMode,
                appData.darkBackTextureMode );
       } else {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
-               Col2Text(2),   // lightSquareColor
-               Col2Text(3) ); // darkSquareColor
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
+      }
+      if(!appData.useBitmaps || transparency[0]) {
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
+      }
+      if(!appData.useBitmaps || transparency[1]) {
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
       }
       if(appData.useBorder) {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub true -border \"%s\"",
                appData.border);
       } else {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false");
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ub false");
       }
       if(appData.useFont) {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s",
                appData.renderPiecesWithFont,
                appData.fontToPieceTable,
                Col2Text(9),    // appData.fontBackColorWhite
                Col2Text(10) ); // appData.fontForeColorBlack
       } else {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
-               appData.pieceDirectory);
-       if(!appData.pieceDirectory[0])
-         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
+       if(appData.pieceDirectory[0]) {
+         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -pid \"%s\"", Shorten(appData.pieceDirectory));
+         if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
+           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
+       }
+       if(!appData.pieceDirectory[0] || !appData.trueColors)
+         snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -wpc %s -bpc %s",
                Col2Text(0),   // whitePieceColor
                Col2Text(1) ); // blackPieceColor
       }
-      snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
+      snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -hsc %s -phc %s\n",
                Col2Text(4),   // highlightSquareColor
                Col2Text(5) ); // premoveHighlightColor
        appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);