Prevent transmission of spurious promo char to other engine
[xboard.git] / backend.c
index 56049a8..b124be6 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 
 
 
@@ -234,6 +241,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,6 +251,8 @@ 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;
@@ -635,6 +645,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
@@ -710,8 +721,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;
@@ -1106,6 +1117,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
@@ -1344,6 +1360,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;
@@ -1976,7 +2005,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;
@@ -2184,8 +2213,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;
 
@@ -2704,7 +2733,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;
@@ -2743,6 +2772,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, "--> * ") ||
@@ -2762,6 +2792,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])) {
@@ -2769,14 +2806,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])) {
@@ -2799,18 +2845,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 (
@@ -3464,6 +3501,7 @@ read_from_ics(isr, closure, data, count, error)
                        ZippyGameStart(whitename, blackname);
                    }
 #endif /*ZIPPY*/
+                   partnerBoardValid = FALSE; // [HGM] bughouse
                    continue;
                }
 
@@ -3504,6 +3542,7 @@ read_from_ics(isr, closure, data, count, error)
                    Reset(TRUE, TRUE);
                }
 #endif /*ZIPPY*/
+               if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
                continue;
            }
 
@@ -3664,7 +3703,7 @@ read_from_ics(isr, closure, data, count, error)
                         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; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own
+                        if(twoBoards) { partnerUp = 0; flipView = !flipView; }
                      }
                    }
                    /* Suppress following prompt */
@@ -3816,8 +3855,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 *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]);
@@ -3826,13 +3866,23 @@ ParseBoard12(string)
              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
         }
       }
-      if(appData.dualBoard) { twoBoards = partnerUp = 1; flipView = !flipView; InitDrawingSizes(-2,0); } // [HGM] dual
       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);
-      if(twoBoards) { partnerUp = 0; flipView = !flipView; DrawPosition(TRUE, boards[currentMove]); } // [HGM] dual: redraw own game!
-      sprintf(partnerStatus, "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(partnerStatus, "");
+       partnerBoardValid = TRUE;
       return;
     }
 
@@ -4480,7 +4530,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) ) {
@@ -4525,6 +4575,10 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       /* POP Fabien */
        sprintf(user_move, "o-o-o\n");
        break;
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
+        sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+        break;
       case WhitePromotionQueen:
       case BlackPromotionQueen:
       case WhitePromotionRook:
@@ -4753,6 +4807,8 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
       case BlackPromotionKnight:
       case WhitePromotionKing:
       case BlackPromotionKing:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
       case NormalMove:
       case WhiteCapturesEnPassant:
       case BlackCapturesEnPassant:
@@ -5210,7 +5266,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:
@@ -6216,7 +6275,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 &&
@@ -6276,11 +6335,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;
       }
@@ -6309,7 +6371,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 {
@@ -6317,7 +6379,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);
            }
@@ -6329,7 +6391,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);
@@ -6367,7 +6429,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;
     }
@@ -6443,6 +6505,8 @@ 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(!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
@@ -6536,6 +6600,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
@@ -6551,61 +6712,16 @@ Adjudicate(ChessProgramState *cps)
        if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
            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) {
@@ -6619,7 +6735,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) {
@@ -6639,7 +6755,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;
                        }
@@ -6673,8 +6789,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
@@ -6705,11 +6821,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;
@@ -6727,13 +6841,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
@@ -6784,14 +6900,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;
@@ -6804,27 +6917,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 */
@@ -6841,6 +6958,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 */
@@ -6909,8 +7037,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
@@ -7127,6 +7254,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) */
@@ -7186,10 +7318,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;
@@ -7295,11 +7424,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;
     }
@@ -7753,6 +7884,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) {
@@ -7774,11 +7906,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;
@@ -7795,31 +7927,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
@@ -8041,6 +8176,8 @@ ParseGameHistory(game)
          case BlackPromotionKnight:
          case WhitePromotionKing:
          case BlackPromotionKing:
+         case WhiteNonPromotion:
+         case BlackNonPromotion:
          case NormalMove:
          case WhiteCapturesEnPassant:
          case BlackCapturesEnPassant:
@@ -8518,7 +8655,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)
@@ -8894,12 +9031,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) { twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0); } // [HGM] dual
-
+    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);
@@ -9228,13 +9367,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) &&
@@ -9243,6 +9381,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).
@@ -9510,6 +9654,8 @@ LoadGameOneMove(readAhead)
       case BlackPromotionKnight:
       case WhitePromotionKing:
       case BlackPromotionKing:
+      case WhiteNonPromotion:
+      case BlackNonPromotion:
       case NormalMove:
       case WhiteKingSideCastle:
       case WhiteQueenSideCastle:
@@ -10107,6 +10253,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) 
@@ -10169,7 +10316,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 ) {
@@ -13356,8 +13505,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) {
@@ -13718,7 +13869,6 @@ ParseFeatures(args, cps)
        }
        continue;
     }
-    if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
     /* End of additions by HGM */
 
     /* unknown feature: complain and skip */
@@ -13782,17 +13932,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);
     }
 }
 
@@ -13874,7 +14024,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;
@@ -14666,7 +14816,7 @@ ParseFEN(board, blackPlaysFirst, fen)
      char *fen;
 {
     int i, j;
-    char *p;
+    char *p, c;
     int emptycount;
     ChessSquare piece;
 
@@ -14759,7 +14909,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;
@@ -15166,3 +15321,4 @@ LoadVariation(int index, char *text)
        CommentPopDown();
        ToNrEvent(currentMove+1);
 }
+