Remove promotion-piece encoding from ChessMove type
[xboard.git] / backend.c
index f806abe..2a6e2e6 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -131,9 +131,16 @@ extern int gettimeofday(struct timeval *, struct timezone *);
 #ifdef ENABLE_NLS 
 # define _(s) gettext (s) 
 # define N_(s) gettext_noop (s) 
+# define T_(s) gettext(s)
 #else 
-# define _(s) (s) 
-# define N_(s) s 
+# ifdef WIN32
+#   define _(s) T_(s)
+#   define N_(s) s
+# else
+#   define _(s) (s) 
+#   define N_(s) s 
+#   define T_(s) s
+# endif
 #endif 
 
 
@@ -151,8 +158,7 @@ void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
 void ics_printf P((char *format, ...));
 void SendToICS P((char *s));
 void SendToICSDelayed P((char *s, long msdelay));
-void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
-                     int toX, int toY));
+void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
 void HandleMachineMove P((char *message, ChessProgramState *cps));
 int AutoPlayOneMove P((void));
 int LoadGameOneMove P((ChessMove readAhead));
@@ -234,6 +240,7 @@ char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
 void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
+VariantClass startVariant; /* [HGM] nicks: initial variant */
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -243,7 +250,12 @@ static int exiting = 0; /* [HGM] moved to top */
 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
+int partnerHighlight[2];
+Boolean partnerBoardValid = 0;
+char partnerStatus[MSG_SIZ];
 Boolean partnerUp;
+Boolean originalFlip;
+Boolean twoBoards = 0;
 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
@@ -632,6 +644,7 @@ InitBackEnd1()
     int matched, min, sec;
 
     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
+    startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
 
     GetTimeMark(&programStartTime);
     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
@@ -707,8 +720,8 @@ InitBackEnd1()
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
     
-    first.which = "first";
-    second.which = "second";
+    first.which = _("first");
+    second.which = _("second");
     first.maybeThinking = second.maybeThinking = FALSE;
     first.pr = second.pr = NoProc;
     first.isr = second.isr = NULL;
@@ -881,7 +894,7 @@ InitBackEnd1()
       case VariantGothic:     /* [HGM] should work */
       case VariantCapablanca: /* [HGM] should work */
       case VariantCourier:    /* [HGM] initial forced moves not implemented */
-      case VariantShogi:      /* [HGM] drops not tested for legality */
+      case VariantShogi:      /* [HGM] could still mate with pawn drop */
       case VariantKnightmate: /* [HGM] should work */
       case VariantCylinder:   /* [HGM] untested */
       case VariantFalcon:     /* [HGM] untested */
@@ -1103,6 +1116,11 @@ InitBackEnd3 P((void))
 
     InitChessProgram(&first, startedFromSetupPosition);
 
+    if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
+       free(programVersion);
+       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
+    }
 
     if (appData.icsActive) {
 #ifdef WIN32
@@ -1341,6 +1359,19 @@ establish()
     }
 }
 
+void EscapeExpand(char *p, char *q)
+{      // [HGM] initstring: routine to shape up string arguments
+       while(*p++ = *q++) if(p[-1] == '\\')
+           switch(*q++) {
+               case 'n': p[-1] = '\n'; break;
+               case 'r': p[-1] = '\r'; break;
+               case 't': p[-1] = '\t'; break;
+               case '\\': p[-1] = '\\'; break;
+               case 0: *p = 0; return;
+               default: p[-1] = q[-1]; break;
+           }
+}
+
 void
 show_bytes(fp, buf, count)
      FILE *fp;
@@ -1973,7 +2004,7 @@ void
 VariantSwitch(Board board, VariantClass newVariant)
 {
    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
-   Board oldBoard;
+   static Board oldBoard;
 
    startedFromPositionFile = FALSE;
    if(gameInfo.variant == newVariant) return;
@@ -2181,8 +2212,8 @@ MatchSoughtLine(char *line)
 int
 DrawSeekGraph()
 {
-    if(!seekGraphUp) return FALSE;
     int i;
+    if(!seekGraphUp) return FALSE;
     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
 
@@ -2701,7 +2732,7 @@ read_from_ics(isr, closure, data, count, error)
            if (appData.autoKibitz && started == STARTED_NONE && 
                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
                (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
-               if(looking_at(buf, &i, "* kibitzes: ") &&
+               if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
                   (StrStr(star_match[0], gameInfo.white) == star_match[0] || 
                    StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
                        suppressKibitz = TRUE;
@@ -2740,6 +2771,7 @@ read_from_ics(isr, closure, data, count, error)
            channel = -1;
            if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || 
                                           looking_at(buf, &i, "* whispers:") ||
+                                          looking_at(buf, &i, "* kibitzes:") ||
                                           looking_at(buf, &i, "* shouts:") ||
                                           looking_at(buf, &i, "* c-shouts:") ||
                                           looking_at(buf, &i, "--> * ") ||
@@ -2759,6 +2791,13 @@ read_from_ics(isr, closure, data, count, error)
                    chattingPartner = p; break;
                    }
                } else
+               if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
+               for(p=0; p<MAX_CHAT; p++) {
+                   if(!strcmp("kibitzes", chatPartner[p])) {
+                       talker[0] = '['; strcat(talker, "] ");
+                       chattingPartner = p; break;
+                   }
+               } else
                if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
                for(p=0; p<MAX_CHAT; p++) {
                    if(!strcmp("whispers", chatPartner[p])) {
@@ -2766,14 +2805,23 @@ read_from_ics(isr, closure, data, count, error)
                        chattingPartner = p; break;
                    }
                } else
-               if(buf[i-3] == 't' || buf[oldi+2] == '>') // shout, c-shout or it; look if there is a 'shouts' chatbox
-               for(p=0; p<MAX_CHAT; p++) {
+               if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
+                 if(buf[i-8] == '-' && buf[i-3] == 't')
+                 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
+                   if(!strcmp("c-shouts", chatPartner[p])) {
+                       talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
+                       chattingPartner = p; break;
+                   }
+                 }
+                 if(chattingPartner < 0)
+                 for(p=0; p<MAX_CHAT; p++) {
                    if(!strcmp("shouts", chatPartner[p])) {
                        if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
                        else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
                        else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
                        chattingPartner = p; break;
                    }
+                 }
                }
                if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
                for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
@@ -2796,18 +2844,9 @@ read_from_ics(isr, closure, data, count, error)
                 /* [DM] Backup address for color zippy lines */
                 backup = i;
 #if ZIPPY
-       #ifdef WIN32
                if (loggedOn == TRUE)
                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
-       #else
-                if (ZippyControl(buf, &i) ||
-                    ZippyConverse(buf, &i) ||
-                    (appData.zippyPlay && ZippyMatch(buf, &i))) {
-                     loggedOn = TRUE;
-                      if (!appData.colorize) continue;
-               }
-       #endif
 #endif
            } // [DM] 'else { ' deleted
                if (
@@ -3461,6 +3500,7 @@ read_from_ics(isr, closure, data, count, error)
                        ZippyGameStart(whitename, blackname);
                    }
 #endif /*ZIPPY*/
+                   partnerBoardValid = FALSE; // [HGM] bughouse
                    continue;
                }
 
@@ -3501,6 +3541,7 @@ read_from_ics(isr, closure, data, count, error)
                    Reset(TRUE, TRUE);
                }
 #endif /*ZIPPY*/
+               if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
                continue;
            }
 
@@ -3659,7 +3700,9 @@ read_from_ics(isr, closure, data, count, error)
                         /* [HGM] copy holdings to partner-board holdings area */
                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
+                        if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
+                        if(twoBoards) { partnerUp = 0; flipView = !flipView; }
                      }
                    }
                    /* Suppress following prompt */
@@ -3811,9 +3854,9 @@ ParseBoard12(string)
     }
     
     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
-        && newGameMode == IcsObserving && appData.bgObserve) {
+        && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
-      char buf[MSG_SIZ];
+      char *toSqr;
       for (k = 0; k < ranks; k++) {
         for (j = 0; j < files; j++)
           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
@@ -3823,10 +3866,22 @@ ParseBoard12(string)
         }
       }
       CopyBoard(partnerBoard, board);
+      if(toSqr = strchr(str, '/')) { // extract highlights from long move
+        partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
+        partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
+      } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
+      if(toSqr = strchr(str, '-')) {
+        partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
+        partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
+      } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
+      if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
+      if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
       if(partnerUp) DrawPosition(FALSE, partnerBoard);
-      sprintf(buf, "W: %d:%d B: %d:%d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
+      if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
+      sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
                 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
-      DisplayMessage(buf, "");
+      DisplayMessage(partnerStatus, "");
+       partnerBoardValid = TRUE;
       return;
     }
 
@@ -4359,6 +4414,7 @@ ParseBoard12(string)
              ClearPremoveHighlights();
 
       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
+       if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
       DrawPosition(j, boards[currentMove]);
 
       DisplayMove(moveNum - 1);
@@ -4473,7 +4529,7 @@ SendMoveToProgram(moveNum, cps)
     /*       Send 'go' if we are in a mode where machine should play. */
     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
         (gameMode == TwoMachinesPlay   ||
-#ifdef ZIPPY
+#if ZIPPY
          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
 #endif
          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
@@ -4486,9 +4542,10 @@ SendMoveToProgram(moveNum, cps)
 }
 
 void
-SendMoveToICS(moveType, fromX, fromY, toX, toY)
+SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
      ChessMove moveType;
      int fromX, fromY, toX, toY;
+     char promoChar;
 {
     char user_move[MSG_SIZ];
 
@@ -4518,20 +4575,12 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       /* POP Fabien */
        sprintf(user_move, "o-o-o\n");
        break;
-      case WhitePromotionQueen:
-      case BlackPromotionQueen:
-      case WhitePromotionRook:
-      case BlackPromotionRook:
-      case WhitePromotionBishop:
-      case BlackPromotionBishop:
-      case WhitePromotionKnight:
-      case BlackPromotionKnight:
-      case WhitePromotionKing:
-      case BlackPromotionKing:
-      case WhitePromotionChancellor:
-      case BlackPromotionChancellor:
-      case WhitePromotionArchbishop:
-      case BlackPromotionArchbishop:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
+        sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+        break;
+      case WhitePromotion:
+      case BlackPromotion:
         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
             sprintf(user_move, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
@@ -4543,7 +4592,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
         else
             sprintf(user_move, "%c%c%c%c=%c\n",
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
-               PieceToChar(PromoPiece(moveType)));
+               promoChar);
        break;
       case WhiteDrop:
       case BlackDrop:
@@ -4732,20 +4781,10 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
 
     switch (*moveType) {
-      case WhitePromotionChancellor:
-      case BlackPromotionChancellor:
-      case WhitePromotionArchbishop:
-      case BlackPromotionArchbishop:
-      case WhitePromotionQueen:
-      case BlackPromotionQueen:
-      case WhitePromotionRook:
-      case BlackPromotionRook:
-      case WhitePromotionBishop:
-      case BlackPromotionBishop:
-      case WhitePromotionKnight:
-      case BlackPromotionKnight:
-      case WhitePromotionKing:
-      case BlackPromotionKing:
+      case WhitePromotion:
+      case BlackPromotion:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
       case NormalMove:
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
@@ -5203,7 +5242,10 @@ InitPosition(redraw)
     for(i=0; i<BOARD_FILES-2; i++)
       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
     initialPosition[EP_STATUS] = EP_NONE;
-    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); 
+    SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+    if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
+         SetCharTable(pieceNickName, appData.pieceNickNames);
+    else SetCharTable(pieceNickName, "............");
 
     switch (gameInfo.variant) {
     case VariantFischeRandom:
@@ -5519,7 +5561,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
 
     piece = boards[currentMove][fromY][fromX];
     if(gameInfo.variant == VariantShogi) {
-        promotionZoneSize = 3;
+        promotionZoneSize = BOARD_HEIGHT/3;
         highestPromotingPiece = (int)WhiteFerz;
     } else if(gameInfo.variant == VariantMakruk) {
         promotionZoneSize = 3;
@@ -5583,8 +5625,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
     if(appData.testLegality && !premove) {
        moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                        fromY, fromX, toY, toX, NULLCHAR);
-       if(moveType != WhitePromotionQueen && moveType  != BlackPromotionQueen &&
-          moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
+       if(moveType != WhitePromotion && moveType  != BlackPromotion)
            return FALSE;
     }
 
@@ -5737,8 +5778,7 @@ OnlyMove(int *x, int *y, Boolean captures) {
     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
     if( cl.kind == NormalMove ||
        cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
-       cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
-       cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+       cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
        cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
       fromX = cl.ff;
       fromY = cl.rf;
@@ -5756,8 +5796,7 @@ OnlyMove(int *x, int *y, Boolean captures) {
     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
     if( cl.kind == NormalMove ||
        cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
-       cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
-       cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+       cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
        cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
       fromX = cl.ff;
       fromY = cl.rf;
@@ -5775,11 +5814,10 @@ int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = (ChessMove) 0;
 
-ChessMove
-UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
      int fromX, fromY, toX, toY;
      int promoChar;
-     Boolean captureOwn;
 {
     ChessMove moveType;
     ChessSquare pdown, pup;
@@ -5804,13 +5842,13 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
       case IcsIdle:
        /* We switched into a game mode where moves are not accepted,
            perhaps while the mouse button was down. */
-        return ImpossibleMove;
+        return;
 
       case MachinePlaysWhite:
        /* User is moving for Black */
        if (WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5818,7 +5856,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
        /* User is moving for White */
        if (!WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is Black's turn"));
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5832,13 +5870,13 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
            /* User is moving for Black */
            if (WhiteOnMove(currentMove)) {
                DisplayMoveError(_("It is White's turn"));
-                return ImpossibleMove;
+                return;
            }
        } else {
            /* User is moving for White */
            if (!WhiteOnMove(currentMove)) {
                DisplayMoveError(_("It is Black's turn"));
-                return ImpossibleMove;
+                return;
            }
        }
        break;
@@ -5860,7 +5898,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5881,7 +5919,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
                            "fromY %d, toX %d, toY %d\n",
                            fromX, fromY, toX, toY);
            }
-            return ImpossibleMove;
+            return;
        }
        break;
 
@@ -5893,7 +5931,8 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
           click-click move is possible */
        if (toX == -2 || toY == -2) {
            boards[0][fromY][fromX] = EmptySquare;
-           return AmbiguousMove;
+           DrawPosition(FALSE, boards[currentMove]);
+           return;
        } else if (toX >= 0 && toY >= 0) {
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
@@ -5909,28 +5948,27 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
                }
            } else
            boards[0][fromY][fromX] = EmptySquare;
-           return AmbiguousMove;
+           DrawPosition(FALSE, boards[currentMove]);
+           return;
        }
-        return ImpossibleMove;
+        return;
     }
 
-    if(toX < 0 || toY < 0) return ImpossibleMove;
+    if(toX < 0 || toY < 0) return;
     pdown = boards[currentMove][fromY][fromX];
     pup = boards[currentMove][toY][toX];
 
-    /* [HGM] If move started in holdings, it means a drop */
+    /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
-         if( pup != EmptySquare ) return ImpossibleMove;
-         if(appData.testLegality) {
-             /* it would be more logical if LegalityTest() also figured out
-              * which drops are legal. For now we forbid pawns on back rank.
-              * Shogi is on its own here...
-              */
-             if( (pdown == WhitePawn || pdown == BlackPawn) &&
-                 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
-                 return(ImpossibleMove); /* no pawn drops on 1st/8th */
-         }
-         return WhiteDrop; /* Not needed to specify white or black yet */
+         if( pup != EmptySquare ) return;
+         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
+          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
+               moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
+          // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
+          if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
+          fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
+          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
+         fromY = DROP_RANK;
     }
 
     /* [HGM] always test for legality, to get promotion info */
@@ -5940,18 +5978,11 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
     if (appData.testLegality) {
        if (moveType == IllegalMove || moveType == ImpossibleMove) {
            DisplayMoveError(_("Illegal move"));
-            return ImpossibleMove;
+            return;
        }
     }
 
-    return moveType;
-    /* [HGM] <popupFix> in stead of calling FinishMove directly, this
-       function is made into one that returns an OK move type if FinishMove
-       should be called. This to give the calling driver routine the
-       opportunity to finish the userMove input with a promotion popup,
-       without bothering the user with this for invalid or illegal moves */
-
-/*    FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
+    FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
 /* Common tail of UserMoveEvent and DropMenuEvent */
@@ -5976,19 +6007,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
        move type in caller when we know the move is a legal promotion */
     if(moveType == NormalMove && promoChar)
-        moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
-
-    /* [HGM] convert drag-and-drop piece drops to standard form */
-    if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
-         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
-          if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
-               moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
-          // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
-          if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
-          fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
-          while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
-         fromY = DROP_RANK;
-    }
+        moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
 
     /* [HGM] <popupFix> The following if has been moved here from
        UserMoveEvent(). Because it seemed to belong here (why not allow
@@ -6076,10 +6095,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
        SendToICS("draw ");
-        SendMoveToICS(moveType, fromX, fromY, toX, toY);
+        SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
       }
       // also send plain move, in case ICS does not understand atomic claims
-      SendMoveToICS(moveType, fromX, fromY, toX, toY);
+      SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
       ics_user_moved = 1;
     }
   } else {
@@ -6147,28 +6166,6 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 }
 
 void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
-     int fromX, fromY, toX, toY;
-     int promoChar;
-{
-    /* [HGM] This routine was added to allow calling of its two logical
-       parts from other modules in the old way. Before, UserMoveEvent()
-       automatically called FinishMove() if the move was OK, and returned
-       otherwise. I separated the two, in order to make it possible to
-       slip a promotion popup in between. But that it always needs two
-       calls, to the first part, (now called UserMoveTest() ), and to
-       FinishMove if the first part succeeded. Calls that do not need
-       to do anything in between, can call this routine the old way. 
-    */
-    ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
-if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
-    if(moveType == AmbiguousMove)
-       DrawPosition(FALSE, boards[currentMove]);
-    else if(moveType != ImpossibleMove && moveType != Comment)
-        FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
-}
-
-void
 Mark(board, flags, kind, rf, ff, rt, ft, closure)
      Board board;
      int flags;
@@ -6209,7 +6206,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0;
+    static int second = 0, promotionChoice = 0, dragging = 0;
     char promoChoice = NULLCHAR;
 
     if(appData.seekGraph && appData.icsActive && loggedOn &&
@@ -6269,11 +6266,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
                fromY = y;
                second = 0;
                MarkTargetSquares(0);
-               DragPieceBegin(xPix, yPix);
+               DragPieceBegin(xPix, yPix); dragging = 1;
                if (appData.highlightDragging) {
                    SetHighlights(x, y, -1, -1);
                }
            }
+       } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
+           DragPieceEnd(xPix, yPix); dragging = 0;
+           DrawPosition(FALSE, NULL);
        }
        return;
       }
@@ -6302,7 +6302,7 @@ void 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);
-          if(!second || !OnlyMove(&x, &y, TRUE)) {
+          if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
            if (appData.highlightDragging) {
                SetHighlights(x, y, -1, -1);
            } else {
@@ -6310,7 +6310,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
            }
            if (OKToStartUserMove(x, y)) {
                fromX = x;
-               fromY = y;
+               fromY = y; dragging = 1;
                MarkTargetSquares(0);
                DragPieceBegin(xPix, yPix);
            }
@@ -6322,7 +6322,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
     }
 
     if (clickType == Release && x == fromX && y == fromY) {
-       DragPieceEnd(xPix, yPix);
+       DragPieceEnd(xPix, yPix); dragging = 0;
        if (appData.animateDragging) {
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
@@ -6360,7 +6360,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix)
        } else {
            ClearHighlights();
        }
-       DragPieceEnd(xPix, yPix);
+       DragPieceEnd(xPix, yPix); dragging = 0;
        /* Don't animate move and drag both */
        appData.animate = FALSE;
     }
@@ -6436,8 +6436,19 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
 
     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
         && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
-       if(action == Press)   { flipView = !flipView; DrawPosition(TRUE, partnerBoard); partnerUp = TRUE; } else
-       if(action == Release) { flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); partnerUp = FALSE; }
+       if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
+       if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
+       if(action == Press)   {
+           originalFlip = flipView;
+           flipView = !flipView; // temporarily flip board to see game from partners perspective
+           DrawPosition(TRUE, partnerBoard);
+           DisplayMessage(partnerStatus, "");
+           partnerUp = TRUE;
+       } else if(action == Release) {
+           flipView = originalFlip;
+           DrawPosition(TRUE, boards[currentMove]);
+           partnerUp = FALSE;
+       }
        return -2;
     }
 
@@ -6520,6 +6531,103 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
     SetProgramStats( &stats );
 }
 
+void
+Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
+{      // count all piece types
+       int p, f, r;
+       *nB = *nW = *wStale = *bStale = *bishopColor = 0;
+       for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+               p = board[r][f];
+               pCnt[p]++;
+               if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
+               if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
+               if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
+               if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
+                  p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
+                       *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
+       }
+}
+
+int
+SufficientDefence(int pCnt[], int side, int nMine, int nHis)
+{
+       int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
+       int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
+                  
+       nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
+       if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
+       if(myPawns == 2 && nMine == 3) // KPP
+           return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
+       if(myPawns == 1 && nMine == 2) // KP
+           return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
+       if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
+           return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
+       if(myPawns) return FALSE;
+       if(pCnt[WhiteRook+side])
+           return pCnt[BlackRook-side] || 
+                  pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
+                  pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
+                  pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
+       if(pCnt[WhiteCannon+side]) {
+           if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
+           return majorDefense || pCnt[BlackAlfil-side] >= 2;
+       }
+       if(pCnt[WhiteKnight+side])
+           return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
+       return FALSE;
+}
+
+int
+MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
+{
+       VariantClass v = gameInfo.variant;
+
+       if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
+       if(v == VariantShatranj) return TRUE; // always winnable through baring
+       if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
+       if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
+
+       if(v == VariantXiangqi) {
+               int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
+
+               nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
+               if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
+               if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
+               if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
+               // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
+               if(stale) // we have at least one last-rank P plus perhaps C
+                   return majors // KPKX
+                       || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
+               else // KCA*E*
+                   return pCnt[WhiteFerz+side] // KCAK
+                       || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
+                       || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
+               // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
+
+       } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
+               int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
+               
+               if(nMine == 1) return FALSE; // bare King
+               if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
+               nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
+               if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
+               // by now we have King + 1 piece (or multiple Bishops on the same color)
+               if(pCnt[WhiteKnight+side])
+                       return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] + 
+                               pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
+                            || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
+               if(nBishops)
+                       return (pCnt[BlackKnight-side]); // KBKN, KFKN
+               if(pCnt[WhiteAlfil+side])
+                       return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
+               if(pCnt[WhiteWazir+side])
+                       return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
+       }
+
+       return TRUE;
+}
+
 int
 Adjudicate(ChessProgramState *cps)
 {      // [HGM] some adjudications useful with buggy engines
@@ -6531,65 +6639,19 @@ Adjudicate(ChessProgramState *cps)
        ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
        Boolean canAdjudicate = !appData.icsActive;
 
-       // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
-       if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+       // most tests only when we understand the game, i.e. legality-checking on
            if( appData.testLegality )
            {   /* [HGM] Some more adjudications for obstinate engines */
-               int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
-                    NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
-                    NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
+               int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
                static int moveCount = 6;
                ChessMove result;
                char *reason = NULL;
 
                 /* Count what is on board. */
-               for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
-               {   ChessSquare p = boards[forwardMostMove][i][j];
-                   int m=i;
-
-                   switch((int) p)
-                   {   /* count B,N,R and other of each side */
-                        case WhiteKing:
-                        case BlackKing:
-                            NrK++; break; // [HGM] atomic: count Kings
-                        case WhiteKnight:
-                             NrWN++; break;
-                        case WhiteBishop:
-                        case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
-                             bishopsColor |= 1 << ((i^j)&1);
-                             NrWB++; break;
-                        case BlackKnight:
-                             NrBN++; break;
-                        case BlackBishop:
-                        case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj
-                             bishopsColor |= 1 << ((i^j)&1);
-                             NrBB++; break;
-                        case WhiteRook:
-                             NrWR++; break;
-                        case BlackRook:
-                             NrBR++; break;
-                        case WhiteQueen:
-                             NrWQ++; break;
-                        case BlackQueen:
-                             NrBQ++; break;
-                        case EmptySquare: 
-                             break;
-                        case BlackPawn:
-                             m = 7-i;
-                        case WhitePawn:
-                             PawnAdvance += m; NrPawns++;
-                    }
-                    NrPieces += (p != EmptySquare);
-                    NrW += ((int)p < (int)BlackPawn);
-                   if(gameInfo.variant == VariantXiangqi && 
-                     (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
-                       NrPieces--; // [HGM] XQ: do not count purely defensive pieces
-                        NrW -= ((int)p < (int)BlackPawn);
-                   }
-                }
+               Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
 
                /* Some material-based adjudications that have to be made before stalemate test */
-               if(gameInfo.variant == VariantAtomic && NrK < 2) {
+               if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
                    // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
                     boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
                     if(canAdjudicate && appData.checkMates) {
@@ -6603,7 +6665,7 @@ Adjudicate(ChessProgramState *cps)
                }
 
                /* Bare King in Shatranj (loses) or Losers (wins) */
-                if( NrW == 1 || NrPieces - NrW == 1) {
+                if( nrW == 1 || nrB == 1) {
                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
                     boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
                     if(canAdjudicate && appData.checkMates) {
@@ -6623,7 +6685,7 @@ Adjudicate(ChessProgramState *cps)
                            if(engineOpponent)
                              SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
                            ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-                           GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, 
+                           GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, 
                                                        "Xboard adjudication: Bare king", GE_XBOARD );
                            return 1;
                        }
@@ -6657,8 +6719,8 @@ Adjudicate(ChessProgramState *cps)
                    if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
                        boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
                    else if(gameInfo.variant == VariantSuicide) // in suicide it depends
-                       boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
-                                                  ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
+                       boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
+                                                  ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
                                                                        EP_CHECKMATE : EP_WINS);
                    else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
                        boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
@@ -6689,11 +6751,9 @@ Adjudicate(ChessProgramState *cps)
                }
 
                 /* Next absolutely insufficient mating material. */
-                if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && 
-                                    gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
-                       (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
-                        NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
-                {    /* KBK, KNK, KK of KBKB with like Bishops */
+                if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
+                   !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
+                {    /* includes KBK, KNK, KK of KBKB with like Bishops */
 
                      /* always flag draws, for judging claims */
                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
@@ -6711,13 +6771,15 @@ Adjudicate(ChessProgramState *cps)
                 }
 
                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
-                if(NrPieces == 4 && 
-                   (   NrWR == 1 && NrBR == 1 /* KRKR */
-                   || NrWQ==1 && NrBQ==1     /* KQKQ */
-                   || NrWN==2 || NrBN==2     /* KNNK */
-                   || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
-                  ) ) {
-                     if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
+                if(gameInfo.variant == VariantXiangqi ?
+                       SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
+                 : nrW + nrB == 4 && 
+                   (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
+                   || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
+                   || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
+                   || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
+                   ) ) {
+                     if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
                      {    /* if the first 3 moves do not show a tactical win, declare draw */
                          if(engineOpponent) {
                            SendToProgram("force\n", engineOpponent); // suppress reply
@@ -6729,7 +6791,6 @@ Adjudicate(ChessProgramState *cps)
                      }
                 } else moveCount = 6;
            }
-       }
          
        if (appData.debugMode) { int i;
            fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
@@ -6768,14 +6829,11 @@ Adjudicate(ChessProgramState *cps)
                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
                                    rights++;
                         }
-                        if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
+                        if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
                             && appData.drawRepeats > 1) {
                              /* adjudicate after user-specified nr of repeats */
-                            if(engineOpponent) {
-                              SendToProgram("force\n", engineOpponent); // suppress reply
-                              SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
-                            }
-                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                            int result = GameIsDrawn;
+                            char *details = "XBoard adjudication: repetition draw";
                             if(gameInfo.variant == VariantXiangqi && appData.testLegality) { 
                                // [HGM] xiangqi: check for forbidden perpetuals
                                int m, ourPerpetual = 1, hisPerpetual = 1;
@@ -6788,27 +6846,31 @@ Adjudicate(ChessProgramState *cps)
                                if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
                                                                        ourPerpetual, hisPerpetual);
                                if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
-                                   GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
-                                          "Xboard adjudication: perpetual checking", GE_XBOARD );
-                                   return 1;
-                               }
-                               if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet
+                                   result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+                                   details = "Xboard adjudication: perpetual checking";
+                               } else
+                               if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
                                    break; // (or we would have caught him before). Abort repetition-checking loop.
+                               } else
                                // Now check for perpetual chases
                                if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
                                    hisPerpetual = PerpetualChase(k, forwardMostMove);
                                    ourPerpetual = PerpetualChase(k+1, forwardMostMove);
                                    if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
-                                       GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, 
-                                                     "Xboard adjudication: perpetual chasing", GE_XBOARD );
-                                       return 1;
-                                   }
+                                       result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+                                       details = "Xboard adjudication: perpetual chasing";
+                                   } else
                                    if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
                                        break; // Abort repetition-checking loop.
                                }
                                // if neither of us is checking or chasing all the time, or both are, it is draw
                             }
-                             GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
+                            if(engineOpponent) {
+                              SendToProgram("force\n", engineOpponent); // suppress reply
+                              SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+                            }
+                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+                             GameEnds( result, details, GE_XBOARD );
                              return 1;
                         }
                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
@@ -6825,6 +6887,17 @@ Adjudicate(ChessProgramState *cps)
                 if( count == backwardMostMove )
                     count -= initialRulePlies;
                 count = forwardMostMove - count; 
+               if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
+                       // adjust reversible move counter for checks in Xiangqi
+                       int i = forwardMostMove - count, inCheck = 0, lastCheck;
+                       if(i < backwardMostMove) i = backwardMostMove;
+                       while(i <= forwardMostMove) {
+                               lastCheck = inCheck; // check evasion does not count
+                               inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
+                               if(inCheck || lastCheck) count--; // check does not count
+                               i++;
+                       }
+               }
                 if( count >= 100)
                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
                          /* this is used to judge if draw claims are legal */
@@ -6893,8 +6966,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
        // after a book hit we never send 'go', and the code after the call to this routine
        // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
        char buf[MSG_SIZ];
-       if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
-       sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+       sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
        SendToProgram(buf, cps);
        if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
@@ -7063,9 +7135,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         /* to make sure an illegal e.p. capture does not slip through,   */
         /* to cause a forfeit on a justified illegal-move complaint      */
         /* of the opponent.                                              */
-        if( gameMode==TwoMachinesPlay && appData.testLegality
-            && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
-                                                              ) {
+        if( gameMode==TwoMachinesPlay && appData.testLegality ) {
            ChessMove moveType;
            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
                              fromY, fromX, toY, toX, promoChar);
@@ -7111,6 +7181,11 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        strcat(machineMove, "\n");
        strcpy(moveList[forwardMostMove], machineMove);
 
+        /* [AS] Save move info*/
+        pvInfoList[ forwardMostMove ].score = programStats.score;
+        pvInfoList[ forwardMostMove ].depth = programStats.depth;
+        pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
+
        MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
 
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
@@ -7150,9 +7225,9 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
          if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
                SendToICS("draw ");
-               SendMoveToICS(moveType, fromX, fromY, toX, toY);
+               SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
          }
-         SendMoveToICS(moveType, fromX, fromY, toX, toY);
+         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
          ics_user_moved = 1;
          if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
                char buf[3*MSG_SIZ];
@@ -7170,10 +7245,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
 #endif
 
-        /* [AS] Save move info and clear stats for next move */
-        pvInfoList[ forwardMostMove-1 ].score = programStats.score;
-        pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
-        pvInfoList[ forwardMostMove-1 ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
+        /* [AS] Clear stats for next move */
         ClearProgramStats();
         thinkOutput[0] = NULLCHAR;
         hiddenThinkOutputState = 0;
@@ -7279,11 +7351,13 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
      * Look for communication commands
      */
     if (!strncmp(message, "telluser ", 9)) {
+       EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
        DisplayNote(message + 9);
        return;
     }
     if (!strncmp(message, "tellusererror ", 14)) {
        cps->userError = 1;
+       EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
        DisplayError(message + 14, 0);
        return;
     }
@@ -7737,6 +7811,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        }
 
        if (!ignore) {
+           ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
            buf1[0] = NULLCHAR;
            if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
                       &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
@@ -7758,11 +7833,11 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                 }
 
 
-               programStats.depth = plylev;
-               programStats.nodes = nodes;
-               programStats.time = time;
-               programStats.score = curscore;
-               programStats.got_only_move = 0;
+               tempStats.depth = plylev;
+               tempStats.nodes = nodes;
+               tempStats.time = time;
+               tempStats.score = curscore;
+               tempStats.got_only_move = 0;
 
                if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
                        int ticklen;
@@ -7779,31 +7854,34 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
 
                /* Buffer overflow protection */
                if (buf1[0] != NULLCHAR) {
-                   if (strlen(buf1) >= sizeof(programStats.movelist)
+                   if (strlen(buf1) >= sizeof(tempStats.movelist)
                        && appData.debugMode) {
                        fprintf(debugFP,
                                "PV is too long; using the first %u bytes.\n",
-                               (unsigned) sizeof(programStats.movelist) - 1);
+                               (unsigned) sizeof(tempStats.movelist) - 1);
                    }
 
-                    safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
+                    safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
                } else {
-                   sprintf(programStats.movelist, " no PV\n");
+                   sprintf(tempStats.movelist, " no PV\n");
                }
 
-               if (programStats.seen_stat) {
-                   programStats.ok_to_send = 1;
+               if (tempStats.seen_stat) {
+                   tempStats.ok_to_send = 1;
                }
 
-               if (strchr(programStats.movelist, '(') != NULL) {
-                   programStats.line_is_book = 1;
-                   programStats.nr_moves = 0;
-                   programStats.moves_left = 0;
+               if (strchr(tempStats.movelist, '(') != NULL) {
+                   tempStats.line_is_book = 1;
+                   tempStats.nr_moves = 0;
+                   tempStats.moves_left = 0;
                } else {
-                   programStats.line_is_book = 0;
+                   tempStats.line_is_book = 0;
                }
 
-                SendProgramStatsToFrontend( cps, &programStats );
+                   if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
+                       programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
+
+                SendProgramStatsToFrontend( cps, &tempStats );
 
                 /* 
                     [AS] Protect the thinkOutput buffer from overflow... this
@@ -8011,20 +8089,10 @@ ParseGameHistory(game)
     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
     setbuf(debugFP, NULL);
   }
-          case WhitePromotionChancellor:
-          case BlackPromotionChancellor:
-          case WhitePromotionArchbishop:
-          case BlackPromotionArchbishop:
-         case WhitePromotionQueen:
-         case BlackPromotionQueen:
-         case WhitePromotionRook:
-         case BlackPromotionRook:
-         case WhitePromotionBishop:
-         case BlackPromotionBishop:
-         case WhitePromotionKnight:
-         case BlackPromotionKnight:
-         case WhitePromotionKing:
-         case BlackPromotionKing:
+         case WhitePromotion:
+         case BlackPromotion:
+         case WhiteNonPromotion:
+         case BlackNonPromotion:
          case NormalMove:
          case WhiteCapturesEnPassant:
          case BlackCapturesEnPassant:
@@ -8171,7 +8239,6 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
 
     /* [HGM] compute & store e.p. status and castling rights for new position */
     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
-    { int i;
 
       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
       oldEP = (signed char)board[EP_STATUS];
@@ -8180,6 +8247,16 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       if( board[toY][toX] != EmptySquare ) 
            board[EP_STATUS] = EP_CAPTURE;  
 
+  /* [HGM] In Shatranj and Courier all promotions are to Ferz */
+  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
+       && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
+         
+  if (fromY == DROP_RANK) {
+       /* must be first */
+        piece = board[toY][toX] = (ChessSquare) fromX;
+  } else {
+      int i;
+
       if( board[fromY][fromX] == WhitePawn ) {
            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
               board[EP_STATUS] = EP_PAWN_MOVE;
@@ -8211,18 +8288,8 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
        }
 
-    }
+     if (fromX == toX && fromY == toY) return;
 
-  /* [HGM] In Shatranj and Courier all promotions are to Ferz */
-  if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
-       && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
-         
-  if (fromX == toX && fromY == toY) return;
-
-  if (fromY == DROP_RANK) {
-       /* must be first */
-        piece = board[toY][toX] = (ChessSquare) fromX;
-  } else {
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
      if(gameInfo.variant == VariantKnightmate)
@@ -8368,10 +8435,6 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = board[fromY][fromX];
        board[fromY][fromX] = EmptySquare;
     }
-
-    /* [HGM] now we promote for Shogi, if needed */
-    if(gameInfo.variant == VariantShogi && promoChar == 'q')
-        board[toY][toX] = (ChessSquare) (PROMOTED piece);
   }
 
     if (gameInfo.holdingsWidth != 0) {
@@ -8447,11 +8510,12 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
        board[toY][toX] = EmptySquare;
       }
     }
-    if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
-        /* [HGM] Shogi promotions */
+    if(promoChar == '+') {
+        /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
         board[toY][toX] = (ChessSquare) (PROMOTED piece);
+    } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
+        board[toY][toX] = CharToPiece(promoChar);
     }
-
     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
                && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
        // [HGM] superchess: take promotion piece out of holdings
@@ -8502,7 +8566,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar)
         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
              boards[forwardMostMove][toY][toX] == EmptySquare
-             && fromX != toX )
+             && fromX != toX && fromY != toY)
                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
         // promotion suffix
         if(promoChar != NULLCHAR)
@@ -8878,11 +8942,14 @@ GameEnds(result, resultDetails, whosays)
 {
     GameMode nextGameMode;
     int isIcsGame;
-    char buf[MSG_SIZ];
+    char buf[MSG_SIZ], popupRequested = 0;
 
     if(endingGame) return; /* [HGM] crash: forbid recursion */
     endingGame = 1;
-
+    if(twoBoards) { // [HGM] dual: switch back to one board
+       twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
+       DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
+    }
     if (appData.debugMode) {
       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
              result, resultDetails ? resultDetails : "(null)", whosays);
@@ -9211,13 +9278,12 @@ GameEnds(result, resultDetails, whosays)
            endingGame = 0; /* [HGM] crash */
            return;
        } else {
-           char buf[MSG_SIZ];
            gameMode = nextGameMode;
            sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
                    first.tidy, second.tidy,
                    first.matchWins, second.matchWins,
                    appData.matchGames - (first.matchWins + second.matchWins));
-           DisplayFatalError(buf, 0, 0);
+           popupRequested++; // [HGM] crash: postpone to after resetting endingGame
        }
     }
     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
@@ -9226,6 +9292,12 @@ GameEnds(result, resultDetails, whosays)
     gameMode = nextGameMode;
     ModeHighlight();
     endingGame = 0;  /* [HGM] crash */
+    if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
+      if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
+       matchMode = FALSE; appData.matchGames = matchGame = 0;
+       DisplayNote(buf);
+      }
+    }
 }
 
 /* Assumes program was just initialized (initString sent).
@@ -9477,22 +9549,10 @@ LoadGameOneMove(readAhead)
 
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
-      case WhitePromotionChancellor:
-      case BlackPromotionChancellor:
-      case WhitePromotionArchbishop:
-      case BlackPromotionArchbishop:
-      case WhitePromotionCentaur:
-      case BlackPromotionCentaur:
-      case WhitePromotionQueen:
-      case BlackPromotionQueen:
-      case WhitePromotionRook:
-      case BlackPromotionRook:
-      case WhitePromotionBishop:
-      case BlackPromotionBishop:
-      case WhitePromotionKnight:
-      case BlackPromotionKnight:
-      case WhitePromotionKing:
-      case BlackPromotionKing:
+      case WhitePromotion:
+      case BlackPromotion:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
       case NormalMove:
       case WhiteKingSideCastle:
       case WhiteQueenSideCastle:
@@ -10090,6 +10150,7 @@ LoadGame(f, gameNumber, title, useList)
         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
         if(gameInfo.variant != oldVariant) {
             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
+           ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
            InitPosition(TRUE);
             oldVariant = gameInfo.variant;
            if (appData.debugMode) 
@@ -10152,7 +10213,9 @@ LoadGame(f, gameNumber, title, useList)
     if (numPGNTags > 0){
         char *tags;
        if (gameInfo.variant == VariantNormal) {
-         gameInfo.variant = StringToVariant(gameInfo.event);
+         VariantClass v = StringToVariant(gameInfo.event);
+         // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
+         if(v < VariantShogi) gameInfo.variant = v;
        }
        if (!matchMode) {
           if( appData.autoDisplayTags ) {
@@ -13339,8 +13402,10 @@ ReceiveFromProgram(isr, closure, message, count, error)
                   sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
                   sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
                   sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
-                  sscanf(message, "pong %c", &c)!=1   && start != '#')
-                       { quote = "# "; print = (appData.engineComments == 2); }
+                  sscanf(message, "pong %c", &c)!=1   && start != '#') {
+                   quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
+                   print = (appData.engineComments >= 2);
+               }
                message[0] = start; // restore original message
        }
        if(print) {
@@ -13701,7 +13766,6 @@ ParseFeatures(args, cps)
        }
        continue;
     }
-    if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
     /* End of additions by HGM */
 
     /* unknown feature: complain and skip */
@@ -13765,17 +13829,17 @@ PonderNextMoveEvent(newState)
 }
 
 void
-NewSettingEvent(option, command, value)
+NewSettingEvent(option, feature, command, value)
      char *command;
-     int option, value;
+     int option, value, *feature;
 {
     char buf[MSG_SIZ];
 
     if (gameMode == EditPosition) EditPositionDone(TRUE);
     sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
-    SendToProgram(buf, &first);
+    if(feature == NULL || *feature) SendToProgram(buf, &first);
     if (gameMode == TwoMachinesPlay) {
-       SendToProgram(buf, &second);
+       if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
     }
 }
 
@@ -13857,7 +13921,7 @@ DisplayMove(moveNumber)
            sprintf(res, " %s", PGNResult(gameInfo.result));
        } else {
            sprintf(res, " {%s} %s",
-                   gameInfo.resultDetails, PGNResult(gameInfo.result));
+                   T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
        }
     } else {
        res[0] = NULLCHAR;
@@ -14649,7 +14713,7 @@ ParseFEN(board, blackPlaysFirst, fen)
      char *fen;
 {
     int i, j;
-    char *p;
+    char *p, c;
     int emptycount;
     ChessSquare piece;
 
@@ -14742,7 +14806,12 @@ ParseFEN(board, blackPlaysFirst, fen)
     while(*p == ' ') p++;
 
     /* Active color */
-    switch (*p++) {
+    c = *p++;
+    if(appData.colorNickNames) {
+      if( c == appData.colorNickNames[0] ) c = 'w'; else
+      if( c == appData.colorNickNames[1] ) c = 'b';
+    }
+    switch (c) {
       case 'w':
         *blackPlaysFirst = FALSE;
        break;
@@ -15149,3 +15218,4 @@ LoadVariation(int index, char *text)
        CommentPopDown();
        ToNrEvent(currentMove+1);
 }
+