Fix double-clicks for copying in Edit Position mode
[xboard.git] / backend.c
index 0d7a577..29e5297 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -2127,7 +2127,7 @@ StringToVariant (char *e)
     } else
     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
       if (p = StrCaseStr(e, variantNames[i])) {
-       if(p && i >= VariantShogi && (p != e || isalpha(p[strlen(variantNames[i])]))) continue;
+       if(p && i >= VariantShogi && (p != e && !appData.icsActive || isalpha(p[strlen(variantNames[i])]))) continue;
        v = (VariantClass) i;
        found = TRUE;
        break;
@@ -5956,7 +5956,7 @@ ptclen (const char *s, char *escapes)
 {
     int n = 0;
     if(!*escapes) return strlen(s);
-    while(*s) n += (*s != ':' && !strchr(escapes, *s)), s++;
+    while(*s) n += (*s != '/' && !strchr(escapes, *s)), s++;
     return n;
 }
 
@@ -5965,25 +5965,25 @@ SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 /* [HGM] moved here from winboard.c because of its general usefulness */
 /*       Basically a safe strcpy that uses the last character as King */
 {
-    int result = FALSE; int NrPieces;
+    int result = FALSE; int NrPieces, offs;
 
     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
                     && NrPieces >= 12 && !(NrPieces&1)) {
         int i, j = 0; /* [HGM] Accept even length from 12 to 88 */
 
         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
-        for( i=0; i<NrPieces/2-1; i++ ) {
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
             char *p;
-            if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
-            table[i] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
+            if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
+            table[i + offs] = map[j++];
+            if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
         }
         table[(int) WhiteKing]  = map[j++];
-        for( i=0; i<NrPieces/2-1; i++ ) {
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
             char *p;
-            if(map[j] == ':' && *escapes) i = CHUPROMOTED WhitePawn, j++;
-            table[WHITE_TO_BLACK i] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i] += 64*(p - escapes + 1);
+            if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
+            table[WHITE_TO_BLACK i + offs] = map[j++];
+            if(p = strchr(escapes, map[j])) j++, table[WHITE_TO_BLACK i + offs] += 64*(p - escapes + 1);
         }
         table[(int) BlackKing]  = map[j++];
 
@@ -6172,8 +6172,8 @@ InitPosition (int redraw)
       gameInfo.boardWidth  = 12;
       gameInfo.boardHeight = 12;
       nrCastlingRights = 0;
-      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN:+.++.++++++++++.+++++K"
-                                   "p.brqsexogcathd.vmlifn:+.++.++++++++++.+++++k", SUFFIXES);
+      SetCharTableEsc(pieceToChar, "P.BRQSEXOGCATHD.VMLIFN/+.++.++++++++++.+++++K"
+                                   "p.brqsexogcathd.vmlifn/+.++.++++++++++.+++++k", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6399,11 +6399,11 @@ SendBoard (ChessProgramState *cps, int moveNum)
          if ((int) *bp < (int) BlackPawn) {
            if(j == BOARD_RGHT+1)
                 snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]);
-           else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i);
+           else snprintf(message, MSG_SIZ, "%c%c%d\n", PieceToChar(*bp), AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
-             snprintf(message, MSG_SIZ,"%c%c%c+\n",
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -6423,12 +6423,12 @@ SendBoard (ChessProgramState *cps, int moveNum)
              && ((int) *bp >= (int) BlackPawn)) {
            if(j == BOARD_LEFT-2)
                 snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]);
-           else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
-                    AAA + j, ONE + i);
+           else snprintf(message,MSG_SIZ, "%c%c%d\n", ToUpper(PieceToChar(*bp)),
+                    AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
-             snprintf(message, MSG_SIZ,"%c%c%c+\n",
+             snprintf(message, MSG_SIZ,"%c%c%d+\n",
                         PieceToChar((ChessSquare)(DEMOTED *bp)),
-                        AAA + j, ONE + i);
+                        AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
                 message[1] = BOARD_RGHT   - 1 - j + '1';
@@ -7540,7 +7540,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;
@@ -7603,10 +7603,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 +7641,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 +7649,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) {
@@ -8551,7 +8551,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 +8634,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 +8700,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            }
            return;
        }
+      }
 
         if(cps->alphaRank) AlphaRank(machineMove, 4);
 
@@ -8980,7 +8984,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
     }
     if(sscanf(message, "piece %s %s", buf2, buf1) == 2) {
       ChessSquare piece = WhitePawn;
-      char *p=buf2, *q, *s = SUFFIXES, ID = *p;
+      char *p=message+6, *q, *s = SUFFIXES, ID = *p;
       if(*p == '+') piece = CHUPROMOTED WhitePawn, ID = *++p;
       if(q = strchr(s, p[1])) ID += 64*(q - s + 1), p++;
       piece += CharToPiece(ID & 255) - WhitePawn;
@@ -9115,6 +9119,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)) {
@@ -10115,6 +10124,9 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
       }
     /* End of code added by Tord */
 
+    } else if (pieceDesc[piece] && piece == king && !strchr(pieceDesc[piece], 'O') && strchr(pieceDesc[piece], 'i')) {
+       board[fromY][fromX] = EmptySquare; // never castle if King has virgin moves defined on it other than castling
+       board[toY][toX] = piece;
     } else if (board[fromY][fromX] == king
         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
         && toY == fromY && toX > fromX+1) {
@@ -12017,7 +12029,7 @@ LoadGameOneMove (ChessMove readAhead)
       case BlackASideCastleFR:
       /* POP Fabien */
        if (appData.debugMode)
-         fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
+         fprintf(debugFP, "Parsed %s into %s virgin=%x,%x\n", yy_text, currentMoveString, boards[forwardMostMove][TOUCHED_W], boards[forwardMostMove][TOUCHED_B]);
         fromX = currentMoveString[0] - AAA;
         fromY = currentMoveString[1] - ONE;
         toX = currentMoveString[2] - AAA;
@@ -14971,6 +14983,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:
@@ -17891,7 +17913,7 @@ char *
 PositionToFEN (int move, char *overrideCastling, int moveCounts)
 {
     int i, j, fromX, fromY, toX, toY;
-    int whiteToPlay;
+    int whiteToPlay, haveRights = nrCastlingRights;
     char buf[MSG_SIZ];
     char *p, *q;
     int emptycount;
@@ -17965,10 +17987,28 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     *p++ = whiteToPlay ? 'w' : 'b';
     *p++ = ' ';
 
+  if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+    haveRights = 0; q = p;
+    for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+      piece = boards[move][0][i];
+      if(piece >= WhitePawn && piece <= WhiteKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+        if(!(boards[move][TOUCHED_W] & 1<<i)) *p++ = 'A' + i; // print file ID if it has not moved
+      }
+    }
+    for(i=BOARD_RGHT-1; i>=BOARD_LEFT; i--) {
+      piece = boards[move][BOARD_HEIGHT-1][i];
+      if(piece >= BlackPawn && piece <= BlackKing && pieceDesc[piece] && strchr(pieceDesc[piece], 'i')) { // piece with initial move
+        if(!(boards[move][TOUCHED_B] & 1<<i)) *p++ = 'a' + i; // print file ID if it has not moved
+      }
+    }
+    if(p == q) *p++ = '-';
+    *p++ = ' ';
+  }
+
   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
   } else {
-  if(nrCastlingRights) {
+  if(haveRights) {
      int handW=0, handB=0;
      if(gameInfo.variant == VariantSChess) { // for S-Chess, all virgin backrank pieces must be listed
        for(i=0; i<BOARD_HEIGHT; i++) handW += boards[move][i][BOARD_RGHT]; // count white held pieces
@@ -18004,9 +18044,9 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
         /* [HGM] write true castling rights */
         if( nrCastlingRights == 6 ) {
             int q, k=0;
-            if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
+            if(boards[move][CASTLING][0] != NoRights &&
                boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
-            q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+            q = (boards[move][CASTLING][1] != NoRights &&
                  boards[move][CASTLING][2] != NoRights  );
             if(handW) { // for S-Chess with pieces in hand, list virgin pieces between K and Q
                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
@@ -18015,9 +18055,9 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
             }
            if(q) *p++ = 'Q';
             k = 0;
-            if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
+            if(boards[move][CASTLING][3] != NoRights &&
                boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
-            q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+            q = (boards[move][CASTLING][4] != NoRights &&
                  boards[move][CASTLING][5] != NoRights  );
             if(handB) {
                 for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q; i--)
@@ -18094,7 +18134,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
     int i, j, k, w=0, subst=0, shuffle=0, wKingRank = -1, bKingRank = -1;
     char *p, c;
     int emptycount, virgin[BOARD_FILES];
-    ChessSquare piece;
+    ChessSquare piece, king = (gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing);
 
     p = fen;
 
@@ -18108,7 +18148,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];
                     }
@@ -18163,8 +18203,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                     p++;
                 }
                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
-                if(piece == WhiteKing) wKingRank = i;
-                if(piece == BlackKing) bKingRank = i;
+                if(piece == king) wKingRank = i;
+                if(piece == WHITE_TO_BLACK king) bKingRank = i;
            } else {
                return FALSE;
            }
@@ -18172,7 +18212,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) {
@@ -18273,6 +18313,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
     /* set defaults in case FEN is incomplete */
     board[EP_STATUS] = EP_UNKNOWN;
+    board[TOUCHED_W] = board[TOUCHED_B] = 0;
     for(i=0; i<nrCastlingRights; i++ ) {
         board[CASTLING][i] =
             appData.fischerCastling ? NoRights : initialRights[i];
@@ -18287,6 +18328,21 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                                  && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
     FENrulePlies = 0;
 
+    if(pieceDesc[WhiteKing] && strchr(pieceDesc[WhiteKing], 'i') && !strchr(pieceDesc[WhiteKing], 'O')) { // redefined without castling
+      char *q = p;
+      int w=0, b=0;
+      while(isalpha(*p)) {
+        if(isupper(*p)) w |= 1 << (*p++ - 'A');
+        if(islower(*p)) b |= 1 << (*p++ - 'a');
+      }
+      if(*p == '-') p++;
+      if(p != q) {
+        board[TOUCHED_W] = ~w;
+        board[TOUCHED_B] = ~b;
+        while(*p == ' ') p++;
+      }
+    } else
+
     if(nrCastlingRights) {
       int fischer = 0;
       if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
@@ -18308,10 +18364,10 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
         }
         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
-        if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
-                                     && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
-        if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
-                                     && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
+        if(whiteKingFile == NoRights || board[castlingRank[2]][whiteKingFile] != WhiteUnicorn
+                                     && board[castlingRank[2]][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
+        if(blackKingFile == NoRights || board[castlingRank[5]][blackKingFile] != BlackUnicorn
+                                     && board[castlingRank[5]][blackKingFile] != BlackKing) blackKingFile = NoRights;
         switch(c) {
           case'K':
               for(i=BOARD_RGHT-1; board[castlingRank[2]][i]!=WhiteRook && i>whiteKingFile; i--);