Do not move to forwadMostMove when unpausing AnalyzeMode
[xboard.git] / backend.c
index 6442bea..e490dc0 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -129,6 +129,7 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 # include "zippy.h"
 #endif
 #include "backendz.h"
+#include "evalgraph.h"
 #include "gettext.h"
 
 #ifdef ENABLE_NLS
@@ -223,6 +224,7 @@ FILE *WriteTourneyFile P((char *results, FILE *f));
 void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
 void ToggleSecond P((void));
+void PauseEngine P((ChessProgramState *cps));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -270,6 +272,7 @@ char lastMsg[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
 int promoDefaultAltered;
+int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -1843,6 +1846,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
 {
     int outError, outCount;
     static int gotEof = 0;
+    static FILE *ini;
 
     /* Pass data read from player on to ICS */
     if (count > 0) {
@@ -1851,6 +1855,17 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
        if (outCount < count) {
             DisplayFatalError(_("Error writing to ICS"), outError, 1);
        }
+       if(have_sent_ICS_logon == 2) {
+         if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
+           fprintf(ini, "%s", message);
+           have_sent_ICS_logon = 3;
+         } else
+           have_sent_ICS_logon = 1;
+       } else if(have_sent_ICS_logon == 3) {
+           fprintf(ini, "%s", message);
+           fclose(ini);
+         have_sent_ICS_logon = 1;
+       }
     } else if (count < 0) {
        RemoveInputSource(isr);
        DisplayFatalError(_("Error reading from keyboard"), error, 1);
@@ -2510,6 +2525,12 @@ PlotSeekAd (int i)
 }
 
 void
+PlotSingleSeekAd (int i)
+{
+       PlotSeekAd(i);
+}
+
+void
 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
 {
        char buf[MSG_SIZ], *ext = "";
@@ -2529,7 +2550,7 @@ AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, i
            seekNrList[nrOfSeekAds] = nr;
            zList[nrOfSeekAds] = 0;
            seekAdList[nrOfSeekAds++] = StrSave(buf);
-           if(plot) PlotSeekAd(nrOfSeekAds-1);
+           if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
        }
 }
 
@@ -2599,7 +2620,7 @@ DrawSeekGraph ()
     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
        int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
        yy = h-1-yy;
-       DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+       DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
        if(i%500 == 0) {
            char buf[MSG_SIZ];
            snprintf(buf, MSG_SIZ, "%d", i);
@@ -3430,9 +3451,15 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                continue;
            }
 
-           if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
-               ICSInitScript();
-               have_sent_ICS_logon = 1;
+           if (looking_at(buf, &i, "login:")) {
+             if (!have_sent_ICS_logon) {
+               if(ICSInitScript())
+                 have_sent_ICS_logon = 1;
+               else // no init script was found
+                 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
+             } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
+                 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
+             }
                continue;
            }
 
@@ -5389,7 +5416,8 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
   Boolean valid;
   int nr = 0;
 
-  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+  lastParseAttempt = pv; if(!*pv) return;    // turns out we crash when we parse an empty PV
+  if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) {
     PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
     pushed = TRUE;
   }
@@ -5494,7 +5522,7 @@ PvToSAN (char *pv)
        static char buf[10*MSG_SIZ];
        int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove;
        *buf = NULLCHAR;
-       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
+       if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk
        ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
        for(i = forwardMostMove; i<endPV; i++){
            if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
@@ -5502,6 +5530,7 @@ PvToSAN (char *pv)
            k += strlen(buf+k);
        }
        snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
+       if(pushed) { PopInner(0); pushed = FALSE; } // restore game continuation shelved by ParsePV
        if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV!
        endPV = savedEnd;
        return buf;
@@ -6744,7 +6773,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
        }
     }
 
-    if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
+    if(doubleClick && gameMode == AnalyzeMode) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing
         if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle
             ClearPremoveHighlights(); // was included
        else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated  by premove highlights
@@ -7101,6 +7130,9 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        return;
       }
       doubleClick = FALSE;
+      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 = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
@@ -7253,17 +7285,19 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
     } else {
+#if 0
+// [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
        /* Finish drag move */
        if (appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
            ClearHighlights();
        }
+#endif
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
     }
-    MarkTargetSquares(1);
 
     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
@@ -7288,6 +7322,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        }
        ClearHighlights();
        fromX = fromY = -1;
+        MarkTargetSquares(1);
        DrawPosition(TRUE, boards[currentMove]);
        return;
     }
@@ -7299,6 +7334,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
+        MarkTargetSquares(1);
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
            // [HGM] super: promotion to captured piece selected from holdings
            ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
@@ -7321,6 +7357,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]);
+        MarkTargetSquares(1);
        fromX = fromY = -1;
     }
     appData.animate = saveAnimate;
@@ -7577,6 +7614,9 @@ MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisCo
                        || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
                // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
 
+       } else if(v == VariantKnightmate) {
+               if(nMine == 1) return FALSE;
+               if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw
        } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
                int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
 
@@ -7629,7 +7669,7 @@ Adjudicate (ChessProgramState *cps)
        //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
        //       Actually ending the game is now based on the additional internal condition canAdjudicate.
        //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
-       int k, count = 0; static int bare = 1;
+       int k, drop, count = 0; static int bare = 1;
        ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
        Boolean canAdjudicate = !appData.icsActive;
 
@@ -7784,10 +7824,12 @@ Adjudicate (ChessProgramState *cps)
 
                 /* Check for rep-draws */
                 count = 0;
+                drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess
+                                              && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand);
                 for(k = forwardMostMove-2;
-                    k>=backwardMostMove && k>=forwardMostMove-100 &&
+                    k>=backwardMostMove && k>=forwardMostMove-100 && (drop ||
                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
-                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+                        (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE);
                     k-=2)
                 {   int rights=0;
                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
@@ -8004,6 +8046,8 @@ DeferredBookMove (void)
 }
 
 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+static ChessProgramState *stalledEngine;
+static char stashedInputMove[MSG_SIZ];
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
@@ -8069,6 +8113,22 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
        (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
     {
+        if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume.
+           if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which);
+           safeStrCpy(stashedInputMove, message, MSG_SIZ);
+           stalledEngine = cps;
+           if(appData.ponderNextMove) { // bring opponent out of ponder
+               if(gameMode == TwoMachinesPlay) {
+                   if(cps->other->pause)
+                       PauseEngine(cps->other);
+                   else
+                       SendToProgram("easy\n", cps->other);
+               }
+           }
+           StopClocks();
+           return;
+       }
+
         /* This method is only useful on engines that support ping */
         if (cps->lastPing != cps->lastPong) {
          if (gameMode == BeginningOfGame) {
@@ -9392,8 +9452,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
-        if(gameInfo.variant==VariantBughouse ||
-           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+        if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
+           && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY >= BOARD_HEIGHT>>1)
@@ -9453,8 +9513,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
-        if(gameInfo.variant==VariantBughouse ||
-           gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+        if((gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) 
+           && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY < BOARD_HEIGHT>>1)
@@ -9707,9 +9767,6 @@ ShowMove (int fromX, int fromY, int toX, int toY)
                AnimateMove(boards[forwardMostMove - 1],
                            fromX, fromY, toX, toY);
            }
-           if (appData.highlightLastMove) {
-               SetHighlights(fromX, fromY, toX, toY);
-           }
        }
        currentMove = forwardMostMove;
     }
@@ -9717,6 +9774,11 @@ ShowMove (int fromX, int fromY, int toX, int toY)
     if (instant) return;
 
     DisplayMove(currentMove - 1);
+    if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+           if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board
+               SetHighlights(fromX, fromY, toX, toY);
+           }
+    }
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
@@ -10202,9 +10264,10 @@ GetEngineLine (char *s, int n)
     char buf[MSG_SIZ];
     extern char *icsNames;
     if(!s || !*s) return 0;
-    NamesToList(n == 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
+    NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
     for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
     if(!mnemonic[i]) return 0;
+    if(n == 11) return 1; // just testing if there was a match
     snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
     if(n == 1) SwapEngines(n);
     ParseArgsFromString(buf);
@@ -10467,6 +10530,8 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
 
     fromX = fromY = -1; // [HGM] abort any move the user is entering.
 
+    if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
+
     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
        /* If we are playing on ICS, the server decides when the
           game is over, but the engine can offer to draw, claim
@@ -10610,6 +10675,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
                    if (*appData.savePositionFile != NULLCHAR) {
                        SavePositionToFile(appData.savePositionFile);
                    }
+                   AddGameToBook(FALSE); // Only does something during Monte-Carlo book building
                }
            }
 
@@ -11007,6 +11073,11 @@ AutoPlayGameLoop ()
     }
 }
 
+void
+AnalyzeNextGame()
+{
+    ReloadGame(1); // next game
+}
 
 int
 AutoPlayOneMove ()
@@ -11028,7 +11099,14 @@ AutoPlayOneMove ()
     }
 
     if (currentMove >= forwardMostMove) {
-      if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
+      if(gameMode == AnalyzeFile) {
+         if(appData.loadGameIndex == -1) {
+           GameEnds(EndOfFile, NULL, GE_FILE);
+          ScheduleDelayedEvent(AnalyzeNextGame, 10);
+         } else {
+          ExitAnalyzeMode(); SendToProgram("force\n", &first);
+        }
+      }
 //      gameMode = EndOfGame;
 //      ModeHighlight();
 
@@ -11730,6 +11808,7 @@ InitSearch ()
 }
 
 GameInfo dummyInfo;
+static int creatingBook;
 
 int
 GameContainsPosition (FILE *f, ListGame *lg)
@@ -11872,6 +11951,9 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            gn = 1;
        }
        else {
+           if(gameMode == AnalyzeFile && appData.loadGameIndex == -1)
+             appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis
+           else
            DisplayError(_("Game number out of range"), 0);
            return FALSE;
        }
@@ -12190,6 +12272,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        cm = (ChessMove) Myylex();
     }
 
+  if(!creatingBook) {
     if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
@@ -12202,6 +12285,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     }
        DisplayBothClocks();
     }
+  }
 
     /* [HGM] server: flag to write setup moves in broadcast file as one */
     loadFlag = appData.suppressLoadMoves;
@@ -12265,9 +12349,11 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
 
     if (oldGameMode == AnalyzeFile ||
        oldGameMode == AnalyzeMode) {
+      appData.loadGameIndex = -1; // [HGM] order auto-stepping through games
       AnalyzeFileEvent();
     }
 
+    if(creatingBook) return TRUE;
     if (!matchMode && pos > 0) {
        ToNrEvent(pos); // [HGM] no autoplay if selected on position
     } else
@@ -13302,6 +13388,20 @@ ExitEvent (int status)
 }
 
 void
+PauseEngine (ChessProgramState *cps)
+{
+    SendToProgram("pause\n", cps);
+    cps->pause = 2;
+}
+
+void
+UnPauseEngine (ChessProgramState *cps)
+{
+    SendToProgram("resume\n", cps);
+    cps->pause = 1;
+}
+
+void
 PauseEvent ()
 {
     if (appData.debugMode)
@@ -13309,8 +13409,24 @@ PauseEvent ()
     if (pausing) {
        pausing = FALSE;
        ModeHighlight();
+       if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move
+           StartClocks();
+           if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering
+               if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other);
+               else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other);
+           }
+           if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine);
+           HandleMachineMove(stashedInputMove, stalledEngine);
+           stalledEngine = NULL;
+           return;
+       }
        if (gameMode == MachinePlaysWhite ||
-           gameMode == MachinePlaysBlack) {
+           gameMode == TwoMachinesPlay   ||
+           gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine
+           if(first.pause)  UnPauseEngine(&first);
+           else if(appData.ponderNextMove) SendToProgram("hard\n", &first);
+           if(second.pause) UnPauseEngine(&second);
+           else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second);
            StartClocks();
        } else {
            DisplayBothClocks();
@@ -13322,7 +13438,7 @@ PauseEvent ()
            Reset(FALSE, TRUE);
            SendToICS(ics_prefix);
            SendToICS("refresh\n");
-       } else if (currentMove < forwardMostMove) {
+       } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) {
            ForwardInner(forwardMostMove);
        }
        pauseExamInvalid = FALSE;
@@ -13353,12 +13469,30 @@ PauseEvent ()
          case TwoMachinesPlay:
            if (forwardMostMove == 0)
              return;           /* don't pause if no one has moved */
-           if ((gameMode == MachinePlaysWhite &&
-                !WhiteOnMove(forwardMostMove)) ||
-               (gameMode == MachinePlaysBlack &&
-                WhiteOnMove(forwardMostMove))) {
+           if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately
+               ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second);
+               if(onMove->pause) {           // thinking engine can be paused
+                   PauseEngine(onMove);      // do it
+                   if(onMove->other->pause)  // pondering opponent can always be paused immediately
+                       PauseEngine(onMove->other);
+                   else
+                       SendToProgram("easy\n", onMove->other);
+                   StopClocks();
+               } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder
+           } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
+               if(first.pause) {
+                   PauseEngine(&first);
+                   StopClocks();
+               } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder
+           } else { // human on move, pause pondering by either method
+               if(first.pause) 
+                   PauseEngine(&first);
+               else if(appData.ponderNextMove) 
+                   SendToProgram("easy\n", &first);
                StopClocks();
            }
+           // if no immediate pausing is possible, wait for engine to move, and stop clocks then
+         case AnalyzeMode:
            pausing = TRUE;
            ModeHighlight();
            break;
@@ -13408,18 +13542,59 @@ ToggleSecond ()
   }
 }
 
+/* Toggle ShowThinking */
 void
+ToggleShowThinking()
+{
+  appData.showThinking = !appData.showThinking;
+  ShowThinkingEvent();
+}
+
+int
 AnalyzeModeEvent ()
 {
-    if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
+    char buf[MSG_SIZ];
+
+    if (!first.analysisSupport) {
+      snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+      DisplayError(buf, 0);
+      return 0;
+    }
+    /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
+    if (appData.icsActive) {
+        if (gameMode != IcsObserving) {
+         snprintf(buf, MSG_SIZ, _("You are not observing a game"));
+            DisplayError(buf, 0);
+            /* secure check */
+            if (appData.icsEngineAnalyze) {
+                if (appData.debugMode)
+                    fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
+                ExitAnalyzeMode();
+                ModeHighlight();
+            }
+            return 0;
+        }
+        /* if enable, user wants to disable icsEngineAnalyze */
+        if (appData.icsEngineAnalyze) {
+                ExitAnalyzeMode();
+                ModeHighlight();
+                return 0;
+        }
+        appData.icsEngineAnalyze = TRUE;
+        if (appData.debugMode)
+            fprintf(debugFP, _("ICS engine analyze starting... \n"));
+    }
+
+    if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; }
     if (appData.noChessProgram || gameMode == AnalyzeMode)
-      return;
+      return 0;
 
     if (gameMode != AnalyzeFile) {
         if (!appData.icsEngineAnalyze) {
                EditGameEvent();
-               if (gameMode != EditGame) return;
+               if (gameMode != EditGame) return 0;
         }
+       if (!appData.showThinking) ToggleShowThinking();
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
@@ -13435,6 +13610,7 @@ AnalyzeModeEvent ()
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
+    return 1;
 }
 
 void
@@ -13443,9 +13619,19 @@ AnalyzeFileEvent ()
     if (appData.noChessProgram || gameMode == AnalyzeFile)
       return;
 
+    if (!first.analysisSupport) {
+      char buf[MSG_SIZ];
+      snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
+      DisplayError(buf, 0);
+      return;
+    }
+
     if (gameMode != AnalyzeMode) {
+       keepInfo = 1; // mere annotating should not alter PGN tags
        EditGameEvent();
+       keepInfo = 0;
        if (gameMode != EditGame) return;
+       if (!appData.showThinking) ToggleShowThinking();
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
@@ -13456,12 +13642,12 @@ AnalyzeFileEvent ()
     gameMode = AnalyzeFile;
     pausing = FALSE;
     ModeHighlight();
-    SetGameInfo();
 
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
+    AnalysisPeriodicEvent(1);
 }
 
 void
@@ -14930,6 +15116,40 @@ HintEvent ()
 }
 
 void
+CreateBookEvent ()
+{
+    ListGame * lg = (ListGame *) gameList.head;
+    FILE *f;
+    int nItem;
+    static int secondTime = FALSE;
+
+    if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+        DisplayError(_("Game list not loaded or empty"), 0);
+        return;
+    }
+
+    if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) {
+        fclose(f);
+       secondTime++;
+       DisplayNote(_("Book file exists! Try again for overwrite."));
+       return;
+    }
+
+    creatingBook = TRUE;
+    secondTime = FALSE;
+
+    /* Get list size */
+    for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){
+       LoadGame(f, nItem, "", TRUE);
+       AddGameToBook(TRUE);
+        lg = (ListGame *) lg->node.succ;
+    }
+
+    creatingBook = FALSE;
+    FlushBook();
+}
+
+void
 BookEvent ()
 {
     if (appData.noChessProgram) return;
@@ -15053,6 +15273,8 @@ SetGameInfo ()
     ChessMove r = GameUnfinished;
     char *p = NULL;
 
+    if(keepInfo) return;
+
     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
        r = gameInfo.result;
        p = gameInfo.resultDetails;
@@ -15801,7 +16023,7 @@ ParseFeatures (char *args, ChessProgramState *cps)
     if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue;
     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
-    if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
+    if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause
     if (IntFeature(&p, "done", &val, cps)) {
       FeatureDone(cps, val);
       continue;
@@ -17392,3 +17614,59 @@ LoadVariation (int index, char *text)
        ToNrEvent(currentMove+1);
 }
 
+void
+LoadTheme ()
+{
+    char *p, *q, buf[MSG_SIZ];
+    if(engineLine && engineLine[0]) { // a theme was selected from the listbox
+       snprintf(buf, MSG_SIZ, "-theme %s", engineLine);
+       ParseArgsFromString(buf);
+       ActivateTheme(TRUE); // also redo colors
+       return;
+    }
+    p = nickName;
+    if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list
+    {
+       int len;
+       q = appData.themeNames;
+       snprintf(buf, MSG_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, 
+               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
+      }
+      if(appData.useBorder) {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
+               appData.border);
+      } else {
+       snprintf(buf+strlen(buf), MSG_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",
+               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",
+               Col2Text(0),   // whitePieceColor
+               Col2Text(1) ); // blackPieceColor
+      }
+      snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n",
+               Col2Text(4),   // highlightSquareColor
+               Col2Text(5) ); // premoveHighlightColor
+       appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1);
+       if(insert != q) insert[-1] = NULLCHAR;
+       snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert);
+       if(q)   free(q);
+    }
+    ActivateTheme(FALSE);
+}