Docs update for new features
[xboard.git] / backend.c
old mode 100755 (executable)
new mode 100644 (file)
index 65f5177..173b537
--- a/backend.c
+++ b/backend.c
@@ -235,6 +235,8 @@ extern char installDir[MSG_SIZ];
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
+char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
+int endPV = -1;
 static int exiting = 0; /* [HGM] moved to top */
 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
@@ -247,6 +249,7 @@ int lastSavedGame; /* [HGM] save: ID of game */
 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
 extern int chatCount;
 int chattingPartner;
+char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -461,7 +464,7 @@ char *savedDetails[MAX_VARIATIONS];
 ChessMove savedResult[MAX_VARIATIONS];
 
 void PushTail P((int firstMove, int lastMove));
-Boolean PopTail P((void));
+Boolean PopTail P((Boolean annotate));
 void CleanupTail P((void));
 
 ChessSquare  FIDEArray[2][BOARD_FILES] = {
@@ -754,7 +757,7 @@ InitBackEnd1()
     /* [HGM] time odds: set factor for each machine */
     first.timeOdds  = appData.firstTimeOdds;
     second.timeOdds = appData.secondTimeOdds;
-    { int norm = 1;
+    { float norm = 1;
         if(appData.timeOddsMode) {
             norm = first.timeOdds;
             if(norm > second.timeOdds) norm = second.timeOdds;
@@ -2053,7 +2056,7 @@ read_from_ics(isr, closure, data, count, error)
      int count;
      int error;
 {
-#define BUF_SIZE 8192
+#define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
 #define STARTED_NONE 0
 #define STARTED_MOVES 1
 #define STARTED_BOARD 2
@@ -4360,7 +4363,8 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
        if (appData.testLegality) {
          return (*moveType != IllegalMove);
        } else {
-         return !(fromX == fromY && toX == toY);
+         return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && 
+                       WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
        }
 
       case WhiteDrop:
@@ -4395,6 +4399,108 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     }
 }
 
+
+void
+ParsePV(char *pv)
+{ // Parse a string of PV moves, and append to current game, behind forwardMostMove
+  int fromX, fromY, toX, toY; char promoChar;
+  ChessMove moveType;
+  Boolean valid;
+  int nr = 0;
+
+  endPV = forwardMostMove;
+  do {
+    while(*pv == ' ') pv++;
+    if(*pv == '(') pv++; // first (ponder) move can be in parentheses
+    valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
+if(appData.debugMode){
+fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
+}
+    if(!valid && nr == 0 &&
+       ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ 
+        nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
+    }
+    while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
+    if(moveType == Comment) { valid++; continue; } // allow comments in PV
+    nr++;
+    if(endPV+1 > framePtr) break; // no space, truncate
+    if(!valid) break;
+    endPV++;
+    CopyBoard(boards[endPV], boards[endPV-1]);
+    ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
+    moveList[endPV-1][0] = fromX + AAA;
+    moveList[endPV-1][1] = fromY + ONE;
+    moveList[endPV-1][2] = toX + AAA;
+    moveList[endPV-1][3] = toY + ONE;
+    parseList[endPV-1][0] = NULLCHAR;
+  } while(valid);
+  currentMove = endPV;
+  if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
+  SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
+                       moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
+  DrawPosition(TRUE, boards[currentMove]);
+}
+
+static int lastX, lastY;
+
+Boolean
+LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
+{
+       int startPV;
+
+       if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
+       lastX = x; lastY = y;
+       while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
+       startPV = index;
+      while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
+      index = startPV;
+       while(buf[index] && buf[index] != '\n') index++;
+       buf[index] = 0;
+       ParsePV(buf+startPV);
+       *start = startPV; *end = index-1;
+       return TRUE;
+}
+
+Boolean
+LoadPV(int x, int y)
+{ // called on right mouse click to load PV
+  int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
+  lastX = x; lastY = y;
+  ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
+  return TRUE;
+}
+
+void
+UnLoadPV()
+{
+  if(endPV < 0) return;
+  endPV = -1;
+  currentMove = forwardMostMove;
+  ClearPremoveHighlights();
+  DrawPosition(TRUE, boards[currentMove]);
+}
+
+void
+MovePV(int x, int y, int h)
+{ // step through PV based on mouse coordinates (called on mouse move)
+  int margin = h>>3, step = 0;
+
+  if(endPV < 0) return;
+  // we must somehow check if right button is still down (might be released off board!)
+  if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
+  if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
+  if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
+  if(!step) return;
+  lastX = x; lastY = y;
+  if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
+  currentMove += step;
+  if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
+  SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
+                       moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
+  DrawPosition(FALSE, boards[currentMove]);
+}
+
+
 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
 // All positions will have equal probability, but the current method will not provide a unique
 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
@@ -4518,12 +4624,12 @@ void SetUpShuffle(Board board, int number)
            // Last King gets castling rights
            while(piecesLeft[(int)WhiteUnicorn]) {
                i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
-               initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
+               initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
            }
 
            while(piecesLeft[(int)WhiteKing]) {
                i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
-               initialRights[2]  = initialRights[5]  = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
+               initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
            }
 
 
@@ -4538,9 +4644,9 @@ void SetUpShuffle(Board board, int number)
                if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
                        if(first) {
                                first=0;
-                               initialRights[1]  = initialRights[4]  = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
+                               initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
                        }
-                       initialRights[0]  = initialRights[3]  = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
+                       initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
                }
        }
        for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
@@ -5277,6 +5383,18 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
            return AmbiguousMove;
        } else if (toX >= 0 && toY >= 0) {
            boards[0][toY][toX] = boards[0][fromY][fromX];
+           if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
+               if(boards[0][fromY][0] != EmptySquare) {
+                   if(boards[0][fromY][1]) boards[0][fromY][1]--;
+                   if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare; 
+               }
+           } else
+           if(fromX == BOARD_RGHT+1) {
+               if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
+                   if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
+                   if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; 
+               }
+           } else
            boards[0][fromY][fromX] = EmptySquare;
            return AmbiguousMove;
        }
@@ -5350,7 +5468,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
 
     /* [HGM] convert drag-and-drop piece drops to standard form */
-    if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
           if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
                moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
@@ -5529,6 +5647,43 @@ if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", move
         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
+void
+Mark(board, flags, kind, rf, ff, rt, ft, closure)
+     Board board;
+     int flags;
+     ChessMove kind;
+     int rf, ff, rt, ft;
+     VOIDSTAR closure;
+{
+    typedef char Markers[BOARD_RANKS][BOARD_FILES];
+    Markers *m = (Markers *) closure;
+    if(rf == fromY && ff == fromX)
+       (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
+                        || kind == WhiteCapturesEnPassant
+                        || kind == BlackCapturesEnPassant);
+    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
+}
+
+void
+MarkTargetSquares(int clear)
+{
+  int x, y;
+  if(!appData.markers || !appData.highlightDragging || 
+     !appData.testLegality || gameMode == EditPosition) return;
+  if(clear) {
+    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
+  } else {
+    int capt = 0;
+    GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
+    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;
+    }
+  }
+  DrawPosition(TRUE, NULL);
+}
+
 void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
@@ -5537,6 +5692,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     char promoChoice = NULLCHAR;
 
     if (clickType == Press) ErrorPopDown();
+    MarkTargetSquares(1);
 
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
@@ -5582,6 +5738,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                fromX = x;
                fromY = y;
                second = 0;
+               MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
                if (appData.highlightDragging) {
                    SetHighlights(x, y, -1, -1);
@@ -5622,6 +5779,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            if (OKToStartUserMove(x, y)) {
                fromX = x;
                fromY = y;
+               MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
            }
            return;
@@ -5674,11 +5832,30 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        appData.animate = FALSE;
     }
 
-    // moves into holding are invalid for now (later perhaps allow in EditPosition)
+    // moves into holding are invalid for now (except in EditPosition, adapting to-square)
     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
+       ChessSquare piece = boards[currentMove][fromY][fromX];
+       if(gameMode == EditPosition && piece != EmptySquare &&
+          fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
+           int n;
+            
+           if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
+               n = PieceToNumber(piece - (int)BlackPawn);
+               if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
+               boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
+               boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
+           } else
+           if(x == BOARD_RGHT+1 && piece < BlackPawn) {
+               n = PieceToNumber(piece);
+               if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
+               boards[currentMove][n][BOARD_WIDTH-1] = piece;
+               boards[currentMove][n][BOARD_WIDTH-2]++;
+           }
+           boards[currentMove][fromY][fromX] = EmptySquare;
+       }
        ClearHighlights();
        fromX = fromY = -1;
-       DrawPosition(TRUE, NULL);
+       DrawPosition(TRUE, boards[currentMove]);
        return;
     }
 
@@ -5735,6 +5912,8 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
         stats.an_move_count = cpstats->nr_moves;
     }
 
+    if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
+
     SetProgramStats( &stats );
 }
 
@@ -5796,6 +5975,8 @@ HandleMachineMove(message, cps)
     int machineWhite;
     char *bookHit;
 
+    cps->userError = 0;
+
 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
     /*
      * Kludge to ignore BEL characters
@@ -6468,6 +6649,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        return;
     }
     if (!strncmp(message, "tellusererror ", 14)) {
+       cps->userError = 1;
        DisplayError(message + 14, 0);
        return;
     }
@@ -6931,7 +7113,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
                 if( cps->scoreIsAbsolute && 
-                    ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
+                    ( gameMode == MachinePlaysBlack ||
+                      gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
+                      gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
+                     (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
+                     !WhiteOnMove(currentMove)
+                    ) )
                 {
                     curscore = -curscore;
                 }
@@ -7700,6 +7887,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
                        0, 1);
       return;
     }
+    UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
     if (commentList[forwardMostMove+1] != NULL) {
        free(commentList[forwardMostMove+1]);
        commentList[forwardMostMove+1] = NULL;
@@ -9862,7 +10050,7 @@ SaveGamePGN(f)
        /* Print comments preceding this move */
        if (commentList[i] != NULL) {
            if (linelen > 0) fprintf(f, "\n");
-           fprintf(f, "%s\n", commentList[i]);
+           fprintf(f, "%s", commentList[i]);
            linelen = 0;
            newblock = TRUE;
        }
@@ -9915,17 +10103,9 @@ SaveGamePGN(f)
         /* [AS] Add PV info if present */
         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
             /* [HGM] add time */
-            char buf[MSG_SIZ]; int seconds = 0;
+            char buf[MSG_SIZ]; int seconds;
 
-            if(i >= backwardMostMove) {
-               if(WhiteOnMove(i))
-                       seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
-                                 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
-               else
-                       seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
-                                  + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
-            }
-            seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
+            seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
 
             if( seconds <= 0) buf[0] = 0; else
             if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
@@ -10095,6 +10275,7 @@ SavePosition(f, dummy, dummy2)
     time_t tm;
     char *fen;
     
+    if (gameMode == EditPosition) EditPositionDone(TRUE);
     if (appData.oldSaveStyle) {
        tm = time((time_t *) NULL);
     
@@ -11303,9 +11484,20 @@ EditPositionMenuEvent(selection, x, y)
 
       case EmptySquare:
        if (gameMode == IcsExamining) {
+            if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
             sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
            SendToICS(buf);
        } else {
+            if(x < BOARD_LEFT || x >= BOARD_RGHT) {
+                if(x == BOARD_LEFT-2) {
+                    if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
+                    boards[0][y][1] = 0;
+                } else
+                if(x == BOARD_RGHT+1) {
+                    if(y >= gameInfo.holdingsSize) break;
+                    boards[0][y][BOARD_WIDTH-2] = 0;
+                } else break;
+            }
            boards[0][y][x] = EmptySquare;
            DrawPosition(FALSE, boards[0]);
        }
@@ -11344,12 +11536,28 @@ EditPositionMenuEvent(selection, x, y)
       default:
         defaultlabel:
        if (gameMode == IcsExamining) {
+            if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
            sprintf(buf, "%s%c@%c%c\n", ics_prefix,
                     PieceToChar(selection), AAA + x, ONE + y);
            SendToICS(buf);
        } else {
+            if(x < BOARD_LEFT || x >= BOARD_RGHT) {
+                int n;
+                if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
+                    n = PieceToNumber(selection - BlackPawn);
+                    if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
+                    boards[0][BOARD_HEIGHT-1-n][0] = selection;
+                    boards[0][BOARD_HEIGHT-1-n][1]++;
+                } else
+                if(x == BOARD_RGHT+1 && selection < BlackPawn) {
+                    n = PieceToNumber(selection);
+                    if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
+                    boards[0][n][BOARD_WIDTH-1] = selection;
+                    boards[0][n][BOARD_WIDTH-2]++;
+                }
+            } else
            boards[0][y][x] = selection;
-           DrawPosition(FALSE, boards[0]);
+           DrawPosition(TRUE, boards[0]);
        }
        break;
     }
@@ -11837,7 +12045,7 @@ ToNrEvent(int to)
 void
 RevertEvent()
 {
-    if(PopTail()) { // [HGM] vari: restore old game tail
+    if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
        return;
     }
     if (gameMode != IcsExamining) {
@@ -12100,6 +12308,14 @@ SetGameInfo()
 {
     /* This routine is used only for certain modes */
     VariantClass v = gameInfo.variant;
+    ChessMove r = GameUnfinished;
+    char *p = NULL;
+
+    if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
+       r = gameInfo.result; 
+       p = gameInfo.resultDetails; 
+       gameInfo.resultDetails = NULL;
+    }
     ClearGameInfo(&gameInfo);
     gameInfo.variant = v;
 
@@ -12152,6 +12368,8 @@ SetGameInfo()
        gameInfo.round = StrSave("-");
        gameInfo.white = StrSave("-");
        gameInfo.black = StrSave("-");
+       gameInfo.result = r;
+       gameInfo.resultDetails = p;
        break;
 
       case EditPosition:
@@ -12201,7 +12419,9 @@ ReplaceComment(index, text)
        commentList[index] = NULL;
        return;
     }
-  if(*text == '{' || *text == '(' || *text == '[') {
+  if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
+      *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
+      *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
     commentList[index] = (char *) malloc(len + 2);
     strncpy(commentList[index], text, len);
     commentList[index][len] = '\n';
@@ -12211,9 +12431,10 @@ ReplaceComment(index, text)
     char *p;
     commentList[index] = (char *) malloc(len + 6);
     strcpy(commentList[index], "{\n");
-    strcat(commentList[index], text);
+    strncpy(commentList[index]+2, text, len);
+    commentList[index][len+2] = NULLCHAR;
     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
-    strcat(commentList[index], "\n}");
+    strcat(commentList[index], "\n}\n");
   }
 }
 
@@ -12254,25 +12475,30 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
     if (commentList[index] != NULL) {
        old = commentList[index];
        oldlen = strlen(old);
-       commentList[index] = (char *) malloc(oldlen + len + 4); // might waste 2
+       while(commentList[index][oldlen-1] ==  '\n')
+         commentList[index][--oldlen] = NULLCHAR;
+       commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
        strcpy(commentList[index], old);
        free(old);
-       // [HGM] braces: join "{A\n}" + "{B}" as "{A\nB\n}"
+       // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
        if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
          if(addBraces) addBraces = FALSE; else { text++; len--; }
          while (*text == '\n') { text++; len--; }
-         commentList[index][oldlen-1] = NULLCHAR;
-         oldlen--;
+         commentList[index][--oldlen] = NULLCHAR;
       }
-       strncpy(&commentList[index][oldlen], text, len);
-       if(addBraces) strcpy(&commentList[index][oldlen + len], "\n}");
-       else          strcpy(&commentList[index][oldlen + len], "\n");
+       if(addBraces) strcat(commentList[index], "\n{\n");
+       else          strcat(commentList[index], "\n");
+       strcat(commentList[index], text);
+       if(addBraces) strcat(commentList[index], "\n}\n");
+       else          strcat(commentList[index], "\n");
     } else {
-       commentList[index] = (char *) malloc(len + 4); // perhaps wastes 2...
-       if(addBraces) commentList[index][0] = '{';
-       strcpy(commentList[index] + addBraces, text);
+       commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
+       if(addBraces)
+            strcpy(commentList[index], "{\n");
+       else commentList[index][0] = NULLCHAR;
+       strcat(commentList[index], text);
        strcat(commentList[index], "\n");
-       if(addBraces) strcat(commentList[index], "}");
+       if(addBraces) strcat(commentList[index], "}\n");
     }
 }
 
@@ -12404,9 +12630,9 @@ SendToProgram(message, cps)
             } else {
                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
             }
-            gameInfo.resultDetails = buf;
+            gameInfo.resultDetails = StrSave(buf);
         }
-        DisplayFatalError(buf, error, 1);
+        if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
     }
 }
 
@@ -12435,10 +12661,10 @@ ReceiveFromProgram(isr, closure, message, count, error)
                 } else {
                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
                 }
-                gameInfo.resultDetails = buf;
+                gameInfo.resultDetails = StrSave(buf);
             }
            RemoveInputSource(cps->isr);
-           DisplayFatalError(buf, 0, 1);
+           if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
        } else {
            sprintf(buf,
                    _("Error reading from %s chess program (%s)"),
@@ -12451,7 +12677,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
                 cps->pr = NoProc;
             }
 
-            DisplayFatalError(buf, error, 1);
+            if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
        }
        return;
     }
@@ -12596,7 +12822,7 @@ SendTimeRemaining(cps, machineWhite)
     /* [HGM] translate opponent's time by time-odds factor */
     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
     if (appData.debugMode) {
-        fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
+        fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
     }
 
     if (time <= 0) time = 1;
@@ -13288,12 +13514,12 @@ DecrementClocks()
        if(whiteNPS >= 0) lastTickLength = 0;
        timeRemaining = whiteTimeRemaining -= lastTickLength;
        DisplayWhiteClock(whiteTimeRemaining - fudge,
-                         WhiteOnMove(currentMove));
+                         WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
        timeRemaining = blackTimeRemaining -= lastTickLength;
        DisplayBlackClock(blackTimeRemaining - fudge,
-                         !WhiteOnMove(currentMove));
+                         !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
     }
 
     if (CheckFlags()) return;
@@ -13686,7 +13912,7 @@ PositionToFEN(move, overrideCastling)
     *p++ = ' ';
 
   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
-    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
+    while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
   if(nrCastlingRights) {
      q = p;
@@ -13896,12 +14122,12 @@ ParseFEN(board, blackPlaysFirst, fen)
         board[CASTLING][i] =
             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
     }   /* assume possible unless obviously impossible */
-    if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
-    if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
-    if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
-    if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
-    if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
-    if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
+    if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
+    if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
+    if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
+    if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
+    if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
+    if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
     FENrulePlies = 0;
 
     while(*p==' ') p++;
@@ -14173,10 +14399,11 @@ PushTail(int firstMove, int lastMove)
        if(storedGames == 1) GreyRevert(FALSE);
 }
 
-Boolean 
-PopTail()
+Boolean
+PopTail(Boolean annotate)
 {
        int i, j, nrMoves;
+       char buf[8000], moveBuf[20];
 
        if(appData.icsActive) return FALSE; // only in local mode
        if(!storedGames) return FALSE; // sanity
@@ -14184,6 +14411,19 @@ PopTail()
        storedGames--;
        ToNrEvent(savedFirst[storedGames]); // sets currentMove
        nrMoves = savedLast[storedGames] - currentMove;
+       if(annotate) {
+               int cnt = 10;
+               if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
+               else strcpy(buf, "(");
+               for(i=currentMove; i<forwardMostMove; i++) {
+                       if(WhiteOnMove(i))
+                            sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
+                       else sprintf(moveBuf, " %s", SavePart(parseList[i]));
+                       strcat(buf, moveBuf);
+                       if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
+               }
+               strcat(buf, ")");
+       }
        for(i=1; i<nrMoves; i++) { // copy last variation back
            CopyBoard(boards[currentMove+i], boards[framePtr+i]);
            for(j=0; j<MOVE_LEN; j++)
@@ -14197,6 +14437,7 @@ PopTail()
            commentList[currentMove+i] = commentList[framePtr+i];
            commentList[framePtr+i] = NULL;
        }
+       if(annotate) AppendComment(currentMove+1, buf, FALSE);
        framePtr = savedFramePtr[storedGames];
        gameInfo.result = savedResult[storedGames];
        if(gameInfo.resultDetails != NULL) {