Prevent FICS bell character fro printing in ICS Console XB
[xboard.git] / backend.c
index 6fca83d..734cf40 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -5,7 +5,8 @@
  * Massachusetts.
  *
  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
+ * Software Foundation, Inc.
  *
  * Enhancements Copyright 2005 Alessandro Scotti
  *
@@ -192,7 +193,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,
@@ -387,7 +387,7 @@ u64ToDouble (u64 value)
    by this function.
  */
 int
-PosFlags (index)
+PosFlags (int index)
 {
   int flags = F_ALL_CASTLE_OK;
   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
@@ -516,7 +516,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)
@@ -1743,6 +1743,11 @@ InitBackEnd3 P((void))
                 CopyBoard(filePosition, boards[0]);
                 CopyBoard(initialPosition, boards[0]);
             }
+       } else if(*appData.fen != NULLCHAR) {
+           if(ParseFEN(filePosition, &blackPlaysFirst, appData.fen, TRUE) && !blackPlaysFirst) {
+                startedFromPositionFile = TRUE;
+               Reset(TRUE, TRUE);
+           }
        }
        if (initialMode == AnalyzeMode) {
          if (appData.noChessProgram) {
@@ -1982,7 +1987,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count
        DisplayFatalError(_("Error reading from keyboard"), error, 1);
     } else if (gotEof++ > 0) {
        RemoveInputSource(isr);
-       DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
+       DisplayFatalError(_("Got end of file from keyboard"), 0, 666); // [HGM] 666 is kludge to alert front end
     }
 }
 
@@ -4163,6 +4168,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 +4176,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);
+                       }
                      }
                    }
 
@@ -5146,16 +5159,26 @@ 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
+       else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
+         *c = m[9];
+         snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
+                                              m[7], m[8] - '0',
+                                              m[7], m[8] - '0',
                                               m[5], m[6] - '0',
                                               m[5], m[6] - '0',
-                                              m[2], m[3] - '0');
+                                              m[2], m[3] - '0', c);
+       } else
+         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', c);
          SendToProgram(buf, cps);
       } else
       if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
@@ -5354,11 +5377,15 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
          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\n", AAA + killX, ONE + killY);
+           if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c\n", AAA + kill2X, ONE + kill2Y);
          }
        } 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 + kill2X, ONE + kill2Y, promoChar);
+         }
        }
     }
 }
@@ -5380,11 +5407,23 @@ static int lastX, lastY, lastLeftX, lastLeftY, selectFlag;
 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
   ChessSquare partner = promoPartner[*p];
   if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0;
+  if(PieceToChar(*p) == '+') partner = boards[currentMove][fromY][fromX];
   *p = partner;
   return 1;
 }
@@ -5409,7 +5448,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)));
@@ -5545,6 +5584,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 + 2*(currentMoveString[8] != 0)];
         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) {
@@ -5992,19 +6032,10 @@ ptclen (const char *s, char *escapes)
 {
     int n = 0;
     if(!*escapes) return strlen(s);
-    while(*s) n += (*s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)), s++;
+    while(*s) n += (*s != '/' && *s != '-' && *s != '^' && *s != '*' && !strchr(escapes, *s)) - 2*(*s == '='), s++;
     return n;
 }
 
-static int pieceOrder[] = {
-  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, // P N B R Q F E A C W M
- 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // O H I J G D V L S U Lion
- 45, 23, 24, 25, 26, 27, 28, 29, 46, 31, 32, // Sword Zebra Camel Tower Wolf Dragon Duck Axe Leopard Gnu Cub
- 44, 51, 56, 57, 58, 59, 60, 61, 62, 63, 34, // Whale Pegasus Wizard Copper Iron Viking Flag Amazon Wheel Shield Claw
- 33, 55, 53, 42, 37, 48, 39, 40, 41, 22, 30, // +P +N =B =R +L +S +E +Ph +Kn Butterfly Hat
- 38, 43, 35, 36, 49, 47, 52, 50, 54, 64, 65 // +V +M =H =D Princess HSword +GB HCrown Wheer Shierd King
-};
-
 int
 SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 /* [HGM] moved here from winboard.c because of its general usefulness */
@@ -6015,25 +6046,28 @@ SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
 
     if( map != NULL && (NrPieces=ptclen(map, escapes)) <= (int) EmptySquare
                     && NrPieces >= 12 && !(NrPieces&1)) {
-        int i, ii, 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( ii=0; ii<NrPieces/2-1; ii++ ) {
+        for( i=offs=0; i<NrPieces/2-1; i++ ) {
             char *p, c=0;
-            i = pieceOrder[ii];
+            if(map[j] == '/') offs = WhitePBishop - i, j++;
             if(*escapes && (map[j] == '*' || map[j] == '-' || map[j] == '^')) c = map[j++];
-            table[i] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
-            if(c) partner[i] = table[i], table[i] = c;
+            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( ii=0; ii<NrPieces/2-1; ii++ ) {
+        for( ii=offs=0; ii<NrPieces/2-1; ii++ ) {
             char *p, c=0;
-            i = WHITE_TO_BLACK pieceOrder[ii];
+            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] = map[j++];
-            if(p = strchr(escapes, map[j])) j++, table[i] += 64*(p - escapes + 1);
-            if(c) partner[i] = table[i], table[i] = c;
+            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++];
 
@@ -6047,8 +6081,15 @@ SetCharTableEsc (unsigned char *table, const char * map, char * escapes)
                     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
+                    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
+                }
             }
         }
 
@@ -6237,8 +6278,8 @@ InitPosition (int redraw)
       gameInfo.boardWidth  = 12;
       gameInfo.boardHeight = 12;
       nrCastlingRights = 0;
-      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);
+      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;
@@ -6418,6 +6459,7 @@ InitPosition (int redraw)
           initialRights[i] = filePosition[CASTLING][i];
       startedFromSetupPosition = TRUE;
     }
+    if(*appData.men) LoadPieceDesc(appData.men);
 
     CopyBoard(boards[0], initialPosition);
 
@@ -6723,7 +6765,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) {
@@ -6822,6 +6864,7 @@ OKToStartUserMove (int x, int y)
       case PlayFromGameFile:
            if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
       case EditGame:
+      case AnalyzeMode:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
            return FALSE;
@@ -6940,7 +6983,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;
@@ -7024,6 +7067,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;
@@ -7045,6 +7089,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;
@@ -7061,11 +7106,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
@@ -7081,6 +7124,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                }
            } else
            boards[0][fromY][fromX] = gatingPiece;
+           ClearHighlights();
            DrawPosition(FALSE, boards[currentMove]);
            return;
        }
@@ -7129,7 +7173,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;
@@ -7375,10 +7420,11 @@ Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VO
 {
     typedef char Markers[BOARD_RANKS][BOARD_FILES];
     Markers *m = (Markers *) closure;
-    if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 : rt == killY && ft == killX || legNr & 2))
+    if(rf == fromY && ff == fromX && (killX < 0 ? !(rt == rf && ft == ff) && legNr & 1 :
+                                     kill2X < 0 ? rt == killY && ft == killX || legNr & 2 : rt == killY && ft == killX || legNr & 4))
        (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
                         || kind == WhiteCapturesEnPassant
-                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0), legal[rt][ft] = 3;
+                        || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && (killX < 0 & legNr || legNr & 2 && kill2X < 0)), legal[rt][ft] = 3;
     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3, legal[rt][ft] = 3;
 }
 
@@ -7391,7 +7437,7 @@ MarkTargetSquares (int clear)
   if(clear) { // no reason to ever suppress clearing
     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) sum += marker[y][x], marker[y][x] = 0;
     hoverSavedValid = 0;
-    if(!sum) return; // nothing was cleared,no redraw needed
+    if(!sum || clear < 0) return; // nothing was cleared,no redraw needed
   } else {
     int capt = 0;
     if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
@@ -7430,8 +7476,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 ||
@@ -7485,11 +7531,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
     static TimeMark lastClickTime, prevClickTime;
 
+    if(flashing) return;
+
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
     if (!flipView && y >= 0) {
@@ -7580,7 +7628,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
       if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves
        doubleClick = TRUE; gatingPiece = boards[currentMove][y][x];
       }
-      fromX = x; fromY = y; toX = toY = killX = killY = -1;
+      fromX = x; fromY = y; toX = toY = killX = killY = kill2X = kill2Y = -1;
       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
         // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
         appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
@@ -7632,13 +7680,13 @@ LeftClick (ClickType clickType, int xPix, int yPix)
             !(fromP == BlackKing && toP == BlackRook && frc)))) {
            /* Clicked again on same color piece -- changed his mind */
            second = (x == fromX && y == fromY);
-           killX = killY = -1;
+           killX = killY = kill2X = kill2Y = -1;
            if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) {
                second = FALSE; // first double-click rather than scond click
                doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves
            }
            promoDefaultAltered = FALSE;
-           MarkTargetSquares(1);
+          if(!second) MarkTargetSquares(1);
           if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
@@ -7695,10 +7743,11 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            second = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
-           MarkTargetSquares(1);
            ClearHighlights();
            gotPremove = 0;
            ClearPremoveHighlights();
+           MarkTargetSquares(-1);
+           DrawPosition(FALSE, NULL); // make user highlights are drawn (and deferred marker clearing)
        } else {
            /* First upclick in same square; start click-click mode */
            SetHighlights(x, y, -1, -1);
@@ -7738,7 +7787,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            return;
        }
        if(x == killX && y == killY) {              // second click on this square, which was selected as first-leg target
-           killX = killY = -1;                     // this informs us no second leg is coming, so treat as to-click without intermediate
+           killX = kill2X; killY = kill2Y; kill2X = kill2Y = -1;   // this informs us no second leg is coming, so treat as to-click without intermediate
        } else
        if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release
        if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
@@ -7747,6 +7796,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            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
+           saveFlash = appData.flashCount; appData.flashCount = 0;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
            sweepSelecting = 1;
            DisplayMessage("", _("Pull pawn backwards to under-promote"));
@@ -7760,14 +7810,16 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        } else {
            ClearHighlights();
        }
+       MarkTargetSquares(1);
     } 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;
+        *promoRestrict = 0; appData.flashCount = saveFlash;
        if (appData.animate || appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
        } else {
            ClearHighlights();
        }
+       MarkTargetSquares(1);
     } else {
 #if 0
 // [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square
@@ -7778,6 +7830,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
@@ -7785,7 +7839,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
          if(x == killX && y == killY) killX = kill2X, killY = kill2Y, kill2X = kill2Y = -1; // cancel last kill
          else {
            kill2X = killX; kill2Y = killY;
-           killX = x; killY = y;     //remeber this square as intermediate
+           killX = x; killY = y;     // remember this square as intermediate
            ReportClick("put", x, y); // and inform engine
            ReportClick("lift", x, y);
            MarkTargetSquares(0);
@@ -7795,6 +7849,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
+        MarkTargetSquares(-1); // -1 defers displaying marker change to prevent piece reappearing on from-square!
     }
 
     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
@@ -7852,19 +7907,20 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        PromotionPopUp(promoChoice);
     } else {
        int oldMove = currentMove;
+       flashing = 1; // prevent recursive calling (by release of to-click) while flashing piece
        UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
        if (!appData.highlightLastMove || gotPremove) ClearHighlights();
        if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
        if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
           Explode(boards[currentMove-1], fromX, fromY, toX, toY))
            DrawPosition(TRUE, boards[currentMove]);
-        MarkTargetSquares(1);
        fromX = fromY = -1;
+       flashing = 0;
     }
     appData.animate = saveAnimate;
     if (appData.animate || appData.animateDragging) {
        /* Undo animation damage if needed */
-       DrawPosition(FALSE, NULL);
+//     DrawPosition(FALSE, NULL);
     }
 }
 
@@ -7959,6 +8015,23 @@ RightClick (ClickType action, int x, int y, int *fromX, int *fromY)
     return whichMenu;
 }
 
+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)
 {
@@ -8624,14 +8697,14 @@ static char stashedInputMove[MSG_SIZ], abortEngineThink;
 void
 HandleMachineMove (char *message, ChessProgramState *cps)
 {
-    static char firstLeg[20];
+    static char firstLeg[20], legs;
     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
     char realname[MSG_SIZ];
     int fromX, fromY, toX, toY;
     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) {
@@ -8723,6 +8796,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 */
@@ -8775,11 +8850,14 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        // [HGM] lion: (some very limited) support for Alien protocol
        killX = killY = kill2X = kill2Y = -1;
        if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move
+           if(legs++) return;                     // middle leg contains only redundant info, ignore (but count it)
            safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives
            return;
        }
        if(p = strchr(machineMove, ',')) {         // we got both legs in one (happens on book move)
+           char *q = strchr(p+1, ',');            // second comma?
            safeStrCpy(firstLeg, machineMove, 20); // kludge: fake we received the first leg earlier, and clip it off
+           if(q) legs = 2, p = q; else legs = 1;  // with 3-leg move we clipof first two legs!
            safeStrCpy(machineMove, firstLeg + (p - machineMove) + 1, 20);
        }
        if(firstLeg[0]) { // there was a previous leg;
@@ -8789,10 +8867,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
            while(isdigit(*q)) q++; // find start of to-square
            safeStrCpy(machineMove, firstLeg, 20);
            while(isdigit(*p)) p++; // to-square of first leg (which is now copied to machineMove)
-           if(*p == *buf)          // if first-leg to not equal to second-leg from first leg says unmodified (assume it ia King move of castling)
+           if(legs == 2) sscanf(p, "%c%d", &f, &kill2Y), kill2X = f - AAA, kill2Y -= ONE - '0'; // in 3-leg move 2nd kill is to-sqr of 1st leg
+           else if(*p == *buf)   // if first-leg to not equal to second-leg from first leg says unmodified (assume it is King move of castling)
            safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move
            sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global
-           firstLeg[0] = NULLCHAR;
+           firstLeg[0] = NULLCHAR; legs = 0;
        }
 
         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
@@ -8804,7 +8883,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;
@@ -8822,7 +8901,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)
@@ -9072,7 +9151,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;
     }
@@ -9716,6 +9795,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                     [AS] Protect the thinkOutput buffer from overflow... this
                     is only useful if buf1 hasn't overflowed first!
                 */
+               if((gameMode == AnalyzeMode && appData.whitePOV || appData.scoreWhite) && !WhiteOnMove(forwardMostMove)) curscore *= -1;
                if(curscore >= MATE_SCORE) 
                    snprintf(score_buf, MSG_SIZ, "#%d", curscore - MATE_SCORE);
                else if(curscore <= -MATE_SCORE) 
@@ -10160,7 +10240,7 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
           if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
        }
 
-     if (fromX == toX && fromY == toY) return;
+     if (fromX == toX && fromY == toY && killX < 0) return;
 
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
@@ -10440,13 +10520,17 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
     ChessSquare p = boards[forwardMostMove][toY][toX];
 //    forwardMostMove++; // [HGM] bare: moved downstream
 
+    if(kill2X >= 0) x = kill2X, y = kill2Y; else
     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(kill2X >= 0 && kill2Y >= 0)
+        sprintf(s + strlen(s), "x%c%d", killX + AAA, killY + ONE - '0'); // 2nd leg of 3-leg move is always capture
     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 || toX== kill2X && toY == kill2Y ? '-' : '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;
@@ -10560,7 +10644,7 @@ ShowMove (int fromX, int fromY, int toX, int toY)
        currentMove = forwardMostMove;
     }
 
-    killX = killY = -1; // [HGM] lion: used up
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
 
     if (instant) return;
 
@@ -11403,7 +11487,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays)
              result, resultDetails ? resultDetails : "(null)", whosays);
     }
 
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move the user is entering. // [HGM] lion
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move the user is entering. // [HGM] lion
 
     if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit)
 
@@ -11901,7 +11985,7 @@ Reset (int redraw, int init)
     ClearPremoveHighlights();
     gotPremove = FALSE;
     alarmSounded = FALSE;
-    killX = killY = -1; // [HGM] lion
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion
 
     GameEnds(EndOfFile, NULL, GE_PLAYER);
     if(appData.serverMovesName != NULL) {
@@ -12019,21 +12103,17 @@ AutoPlayOneMove ()
            SetHighlights(-1, -1, toX, toY);
        }
     } else {
-        int viaX = moveList[currentMove][5] - AAA;
-        int viaY = moveList[currentMove][6] - ONE;
         fromX = moveList[currentMove][0] - AAA;
         fromY = moveList[currentMove][1] - ONE;
 
         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
 
         if(moveList[currentMove][4] == ';') { // multi-leg
-            ChessSquare piece = boards[currentMove][viaY][viaX];
-           AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
-            boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
-            AnimateMove(boards[currentMove], fromX=viaX, fromY=viaY, toX, toY);
-            boards[currentMove][viaY][viaX] = piece;
-        } else
+            killX = moveList[currentMove][5] - AAA;
+            killY = moveList[currentMove][6] - ONE;
+        }
        AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+       killX = killY = -1;
 
        if (appData.highlightLastMove) {
            SetHighlights(fromX, fromY, toX, toY);
@@ -12113,7 +12193,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:
@@ -12285,7 +12365,7 @@ LoadGameOneMove (ChessMove readAhead)
 
        thinkOutput[0] = NULLCHAR;
        MakeMove(fromX, fromY, toX, toY, promoChar);
-       killX = killY = -1; // [HGM] lion: used up
+       killX = killY = kill2X = kill2Y = -1; // [HGM] lion: used up
        currentMove = forwardMostMove;
        return TRUE;
     }
@@ -12854,7 +12934,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     char buf[MSG_SIZ];
     int gn = gameNumber;
     ListGame *lg = NULL;
-    int numPGNTags = 0;
+    int numPGNTags = 0, i;
     int err, pos = -1;
     GameMode oldGameMode;
     VariantClass v, oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
@@ -12872,7 +12952,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (gameMode != BeginningOfGame) {
       Reset(FALSE, TRUE);
     }
-    killX = killY = -1; // [HGM] lion: in case we did not Reset
+    killX = killY = kill2X = kill2Y = -1; // [HGM] lion: in case we did not Reset
 
     gameFileFP = f;
     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
@@ -13043,6 +13123,8 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
     if (appData.debugMode)
       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
 
+    for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; } // reset VariantMen
+
     if (cm == XBoardGame) {
        /* Skip any header junk before position diagram and/or move 1 */
        for (;;) {
@@ -13094,7 +13176,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;
@@ -15316,7 +15398,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
 
     switch (selection) {
       case ClearBoard:
-       fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+       fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
        MarkTargetSquares(1);
        CopyBoard(currentBoard, boards[0]);
        CopyBoard(menuBoard, initialPosition);
@@ -15769,7 +15851,7 @@ ForwardInner (int target)
 
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
 
     if (gameMode == PlayFromGameFile && !pausing)
       PauseEvent();
@@ -15790,19 +15872,15 @@ ForwardInner (int target)
                SetHighlights(-1, -1, toX, toY);
            }
        } else {
-            int viaX = moveList[target - 1][5] - AAA;
-            int viaY = moveList[target - 1][6] - ONE;
             fromX = moveList[target - 1][0] - AAA;
             fromY = moveList[target - 1][1] - ONE;
            if (target == currentMove + 1) {
                if(moveList[target - 1][4] == ';') { // multi-leg
-                   ChessSquare piece = boards[currentMove][viaY][viaX];
-                   AnimateMove(boards[currentMove], fromX, fromY, viaX, viaY);
-                   boards[currentMove][viaY][viaX] = boards[currentMove][fromY][fromX];
-                   AnimateMove(boards[currentMove], viaX, viaY, toX, toY);
-                   boards[currentMove][viaY][viaX] = piece;
-               } else
+                   killX = moveList[target - 1][5] - AAA;
+                   killY = moveList[target - 1][6] - ONE;
+               }
                AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+               killX = killY = -1;
            }
            if (appData.highlightLastMove) {
                SetHighlights(fromX, fromY, toX, toY);
@@ -15888,7 +15966,7 @@ BackwardInner (int target)
     if (gameMode == EditPosition) return;
     seekGraphUp = FALSE;
     MarkTargetSquares(1);
-    fromX = fromY = killX = killY = -1; // [HGM] abort any move entry in progress
+    fromX = fromY = killX = killY = kill2X = kill2Y = -1; // [HGM] abort any move entry in progress
     if (currentMove <= backwardMostMove) {
        ClearHighlights();
        DrawPosition(full_redraw, boards[currentMove]);
@@ -16264,7 +16342,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);
        }
     }