Third method of sweep selection
[xboard.git] / backend.c
index 92873a9..3f36345 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
  *
@@ -267,6 +267,9 @@ 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 */
+ChessSquare pieceSweep = EmptySquare;
+ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
+int promoDefaultAltered;
 
 /* States for ics_getting_history */
 #define H_FALSE 0
@@ -737,8 +740,8 @@ InitBackEnd1()
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
 
-    first.which = _("first");
-    second.which = _("second");
+    first.which = "first";
+    second.which = "second";
     first.maybeThinking = second.maybeThinking = FALSE;
     first.pr = second.pr = NoProc;
     first.isr = second.isr = NULL;
@@ -2861,7 +2864,7 @@ read_from_ics(isr, closure, data, count, error)
 
                if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
                for(p=0; p<MAX_CHAT; p++) {
-                   if(channel == atoi(chatPartner[p])) {
+                   if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
                    talker[0] = '['; strcat(talker, "] ");
                    Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
                    chattingPartner = p; break;
@@ -4368,7 +4371,8 @@ ParseBoard12(string)
            safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
            strcat(moveList[moveNum - 1], "\n");
 
-            if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
+            if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
+                                 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
                 ChessSquare old, new = boards[moveNum][k][j];
                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
@@ -4810,6 +4814,59 @@ ProcessICSInitScript(f)
 }
 
 
+static int lastX, lastY, selectFlag, dragging;
+
+void
+Sweep(int step)
+{
+    ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
+    if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
+    if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
+    if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
+    if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
+    if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
+    do {
+       promoSweep -= step;
+       if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
+       else if((int)promoSweep == -1) promoSweep = WhiteKing;
+       else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
+       else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
+       if(!step) step = 1;
+    } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
+           appData.testLegality && (promoSweep == king ||
+           gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
+    ChangeDragPiece(promoSweep);
+}
+
+int PromoScroll(int x, int y)
+{
+  int step = 0;
+
+  if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
+  if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
+  if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
+  if(!step) return FALSE;
+  lastX = x; lastY = y;
+  if((promoSweep < BlackPawn) == flipView) step = -step;
+  if(step > 0) selectFlag = 1;
+  if(!selectFlag) Sweep(step);
+  return FALSE;
+}
+
+void
+NextPiece(int step)
+{
+    ChessSquare piece = boards[currentMove][toY][toX];
+    do {
+       pieceSweep -= step;
+       if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
+       if((int)pieceSweep == -1) pieceSweep = BlackKing;
+       if(!step) step = -1;
+    } while(PieceToChar(pieceSweep) == '.');
+    boards[currentMove][toY][toX] = pieceSweep;
+    DrawPosition(FALSE, boards[currentMove]);
+    boards[currentMove][toY][toX] = piece;
+}
 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
 void
 AlphaRank(char *move, int n)
@@ -5017,8 +5074,6 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
   DrawPosition(TRUE, boards[currentMove]);
 }
 
-static int lastX, lastY;
-
 Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
@@ -5064,13 +5119,17 @@ 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(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
+  if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
+  if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
   if(!step) return;
   lastX = x; lastY = y;
+
+  if(pieceSweep != EmptySquare) { NextPiece(step); return; }
+  if(endPV < 0) return;
+  if(y < margin) step = 1; else
+  if(y > h - margin) step = -1;
   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
   currentMove += step;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
@@ -5647,6 +5706,21 @@ SendBoard(cps, moveNum)
     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
 }
 
+ChessSquare
+DefaultPromoChoice(int white)
+{
+    ChessSquare result;
+    if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
+       result = WhiteFerz; // no choice
+    else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
+       result= WhiteKing; // in Suicide Q is the last thing we want
+    else if(gameInfo.variant == VariantSpartan)
+       result = white ? WhiteQueen : WhiteAngel;
+    else result = WhiteQueen;
+    if(!white) result = WHITE_TO_BLACK result;
+    return result;
+}
+
 static int autoQueen; // [HGM] oneclick
 
 int
@@ -5730,12 +5804,13 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
        *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
        return FALSE;
     }
-    if(autoQueen) { // predetermined
-       if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
-            *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
-       else *promoChoice = PieceToChar(BlackQueen);
-       return FALSE;
-    }
+    // give caller the default choice even if we will not make it
+    *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+    if(gameInfo.variant == VariantShogi) *promoChoice = '+';
+    if(appData.sweepSelect && gameInfo.variant != VariantGreat
+                          && gameInfo.variant != VariantShogi
+                          && gameInfo.variant != VariantSuper) return FALSE;
+    if(autoQueen) return FALSE; // predetermined
 
     // suppress promotion popup on illegal moves that are not premoves
     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
@@ -6188,7 +6263,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
 
-  if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
+  if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
+    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+    return 1;
+  }
 
   if (gameMode == BeginningOfGame) {
     if (appData.noChessProgram) {
@@ -6273,6 +6351,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
   }
 
   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
+  promoDefaultAltered = FALSE; // [HGM] fall back on default choice
 
   if(bookHit) { // [HGM] book: simulate book reply
        static char bookMove[MSG_SIZ]; // a bit generous?
@@ -6341,12 +6420,27 @@ Explode(Board board, int fromX, int fromY, int toX, int toY)
 
 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
 
+int CanPromote(ChessSquare piece, int y)
+{
+       if(gameMode == EditPosition) return FALSE; // no promotions when editing position
+       // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
+       if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
+          gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
+          gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+                                                 gameInfo.variant == VariantMakruk) return FALSE;
+       return (piece == BlackPawn && y == 1 ||
+               piece == WhitePawn && y == BOARD_HEIGHT-2 ||
+               piece == BlackLance && y == 1 ||
+               piece == WhiteLance && y == BOARD_HEIGHT-2 );
+}
+
 void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, dragging = 0;
+    static int second = 0, promotionChoice = 0;
     char promoChoice = NULLCHAR;
+    ChessSquare piece;
 
     if(appData.seekGraph && appData.icsActive && loggedOn &&
        (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
@@ -6366,6 +6460,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        x = BOARD_WIDTH - 1 - x;
     }
 
+    if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
+       defaultPromoChoice = promoSweep;
+       promoSweep = EmptySquare;   // terminate sweep
+       promoDefaultAltered = TRUE;
+       if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+    }
+
     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
        if(clickType == Release) return; // ignore upclick of click-click destination
        promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
@@ -6394,9 +6495,19 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
        return;
 
+    if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
+       fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
+
+    if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
+       int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
+                   gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
+       defaultPromoChoice = DefaultPromoChoice(side);
+    }
+
     autoQueen = appData.alwaysPromoteToQueen;
 
     if (fromX == -1) {
+      int originalY = y;
       gatingPiece = EmptySquare;
       if (clickType != Press) {
        if(dragging) { // [HGM] from-square must have been reset due to game end since last press
@@ -6405,16 +6516,23 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        }
        return;
       }
-      if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
+      fromX = x; fromY = y;
+      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
+        appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
            /* First square */
-           if (OKToStartUserMove(x, y)) {
-               fromX = x;
-               fromY = y;
+           if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix); dragging = 1;
+               if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
+                   promoSweep = defaultPromoChoice;
+                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   Sweep(0); // Pawn that is going to promote: preview promotion piece
+                   DisplayMessage("", _("Pull pawn backwards to under-promote"));
+               }
                if (appData.highlightDragging) {
-                   SetHighlights(x, y, -1, -1);
+                   SetHighlights(fromX, fromY, -1, -1);
                }
            }
            return;
@@ -6444,6 +6562,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
             !(fromP == BlackKing && toP == BlackRook && frc))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
+           promoDefaultAltered = FALSE;
           if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
@@ -6460,6 +6579,11 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                fromY = y; dragging = 1;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
+               if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
+                   promoSweep = defaultPromoChoice;
+                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   Sweep(0); // Pawn that is going to promote: preview promotion piece
+               }
            }
           }
           if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
@@ -6609,16 +6733,25 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
 
     xSqr = EventToSquare(x, BOARD_WIDTH);
     ySqr = EventToSquare(y, BOARD_HEIGHT);
-    if (action == Release) UnLoadPV(); // [HGM] pv
+    if (action == Release) {
+       if(pieceSweep != EmptySquare) {
+           EditPositionMenuEvent(pieceSweep, toX, toY);
+           pieceSweep = EmptySquare;
+       } else UnLoadPV(); // [HGM] pv
+    }
     if (action != Press) return -2; // return code to be ignored
     switch (gameMode) {
       case IcsExamining:
        if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
       case EditPosition:
        if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
-       if (xSqr < 0 || ySqr < 0) return -1;\r
-       whichMenu = 0; // edit-position menu
-       break;
+       if (xSqr < 0 || ySqr < 0) return -1;
+       if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
+       pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
+       toX = xSqr; toY = ySqr; lastX = x, lastY = y;
+       if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
+       NextPiece(0);
+       return -2;\r
       case IcsObserving:
        if(!appData.icsEngineAnalyze) return -1;
       case IcsPlayingWhite:
@@ -6812,7 +6945,6 @@ Adjudicate(ChessProgramState *cps)
                     if(canAdjudicate && appData.checkMates) {
                         if(engineOpponent)
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
                                                        "Xboard adjudication: King destroyed", GE_XBOARD );
                          return 1;
@@ -6826,7 +6958,6 @@ Adjudicate(ChessProgramState *cps)
                     if(canAdjudicate && appData.checkMates) {
                         if(engineOpponent)
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                          return 1;
@@ -6839,7 +6970,6 @@ Adjudicate(ChessProgramState *cps)
                            /* but only adjudicate if adjudication enabled */
                            if(engineOpponent)
                              SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
-                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                            GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                            return 1;
@@ -6900,7 +7030,6 @@ Adjudicate(ChessProgramState *cps)
                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
                    if(engineOpponent)
                      SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
-                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                    GameEnds( result, reason, GE_XBOARD );
                    return 1;
                }
@@ -6919,7 +7048,6 @@ Adjudicate(ChessProgramState *cps)
                           SendToProgram("force\n", engineOpponent); // suppress reply
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
                         }
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
                          return 1;
                      }
@@ -6940,7 +7068,6 @@ Adjudicate(ChessProgramState *cps)
                            SendToProgram("force\n", engineOpponent); // suppress reply
                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                          }
-                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
                           return 1;
                      }
@@ -7023,7 +7150,6 @@ Adjudicate(ChessProgramState *cps)
                               SendToProgram("force\n", engineOpponent); // suppress reply
                               SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                             }
-                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                              GameEnds( result, details, GE_XBOARD );
                              return 1;
                         }
@@ -7060,7 +7186,6 @@ Adjudicate(ChessProgramState *cps)
                           SendToProgram("force\n", engineOpponent); // suppress reply
                           SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                         }
-                         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
                          return 1;
                 }
@@ -7083,7 +7208,6 @@ Adjudicate(ChessProgramState *cps)
                               SendToProgram("force\n", engineOpponent); // suppress reply
                               SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                             }
-                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                              GameEnds( GameIsDrawn, p, GE_XBOARD );
                              return 1;
                          }
@@ -7094,7 +7218,6 @@ Adjudicate(ChessProgramState *cps)
                      SendToProgram("force\n", engineOpponent); // suppress reply
                      SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
                    }
-                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
                    GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
                    return 1;
                }
@@ -7273,7 +7396,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                               &fromX, &fromY, &toX, &toY, &promoChar)) {
            /* Machine move could not be parsed; ignore it. */
          snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
-                   machineMove, cps->which);
+                   machineMove, _(cps->which));
            DisplayError(buf1, 0);
             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
@@ -7367,7 +7490,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         }
 
        if(Adjudicate(cps)) {
-           DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
            ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
            return; // [HGM] adjudicate: for all automatic game ends
        }
@@ -7704,7 +7826,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        SwitchClocks(forwardMostMove-1); // [HGM] race
        DisplayBothClocks();
        snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
-               parseList[currentMove], cps->which);
+               parseList[currentMove], _(cps->which));
        DisplayMoveError(buf1);
        DrawPosition(FALSE, boards[currentMove]);
        return;
@@ -7729,7 +7851,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
        cps->maybeThinking = FALSE;
        snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
-               cps->which, cps->program, cps->host, message);
+               _(cps->which), cps->program, cps->host, message);
        RemoveInputSource(cps->isr);
        DisplayFatalError(buf1, 0, 1);
        return;
@@ -7752,7 +7874,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                /* Hint move could not be parsed!? */
              snprintf(buf2, sizeof(buf2),
                        _("Illegal hint move \"%s\"\nfrom %s chess program"),
-                       buf1, cps->which);
+                       buf1, _(cps->which));
                DisplayError(buf2, 0);
            }
        } else {
@@ -8903,7 +9025,7 @@ InitChessProgram(cps, setup)
       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
-                               gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
+          gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
       if( gameInfo.variant == VariantCourier )
            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
@@ -10429,6 +10551,7 @@ LoadGame(f, gameNumber, title, useList)
             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
                switch (*p) {
+                 case '{':
                  case '[':
                  case '-':
                  case ' ':
@@ -12620,6 +12743,7 @@ ClockClick(int which)
 {      // [HGM] code moved to back-end from winboard.c
        if(which) { // black clock
          if (gameMode == EditPosition || gameMode == IcsExamining) {
+           if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetBlackToPlayEvent();
          } else if (gameMode == EditGame || shiftKey) {
            AdjustClock(which, -1);
@@ -12629,6 +12753,7 @@ ClockClick(int which)
          }
        } else { // white clock
          if (gameMode == EditPosition || gameMode == IcsExamining) {
+           if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetWhiteToPlayEvent();
          } else if (gameMode == EditGame || shiftKey) {
            AdjustClock(which, -1);
@@ -13574,11 +13699,11 @@ SendToProgram(message, cps)
     outCount = OutputToProcess(cps->pr, message, count, &error);
     if (outCount < count && !exiting
                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
-      snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
+      snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
-                snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
+                snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
             } else {
                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
             }
@@ -13604,11 +13729,11 @@ ReceiveFromProgram(isr, closure, message, count, error)
     if (count <= 0) {
        if (count == 0) {
            snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
-                   cps->which, cps->program);
+                   _(cps->which), cps->program);
         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
-                    snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
+                    snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
                 } else {
                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
                 }
@@ -13618,7 +13743,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
            if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
        } else {
            snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
-                   cps->which, cps->program);
+                   _(cps->which), cps->program);
            RemoveInputSource(cps->isr);
 
             /* [AS] Program is misbehaving badly... kill it */
@@ -13736,7 +13861,7 @@ SendTimeControl(cps, mps, tc, inc, sd, st)
       SendToProgram(buf, cps);
     }
 
-    if(cps->nps > 0) { /* [HGM] nps */
+    if(cps->nps >= 0) { /* [HGM] nps */
        if(cps->supportsNPS == FALSE)
          cps->nps = -1; // don't use if engine explicitly says not supported!
        else {
@@ -13889,11 +14014,11 @@ ParseOption(Option *opt, ChessProgramState *cps)
        } else if((p = strstr(opt->name, " -file "))) {
            // for now -file is a synonym for -string, to already provide compatibility with future polyglots
            opt->textValue = p+7;
-           opt->type = TextBox; // FileName;
+           opt->type = FileName; // FileName;
        } else if((p = strstr(opt->name, " -path "))) {
            // for now -file is a synonym for -string, to already provide compatibility with future polyglots
            opt->textValue = p+7;
-           opt->type = TextBox; // PathName;
+           opt->type = PathName; // PathName;
        } else if(p = strstr(opt->name, " -check ")) {
            if(sscanf(p, " -check %d", &def) < 1) return FALSE;
            opt->value = (def != 0);
@@ -14029,7 +14154,7 @@ ParseFeatures(args, cps)
        }
        if(cps->nrOptions >= MAX_OPTIONS) {
            cps->nrOptions--;
-           snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
+           snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
            DisplayError(buf, 0);
        }
        continue;