Fix recognition of variant seirawan in examined ICS games
[xboard.git] / backend.c
index 6b206ea..57c06b9 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -981,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;
@@ -1030,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 : "",
@@ -1040,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);
 }
@@ -1591,8 +1593,6 @@ MatchEvent (int mode)
        NextMatchGame();
 }
 
-char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
-
 void
 InitBackEnd3 P((void))
 {
@@ -1600,6 +1600,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
@@ -1623,7 +1624,7 @@ InitBackEnd3 P((void))
        free(programVersion);
        programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
        sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
-       FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
+       FloatToFront(&appData.recentEngineList, currentEngine[0] ? currentEngine[0] : appData.firstChessProgram);
     }
 
     if (appData.icsActive) {
@@ -4208,6 +4209,12 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                                                         parse, currentMove);
                    if (sscanf(parse, " game %d", &gamenum) == 1) {
                      if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
+                       new_piece[0] = NULLCHAR;
+                       sscanf(parse, "game %d white [%s black [%s <- %s",
+                              &gamenum, white_holding, black_holding,
+                              new_piece);
+                        white_holding[strlen(white_holding)-1] = NULLCHAR;
+                        black_holding[strlen(black_holding)-1] = NULLCHAR;
                        if (gameInfo.variant == VariantNormal) {
                           /* [HGM] We seem to switch variant during a game!
                            * Presumably no holdings were displayed, so we have
@@ -4218,7 +4225,10 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                          switch(gameInfo.boardWidth) { // base guess on board width
                                case 9:  newVariant = VariantShogi; break;
                                case 10: newVariant = VariantGreat; break;
-                               default: newVariant = VariantCrazyhouse; break;
+                               default: newVariant = VariantCrazyhouse;
+                                    if(strchr(white_holding, 'E') || strchr(black_holding, 'E') || 
+                                       strchr(white_holding, 'H') || strchr(black_holding, 'H')   )
+                                        newVariant = VariantSChess;
                          }
                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
                          /* Get a move list just to see the header, which
@@ -4229,12 +4239,6 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                            SendToICS(str);
                          }
                        }
-                       new_piece[0] = NULLCHAR;
-                       sscanf(parse, "game %d white [%s black [%s <- %s",
-                              &gamenum, white_holding, black_holding,
-                              new_piece);
-                        white_holding[strlen(white_holding)-1] = NULLCHAR;
-                        black_holding[strlen(black_holding)-1] = NULLCHAR;
                         /* [HGM] copy holdings to board holdings area */
                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
@@ -4301,7 +4305,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);
     }
@@ -6450,6 +6454,7 @@ InitPosition (int redraw)
       initialPosition[1][1] = initialPosition[2][1] =
       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
      }
+     initialPosition[CHECK_COUNT] = (gameInfo.variant == Variant3Check ? 0x303 : 0);
   if (appData.debugMode) {
     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
   }
@@ -6498,7 +6503,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
 
@@ -7461,7 +7466,7 @@ MarkTargetSquares (int clear)
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
       if(capt)
-      for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
+      for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = legal[y][x] = 0;
     }
   }
   DrawPosition(FALSE, NULL);
@@ -7898,8 +7903,10 @@ 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));
  }
@@ -7943,6 +7950,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        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;
     }
@@ -10026,7 +10034,7 @@ void
 ParseGameHistory (char *game)
 {
     ChessMove moveType;
-    int fromX, fromY, toX, toY, boardIndex;
+    int fromX, fromY, toX, toY, boardIndex, mask;
     char promoChar;
     char *p, *q;
     char buf[MSG_SIZ];
@@ -10187,15 +10195,18 @@ ParseGameHistory (char *game)
        strcat(moveList[boardIndex], "\n");
        boardIndex++;
        ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
+       mask = (WhiteOnMove(boardIndex) ? 0xFF : 0xFF00);
         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
          case MT_NONE:
          case MT_STALEMATE:
          default:
            break;
          case MT_CHECK:
-            if(!IS_SHOGI(gameInfo.variant))
-                strcat(parseList[boardIndex - 1], "+");
-           break;
+            if(boards[boardIndex][CHECK_COUNT]) boards[boardIndex][CHECK_COUNT] -= mask & 0x101;
+            if(!boards[boardIndex][CHECK_COUNT] || boards[boardIndex][CHECK_COUNT] & mask) {
+                if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+");
+                break;
+           }
          case MT_CHECKMATE:
          case MT_STAINMATE:
            strcat(parseList[boardIndex - 1], "#");
@@ -10209,16 +10220,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_TO] & 255,lastRank = board[LAST_TO] >> 8;
       board[EP_STATUS] = EP_NONE;
-      board[EP_FILE] = board[EP_RANK] = 100;
+      board[EP_FILE] = board[EP_RANK] = 100, board[LAST_TO] = 0x4040;
 
   if (fromY == DROP_RANK) {
        /* must be first */
@@ -10250,6 +10261,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
@@ -10267,6 +10285,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_TO] = toX + 256*toY;
           }
       } else
       if( pawn == BlackPawn ) {
@@ -10280,6 +10299,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_TO] = toX + 256*toY;
           }
        }
 
@@ -10374,18 +10394,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
@@ -10441,18 +10459,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
@@ -10580,7 +10596,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
 void
 MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
 {
-    int x = toX, y = toY;
+    int x = toX, y = toY, mask;
     char *s = parseList[forwardMostMove];
     ChessSquare p = boards[forwardMostMove][toY][toX];
 //    forwardMostMove++; // [HGM] bare: moved downstream
@@ -10676,15 +10692,18 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
     }
     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
                              moveList[forwardMostMove - 1]);
+    mask = (WhiteOnMove(forwardMostMove) ? 0xFF : 0xFF00);
     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
       case MT_NONE:
       case MT_STALEMATE:
       default:
        break;
       case MT_CHECK:
-        if(!IS_SHOGI(gameInfo.variant))
-            strcat(parseList[forwardMostMove - 1], "+");
-       break;
+        if(boards[forwardMostMove][CHECK_COUNT]) boards[forwardMostMove][CHECK_COUNT] -= mask & 0x101;
+       if(!boards[forwardMostMove][CHECK_COUNT] || boards[forwardMostMove][CHECK_COUNT] & mask) {
+            if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+");
+            break;
+        }
       case MT_CHECKMATE:
       case MT_STAINMATE:
        strcat(parseList[forwardMostMove - 1], "#");
@@ -10919,31 +10938,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
@@ -10991,7 +11026,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);
     }
@@ -11212,7 +11247,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
@@ -11245,6 +11280,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]) { DisplayMessage("saving failed: engine not from list", ""); return; } // no engine from list is loaded
+    p = strstr(firstChessProgramNames, currentEngine[n]);
+    if(!p) { DisplayMessage("saving failed: engine not found in list", ""); 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;
 
@@ -11292,6 +11353,7 @@ GetEngineLine (char *s, int n)
     if(n == 1) SwapEngines(n);
     ParseArgsFromString(buf);
     if(n == 1) SwapEngines(n);
+    if(n < 2) { ASSIGN(currentEngine[n], command[i]); }
     if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
        SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
        ParseArgsFromString(buf);
@@ -11669,7 +11731,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);
                }
@@ -14874,7 +14936,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);
     }
 }
 
@@ -14949,7 +15013,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);
     }
 }
 
@@ -15019,7 +15085,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;
@@ -15136,8 +15202,8 @@ TwoMachinesEvent P((void))
     }
   }
 
-    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;
     }
@@ -17163,8 +17229,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);
@@ -17266,6 +17334,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;
 }
 
@@ -17346,6 +17416,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
@@ -18399,6 +18470,12 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
   }
   }
 
+    i = boards[move][CHECK_COUNT];
+    if(i) {
+       sprintf(p, "%d+%d ", i&255, i>>8);
+       while(*p) p++;
+    }
+
     if(moveCounts)
     {   int i = 0, j=move;
 
@@ -18759,10 +18836,19 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
       }
     }
 
+    while(*p == ' ') p++;
+
+    board[CHECK_COUNT] = 0; // [HGM] 3check: check-count field
+    if(sscanf(p, "%d+%d", &i, &j) == 2) {
+       board[CHECK_COUNT] = i + 256*j;
+       while(*p && *p != ' ') p++;
+    }
 
-    if(sscanf(p, "%d", &i) == 1) {
+    c = sscanf(p, "%d%*d +%d+%d", &i, &j, &k);
+    if(c > 0) {
         FENrulePlies = i; /* 50-move ply counter */
         /* (The move number is still ignored)    */
+       if(c == 3 && !board[CHECK_COUNT]) board[CHECK_COUNT] = (3 - j) + 256*(3 - k); // SCIDB-style check count
     }
 
     return TRUE;
@@ -19063,9 +19149,10 @@ 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;
@@ -19075,46 +19162,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");
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -ubt false");
       }
       if(!appData.useBitmaps || transparency[0]) {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
       }
       if(!appData.useBitmaps || transparency[1]) {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
+       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");
+       snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -upf false");
        if(appData.pieceDirectory[0]) {
-         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -pid \"%s\"", appData.pieceDirectory);
+         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), MSG_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
+           snprintf(buf+strlen(buf), BUF_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
        }
        if(!appData.pieceDirectory[0] || !appData.trueColors)
-         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
+         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);