changes from H.G. Muller; version 4.3.16
[xboard.git] / backend.c
index 5496797..25ee4c0 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -55,7 +55,7 @@
 \r
 #else\r
 \r
-#define DoSleep( n )\r
+#define DoSleep( n ) if( (n) >= 0) sleep(n)\r
 \r
 #endif\r
 \r
@@ -127,23 +127,6 @@ typedef struct {
     int ms;    /* Assuming this is >= 16 bits */\r
 } TimeMark;\r
 \r
-/* Search stats from chessprogram */\r
-typedef struct {\r
-  char movelist[2*MSG_SIZ]; /* Last PV we were sent */\r
-  int depth;              /* Current search depth */\r
-  int nr_moves;           /* Total nr of root moves */\r
-  int moves_left;         /* Moves remaining to be searched */\r
-  char move_name[MOVE_LEN];  /* Current move being searched, if provided */\r
-  unsigned long nodes;    /* # of nodes searched */\r
-  int time;               /* Search time (centiseconds) */\r
-  int score;              /* Score (centipawns) */\r
-  int got_only_move;      /* If last msg was "(only move)" */\r
-  int got_fail;           /* 0 - nothing, 1 - got "--", 2 - got "++" */\r
-  int ok_to_send;         /* handshaking between send & recv */\r
-  int line_is_book;       /* 1 if movelist is book moves */\r
-  int seen_stat;          /* 1 if we've seen the stat01: line */\r
-} ChessProgramStats;\r
-\r
 int establish P((void));\r
 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
                         char *buf, int count, int error));\r
@@ -216,6 +199,8 @@ void InitBackEnd3 P((void));
 void FeatureDone P((ChessProgramState* cps, int val));\r
 void InitChessProgram P((ChessProgramState *cps, int setup));\r
 ChessProgramState *WhitePlayer();\r
+void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
+int VerifyDisplayMode P(());\r
 \r
 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
@@ -224,7 +209,7 @@ char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [H
 extern char installDir[MSG_SIZ];\r
 \r
 extern int tinyLayout, smallLayout;\r
-static ChessProgramStats programStats;\r
+ChessProgramStats programStats;\r
 static int exiting = 0; /* [HGM] moved to top */\r
 static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0;\r
 extern int startedFromPositionFile;\r
@@ -233,6 +218,7 @@ char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds()
 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */\r
 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */\r
+int opponentKibitzes;\r
 \r
 /* States for ics_getting_history */\r
 #define H_FALSE 0\r
@@ -318,9 +304,9 @@ PosFlags(index)
   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
   switch (gameInfo.variant) {\r
   case VariantSuicide:\r
-  case VariantGiveaway:\r
-    flags |= F_IGNORE_CHECK;\r
     flags &= ~F_ALL_CASTLE_OK;\r
+  case VariantGiveaway:                // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
+    flags |= F_IGNORE_CHECK;\r
     break;\r
   case VariantAtomic:\r
     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
@@ -495,6 +481,13 @@ ChessSquare CapablancaArray[2][BOARD_SIZE] = {
         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
 };\r
 \r
+ChessSquare GreatArray[2][BOARD_SIZE] = {\r
+    { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
+        WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
+    { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
+        BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
+};\r
+\r
 ChessSquare JanusArray[2][BOARD_SIZE] = {\r
     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
@@ -528,6 +521,7 @@ ChessSquare FalconArray[2][BOARD_SIZE] = {
 #define XiangqiPosition FIDEArray\r
 #define CapablancaArray FIDEArray\r
 #define GothicArray FIDEArray\r
+#define GreatArray FIDEArray\r
 #endif // !(BOARD_SIZE>=10)\r
 \r
 #if (BOARD_SIZE>=12)\r
@@ -860,6 +854,7 @@ InitBackEnd1()
       case VariantCapaRandom: /* should work */\r
       case VariantJanus:      /* should work */\r
       case VariantSuper:      /* experimental */\r
+      case VariantGreat:      /* experimental, requires legality testing to be off */\r
        break;\r
       }\r
     }\r
@@ -1531,7 +1526,8 @@ StringToVariant(e)
 \r
     if (!found) {\r
       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
-         || StrCaseStr(e, "wild/fr")) {\r
+         || StrCaseStr(e, "wild/fr") \r
+         || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
         v = VariantFischeRandom;\r
       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
                 (i = 1, p = StrCaseStr(e, "w"))) {\r
@@ -1673,6 +1669,9 @@ StringToVariant(e)
         case 49:\r
           v = VariantSuper;\r
          break;\r
+        case 50:\r
+          v = VariantGreat;\r
+         break;\r
        case -1:\r
          /* Found "wild" or "w" in the string but no number;\r
             must assume it's normal chess. */\r
@@ -1889,7 +1888,7 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
         j = PieceToNumber(piece);\r
         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
         if(j < 0) continue;               /* should not happen */\r
-        piece = (ChessSquare) ( j + (int)lowestPiece );\r
+        piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
         board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
         board[holdingsStartRow+j*direction][countsColumn]++;\r
     }\r
@@ -1996,6 +1995,7 @@ static int player2Rating = -1;
 /*----------------------------*/\r
 \r
 ColorClass curColor = ColorNormal;\r
+int suppressKibitz = 0;\r
 \r
 void\r
 read_from_ics(isr, closure, data, count, error)\r
@@ -2208,7 +2208,28 @@ read_from_ics(isr, closure, data, count, error)
                parse[parse_pos++] = buf[i];\r
                if (buf[i] == '\n') {\r
                    parse[parse_pos] = NULLCHAR;\r
-                   AppendComment(forwardMostMove, StripHighlight(parse));\r
+                   if(!suppressKibitz) // [HGM] kibitz\r
+                       AppendComment(forwardMostMove, StripHighlight(parse));\r
+                   else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
+                       int nrDigit = 0, nrAlph = 0, i;\r
+                       if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
+                       { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
+                       parse[parse_pos] = NULLCHAR;\r
+                       // try to be smart: if it does not look like search info, it should go to\r
+                       // ICS interaction window after all, not to engine-output window.\r
+                       for(i=0; i<parse_pos; i++) { // count letters and digits\r
+                           nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
+                           nrAlph  += (parse[i] >= 'a' && parse[i] <= 'z');\r
+                           nrAlph  += (parse[i] >= 'A' && parse[i] <= 'Z');\r
+                       }\r
+                       if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
+                           OutputKibitz(suppressKibitz, parse);\r
+                       } else {\r
+                           char tmp[MSG_SIZ];\r
+                           sprintf(tmp, "your opponent kibitzes: %s", parse);\r
+                           SendToPlayer(tmp, strlen(tmp));\r
+                       }\r
+                   }\r
                    started = STARTED_NONE;\r
                } else {\r
                    /* Don't match patterns against characters in chatter */\r
@@ -2289,6 +2310,33 @@ read_from_ics(isr, closure, data, count, error)
            }\r
 \r
            oldi = i;\r
+           // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
+           if (appData.autoKibitz && started == STARTED_NONE && \r
+               (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
+               if(looking_at(buf, &i, "* kibitzes: ") &&\r
+                  (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
+                   StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent\r
+                       suppressKibitz = TRUE;\r
+                       if((StrStr(star_match[0], gameInfo.white) == star_match[0])\r
+                               && (gameMode == IcsPlayingWhite) ||\r
+                          (StrStr(star_match[0], gameInfo.black) == star_match[0])\r
+                               && (gameMode == IcsPlayingBlack)   ) // opponent kibitz\r
+                           started = STARTED_CHATTER; // own kibitz we simply discard\r
+                       else {\r
+                           started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
+                           parse_pos = 0; parse[0] = NULLCHAR;\r
+                           savingComment = TRUE;\r
+                           suppressKibitz = gameMode != IcsObserving ? 2 :\r
+                               (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
+                       } \r
+                       continue;\r
+               } else\r
+               if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
+                   started = STARTED_CHATTER;\r
+                   suppressKibitz = TRUE;\r
+               }\r
+           } // [HGM] kibitz: end of patch\r
+\r
            if (appData.zippyTalk || appData.zippyPlay) {\r
 #if ZIPPY\r
                if (ZippyControl(buf, &i) ||\r
@@ -2637,7 +2685,7 @@ read_from_ics(isr, closure, data, count, error)
            \r
            if (looking_at(buf, &i, "% ") ||\r
                ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
-                && looking_at(buf, &i, "}*"))) {\r
+                && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
                savingComment = FALSE;\r
                switch (started) {\r
                  case STARTED_MOVES:\r
@@ -2656,11 +2704,18 @@ read_from_ics(isr, closure, data, count, error)
                                  }\r
                                  SendTimeRemaining(&first, TRUE);\r
                                }\r
+#if 0\r
                                if (first.useColors) {\r
                                  SendToProgram("white\ngo\n", &first);\r
                                } else {\r
                                  SendToProgram("go\n", &first);\r
                                }\r
+#else\r
+                               if (first.useColors) {\r
+                                 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
+                               }\r
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
+#endif\r
                                first.maybeThinking = TRUE;\r
                            } else {\r
                                if (first.usePlayother) {\r
@@ -2681,11 +2736,18 @@ read_from_ics(isr, closure, data, count, error)
                                  }\r
                                  SendTimeRemaining(&first, FALSE);\r
                                }\r
+#if 0\r
                                if (first.useColors) {\r
                                  SendToProgram("black\ngo\n", &first);\r
                                } else {\r
                                  SendToProgram("go\n", &first);\r
                                }\r
+#else\r
+                               if (first.useColors) {\r
+                                 SendToProgram("black\n", &first);\r
+                               }\r
+                               bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
+#endif\r
                                first.maybeThinking = TRUE;\r
                            } else {\r
                                if (first.usePlayother) {\r
@@ -2738,6 +2800,17 @@ read_from_ics(isr, closure, data, count, error)
                  default:\r
                    break;\r
                }\r
+               if(bookHit) { // [HGM] book: simulate book reply\r
+                   static char bookMove[MSG_SIZ]; // a bit generous?\r
+\r
+                   programStats.depth = programStats.nodes = programStats.time = \r
+                   programStats.score = programStats.got_only_move = 0;\r
+                   sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
+\r
+                   strcpy(bookMove, "move ");\r
+                   strcat(bookMove, bookHit);\r
+                   HandleMachineMove(bookMove, &first);\r
+               }\r
                continue;\r
            }\r
            \r
@@ -3120,11 +3193,12 @@ read_from_ics(isr, closure, data, count, error)
            i++;                /* skip unparsed character and loop back */\r
        }\r
        \r
-       if (started != STARTED_MOVES && started != STARTED_BOARD &&\r
+       if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
            started != STARTED_HOLDINGS && i > next_out) {\r
            SendToPlayer(&buf[next_out], i - next_out);\r
            next_out = i;\r
        }\r
+       suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
        \r
        leftover_len = buf_len - leftover_start;\r
        /* if buffer ends with something we couldn't parse,\r
@@ -3548,8 +3622,37 @@ ParseBoard12(string)
            strcat(parseList[moveNum - 1], " ");\r
            strcat(parseList[moveNum - 1], elapsed_time);\r
            moveList[moveNum - 1][0] = NULLCHAR;\r
-       } else if (ParseOneMove(move_str, moveNum - 1, &moveType,\r
-                               &fromX, &fromY, &toX, &toY, &promoChar)) {\r
+       } else if (strcmp(move_str, "none") == 0) {\r
+           // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
+           /* Again, we don't know what the board looked like;\r
+              this is really the start of the game. */\r
+           parseList[moveNum - 1][0] = NULLCHAR;\r
+           moveList[moveNum - 1][0] = NULLCHAR;\r
+           backwardMostMove = moveNum;\r
+           startedFromSetupPosition = TRUE;\r
+           fromX = fromY = toX = toY = -1;\r
+       } else {\r
+         // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
+         //                 So we parse the long-algebraic move string in stead of the SAN move\r
+         int valid; char buf[MSG_SIZ], *prom;\r
+\r
+         // str looks something like "Q/a1-a2"; kill the slash\r
+         if(str[1] == '/') \r
+               sprintf(buf, "%c%s", str[0], str+2);\r
+         else  strcpy(buf, str); // might be castling\r
+         if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
+               strcat(buf, prom); // long move lacks promo specification!\r
+         if(!appData.testLegality) {\r
+               if(appData.debugMode) \r
+                       fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
+               strcpy(move_str, buf);\r
+          }\r
+         valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
+                               &fromX, &fromY, &toX, &toY, &promoChar)\r
+              || ParseOneMove(buf, moveNum - 1, &moveType,\r
+                               &fromX, &fromY, &toX, &toY, &promoChar);\r
+         // end of long SAN patch\r
+         if (valid) {\r
            (void) CoordsToAlgebraic(boards[moveNum - 1],\r
                                     PosFlags(moveNum - 1), EP_UNKNOWN,\r
                                     fromY, fromX, toY, toX, promoChar,\r
@@ -3573,15 +3676,7 @@ ParseBoard12(string)
            /* currentMoveString is set as a side-effect of ParseOneMove */\r
            strcpy(moveList[moveNum - 1], currentMoveString);\r
            strcat(moveList[moveNum - 1], "\n");\r
-       } else if (strcmp(move_str, "none") == 0) {\r
-           /* Again, we don't know what the board looked like;\r
-              this is really the start of the game. */\r
-           parseList[moveNum - 1][0] = NULLCHAR;\r
-           moveList[moveNum - 1][0] = NULLCHAR;\r
-           backwardMostMove = moveNum;\r
-           startedFromSetupPosition = TRUE;\r
-           fromX = fromY = toX = toY = -1;\r
-       } else {\r
+         } else {\r
            /* Move from ICS was illegal!?  Punt. */\r
   if (appData.debugMode) {\r
     fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
@@ -3598,6 +3693,7 @@ ParseBoard12(string)
            strcat(parseList[moveNum - 1], elapsed_time);\r
            moveList[moveNum - 1][0] = NULLCHAR;\r
            fromX = fromY = toX = toY = -1;\r
+         }\r
        }\r
   if (appData.debugMode) {\r
     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
@@ -3724,7 +3820,7 @@ ParseBoard12(string)
 \r
        programStats.depth = programStats.nodes = programStats.time = \r
        programStats.score = programStats.got_only_move = 0;\r
-       sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
 \r
        strcpy(bookMove, "move ");\r
        strcat(bookMove, bookHit);\r
@@ -3874,10 +3970,14 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY)
       case BlackPromotionChancellor:\r
       case WhitePromotionArchbishop:\r
       case BlackPromotionArchbishop:\r
-        if(gameInfo.variant == VariantShatranj)\r
+        if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
             sprintf(user_move, "%c%c%c%c=%c\n",\r
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
                PieceToChar(WhiteFerz));\r
+        else if(gameInfo.variant == VariantGreat)\r
+            sprintf(user_move, "%c%c%c%c=%c\n",\r
+                AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
+               PieceToChar(WhiteMan));\r
         else\r
             sprintf(user_move, "%c%c%c%c=%c\n",\r
                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
@@ -4521,6 +4621,12 @@ InitPosition(redraw)
       pieces = fairyArray;\r
       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
       break;\r
+    case VariantGreat:\r
+      pieces = GreatArray;\r
+      gameInfo.boardWidth = 10;\r
+      SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
+      gameInfo.holdingsSize = 8;\r
+      break;\r
     case VariantSuper:\r
       pieces = FIDEArray;\r
       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
@@ -4619,6 +4725,12 @@ InitPosition(redraw)
      }\r
 \r
      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
+     if(gameInfo.variant == VariantGreat) { // promotion commoners\r
+       initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;\r
+       initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;\r
+       initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
+       initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
+     }\r
 #if 0\r
     if(gameInfo.variant == VariantFischeRandom) {\r
       if( appData.defaultFrcPosition < 0 ) {\r
@@ -5077,7 +5189,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar)
             return ImpossibleMove;\r
        }\r
     }\r
-\r
+if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
     return moveType;\r
     /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
        function is made into one that returns an OK move type if FinishMove\r
@@ -5096,8 +5208,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
      /*char*/int promoChar;\r
 {\r
     char *bookHit = 0;\r
-\r
-    if(gameInfo.variant == VariantSuper && promoChar != NULLCHAR) { \r
+if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
        // [HGM] superchess: suppress promotions to non-available piece\r
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
        if(WhiteOnMove(currentMove)) {\r
@@ -5111,7 +5223,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
        move type in caller when we know the move is a legal promotion */\r
     if(moveType == NormalMove && promoChar)\r
         moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
-\r
+if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
     /* [HGM] convert drag-and-drop piece drops to standard form */\r
     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
@@ -5175,7 +5287,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 \r
   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
 \r
-    if(gameInfo.variant == VariantSuper && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
+    if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
+               && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
        // [HGM] superchess: take promotion piece out of holdings\r
        int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
        if(WhiteOnMove(forwardMostMove-1)) {\r
@@ -5206,7 +5319,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
     }\r
     ModeHighlight();\r
   }\r
-\r
+if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
   /* Relay move to ICS or chess engine */\r
   if (appData.icsActive) {\r
     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
@@ -5267,7 +5380,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 \r
        programStats.depth = programStats.nodes = programStats.time = \r
        programStats.score = programStats.got_only_move = 0;\r
-       sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
 \r
        strcpy(bookMove, "move ");\r
        strcat(bookMove, bookHit);\r
@@ -5291,7 +5404,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        to do anything in between, can call this routine the old way. \r
     */\r
     ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
-\r
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
     if(moveType != ImpossibleMove)\r
         FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
 }\r
@@ -5327,7 +5440,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
     //first determine if the incoming move brings opponent into his book\r
     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
        bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
-    if(appData.debugMode && bookHit) fprintf(debugFP, "book hit = %s\n", bookHit);\r
+    if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
     if(bookHit != NULL && !cps->bookSuspend) {\r
        // make sure opponent is not going to reply after receiving move to book position\r
        SendToProgram("force\n", cps);\r
@@ -5614,7 +5727,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
        if( gameMode == TwoMachinesPlay ) {\r
          // [HGM] some adjudications useful with buggy engines\r
             int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
-         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper) {\r
+         if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
 \r
             if(appData.testLegality)\r
             // don't wait for engine to announce game end if we can judge ourselves\r
@@ -5628,6 +5741,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
              case MT_STALEMATE:\r
                epStatus[forwardMostMove] = EP_STALEMATE;\r
                 if(appData.checkMates) {\r
+                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                    GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",\r
                        GE_XBOARD );\r
@@ -5636,6 +5750,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
              case MT_CHECKMATE:\r
                epStatus[forwardMostMove] = EP_CHECKMATE;\r
                 if(appData.checkMates) {\r
+                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                    ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                    GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
                    "Xboard adjudication: Checkmate", \r
@@ -5661,11 +5776,13 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                         case WhiteKnight:\r
                              NrWN++; break;\r
                         case WhiteBishop:\r
+                        case WhiteFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
                              bishopsColor |= 1 << ((i^j)&1);\r
                              NrWB++; break;\r
                         case BlackKnight:\r
                              NrBN++; break;\r
                         case BlackBishop:\r
+                        case BlackFerz:    // [HGM] shatranj: kludge to mke it work in shatranj\r
                              bishopsColor |= 1 << ((i^j)&1);\r
                              NrBB++; break;\r
                         case WhiteRook:\r
@@ -5685,10 +5802,16 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                     }\r
                     NrPieces += (p != EmptySquare);\r
                     NrW += ((int)p < (int)BlackPawn);\r
+                   if(gameInfo.variant == VariantXiangqi && \r
+                     (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
+                       NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
+                        NrW -= ((int)p < (int)BlackPawn);\r
+                   }\r
                 }\r
 \r
-                if( NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || NrPieces == 2\r
-                 || NrPieces == 4 && NrBB+NrWB == NrPieces-2 && bishopsColor != 3)\r
+                if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&\r
+                       (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
+                        NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
                 {    /* KBK, KNK, KK of KBKB with like Bishops */\r
 \r
                      /* always flag draws, for judging claims */\r
@@ -5696,6 +5819,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 \r
                      if(appData.materialDraws) {\r
                          /* but only adjudicate them if adjudication enabled */\r
+                        SendToProgram("force\n", cps->other); // suppress reply\r
+                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
                          return;\r
@@ -5708,6 +5833,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
 \r
                      if(--bare < 0 && appData.checkMates) {\r
                          /* but only adjudicate them if adjudication enabled */\r
+                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                          GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
                                                        "Xboard adjudication: Bare king", GE_XBOARD );\r
@@ -5724,6 +5850,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                   ) ) {\r
                      if(--moveCount < 0 && appData.trivialDraws)\r
                      {    /* if the first 3 moves do not show a tactical win, declare draw */\r
+                         SendToProgram("force\n", cps->other); // suppress reply\r
+                         SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
                           return;\r
@@ -5790,7 +5918,30 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                         if( rights == 0 && ++count > appData.drawRepeats-2\r
                             && appData.drawRepeats > 1) {\r
                              /* adjudicate after user-specified nr of repeats */\r
+                            SendToProgram("force\n", cps->other); // suppress reply\r
+                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
+                            if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
+                               // [HGM] xiangqi: check for forbidden perpetuals\r
+                               int m, ourPerpetual = 1, hisPerpetual = 1;\r
+                               for(m=forwardMostMove; m>k; m-=2) {\r
+                                   if(MateTest(boards[m], PosFlags(m), \r
+                                                       EP_NONE, castlingRights[m]) != MT_CHECK)\r
+                                       ourPerpetual = 0; // the current mover did not always check\r
+                                   if(MateTest(boards[m-1], PosFlags(m-1), \r
+                                                       EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
+                                       hisPerpetual = 0; // the opponent did not always check\r
+                               }\r
+                               if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
+                                   GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
+                                          "Xboard adjudication: perpetual checking", GE_XBOARD );\r
+                                   return;\r
+                               }\r
+                               if(hisPerpetual && !ourPerpetual)   // he is checking us, but did not repeat yet\r
+                                   break; // (or we would have caught him before). Abort repetition-checking loop.\r
+                               // if neither of us is checking all the time, or both are, it is draw\r
+                               // (illegal-chase forfeits not implemented yet!)\r
+                            }\r
                              GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
                              return;\r
                         }\r
@@ -5812,6 +5963,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                          epStatus[forwardMostMove] = EP_RULE_DRAW;\r
                          /* this is used to judge if draw claims are legal */\r
                 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
+                        SendToProgram("force\n", cps->other); // suppress reply\r
+                        SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
                          return;\r
@@ -5831,6 +5984,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                          if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
                              p = "Draw claim: insufficient mating material";\r
                          if( p != NULL ) {\r
+                            SendToProgram("force\n", cps->other); // suppress reply\r
+                            SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
                              GameEnds( GameIsDrawn, p, GE_XBOARD );\r
                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
                              return;\r
@@ -5838,14 +5993,15 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 }\r
 \r
 \r
-        }\r
-\r
-        if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
-           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
+               if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
+                   SendToProgram("force\n", cps->other); // suppress reply\r
+                   SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
+                   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
 \r
-            GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
+                   GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
 \r
-            return;\r
+                   return;\r
+               }\r
         }\r
 \r
        bookHit = NULL;\r
@@ -5895,7 +6051,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                cps = cps->other;\r
                programStats.depth = programStats.nodes = programStats.time = \r
                programStats.score = programStats.got_only_move = 0;\r
-               sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
+               sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
 \r
                if(cps->lastPing != cps->lastPong) {\r
                    savedMessage = message; // args for deferred call\r
@@ -6836,24 +6992,28 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
            epStatus[p] = EP_CAPTURE;  \r
 \r
       if( board[fromY][fromX] == WhitePawn ) {\r
-           epStatus[p] = EP_PAWN_MOVE; \r
-           if( toY-fromY==2)\r
+           if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
+              epStatus[p] = EP_PAWN_MOVE;\r
+           if( toY-fromY==2) {\r
                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&\r
                        gameInfo.variant != VariantBerolina || toX < fromX)\r
                      epStatus[p] = toX | berolina;\r
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
                        gameInfo.variant != VariantBerolina || toX > fromX) \r
                      epStatus[p] = toX;\r
+          }\r
       } else \r
       if( board[fromY][fromX] == BlackPawn ) {\r
-           epStatus[p] = EP_PAWN_MOVE; \r
-           if( toY-fromY== -2)\r
+           if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
+              epStatus[p] = EP_PAWN_MOVE; \r
+           if( toY-fromY== -2) {\r
                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&\r
                        gameInfo.variant != VariantBerolina || toX < fromX)\r
                      epStatus[p] = toX | berolina;\r
                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
                        gameInfo.variant != VariantBerolina || toX > fromX) \r
                      epStatus[p] = toX;\r
+          }\r
        }\r
 \r
        for(i=0; i<nrCastlingRights; i++) {\r
@@ -7051,7 +7211,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board)
       if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
           && gameInfo.variant != VariantBughouse        ) {\r
         /* [HGM] holdings: Add to holdings, if holdings exist */\r
-       if(gameInfo.variant == VariantSuper) { \r
+       if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
                // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
                captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
        }\r
@@ -7308,6 +7468,8 @@ InitChessProgram(cps, setup)
            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
       if( gameInfo.variant == VariantSuper )\r
            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
+      if( gameInfo.variant == VariantGreat )\r
+           overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
 \r
       if(overruled) {\r
            sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
@@ -7408,6 +7570,8 @@ StartChessProgram(cps)
     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
     if (cps->protocolVersion > 1) {\r
       sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
+      cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
+      cps->comboCnt = 0;  //                and values of combo boxes\r
       SendToProgram(buf, cps);\r
     } else {\r
       SendToProgram("xboard\n", cps);\r
@@ -7545,7 +7709,7 @@ GameEnds(result, resultDetails, whosays)
                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */\r
                                             first.twoMachinesColor[0] :\r
                                             second.twoMachinesColor[0] ;\r
-                if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper) &&\r
+                if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&\r
                     (result == WhiteWins && claimer == 'w' ||\r
                      result == BlackWins && claimer == 'b'   ) ) {\r
                if (appData.debugMode) {\r
@@ -7573,7 +7737,8 @@ GameEnds(result, resultDetails, whosays)
                 /* (Claiming a loss is accepted no questions asked!) */\r
            }\r
            /* [HGM] bare: don't allow bare King to win */\r
-           if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper) && result != GameIsDrawn)\r
+           if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
+                        && result != GameIsDrawn)\r
            {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
                for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
                        int p = (int)boards[forwardMostMove][i][j] - color;\r
@@ -7905,6 +8070,7 @@ Reset(redraw, init)
     ics_gamenum = -1;\r
     white_holding[0] = black_holding[0] = NULLCHAR;\r
     ClearProgramStats();\r
+    opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
     \r
     ResetFrontEnd();\r
     ClearHighlights();\r
@@ -10184,6 +10350,7 @@ MachineWhiteEvent()
     if (appData.autoFlipView && !flipView) {\r
       flipView = !flipView;\r
       DrawPosition(FALSE, NULL);\r
+      DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
     }\r
 \r
     if(bookHit) { // [HGM] book: simulate book reply\r
@@ -10191,7 +10358,7 @@ MachineWhiteEvent()
 \r
        programStats.depth = programStats.nodes = programStats.time = \r
        programStats.score = programStats.got_only_move = 0;\r
-       sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
 \r
        strcpy(bookMove, "move ");\r
        strcat(bookMove, bookHit);\r
@@ -10259,13 +10426,14 @@ MachineBlackEvent()
     if (appData.autoFlipView && flipView) {\r
       flipView = !flipView;\r
       DrawPosition(FALSE, NULL);\r
+      DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;\r
     }\r
     if(bookHit) { // [HGM] book: simulate book reply\r
        static char bookMove[MSG_SIZ]; // a bit generous?\r
 \r
        programStats.depth = programStats.nodes = programStats.time = \r
        programStats.score = programStats.got_only_move = 0;\r
-       sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
 \r
        strcpy(bookMove, "move ");\r
        strcat(bookMove, bookHit);\r
@@ -10411,7 +10579,7 @@ TwoMachinesEvent P((void))
 \r
        programStats.depth = programStats.nodes = programStats.time = \r
        programStats.score = programStats.got_only_move = 0;\r
-       sprintf(programStats.movelist, "%s (xbook)", bookMove);\r
+       sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
 \r
        strcpy(bookMove, "move ");\r
        strcat(bookMove, bookHit);\r
@@ -12082,6 +12250,55 @@ StringFeature(p, name, loc, cps)
   return FALSE;\r
 }\r
 \r
+int \r
+ParseOption(Option *opt, ChessProgramState *cps)\r
+// [HGM] options: process the string that defines an engine option, and determine\r
+// name, type, default value, and allowed value range\r
+{\r
+       char *p, *q, buf[MSG_SIZ];\r
+       int n, min = (-1)<<31, max = 1<<31, def;\r
+\r
+       if(p = strstr(opt->name, " -spin ")) {\r
+           if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
+           if(max < min) max = min; // enforce consistency\r
+           if(def < min) def = min;\r
+           if(def > max) def = max;\r
+           opt->value = def;\r
+           opt->min = min;\r
+           opt->max = max;\r
+           opt->type = Spin;\r
+       } else if(p = strstr(opt->name, " -string ")) {\r
+           opt->textValue = p+9;\r
+           opt->type = TextBox;\r
+       } else if(p = strstr(opt->name, " -check ")) {\r
+           if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
+           opt->value = (def != 0);\r
+           opt->type = CheckBox;\r
+       } else if(p = strstr(opt->name, " -combo ")) {\r
+           opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
+           cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
+           opt->value = n = 0;\r
+           while(q = StrStr(q, " /// ")) {\r
+               n++; *q = 0;    // count choices, and null-terminate each of them\r
+               q += 5;\r
+               if(*q == '*') { // remember default, which is marked with * prefix\r
+                   q++;\r
+                   opt->value = n;\r
+               }\r
+               cps->comboList[cps->comboCnt++] = q;\r
+           }\r
+           cps->comboList[cps->comboCnt++] = NULL;\r
+           opt->max = n + 1;\r
+           opt->type = ComboBox;\r
+       } else if(p = strstr(opt->name, " -button")) {\r
+           opt->type = Button;\r
+       } else if(p = strstr(opt->name, " -save")) {\r
+           opt->type = SaveButton;\r
+       } else return FALSE;\r
+       *p = 0; // terminate option name\r
+       return TRUE;\r
+}\r
+\r
 void\r
 FeatureDone(cps, val)\r
      ChessProgramState* cps;\r
@@ -12155,6 +12372,16 @@ ParseFeatures(args, cps)
     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
+    if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
+       ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
+       if(cps->nrOptions >= MAX_OPTIONS) {\r
+           cps->nrOptions--;\r
+           sprintf(buf, "%s engine has too many options\n", cps->which);\r
+           DisplayError(buf, 0);\r
+       }\r
+       continue;\r
+    }\r
+    if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
     /* End of additions by HGM */\r
 \r
     /* unknown feature: complain and skip */\r