Fix disappearance of premoved piece
[xboard.git] / backend.c
index 3254ff7..7243ee7 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -192,7 +192,6 @@ void GameEnds P((ChessMove result, char *resultDetails, int whosays));
 void EditPositionDone P((Boolean fakeRights));
 void PrintOpponents P((FILE *fp));
 void PrintPosition P((FILE *fp, int move));
-void StartChessProgram P((ChessProgramState *cps));
 void SendToProgram P((char *message, ChessProgramState *cps));
 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
@@ -516,7 +515,7 @@ AppData appData;
 Board boards[MAX_MOVES];
 /* [HGM] Following 7 needed for accurate legality tests: */
 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
-signed char  initialRights[BOARD_FILES];
+unsigned char initialRights[BOARD_FILES];
 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
 int   initialRulePlies, FENrulePlies;
 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
@@ -4163,6 +4162,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                          fprintf(debugFP, "Sending premove:\n");
                        SendToICS(str);
                      } else if (gotPremove) {
+                       int oldFMM = forwardMostMove;
                        gotPremove = 0;
                        ClearPremoveHighlights();
                        if (appData.debugMode)
@@ -4170,6 +4170,13 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                           UserMoveEvent(premoveFromX, premoveFromY,
                                        premoveToX, premoveToY,
                                         premovePromoChar);
+                       if(forwardMostMove == oldFMM) { // premove was rejected, highlight last opponent move
+                         if(moveList[oldFMM-1][1] != '@')
+                           SetHighlights(moveList[oldFMM-1][0]-AAA, moveList[oldFMM-1][1]-ONE,
+                                         moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+                         else // (drop)
+                           SetHighlights(-1, -1, moveList[oldFMM-1][2]-AAA, moveList[oldFMM-1][3]-ONE);
+                       }
                      }
                    }
 
@@ -4890,13 +4897,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. */
@@ -5146,16 +5153,18 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
       } else
       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
        char *m = moveList[moveNum];
+       static char c[2];
+       *c = m[7]; // promoChar
        if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
          snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
                                               m[2], m[3] - '0',
                                               m[5], m[6] - '0',
                                               m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
        else
-         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to two moves
                                               m[5], m[6] - '0',
                                               m[5], m[6] - '0',
-                                              m[2], m[3] - '0');
+                                              m[2], m[3] - '0', c);
          SendToProgram(buf, cps);
       } else
       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
@@ -5359,6 +5368,10 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
        } else {
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
+         if(killX >= 0 && killY >= 0) {
+           sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + killX, ONE + killY, promoChar);
+         }
        }
     }
 }
@@ -5381,12 +5394,22 @@ int dragging;
 static ClickType lastClickType;
 
 int
+PieceInString (char *s, ChessSquare piece)
+{
+  char *p, ID = ToUpper(PieceToChar(piece)), suffix = PieceSuffix(piece);
+  while((p = strchr(s, ID))) {
+    if(!suffix || p[1] == suffix) return TRUE;
+    s = p;
+  }
+  return FALSE;
+}
+
+int
 Partner (ChessSquare *p)
 { // change piece into promotion partner if one shogi-promotes to the other
-  int stride = gameInfo.variant == VariantChu ? WhiteTokin : 11;
-  ChessSquare partner;
-  partner = (*p/stride & 1 ? *p - stride : *p + stride);
+  ChessSquare partner = promoPartner[*p];
   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
+  if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
   *p = partner;
   return 1;
 }
@@ -5411,7 +5434,7 @@ Sweep (int step)
        if(!step) step = -1;
     } 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 
+           promoRestrict[0] ? !PieceInString(promoRestrict, promoSweep) : // if choice set available, use it 
            promoSweep == pawn ||
            appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess &&
             (promoSweep == WhiteLion || promoSweep == BlackLion)));
@@ -5547,6 +5570,7 @@ ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fro
         *toX = currentMoveString[2] - AAA;
         *toY = currentMoveString[3] - ONE;
        *promoChar = currentMoveString[4];
+       if(*promoChar == ';') *promoChar = currentMoveString[7];
         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
     if (appData.debugMode) {
@@ -5994,7 +6018,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)) - 2*(*s == '='), s++;
     return n;
 }
 
@@ -6003,28 +6027,58 @@ 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, offs;
+    int result = FALSE; int NrPieces;
+    unsigned char partner[EmptySquare];
 
     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 */
+        int i, ii, offs, j = 0; /* [HGM] Accept even length from 12 to 88 */
 
         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
         for( i=offs=0; i<NrPieces/2-1; i++ ) {
-            char *p;
-            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);
+            char *p, c=0;
+            if(map[j] == '/') offs = WhitePBishop - 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;
+            if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
         }
         table[(int) WhiteKing]  = map[j++];
-        for( i=offs=0; i<NrPieces/2-1; i++ ) {
-            char *p;
-            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);
+        for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
+            char *p, c=0;
+            if(map[j] == '/') offs = WhitePBishop - ii, j++;
+            i = WHITE_TO_BLACK ii;
+            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;
+            if(*escapes && map[j] == '=') pieceNickName[i+offs] = map[++j], j++;
         }
         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) {
+                        if(promoPartner[promoPartner[p]] == p) promoPartner[promoPartner[p]] = promoPartner[p]; // divorce old partners
+                        if(promoPartner[promoPartner[i]] == i) promoPartner[promoPartner[i]] = promoPartner[i];
+                        promoPartner[p] = i, promoPartner[i] = p; // and marry this couple
+                    }
+                } else if(c == '*') {
+                    table[i] = partner[i];
+                    promoPartner[i] = (i < BlackPawn ? WhiteTokin : BlackTokin); // promotes to Tokin
+                }
+            }
+        }
+
         result = TRUE;
     }
 
@@ -6117,7 +6171,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, "............");
@@ -6210,8 +6264,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.........^T..^L......^A^H/^F^G^M.^E^X^O^I.^P.^B^R..^D^S^C^VK"
+                                   "p.brqsexogcathd.vmlifn.........^t..^l......^a^h/^f^g^m.^e^x^o^i.^p.^b^r..^d^s^c^vk", SUFFIXES);
       break;
     case VariantCourier:
       pieces = CourierArray;
@@ -6440,7 +6494,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 */
@@ -6465,7 +6519,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 */
@@ -6630,9 +6684,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 >= WhiteTokin || PieceToChar(piece + WhiteTokin) != '+') ? WhitePawn : WhiteTokin-1;
+        highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
         highestPromotingPiece = (int)WhiteAlfil;
@@ -6697,7 +6750,7 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
            *promoChoice = PieceToChar(p++);
            if(*promoChoice != '.') break;
        }
-       return FALSE;
+       if(!*engineVariant) return FALSE; // if used as parent variant there might be promotion choice
     }
     // no sense asking what we must promote to if it is going to explode...
     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
@@ -6914,7 +6967,7 @@ int doubleClick;
 Boolean addToBookFlag;
 
 void
-UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
+UserMoveEvent (int fromX, int fromY, int toX, int toY, int promoChar)
 {
     ChessMove moveType;
     ChessSquare pup;
@@ -6998,6 +7051,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
+            DrawPosition(TRUE, boards[currentMove]); // [HGM] repair animation damage done by premove (in particular emptying from-square)
             return;
        }
        break;
@@ -7019,6 +7073,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
+            DrawPosition(TRUE, boards[currentMove]);
             return;
        }
        break;
@@ -7035,11 +7090,9 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
            return;
        } else if (toX >= 0 && toY >= 0) {
            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];
-               else p = CHUDEMOTED (q = boards[0][rf][ff]);
-               if(PieceToChar(q) == '+') gatingPiece = p;
+               ChessSquare p = boards[0][rf][ff];
+               if(PieceToChar(p) == '+') gatingPiece = CHUDEMOTED(p); else
+               if(PieceToChar(CHUPROMOTED(p)) =='+') gatingPiece = CHUPROMOTED(p); 
            }
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
@@ -7055,6 +7108,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                }
            } else
            boards[0][fromY][fromX] = gatingPiece;
+           ClearHighlights();
            DrawPosition(FALSE, boards[currentMove]);
            return;
        }
@@ -7103,7 +7157,8 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     if(addToBookFlag) { // adding moves to book
        char buf[MSG_SIZ], move[MSG_SIZ];
         CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
-       if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d", fromX + AAA, fromY + ONE - '0', killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0');
+       if(killX >= 0) snprintf(move, MSG_SIZ, "%c%dx%c%d-%c%d%c", fromX + AAA, fromY + ONE - '0',
+                                                                  killX + AAA, killY + ONE - '0', toX + AAA, toY + ONE - '0', promoChar);
        snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
        AddBookMove(buf);
        addToBookFlag = FALSE;
@@ -7404,8 +7459,8 @@ CanPromote (ChessSquare piece, int y)
        // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
        if(IS_SHOGI(gameInfo.variant)          || gameInfo.variant == VariantXiangqi ||
           gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
-          gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
-         gameInfo.variant == VariantMakruk) return FALSE;
+         (gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
+           gameInfo.variant == VariantMakruk) && !*engineVariant) return FALSE;
        return (piece == BlackPawn && y <= zone ||
                piece == WhitePawn && y >= BOARD_HEIGHT-1-zone ||
                piece == BlackLance && y <= zone ||
@@ -7718,7 +7773,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        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
@@ -7752,6 +7807,8 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            ClearHighlights();
        }
 #endif
+       if(PieceToChar(CHUPROMOTED(boards[currentMove][fromY][fromX])) == '+')
+         defaultPromoChoice = CHUPROMOTED(boards[currentMove][fromY][fromX]);
        if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece;
        if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square
          dragging *= 2;            // flag button-less dragging if we are dragging
@@ -7934,6 +7991,23 @@ RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
 }
 
 void
+Wheel (int dir, int x, int y)
+{
+    if(gameMode == EditPosition) {
+       int xSqr = EventToSquare(x, BOARD_WIDTH);
+       int ySqr = EventToSquare(y, BOARD_HEIGHT);
+       if(ySqr < 0 || xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return;
+       if(flipView) xSqr = BOARD_WIDTH - 1 - xSqr; else ySqr = BOARD_HEIGHT - 1 - ySqr;
+       do {
+           boards[currentMove][ySqr][xSqr] += dir;
+           if((int) boards[currentMove][ySqr][xSqr] < WhitePawn) boards[currentMove][ySqr][xSqr] = BlackKing;
+           if((int) boards[currentMove][ySqr][xSqr] > BlackKing) boards[currentMove][ySqr][xSqr] = WhitePawn;
+       } while(PieceToChar(boards[currentMove][ySqr][xSqr]) == '.');
+       DrawPosition(FALSE, boards[currentMove]);
+    } else if(dir > 0) ForwardEvent(); else BackwardEvent();
+}
+
+void
 SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats)
 {
 //    char * hint = lastHint;
@@ -8605,7 +8679,7 @@ HandleMachineMove (char *message, ChessProgramState *cps)
     ChessMove moveType;
     char promoChar, roar;
     char *p, *pv=buf1;
-    int machineWhite, oldError;
+    int oldError;
     char *bookHit;
 
     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
@@ -8697,6 +8771,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 
       } else {
 
+       int machineWhite = FALSE;
+
        switch (gameMode) {
          case BeginningOfGame:
            /* Extra move from before last reset; ignore */
@@ -8778,7 +8854,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c, %c%c) res=%d",
                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, kill2X+AAA, kill2Y+ONE, moveType);
            if (gameMode == TwoMachinesPlay) {
-             GameEnds(machineWhite ? BlackWins : WhiteWins,
+             GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
                        buf1, GE_XBOARD);
            }
            return;
@@ -8796,7 +8872,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
             if(moveType == IllegalMove) {
              snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
-                GameEnds(machineWhite ? BlackWins : WhiteWins,
+                GameEnds(cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
                            buf1, GE_XBOARD);
                return;
            } else if(!appData.fischerCastling)
@@ -9027,7 +9103,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
@@ -9046,7 +9122,7 @@ 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));
+      promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
       Sweep(0);
       return;
     }
@@ -10201,8 +10277,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)
@@ -10266,8 +10342,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)
@@ -10341,10 +10417,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; }
@@ -10352,9 +10428,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; }
@@ -10382,13 +10458,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)
@@ -10417,10 +10493,10 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
     if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one
     (void) CoordsToAlgebraic(boards[forwardMostMove],
                             PosFlags(forwardMostMove),
-                            fromY, fromX, y, x, promoChar,
+                            fromY, fromX, y, x, (killX < 0)*promoChar,
                             s);
     if(killX >= 0 && killY >= 0)
-        sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0');
+        sprintf(s + strlen(s), "%c%c%d%c", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0', promoChar);
 
     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
         int timeLeft; static int lastLoadFlag=0; int king, piece;
@@ -12087,7 +12163,7 @@ LoadGameOneMove (ChessMove readAhead)
         toX = currentMoveString[2] - AAA;
         toY = currentMoveString[3] - ONE;
        promoChar = currentMoveString[4];
-       if(promoChar == ';') promoChar = NULLCHAR;
+       if(promoChar == ';') promoChar = currentMoveString[7];
        break;
 
       case WhiteDrop:
@@ -13068,7 +13144,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
            return FALSE;
          }
          CopyBoard(boards[0], initial_position);
-         if(*engineVariant) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
+         if(*engineVariant || gameInfo.variant == VariantFairy) // [HGM] for now, assume FEN in engine-defined variant game is default initial position
            CopyBoard(initialPosition, initial_position);
          if (blackPlaysFirst) {
            currentMove = forwardMostMove = backwardMostMove = 1;
@@ -15378,7 +15454,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;
@@ -15386,7 +15462,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;
@@ -16238,7 +16314,7 @@ PrintPosition (FILE *fp, int move)
     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
            char c = PieceToChar(boards[move][i][j]);
-           fputc(c == 'x' ? '.' : c, fp);
+           fputc(c == '?' ? '.' : c, fp);
             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
        }
     }
@@ -16955,6 +17031,7 @@ ParseOption (Option *opt, ChessProgramState *cps)
        char *p, *q, buf[MSG_SIZ];
        int n, min = (-1)<<31, max = 1<<31, def;
 
+       opt->target = &opt->value;   // OK for spin/slider and checkbox
        if(p = strstr(opt->name, " -spin ")) {
            if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
            if(max < min) max = min; // enforce consistency
@@ -16977,14 +17054,17 @@ ParseOption (Option *opt, ChessProgramState *cps)
        } else if((p = strstr(opt->name, " -string "))) {
            opt->textValue = p+9;
            opt->type = TextBox;
+           opt->target = &opt->textValue;
        } 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->target = opt->textValue = p+7;
            opt->type = FileName; // FileName;
+           opt->target = &opt->textValue;
        } 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->target = opt->textValue = p+7;
            opt->type = PathName; // PathName;
+           opt->target = &opt->textValue;
        } else if(p = strstr(opt->name, " -check ")) {
            if(sscanf(p, " -check %d", &def) < 1) return FALSE;
            opt->value = (def != 0);
@@ -17048,9 +17128,9 @@ FeatureDone (ChessProgramState *cps, int val)
       (cb == TwoMachinesEventIfReady)) {
     CancelDelayedEvent();
     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
-  }
+  } else if(!val && !cps->reload) ClearOptions(cps); // let 'spurious' done=0 clear engine's option list
   cps->initDone = val;
-  if(val) cps->reload = FALSE;
+  if(val) cps->reload = FALSE,  RefreshSettingsDialog(cps, val);
 }
 
 /* Parse feature command from engine */
@@ -17992,13 +18072,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++ = '~';
                 }
            }
@@ -18240,7 +18320,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++;
@@ -18250,7 +18330,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++;
                 }