Use flexible promotion assignment
[xboard.git] / backend.c
index d362be5..58905cb 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) {
@@ -4888,13 +4890,13 @@ ParseBoard12 (char *string)
                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
                   if(old == new) continue;
-                  if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
+                  if(old == PROMOTED(new)) boards[moveNum][k][j] = old;// prevent promoted pieces to revert to primordial ones
                   else if(new == WhiteWazir || new == BlackWazir) {
                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
-                           boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
+                           boards[moveNum][k][j] = PROMOTED(old); // choose correct type of Gold in promotion
                       else boards[moveNum][k][j] = old; // preserve type of Gold
                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
-                      boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
+                      boards[moveNum][k][j] = PROMOTED(new); // use non-primordial representation of chosen piece
               }
          } else {
            /* Move from ICS was illegal!?  Punt. */
@@ -5381,9 +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;
-  ChessSquare partner;
-  partner = (*p/stride & 1 ? *p - stride : *p + stride);
+  ChessSquare partner = promoPartner[*p];
   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
   *p = partner;
   return 1;
@@ -5407,8 +5407,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 +5662,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 +5695,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 +5758,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 +5796,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;
@@ -5956,7 +5992,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 != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
     return n;
 }
 
@@ -5966,6 +6002,7 @@ SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 /*       Basically a safe strcpy that uses the last character as King */
 {
     int result = FALSE; int NrPieces, offs;
+    unsigned char partner[EmptySquare];
 
     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
                     && NrPieces >= 12 && !(NrPieces&1)) {
@@ -5973,20 +6010,39 @@ SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 
         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
         for( i=offs=0; i<NrPieces/2-1; i++ ) {
-            char *p;
+            char *p, c=0;
             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
+            if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
             table[i + offs] = map[j++];
             if(p = strchr(escapes, map[j])) j++, table[i + offs] += 64*(p - escapes + 1);
+            if(c) partner[i + offs] = table[i + offs], table[i + offs] = c;
         }
         table[(int) WhiteKing]  = map[j++];
         for( i=offs=0; i<NrPieces/2-1; i++ ) {
-            char *p;
+            char *p, c=0;
             if(map[j] == '/' && *escapes) offs = WhiteTokin - i, j++;
+            if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[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);
+            if(c) partner[WHITE_TO_BLACK i + offs] = table[WHITE_TO_BLACK i + offs], table[WHITE_TO_BLACK i + offs] = c;
         }
         table[(int) BlackKing]  = map[j++];
 
+
+        if(*escapes) { // set up promotion pairing
+            for( i=0; i<(int) EmptySquare; i++ ) promoPartner[i] = (i%BlackPawn < 11 ? i + 11 : i%BlackPawn < 22 ? i - 11 : i); // default
+            // pieceToChar entirely filled, so we can look up specified partners
+            for(i=0; i<EmptySquare; i++) { // adjust promotion pairing
+                int c = table[i];
+                if(c == '^' || c == '-') { // has specified partner
+                    int p;
+                    for(p=0; p<EmptySquare; p++) if(table[p] == partner[i]) break;
+                    if(c == '^') table[i] = '+';
+                    if(p < EmptySquare) promoPartner[p] = i, promoPartner[i] = p; // marry them
+                } else if(c == '*') promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
+            }
+        }
+
         result = TRUE;
     }
 
@@ -6079,7 +6135,7 @@ InitPosition (int redraw)
       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
     initialPosition[EP_STATUS] = EP_NONE;
     initialPosition[TOUCHED_W] = initialPosition[TOUCHED_B] = 0;
-    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+    SetCharTableEsc(pieceToChar, "PNBRQ...........Kpnbrq...........k", SUFFIXES);
     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
          SetCharTable(pieceNickName, appData.pieceNickNames);
     else SetCharTable(pieceNickName, "............");
@@ -6172,8 +6228,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/^P.^B^R.^S^E^X^O^G^C^A^T^H^D.^V^M^L^I^FK"
+                                   "p.brqsexogcathd.vmlifn/^p.^b^r.^s^e^x^o^g^c^a^t^h^d.^v^m^l^i^fk", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6402,7 +6458,7 @@ SendBoard (ChessProgramState *cps, int moveNum)
            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%d+\n",
-                        PieceToChar((ChessSquare)(DEMOTED *bp)),
+                        PieceToChar((ChessSquare)(DEMOTED(*bp))),
                         AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
@@ -6427,7 +6483,7 @@ SendBoard (ChessProgramState *cps, int moveNum)
                     AAA + j, ONE + i - '0');
             if(message[0] == '+' || message[0] == '~') {
              snprintf(message, MSG_SIZ,"%c%c%d+\n",
-                        PieceToChar((ChessSquare)(DEMOTED *bp)),
+                        PieceToChar((ChessSquare)(DEMOTED(*bp))),
                         AAA + j, ONE + i - '0');
             }
             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
@@ -6592,9 +6648,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
 
     piece = boards[currentMove][fromY][fromX];
     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 = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
         highestPromotingPiece = (int)WhiteAlfil;
@@ -6999,7 +7054,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
            if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
                ChessSquare q, p = boards[0][rf][ff];
                if(p >= BlackPawn) p = BLACK_TO_WHITE p;
-               if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
+               if(CHUPROMOTED(p) < BlackPawn) p = q = CHUPROMOTED(boards[0][rf][ff]);
                else p = CHUDEMOTED (q = boards[0][rf][ff]);
                if(PieceToChar(q) == '+') gatingPiece = p;
            }
@@ -7045,6 +7100,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 +7337,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 +7369,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 +7584,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 +7597,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 +7649,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 +7660,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 +7698,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 +7706,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) {
@@ -7678,8 +7735,9 @@ printf("(%d,%d)-(%d,%d) %d %d\n",fromX,fromY,toX,toY,x,y);
        if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
          if(appData.sweepSelect) {
            promoSweep = defaultPromoChoice;
-           if(gameInfo.variant != VariantChuChess && PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece;
+           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 +7753,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 {
@@ -8985,7 +9044,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=message+6, *q, *s = SUFFIXES, ID = *p;
-      if(*p == '+') piece = CHUPROMOTED WhitePawn, 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;
       if(cps != &first || appData.testLegality && *engineVariant == NULLCHAR
@@ -9003,6 +9062,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.
      */
@@ -10100,6 +10164,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 ||
@@ -10149,8 +10218,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* white pawn promotion */
         board[toY][toX] = CharToPiece(ToUpper(promoChar));
-        if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
-            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+        if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
+            board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY >= BOARD_HEIGHT>>1)
               && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
@@ -10214,8 +10283,8 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
                ) {
        /* black pawn promotion */
        board[toY][toX] = CharToPiece(ToLower(promoChar));
-        if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */
-            board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+        if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED(board[toY][toX])) == '~') /* [HGM] use shadow piece (if available) */
+            board[toY][toX] = (ChessSquare) (PROMOTED(board[toY][toX]));
        board[fromY][fromX] = EmptySquare;
     } else if ((fromY < BOARD_HEIGHT>>1)
               && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality || abs(toX - fromX) > 4)
@@ -10289,10 +10358,10 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
         p = (int) captured;
         if (p >= (int) BlackPawn) {
           p -= (int)BlackPawn;
-          if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
+          if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
                   /* Restore shogi-promoted piece to its original  first */
-                  captured = (ChessSquare) (DEMOTED captured);
-                  p = DEMOTED p;
+                  captured = (ChessSquare) (DEMOTED(captured));
+                  p = DEMOTED(p);
           }
           p = PieceToNumber((ChessSquare)p);
           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
@@ -10300,9 +10369,9 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
        } else {
           p -= (int)WhitePawn;
-          if(DEMOTED p >= 0 && PieceToChar(p) == '+') {
-                  captured = (ChessSquare) (DEMOTED captured);
-                  p = DEMOTED p;
+          if(DEMOTED(p) >= 0 && PieceToChar(p) == '+') {
+                  captured = (ChessSquare) (DEMOTED(captured));
+                  p = DEMOTED(p);
           }
           p = PieceToNumber((ChessSquare)p);
           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
@@ -10330,13 +10399,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
     } else
     if(promoChar == '+') {
         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */
-        board[toY][toX] = (ChessSquare) (CHUPROMOTED piece);
+        board[toY][toX] = (ChessSquare) (CHUPROMOTED(piece));
         if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight))
           board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion
     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
         ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
-       if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified
-          && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available
+       if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan)  // unpromoted piece specified
+          && pieceToChar[PROMOTED(newPiece)] == '~') newPiece = PROMOTED(newPiece);// but promoted version available
         board[toY][toX] = newPiece;
     }
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
@@ -12989,7 +13058,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);
@@ -15326,7 +15395,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
       case PromotePiece:
         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
-            selection = (ChessSquare) (PROMOTED piece);
+            selection = (ChessSquare) (PROMOTED(piece));
         } else if(piece == EmptySquare) selection = WhiteSilver;
         else selection = (ChessSquare)((int)piece - 1);
         goto defaultlabel;
@@ -15334,7 +15403,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
       case DemotePiece:
         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
-            selection = (ChessSquare) (DEMOTED piece);
+            selection = (ChessSquare) (DEMOTED(piece));
         } else if(piece == EmptySquare) selection = BlackSilver;
         else selection = (ChessSquare)((int)piece + 1);
         goto defaultlabel;
@@ -17940,13 +18009,13 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                 if(PieceToChar(piece) == '+') {
                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
                     *p++ = '+';
-                    piece = (ChessSquare)(CHUDEMOTED piece);
+                    piece = (ChessSquare)(CHUDEMOTED(piece));
                 }
                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
                 if(*p = PieceSuffix(piece)) p++;
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
-                    p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
+                    p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED(piece)));
                     *p++ = '~';
                 }
            }
@@ -18188,7 +18257,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                     if(q = strchr(s, p[1])) p++;
                     piece = CharToPiece(c + (q ? 64*(q - s + 1) : 0));
                     if(piece == EmptySquare) return FALSE; /* unknown piece */
-                    piece = (ChessSquare) (CHUPROMOTED piece ); p++;
+                    piece = (ChessSquare) (CHUPROMOTED(piece)); p++;
                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
                 } else {
                     char c = *p++;
@@ -18198,7 +18267,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
                 if(piece==EmptySquare) return FALSE; /* unknown piece */
                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
-                    piece = (ChessSquare) (PROMOTED piece);
+                    piece = (ChessSquare) (PROMOTED(piece));
                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
                     p++;
                 }