Fix Chu promotion with added pieces
[xboard.git] / backend.c
index a3a81ef..3254ff7 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -443,6 +443,7 @@ char *currentDebugFile; // [HGM] debug split: to remember name
 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
 char thinkOutput1[MSG_SIZ*10];
+char promoRestrict[MSG_SIZ];
 
 ChessProgramState first, second, pairing;
 
@@ -1740,6 +1741,7 @@ InitBackEnd3 P((void))
             if(!blackPlaysFirst) {
                 startedFromPositionFile = TRUE;
                 CopyBoard(filePosition, boards[0]);
+                CopyBoard(initialPosition, boards[0]);
             }
        }
        if (initialMode == AnalyzeMode) {
@@ -5381,7 +5383,7 @@ static ClickType lastClickType;
 int
 Partner (ChessSquare *p)
 { // change piece into promotion partner if one shogi-promotes to the other
-  int stride = gameInfo.variant == VariantChu ? 22 : 11;
+  int stride = gameInfo.variant == VariantChu ? WhiteTokin : 11;
   ChessSquare partner;
   partner = (*p/stride & 1 ? *p - stride : *p + stride);
   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
@@ -5407,8 +5409,10 @@ Sweep (int step)
        else if(promoSweep == BlackPawn && step < 0 && !toggleFlag) promoSweep = WhitePawn;
        else if(promoSweep == WhiteKing && step > 0 && !toggleFlag) promoSweep = BlackKing;
        if(!step) step = -1;
-    } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
+    } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' ||
            !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other
+           promoRestrict[0] ? !strchr(promoRestrict, ToUpper(PieceToChar(promoSweep))) : // if choice set available, use it 
+           promoSweep == pawn ||
            appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
             (promoSweep == WhiteLion || promoSweep == BlackLion)));
     if(toX >= 0) {
@@ -5660,21 +5664,26 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd)
 }
 
 int
-MultiPV (ChessProgramState *cps)
+MultiPV (ChessProgramState *cps, int kind)
 {      // check if engine supports MultiPV, and if so, return the number of the option that sets it
        int i;
-       for(i=0; i<cps->nrOptions; i++)
-           if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
-               return i;
+       for(i=0; i<cps->nrOptions; i++) {
+           char *s = cps->option[i].name;
+           if((kind & 1) && !StrCaseCmp(s, "MultiPV") && cps->option[i].type == Spin) return i;
+           if((kind & 2) && StrCaseStr(s, "multi") && StrCaseStr(s, "PV")
+                         && StrCaseStr(s, "margin") && cps->option[i].type == Spin) return -i-2;
+       }
        return -1;
 }
 
 Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game
+static int multi, pv_margin;
+static ChessProgramState *activeCps;
 
 Boolean
 LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
 {
-       int startPV, multi, lineStart, origIndex = index;
+       int startPV, lineStart, origIndex = index;
        char *p, buf2[MSG_SIZ];
        ChessProgramState *cps = (pane ? &second : &first);
 
@@ -5688,14 +5697,22 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
        do{ while(buf[index] && buf[index] != '\n') index++;
        } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
-               int n = cps->option[multi].value;
-               if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+       if(lineStart == 0 && gameMode == AnalyzeMode) {
+           int n = 0;
+           if(origIndex > 17 && origIndex < 24) n--; else if(origIndex > index - 6) n++;
+           if(n == 0) { // click not on "fewer" or "more"
+               if((multi = -2 - MultiPV(cps, 2)) >= 0) {
+                   pv_margin = cps->option[multi].value;
+                   activeCps = cps; // non-null signals margin adjustment
+               }
+           } else if((multi = MultiPV(cps, 1)) >= 0) {
+               n += cps->option[multi].value; if(n < 1) n = 1;
                snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
                if(cps->option[multi].value != n) SendToProgram(buf2, cps);
                cps->option[multi].value = n;
                *start = *end = 0;
                return FALSE;
+           }
        } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
                ExcludeClick(origIndex - lineStart);
                return FALSE;
@@ -5743,6 +5760,16 @@ void
 UnLoadPV ()
 {
   int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
+  if(activeCps) {
+    if(pv_margin != activeCps->option[multi].value) {
+      char buf[MSG_SIZ];
+      snprintf(buf, MSG_SIZ, "option %s=%d\n", "Multi-PV Margin", pv_margin);
+      SendToProgram(buf, activeCps);
+      activeCps->option[multi].value = pv_margin;
+    }
+    activeCps = NULL;
+    return;
+  }
   if(endPV < 0) return;
   if(appData.autoCopyPV) CopyFENToClipboard();
   endPV = -1;
@@ -5771,6 +5798,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, threshold = (pieceSweep == EmptySquare ? 10 : 15);
 
+  if(activeCps) { // adjusting engine's multi-pv margin
+    if(x > lastX) pv_margin++; else
+    if(x < lastX) pv_margin -= (pv_margin > 0);
+    if(x != lastX) {
+      char buf[MSG_SIZ];
+      snprintf(buf, MSG_SIZ, "margin = %d", pv_margin);
+      DisplayMessage(buf, "");
+    }
+    lastX = x;
+    return;
+  }
   // we must somehow check if right button is still down (might be released off board!)
   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
   if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return;
@@ -6594,7 +6632,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
     if(gameInfo.variant == VariantChu) {
         int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece;
         promotionZoneSize = BOARD_HEIGHT/3;
-        highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion;
+        highestPromotingPiece = (p >= WhiteTokin || PieceToChar(piece + WhiteTokin) != '+') ? WhitePawn : WhiteTokin-1;
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
         highestPromotingPiece = (int)WhiteAlfil;
@@ -7045,6 +7083,8 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
 
     if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
 
+    if(moveType == IllegalMove && legal[toY][toX] > 1) moveType = NormalMove; // someone explicitly told us this move is legal
+
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
@@ -7280,7 +7320,7 @@ MarkByFEN(char *fen)
            int s = 0;
            marker[r][f] = 0;
            if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
-           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 1; else
+           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
            if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
            if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
            if(*fen == 'T') marker[r][f++] = 0; else
@@ -7312,8 +7352,8 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
     if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
        (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
                         || kind == WhiteCapturesEnPassant
-                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 1;
-    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 1;
+                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
+    else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
 }
 
 static int hoverSavedValid;
@@ -7527,7 +7567,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
                    promoSweep = defaultPromoChoice;
-                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
                    Sweep(0); // Pawn that is going to promote: preview promotion piece
                    DisplayMessage("", _("Pull pawn backwards to under-promote"));
                }
@@ -7540,7 +7580,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            return;
        }
     }
-printf("to click %d,%d\n",x,y);
+
     /* fromX != -1 */
     if (clickType == Press && gameMode != EditPosition) {
        ChessSquare fromP;
@@ -7592,7 +7632,7 @@ printf("to click %d,%d\n",x,y);
                DragPieceBegin(xPix, yPix, FALSE);
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
                    promoSweep = defaultPromoChoice;
-                   selectFlag = 0; lastX = xPix; lastY = yPix;
+                   selectFlag = 0; lastX = xPix; lastY = yPix; *promoRestrict = 0;
                    Sweep(0); // Pawn that is going to promote: preview promotion piece
                }
            }
@@ -7603,10 +7643,10 @@ printf("to click %d,%d\n",x,y);
        // ignore clicks on holdings
        if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
     }
-printf("A type=%d\n",clickType);
 
-    if(x == fromX && y == fromY && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
+    if(x == fromX && y == fromY && clickType == Press && gameMode == EditPosition && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
        gatingPiece = boards[currentMove][fromY][fromX]; // prepare to copy rather than move
+       DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
        return;
     }
 
@@ -7641,7 +7681,7 @@ printf("A type=%d\n",clickType);
     }
 
     clearFlag = 0;
-printf("B\n");
+
     if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] &&
        fromX >= BOARD_LEFT && fromX < BOARD_RGHT && (x != killX || y != killY) && !sweepSelecting) {
        if(dragging) DragPieceEnd(xPix, yPix), dragging = 0;
@@ -7649,7 +7689,7 @@ printf("B\n");
        DrawPosition(TRUE, NULL);
        return; // ignore to-click
     }
-printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
+
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
     if(!sweepSelecting) {
@@ -7680,6 +7720,7 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
            promoSweep = defaultPromoChoice;
            if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
            selectFlag = 0; lastX = xPix; lastY = yPix;
+           ReportClick("put", x, y); // extra put to prompt engine for 'choice' command
            Sweep(0); // Pawn that is going to promote: preview promotion piece
            sweepSelecting = 1;
            DisplayMessage("", _("Pull pawn backwards to under-promote"));
@@ -7695,6 +7736,7 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
        }
     } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep
        sweepSelecting = 0; appData.animate = FALSE; // do not animate, a selected piece already on to-square
+        *promoRestrict = 0;
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
@@ -8551,7 +8593,7 @@ DeferredBookMove (void)
 
 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
 static ChessProgramState *stalledEngine;
-static char stashedInputMove[MSG_SIZ];
+static char stashedInputMove[MSG_SIZ], abortEngineThink;
 
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
@@ -8634,24 +8676,27 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            return;
        }
 
+      if(cps->usePing) {
+
         /* This method is only useful on engines that support ping */
+        if(abortEngineThink) {
+           if (appData.debugMode) {
+               fprintf(debugFP, "Undoing move from aborted think of %s\n", cps->which);
+           }
+            SendToProgram("undo\n", cps);
+           return;
+       }
+
         if (cps->lastPing != cps->lastPong) {
-         if (gameMode == BeginningOfGame) {
            /* Extra move from before last new; ignore */
            if (appData.debugMode) {
                fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
            }
-         } else {
-           if (appData.debugMode) {
-               fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
-                       cps->which, gameMode);
-           }
-
-            SendToProgram("undo\n", cps);
-         }
          return;
        }
 
+      } else {
+
        switch (gameMode) {
          case BeginningOfGame:
            /* Extra move from before last reset; ignore */
@@ -8697,6 +8742,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            }
            return;
        }
+      }
 
         if(cps->alphaRank) AlphaRank(machineMove, 4);
 
@@ -8999,6 +9045,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       }
       return;
     }
+    if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
+      promoSweep = PieceToChar(forwardMostMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
+      Sweep(0);
+      return;
+    }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
      * want this, I was asked to put it in, and obliged.
      */
@@ -9115,6 +9166,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            }
            initPing = -1;
         }
+       if(cps->lastPing == cps->lastPong && abortEngineThink) {
+           abortEngineThink = FALSE;
+           DisplayMessage("", "");
+           ThawUI();
+       }
        return;
     }
     if(!strncmp(message, "highlight ", 10)) {
@@ -10091,6 +10147,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        board[toY][toX + (killX < fromX ? 1 : -1)] = killed;
         board[EP_STATUS] = EP_NONE; // capture was fake!
     } else
+    if(nrCastlingRights == 0 && board[toY][toX] < EmptySquare && (piece < BlackPawn) == (board[toY][toX] < BlackPawn)) {
+        board[fromY][fromX] = board[toY][toX]; // capture own will lead to swapping
+        board[toY][toX] = piece;
+        board[EP_STATUS] = EP_NONE; // capture was fake!
+    } else
     /* Code added by Tord: */
     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
@@ -12980,7 +13041,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
        gameInfo.event = StrSave(yy_text);
     }
 
-    startedFromSetupPosition = FALSE;
+    startedFromSetupPosition = startedFromPositionFile; // [HGM]
     while (cm == PGNTag) {
        if (appData.debugMode)
          fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
@@ -14974,6 +15035,16 @@ EditGameEvent ()
       case MachinePlaysBlack:
       case BeginningOfGame:
        SendToProgram("force\n", &first);
+       if(gameMode == (forwardMostMove & 1 ? MachinePlaysBlack : MachinePlaysWhite)) { // engine is thinking
+           if (first.usePing) { // [HGM] always send ping when we might interrupt machine thinking
+               char buf[MSG_SIZ];
+               abortEngineThink = TRUE;
+               snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++first.lastPing);
+               SendToProgram(buf, &first);
+               DisplayMessage("Aborting engine think", "");
+               FreezeUI();
+           }
+       }
        SetUserThinkingEnables();
        break;
       case PlayFromGameFile:
@@ -18129,7 +18200,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
                 if (*p == '/') p++;
-               else if(autoSize) { // we stumbled unexpectedly into end of board
+               else if(autoSize && i != BOARD_HEIGHT-1) { // we stumbled unexpectedly into end of board
                     for(k=i; k<BOARD_HEIGHT; k++) { // too few ranks; shift towards bottom
                        for(j=0; j<BOARD_WIDTH; j++) board[k-i][j] = board[k][j];
                     }
@@ -18193,7 +18264,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     }
     while (*p == '/' || *p == ' ') p++;
 
-    if(autoSize) appData.NrFiles = w, InitPosition(TRUE);
+    if(autoSize && w != 0) appData.NrFiles = w, InitPosition(TRUE);
 
     /* [HGM] by default clear Crazyhouse holdings, if present */
     if(gameInfo.holdingsWidth) {