Updated copyright notice to 2011
[xboard.git] / backend.c
index 0026207..6eba7f6 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,7 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -318,11 +318,11 @@ safeStrCpy( char *dst, const char *src, size_t count )
   assert( count > 0 );
 
   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
-  if(  i == count-1 && dst[i] != NULLCHAR)
+  if(  i == count && dst[count-1] != NULLCHAR)
     {
       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
       if(appData.debugMode)
-      printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
+      fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
     }
 
   return dst;
@@ -448,9 +448,6 @@ long lastNodeCount=0;
 int shiftKey; // [HGM] set by mouse handler
 
 int have_sent_ICS_logon = 0;
-int sending_ICS_login    = 0;
-int sending_ICS_password = 0;
-
 int movesPerSession;
 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
@@ -519,11 +516,18 @@ ChessSquare  KnightmateArray[2][BOARD_FILES] = {
         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
 };
 
+ChessSquare SpartanArray[2][BOARD_FILES] = {
+    { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
+        WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+    { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
+        BlackDragon, BlackKing, BlackAngel, BlackAlfil }
+};
+
 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
-    { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
-       BlackKing, BlackMarshall, BlackAlfil, BlackLance }
+    { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
+       BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
 };
 
 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
@@ -957,6 +961,8 @@ InitBackEnd1()
       case VariantJanus:      /* should work */
       case VariantSuper:      /* experimental */
       case VariantGreat:      /* experimental, requires legality testing to be off */
+      case VariantSChess:     /* S-Chess, should work */
+      case VariantSpartan:    /* should work */
        break;
       }
     }
@@ -1088,16 +1094,16 @@ ParseTimeControl(tc, ti, mps)
 
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
-    else
+    else 
       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
   } else {
     if(mps)
       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
-    else
+    else 
       snprintf(buf, MSG_SIZ, ":%s", mytc);
   }
   fullTimeControlString = StrSave(buf); // this should now be in PGN format
-
+  
   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
     return FALSE;
   }
@@ -3115,23 +3121,8 @@ read_from_ics(isr, closure, data, count, error)
            if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
                ICSInitScript();
                have_sent_ICS_logon = 1;
-               /* if we don't send the login/password via icsLogon, use special readline
-                  code for it */
-               if (strlen(appData.icsLogon)==0)
-                 {
-                   sending_ICS_password = 0; // in case we come back to login
-                   sending_ICS_login = 1;
-                 };
                continue;
            }
-           /* need to shadow the password */
-           if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
-             /* if we don't send the login/password via icsLogon, use special readline
-                code for it */
-             if (strlen(appData.icsLogon)==0)
-               sending_ICS_password = 1;
-             continue;
-           }
 
            if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
                (looking_at(buf, &i, "\n<12> ") ||
@@ -3854,7 +3845,7 @@ ParseBoard12(string)
     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
     char to_play, board_chars[200];
-    char move_str[500], str[500], elapsed_time[500];
+    char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
     char black[32], white[32];
     Board board;
     int prevMove = currentMove;
@@ -3895,7 +3886,7 @@ ParseBoard12(string)
               &ticking);
 
     if (n < 21) {
-        snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
+        snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
        DisplayError(str, 0);
        return;
     }
@@ -4344,7 +4335,7 @@ ParseBoard12(string)
          if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
                if(appData.debugMode)
                        fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
-               safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
+               safeStrCpy(move_str, buf, MSG_SIZ);
           }
          valid = ParseOneMove(move_str, moveNum - 1, &moveType,
                                &fromX, &fromY, &toX, &toY, &promoChar)
@@ -5008,6 +4999,9 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
     moveList[endPV-1][1] = fromY + ONE;
     moveList[endPV-1][2] = toX + AAA;
     moveList[endPV-1][3] = toY + ONE;
+    moveList[endPV-1][4] = promoChar;
+    moveList[endPV-1][5] = NULLCHAR;
+    strncat(moveList[endPV-1], "\n", MOVE_LEN);
     if(storeComments)
        CoordsToAlgebraic(boards[endPV - 1],
                             PosFlags(endPV - 1),
@@ -5309,8 +5303,8 @@ InitPosition(redraw)
     int i, j, pawnRow, overrule,
     oldx = gameInfo.boardWidth,
     oldy = gameInfo.boardHeight,
-    oldh = gameInfo.holdingsWidth,
-    oldv = gameInfo.variant;
+    oldh = gameInfo.holdingsWidth;
+    static int oldv;
 
     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
 
@@ -5348,12 +5342,12 @@ InitPosition(redraw)
     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
          SetCharTable(pieceNickName, appData.pieceNickNames);
     else SetCharTable(pieceNickName, "............");
+    pieces = FIDEArray;
 
     switch (gameInfo.variant) {
     case VariantFischeRandom:
       shuffleOpenings = TRUE;
     default:
-      pieces = FIDEArray;
       break;
     case VariantShatranj:
       pieces = ShatranjArray;
@@ -5381,6 +5375,10 @@ InitPosition(redraw)
       gameInfo.boardWidth = 10;
       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
       break;
+    case VariantSChess:
+      SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
+      gameInfo.holdingsSize = 7;
+      break;
     case VariantJanus:
       pieces = JanusArray;
       gameInfo.boardWidth = 10;
@@ -5423,6 +5421,10 @@ InitPosition(redraw)
       pieces = KnightmateArray;
       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
       break;
+    case VariantSpartan:
+      pieces = SpartanArray;
+      SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
+      break;
     case VariantFairy:
       pieces = fairyArray;
       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
@@ -5493,7 +5495,7 @@ InitPosition(redraw)
         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
         initialPosition[pawnRow][j] = WhitePawn;
-        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+        initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
         if(gameInfo.variant == VariantXiangqi) {
             if(j&1) {
                 initialPosition[pawnRow][j] =
@@ -5537,6 +5539,14 @@ InitPosition(redraw)
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
        initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
      }
+     if( gameInfo.variant == VariantSChess ) {
+      initialPosition[1][0] = BlackMarshall;
+      initialPosition[2][0] = BlackAngel;
+      initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
+      initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
+      initialPosition[1][1] = initialPosition[2][1] = 
+      initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
+     }
   if (appData.debugMode) {
     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
   }
@@ -5556,18 +5566,12 @@ InitPosition(redraw)
 
     if(oldx != gameInfo.boardWidth ||
        oldy != gameInfo.boardHeight ||
+       oldv != gameInfo.variant ||
        oldh != gameInfo.holdingsWidth
-#ifdef GOTHIC
-       || oldv == VariantGothic ||        // For licensing popups
-       gameInfo.variant == VariantGothic
-#endif
-#ifdef FALCON
-       || oldv == VariantFalcon ||
-       gameInfo.variant == VariantFalcon
-#endif
                                          )
             InitDrawingSizes(-2 ,0);
 
+    oldv = gameInfo.variant;
     if (redraw)
       DrawPosition(TRUE, boards[currentMove]);
 }
@@ -5670,6 +5674,12 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
         promotionZoneSize = 3;
     }
 
+    // Treat Lance as Pawn when it is not representing Amazon
+    if(gameInfo.variant != VariantSuper) {
+        if(piece == WhiteLance) piece = WhitePawn; else
+        if(piece == BlackLance) piece = BlackPawn;
+    }
+
     // next weed out all moves that do not touch the promotion zone at all
     if((int)piece >= BlackPawn) {
         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
@@ -5975,6 +5985,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
       case BeginningOfGame:
       case AnalyzeMode:
       case Training:
+       if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
        if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
            /* User is moving for Black */
@@ -6069,15 +6080,15 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
     pup = boards[currentMove][toY][toX];
 
     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
-    if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
          if( pup != EmptySquare ) return;
          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
-          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
+          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]);
           // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
           if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
           fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
-          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
+          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
          fromY = DROP_RANK;
     }
 
@@ -6232,6 +6243,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   switch (gameMode) {
   case EditGame:
+    if(appData.testLegality)
     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
     case MT_NONE:
     case MT_CHECK:
@@ -6327,6 +6339,8 @@ Explode(Board board, int fromX, int fromY, int toX, int toY)
     return FALSE;
 }
 
+ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
+
 void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
@@ -6383,8 +6397,15 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     autoQueen = appData.alwaysPromoteToQueen;
 
     if (fromX == -1) {
+      gatingPiece = EmptySquare;
+      if (clickType != Press) {
+       if(dragging) { // [HGM] from-square must have been reset due to game end since last press
+           DragPieceEnd(xPix, yPix); dragging = 0;
+           DrawPosition(FALSE, NULL);
+       }
+       return;
+      }
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
-       if (clickType == Press) {
            /* First square */
            if (OKToStartUserMove(x, y)) {
                fromX = x;
@@ -6396,12 +6417,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                    SetHighlights(x, y, -1, -1);
                }
            }
-       } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
-           DragPieceEnd(xPix, yPix); dragging = 0;
-           DrawPosition(FALSE, NULL);
+           return;
        }
-       return;
-      }
     }
 
     /* fromX != -1 */
@@ -6416,7 +6433,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        /* Check if clicking again on the same color piece */
        fromP = boards[currentMove][fromY][fromX];
        toP = boards[currentMove][y][x];
-       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
+       frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
        if ((WhitePawn <= fromP && fromP <= WhiteKing &&
             WhitePawn <= toP && toP <= WhiteKing &&
             !(fromP == WhiteKing && toP == WhiteRook && frc) &&
@@ -6434,6 +6451,11 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                ClearHighlights();
            }
            if (OKToStartUserMove(x, y)) {
+               if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
+                 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
+               y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
+                 gatingPiece = boards[currentMove][fromY][fromX];
+               else gatingPiece = EmptySquare;
                fromX = x;
                fromY = y; dragging = 1;
                MarkTargetSquares(0);
@@ -6457,6 +6479,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            /* Second up/down in same square; just abort move */
            second = 0;
            fromX = fromY = -1;
+           gatingPiece = EmptySquare;
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
@@ -6521,6 +6544,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     // off-board moves should not be highlighted
     if(x < 0 || y < 0) ClearHighlights();
 
+    if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
+
     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
        SetHighlights(fromX, fromY, toX, toY);
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
@@ -7305,11 +7330,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if (cps->sendTime == 2) cps->sendTime = 1;
        if (cps->offeredDraw) cps->offeredDraw--;
 
-       /* currentMoveString is set as a side-effect of ParseOneMove */
-       safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
-       strcat(machineMove, "\n");
-       safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
-
         /* [AS] Save move info*/
         pvInfoList[ forwardMostMove ].score = programStats.score;
         pvInfoList[ forwardMostMove ].depth = programStats.depth;
@@ -7346,7 +7366,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             }
         }
 
-       if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
+       if(Adjudicate(cps)) {
+           DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
+           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+           return; // [HGM] adjudicate: for all automatic game ends
+       }
 
 #if ZIPPY
        if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
@@ -7668,6 +7692,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            gameMode = EditGame;
            ModeHighlight();
        }
+        /* [HGM] illegal-move claim should forfeit game when Xboard */
+        /* only passes fully legal moves                            */
+        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
+            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
+                                "False illegal-move claim", GE_XBOARD );
+            return; // do not take back move we tested as valid
+        }
        currentMove = forwardMostMove-1;
        DisplayMove(currentMove-1); /* before DisplayMoveError */
        SwitchClocks(forwardMostMove-1); // [HGM] race
@@ -7676,13 +7707,6 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                parseList[currentMove], cps->which);
        DisplayMoveError(buf1);
        DrawPosition(FALSE, boards[currentMove]);
-
-        /* [HGM] illegal-move claim should forfeit game when Xboard */
-        /* only passes fully legal moves                            */
-        if( appData.testLegality && gameMode == TwoMachinesPlay ) {
-            GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
-                                "False illegal-move claim", GE_XBOARD );
-        }
        return;
     }
     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
@@ -8332,6 +8356,7 @@ ParseGameHistory(game)
                if (q != NULL) *q = NULLCHAR;
                p++;
            }
+           while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
            gameInfo.resultDetails = StrSave(p);
            continue;
        }
@@ -8393,6 +8418,10 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
   } else {
       int i;
 
+      if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
+           if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
+               board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
+      } else
       if( board[fromY][fromX] == WhitePawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
@@ -8432,21 +8461,21 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
          king += (int) WhiteUnicorn - (int) WhiteKing;
 
     /* Code added by Tord: */
-    /* FRC castling assumed when king captures friendly rook. */
-    if (board[fromY][fromX] == WhiteKing &&
-            board[toY][toX] == WhiteRook) {
+    /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
+    if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
+        board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
-      if(toX > fromX) {
+      if((toX > fromX) != (piece == WhiteRook)) {
         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
       } else {
         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
       }
-    } else if (board[fromY][fromX] == BlackKing &&
-              board[toY][toX] == BlackRook) {
+    } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
+               board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
       board[fromY][fromX] = EmptySquare;
       board[toY][toX] = EmptySquare;
-      if(toX > fromX) {
+      if((toX > fromX) != (piece == BlackRook)) {
         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
       } else {
         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
@@ -8467,9 +8496,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         board[toY][toX] = king;
         board[toY][toX+1] = board[fromY][BOARD_LEFT];
         board[fromY][BOARD_LEFT] = EmptySquare;
-    } else if (board[fromY][fromX] == WhitePawn
+    } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
+                board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
                && toY >= BOARD_HEIGHT-promoRank
-               && gameInfo.variant != VariantXiangqi
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
@@ -8531,9 +8560,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = BlackKing;
        board[fromY][0] = EmptySquare;
        board[toY][2] = BlackRook;
-    } else if (board[fromY][fromX] == BlackPawn
+    } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
+                board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
               && toY < promoRank
-               && gameInfo.variant != VariantXiangqi
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
@@ -8579,10 +8608,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       /* [HGM] OK, so I have written it. Holdings are stored in the */
       /* penultimate board files, so they are automaticlly stored   */
       /* in the game history.                                       */
-      if (fromY == DROP_RANK) {
+      if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
+                                && promoChar && piece != WhitePawn && piece != BlackPawn) {
         /* Delete from holdings, by decreasing count */
         /* and erasing image if necessary            */
-        p = (int) fromX;
+        p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
         if(p < (int) BlackPawn) { /* white drop */
              p -= (int)WhitePawn;
                 p = PieceToNumber((ChessSquare)p);
@@ -8602,7 +8632,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
         }
       }
       if (captured != EmptySquare && gameInfo.holdingsSize > 0
-          && gameInfo.variant != VariantBughouse        ) {
+          && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
         /* [HGM] holdings: Add to holdings, if holdings exist */
        if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
                // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
@@ -8646,14 +8676,17 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = EmptySquare;
       }
     }
+    if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
+        board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
+    } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
         board[toY][toX] = (ChessSquare) (PROMOTED piece);
     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
     }
-    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
-               && promoChar != NULLCHAR && gameInfo.holdingsSize) {
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
+               && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
        // [HGM] superchess: take promotion piece out of holdings
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
        if((int)piece < (int)BlackPawn) { // determine stm from piece color
@@ -8878,6 +8911,8 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
       if( gameInfo.variant == VariantGreat )
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
+      if( gameInfo.variant == VariantSChess )
+           overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
 
       if(overruled) {
        snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
@@ -9420,6 +9455,13 @@ GameEnds(result, resultDetails, whosays)
                     first.matchWins, second.matchWins,
                     appData.matchGames - (first.matchWins + second.matchWins));
            popupRequested++; // [HGM] crash: postpone to after resetting endingGame
+           if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
+               first.twoMachinesColor = "black\n";
+               second.twoMachinesColor = "white\n";
+           } else {
+               first.twoMachinesColor = "white\n";
+               second.twoMachinesColor = "black\n";
+           }
        }
     }
     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
@@ -9589,7 +9631,7 @@ AutoPlayGameLoop()
          return;
        if (matchMode || appData.timeDelay == 0)
          continue;
-       if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
+       if (appData.timeDelay < 0)
          return;
        StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
        break;
@@ -9606,10 +9648,18 @@ AutoPlayOneMove()
       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
     }
 
-    if (gameMode != PlayFromGameFile)
+    if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
       return FALSE;
 
+    if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
+      pvInfoList[currentMove].depth = programStats.depth;
+      pvInfoList[currentMove].score = programStats.score;
+      pvInfoList[currentMove].time  = 0;
+      if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
+    }
+
     if (currentMove >= forwardMostMove) {
+      if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
       gameMode = EditGame;
       ModeHighlight();
 
@@ -9742,6 +9792,7 @@ LoadGameOneMove(readAhead)
            if (q != NULL) *q = NULLCHAR;
            p++;
        }
+       while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
        GameEnds(moveType, p, GE_FILE);
        done = TRUE;
        if (cmailMsgLoaded) {
@@ -9874,8 +9925,6 @@ LoadGameOneMove(readAhead)
        return FALSE;
     } else {
        /* currentMoveString is set as a side-effect of yylex */
-       strcat(currentMoveString, "\n");
-       safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
@@ -11614,7 +11663,7 @@ void
 EditTagsEvent()
 {
     char *tags = PGNTags(&gameInfo);
-    EditTagsPopUp(tags);
+    EditTagsPopUp(tags, NULL);
     free(tags);
 }
 
@@ -12567,6 +12616,30 @@ CallFlagEvent()
 }
 
 void
+ClockClick(int which)
+{      // [HGM] code moved to back-end from winboard.c
+       if(which) { // black clock
+         if (gameMode == EditPosition || gameMode == IcsExamining) {
+           SetBlackToPlayEvent();
+         } else if (gameMode == EditGame || shiftKey) {
+           AdjustClock(which, -1);
+         } else if (gameMode == IcsPlayingWhite ||
+                    gameMode == MachinePlaysBlack) {
+           CallFlagEvent();
+         }
+       } else { // white clock
+         if (gameMode == EditPosition || gameMode == IcsExamining) {
+           SetWhiteToPlayEvent();
+         } else if (gameMode == EditGame || shiftKey) {
+           AdjustClock(which, -1);
+         } else if (gameMode == IcsPlayingBlack ||
+                  gameMode == MachinePlaysWhite) {
+           CallFlagEvent();
+         }
+       }
+}
+
+void
 DrawEvent()
 {
     /* Offer draw or accept pending draw offer from opponent */
@@ -13270,7 +13343,13 @@ ReplaceComment(index, text)
      char *text;
 {
     int len;
+    char *p;
+    float score;
 
+    if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
+       pvInfoList[index-1].depth == len &&
+       fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
+       (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
     while (*text == '\n') text++;
     len = strlen(text);
     while (len > 0 && text[len - 1] == '\n') len--;
@@ -13341,27 +13420,27 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces);
        while(commentList[index][oldlen-1] ==  '\n')
          commentList[index][--oldlen] = NULLCHAR;
        commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
-       safeStrCpy(commentList[index], old, oldlen);
+       safeStrCpy(commentList[index], old, oldlen + len + 6);
        free(old);
        // [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--; }
+       if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
+         if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
          while (*text == '\n') { text++; len--; }
          commentList[index][--oldlen] = NULLCHAR;
       }
-       if(addBraces) strcat(commentList[index], "\n{\n");
+       if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
        else          strcat(commentList[index], "\n");
        strcat(commentList[index], text);
-       if(addBraces) strcat(commentList[index], "\n}\n");
+       if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
        else          strcat(commentList[index], "\n");
     } else {
        commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
        if(addBraces)
-         safeStrCpy(commentList[index], "{\n", 3);
+         safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
        else commentList[index][0] = NULLCHAR;
        strcat(commentList[index], text);
-       strcat(commentList[index], "\n");
-       if(addBraces) strcat(commentList[index], "}\n");
+       strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
+       if(addBraces == TRUE) strcat(commentList[index], "}\n");
     }
 }
 
@@ -13380,7 +13459,7 @@ static char * FindStr( char * text, char * sub_text )
 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
 char *GetInfoFromComment( int index, char * text )
 {
-    char * sep = text;
+    char * sep = text, *p;
 
     if( text != NULL && index > 0 ) {
         int score = 0;
@@ -13418,11 +13497,20 @@ char *GetInfoFromComment( int index, char * text )
                 return text;
             }
 
+            p = text;
+            if(p[1] == '(') { // comment starts with PV
+               p = strchr(p, ')'); // locate end of PV
+               if(p == NULL || sep < p+5) return text;
+               // at this point we have something like "{(.*) +0.23/6 ..."
+               p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
+               *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
+               // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
+            }
             time = -1; sec = -1; deci = -1;
-            if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
-               sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
-                sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
-                sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
+            if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+               sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+                sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+                sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
                 return text;
             }
 
@@ -13438,7 +13526,7 @@ char *GetInfoFromComment( int index, char * text )
             /* [HGM] PV time: now locate end of PV info */
             while( *++sep >= '0' && *sep <= '9'); // strip depth
             if(time >= 0)
-            while( *++sep >= '0' && *sep <= '9'); // strip time
+            while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
             if(sec >= 0)
             while( *++sep >= '0' && *sep <= '9'); // strip seconds
             if(deci >= 0)
@@ -13458,6 +13546,7 @@ char *GetInfoFromComment( int index, char * text )
         pvInfoList[index-1].score = score;
         pvInfoList[index-1].time  = 10*time; // centi-sec
         if(*sep == '}') *sep = 0; else *--sep = '{';
+        if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
     }
     return sep;
 }
@@ -14476,15 +14565,15 @@ SwitchClocks(int newMoveNr)
            if(blackNPS >= 0) lastTickLength = 0;
            blackTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
-//         if(pvInfoList[forwardMostMove-1].time == -1)
-                 pvInfoList[forwardMostMove-1].time =               // use GUI time
+//         if(pvInfoList[forwardMostMove].time == -1)
+                 pvInfoList[forwardMostMove].time =               // use GUI time
                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
        } else {
           if(whiteNPS >= 0) lastTickLength = 0;
           whiteTimeRemaining -= lastTickLength;
            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
-//         if(pvInfoList[forwardMostMove-1].time == -1)
-                 pvInfoList[forwardMostMove-1].time =
+//         if(pvInfoList[forwardMostMove].time == -1)
+                 pvInfoList[forwardMostMove].time =
                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
        }
        flagged = CheckFlags();