Implement use of pause / resume protocol commands
[xboard.git] / backend.c
index 7e8ed59..7f9b664 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
@@ -1844,6 +1845,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) {
@@ -1852,6 +1854,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);
@@ -2513,9 +2526,7 @@ PlotSeekAd (int i)
 void
 PlotSingleSeekAd (int i)
 {
-       DrawSeekOpen();
        PlotSeekAd(i);
-       DrawSeekClose();
 }
 
 void
@@ -2602,14 +2613,13 @@ DrawSeekGraph ()
     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
 
-    DrawSeekOpen();
     DrawSeekBackground(0, 0, w, h);
     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
     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);
@@ -2627,7 +2637,6 @@ DrawSeekGraph ()
        }
     }
     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
-    DrawSeekClose();
     return TRUE;
 }
 
@@ -3441,9 +3450,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;
            }
 
@@ -6755,7 +6770,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
@@ -8023,6 +8038,8 @@ DeferredBookMove (void)
 }
 
 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+static ChessProgramState *stalledEngine;
+static char stashedInputMove[MSG_SIZ];
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
@@ -8088,6 +8105,18 @@ 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 both engines out of ponder
+               SendToProgram("easy\n", &first);
+               if(gameMode == TwoMachinesPlay) SendToProgram("easy\n", &second);
+           }
+           StopClocks();
+           return;
+       }
+
         /* This method is only useful on engines that support ping */
         if (cps->lastPing != cps->lastPong) {
          if (gameMode == BeginningOfGame) {
@@ -9411,8 +9440,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)
@@ -9472,8 +9501,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)
@@ -9733,12 +9762,12 @@ ShowMove (int fromX, int fromY, int toX, int toY)
     if (instant) return;
 
     DisplayMove(currentMove - 1);
-    DrawPosition(FALSE, boards[currentMove]);
     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);
 }
@@ -10489,6 +10518,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
@@ -10632,6 +10663,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
                }
            }
 
@@ -11764,6 +11796,7 @@ InitSearch ()
 }
 
 GameInfo dummyInfo;
+static int creatingBook;
 
 int
 GameContainsPosition (FILE *f, ListGame *lg)
@@ -12227,6 +12260,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        cm = (ChessMove) Myylex();
     }
 
+  if(!creatingBook) {
     if (first.pr == NoProc) {
        StartChessProgram(&first);
     }
@@ -12239,6 +12273,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;
@@ -12308,6 +12343,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
       keepInfo = 0;
     }
 
+    if(creatingBook) return TRUE;
     if (!matchMode && pos > 0) {
        ToNrEvent(pos); // [HGM] no autoplay if selected on position
     } else
@@ -13342,6 +13378,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)
@@ -13349,8 +13399,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) 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();
@@ -13393,12 +13459,29 @@ 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(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move
+               if(first.pause) {
+                   PauseEngine(&first);
+                   StopClocks();
+               }
+           } else { // human on move, pause pondering by either method
+               if(first.pause) 
+                   PauseEngine(&first);
+               else
+                   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();
@@ -13449,18 +13532,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;
@@ -13476,6 +13600,7 @@ AnalyzeModeEvent ()
     StartAnalysisClock();
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
+    return 1;
 }
 
 void
@@ -13484,9 +13609,17 @@ 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) {
        EditGameEvent();
        if (gameMode != EditGame) return;
+       if (!appData.showThinking) ToggleShowThinking();
        ResurrectChessProgram();
        SendToProgram("analyze\n", &first);
        first.analyzing = TRUE;
@@ -13503,6 +13636,7 @@ AnalyzeFileEvent ()
     GetTimeMark(&lastNodeCountTime);
     lastNodeCount = 0;
     if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay));
+    AnalysisPeriodicEvent(1);
 }
 
 void
@@ -14971,6 +15105,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;
@@ -15844,7 +16012,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;
@@ -17435,3 +17603,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);
+}