Refactor move type-in code
[xboard.git] / backend.c
index ae15f1a..b1df197 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -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;
@@ -1173,6 +1176,40 @@ InitBackEnd2()
 }
 
 void
+MatchEvent(int mode)
+{      // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
+       /* Set up machine vs. machine match */
+       if (appData.noChessProgram) {
+           DisplayFatalError(_("Can't have a match with no chess programs"),
+                             0, 2);
+           return;
+       }
+       matchMode = mode;
+       matchGame = 1;
+       if (*appData.loadGameFile != NULLCHAR) {
+           int index = appData.loadGameIndex; // [HGM] autoinc
+           if(index<0) lastIndex = index = 1;
+           if (!LoadGameFromFile(appData.loadGameFile,
+                                 index,
+                                 appData.loadGameFile, FALSE)) {
+               DisplayFatalError(_("Bad game file"), 0, 1);
+               return;
+           }
+       } else if (*appData.loadPositionFile != NULLCHAR) {
+           int index = appData.loadPositionIndex; // [HGM] autoinc
+           if(index<0) lastIndex = index = 1;
+           if (!LoadPositionFromFile(appData.loadPositionFile,
+                                     index,
+                                     appData.loadPositionFile)) {
+               DisplayFatalError(_("Bad position file"), 0, 1);
+               return;
+           }
+       }
+       first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
+       TwoMachinesEvent();
+}
+
+void
 InitBackEnd3 P((void))
 {
     GameMode initialMode;
@@ -1258,34 +1295,7 @@ InitBackEnd3 P((void))
     }
 
     if (appData.matchMode) {
-       /* Set up machine vs. machine match */
-       if (appData.noChessProgram) {
-           DisplayFatalError(_("Can't have a match with no chess programs"),
-                             0, 2);
-           return;
-       }
-       matchMode = TRUE;
-       matchGame = 1;
-       if (*appData.loadGameFile != NULLCHAR) {
-           int index = appData.loadGameIndex; // [HGM] autoinc
-           if(index<0) lastIndex = index = 1;
-           if (!LoadGameFromFile(appData.loadGameFile,
-                                 index,
-                                 appData.loadGameFile, FALSE)) {
-               DisplayFatalError(_("Bad game file"), 0, 1);
-               return;
-           }
-       } else if (*appData.loadPositionFile != NULLCHAR) {
-           int index = appData.loadPositionIndex; // [HGM] autoinc
-           if(index<0) lastIndex = index = 1;
-           if (!LoadPositionFromFile(appData.loadPositionFile,
-                                     index,
-                                     appData.loadPositionFile)) {
-               DisplayFatalError(_("Bad position file"), 0, 1);
-               return;
-           }
-       }
-       TwoMachinesEvent();
+       MatchEvent(TRUE);
     } else if (*appData.cmailGameName != NULLCHAR) {
        /* Set up cmail mode */
        ReloadCmailMsgEvent(TRUE);
@@ -2861,7 +2871,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 +4378,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 +4821,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 +5081,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 +5126,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 +5713,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 +5811,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 +6270,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 +6358,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 +6427,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 +6467,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 +6502,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 +6523,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 +6569,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 +6586,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 +6740,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:
@@ -7263,7 +7403,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);
@@ -7693,7 +7833,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;
@@ -7718,7 +7858,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;
@@ -7741,7 +7881,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 {
@@ -10418,6 +10558,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 ' ':
@@ -12609,6 +12750,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);
@@ -12618,6 +12760,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);
@@ -13563,11 +13706,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;
             }
@@ -13593,11 +13736,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;
                 }
@@ -13607,7 +13750,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 */
@@ -13725,7 +13868,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 {
@@ -13878,11 +14021,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);
@@ -14018,7 +14161,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;
@@ -14137,6 +14280,55 @@ AskQuestionEvent(title, question, replyPrefix, which)
 }
 
 void
+TypeInEvent(char firstChar)
+{
+    if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
+        gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
+       gameMode == AnalyzeMode || gameMode == EditGame || \r
+       gameMode == EditPosition || gameMode == IcsExamining ||\r
+       gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
+       isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
+               ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
+                 gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
+       gameMode == Training) PopUpMoveDialog(firstChar);
+}
+
+void
+TypeInDoneEvent(char *move)
+{
+       Board board;
+       int n, fromX, fromY, toX, toY;
+       char promoChar;
+       ChessMove moveType;\r
+
+       // [HGM] FENedit\r
+       if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
+               EditPositionPasteFEN(move);\r
+               return;\r
+       }\r
+       // [HGM] movenum: allow move number to be typed in any mode\r
+       if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
+         ToNrEvent(2*n-1);\r
+         return;\r
+       }\r
+
+      if (gameMode != EditGame && currentMove != forwardMostMove && \r
+       gameMode != Training) {\r
+       DisplayMoveError(_("Displayed move is not current"));\r
+      } else {\r
+       int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
+       if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
+       if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
+         &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
+         UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
+       } else {\r
+         DisplayMoveError(_("Could not parse move"));\r
+       }
+      }\r
+}\r
+
+void
 DisplayMove(moveNumber)
      int moveNumber;
 {