Add final piece count to search criteria
[xboard.git] / backend.c
index f8c277a..c81e0fd 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -289,6 +289,7 @@ int chattingPartner;
 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
 char lastMsg[MSG_SIZ];
+char lastTalker[MSG_SIZ];
 ChessSquare pieceSweep = EmptySquare;
 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
 int promoDefaultAltered;
@@ -412,6 +413,11 @@ PosFlags (index)
   case VariantGrand:
     flags &= ~F_ALL_CASTLE_OK;
     break;
+  case VariantChu:
+  case VariantChuChess:
+  case VariantLion:
+    flags |= F_NULL_MOVE;
+    break;
   default:
     break;
   }
@@ -846,6 +852,7 @@ InitEngine (ChessProgramState *cps, int n)
     cps->analyzing = FALSE;
     cps->initDone = FALSE;
     cps->reload = FALSE;
+    cps->pseudo = appData.pseudo[n];
 
     /* New features added by Tord: */
     cps->useFEN960 = FALSE;
@@ -2829,7 +2836,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
     int backup;    /* [DM] For zippy color lines */
     char *p;
     char talker[MSG_SIZ]; // [HGM] chat
-    int channel;
+    int channel, collective=0;
 
     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
 
@@ -3071,8 +3078,18 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                        char mess[MSG_SIZ];
                        snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
                        OutputChatMessage(chattingPartner, mess);
+                       if(collective == 1) { // broadcasted talk also goes to private chatbox of talker
+                           int p;
+                           talker[strlen(talker+1)-1] = NULLCHAR; // strip closing delimiter
+                           for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
+                               snprintf(mess, MSG_SIZ, "%s: %s", chatPartner[chattingPartner], parse);
+                               OutputChatMessage(p, mess);
+                               break;
+                           }
+                       }
                        chattingPartner = -1;
-                       next_out = i+1; // [HGM] suppress printing in ICS window
+                       if(collective != 3) next_out = i+1; // [HGM] suppress printing in ICS window
+                       collective = 0;
                    } else
                    if(!suppressKibitz) // [HGM] kibitz
                        AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
@@ -3288,18 +3305,20 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                                           looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
                int p;
                sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
-               chattingPartner = -1;
+               chattingPartner = -1; collective = 0;
 
                if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
                for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
                    talker[0] = '['; strcat(talker, "] ");
-                   Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
+                   Colorize((channel == 1 ? ColorChannel1 : ColorChannel), FALSE);
                    chattingPartner = p; break;
                    }
                } else
                if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
                for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(!strcmp("kibitzes", chatPartner[p])) {
                        talker[0] = '['; strcat(talker, "] ");
                        chattingPartner = p; break;
@@ -3307,6 +3326,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                } else
                if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
                for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    if(!strcmp("whispers", chatPartner[p])) {
                        talker[0] = '['; strcat(talker, "] ");
                        chattingPartner = p; break;
@@ -3315,6 +3335,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                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
+                   collective = 1;
                    if(!strcmp("c-shouts", chatPartner[p])) {
                        talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
                        chattingPartner = p; break;
@@ -3322,6 +3343,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                  }
                  if(chattingPartner < 0)
                  for(p=0; p<MAX_CHAT; p++) {
+                   collective = 1;
                    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); }
@@ -3332,18 +3354,23 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                }
                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])) {
-                   talker[0] = 0; Colorize(ColorTell, FALSE);
+                   talker[0] = 0;
+                   Colorize(ColorTell, FALSE);
+                   if(collective) safeStrCpy(talker, "broadcasts: ", MSG_SIZ);
+                   collective |= 2;
                    chattingPartner = p; break;
                }
-               if(chattingPartner<0) i = oldi; else {
+               if(chattingPartner<0) i = oldi, safeStrCpy(lastTalker, talker+1, MSG_SIZ); else {
                    Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
-                   if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
-                   if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
                    started = STARTED_COMMENT;
                    parse_pos = 0; parse[0] = NULLCHAR;
                    savingComment = 3 + chattingPartner; // counts as TRUE
-                   suppressKibitz = TRUE;
-                   continue;
+                   if(collective == 3) i = oldi; else {
+                       suppressKibitz = TRUE;
+                       if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
+                       if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+                       continue;
+                   }
                }
            } // [HGM] chat: end of patch
 
@@ -3427,7 +3454,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                      parse[parse_pos] = NULLCHAR;
                      started = STARTED_COMMENT;
                      savingComment = TRUE;
-                   } else {
+                   } else if(collective != 3) {
                      started = STARTED_CHATTER;
                      savingComment = FALSE;
                    }
@@ -6795,6 +6822,7 @@ int lastLoadGameUseList = FALSE;
 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
 ChessMove lastLoadGameStart = EndOfFile;
 int doubleClick;
+Boolean addToBookFlag;
 
 void
 UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
@@ -6917,6 +6945,13 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
            DrawPosition(FALSE, boards[currentMove]);
            return;
        } else if (toX >= 0 && toY >= 0) {
+           if(!appData.pieceMenu && toX == fromX && toY == fromY && boards[0][rf][ff] != EmptySquare) {
+               ChessSquare q, p = boards[0][rf][ff];
+               if(p >= BlackPawn) p = BLACK_TO_WHITE p;
+               if(CHUPROMOTED p < BlackPawn) p = q = CHUPROMOTED boards[0][rf][ff];
+               else p = CHUDEMOTED (q = boards[0][rf][ff]);
+               if(PieceToChar(q) == '+') gatingPiece = p;
+           }
            boards[0][toY][toX] = boards[0][fromY][fromX];
            if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
                if(boards[0][fromY][0] != EmptySquare) {
@@ -6937,7 +6972,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
         return;
     }
 
-    if(toX < 0 || toY < 0) return;
+    if((toX < 0 || toY < 0) && (fromY != DROP_RANK || fromX != EmptySquare)) return;
     pup = boards[currentMove][toY][toX];
 
     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
@@ -6957,7 +6992,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
                                          fromY, fromX, toY, toX, promoChar);
 
-    if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove;
+    if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame || PosFlags(0) & F_NULL_MOVE)) moveType = NormalMove;
 
     /* [HGM] but possibly ignore an IllegalMove result */
     if (appData.testLegality) {
@@ -6974,6 +7009,16 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
        return;
     }
 
+    if(addToBookFlag) { // adding moves to book
+       char buf[MSG_SIZ], move[MSG_SIZ];
+        CoordsToAlgebraic(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar, move);
+       snprintf(buf, MSG_SIZ, "  0.0%%     1  %s\n", move);
+       AddBookMove(buf);
+       addToBookFlag = FALSE;
+       ClearHighlights();
+       return;
+    }
+
     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
 }
 
@@ -8673,7 +8718,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         }
 
         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
-        if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
+        if( gameMode == TwoMachinesPlay && appData.adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
             int count = 0;
 
             while( count < adjudicateLossPlies ) {
@@ -8682,8 +8727,8 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                 if( count & 1 ) {
                     score = -score; /* Flip score for winning side */
                 }
-
-                if( score > adjudicateLossThreshold ) {
+printf("score=%d count=%d\n",score,count);
+                if( score > appData.adjudicateLossThreshold ) {
                     break;
                 }
 
@@ -8727,7 +8772,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
                        (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
                        programStats.movelist);
                SendToICS(buf);
-if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
          }
        }
 #endif
@@ -8821,7 +8865,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
       int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ];
       if(appData.icsActive || forwardMostMove != 0 || cps != &first) return;
       *buf = NULLCHAR;
-      if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+      if(sscanf(message, "setup (%s", buf) == 1) {
+        s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
+        ASSIGN(appData.pieceToCharTable, buf);
+      }
       if(startedFromSetupPosition) return;
       dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
       if(dummy >= 3) {
@@ -9102,6 +9149,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
           Don't use it. */
        cps->sendTime = 0;
     }
+    if (cps->pseudo) { // [HGM] pseudo-engine, granted unusual powers
+       if (sscanf(message, "wtime %ld\n", &whiteTimeRemaining) == 1 || // adjust clock times
+           sscanf(message, "btime %ld\n", &blackTimeRemaining) == 1   ) return;
+    }
 
     /*
      * If chess program startup fails, exit with an error message.
@@ -9822,11 +9873,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
        }
         piece = board[toY][toX] = (ChessSquare) fromX;
   } else {
-      ChessSquare victim;
+//      ChessSquare victim;
       int i;
 
       if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something
-           victim = board[killY][killX],
+//           victim = board[killY][killX],
            board[killY][killX] = EmptySquare,
            board[EP_STATUS] = EP_CAPTURE;
 
@@ -10393,6 +10444,7 @@ InitChessProgram (ChessProgramState *cps, int setup)
        SendToProgram(buf, cps);
     }
 
+    setboardSpoiledMachineBlack = FALSE;
     SendToProgram(cps->initString, cps);
     if (gameInfo.variant != VariantNormal &&
        gameInfo.variant != VariantLoadable
@@ -10806,6 +10858,7 @@ SwapEngines (int n)
     SWAP(accumulateTC, h)
     SWAP(drawDepth, h)
     SWAP(host, p)
+    SWAP(pseudo, h)
 }
 
 int
@@ -12311,12 +12364,26 @@ QuickCompare (Board board, int *minCounts, int *maxCounts)
 int
 QuickScan (Board board, Move *move)
 {   // reconstruct game,and compare all positions in it
-    int cnt=0, stretch=0, total = MakePieceList(board, counts);
+    int cnt=0, stretch=0, found = -1, total = MakePieceList(board, counts);
     do {
        int piece = move->piece;
        int to = move->to, from = pieceList[piece];
+       if(!found) { // if already found just scan to game end for final piece count
+         if(QuickCompare(soughtBoard, minSought, maxSought) ||
+          appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
+          flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
+                               appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
+           ) {
+           static int lastCounts[EmptySquare+1];
+           int i;
+           if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
+           if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
+         } else stretch = 0;
+         if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) found = cnt + 1 - stretch;
+         if(found && !appData.minPieces) return found;
+       }
        if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
-         if(!piece) return -1;
+         if(!piece) return (appData.minPieces && (total < appData.minPieces || total > appData.maxPieces) ? -1 : found);
          if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
            piece = (++move)->piece;
            from = pieceList[piece];
@@ -12347,17 +12414,6 @@ QuickScan (Board board, Move *move)
        quickBoard[to] = piece;
        pieceList[piece] = to;
        cnt++; turn ^= 3;
-       if(QuickCompare(soughtBoard, minSought, maxSought) ||
-          appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) ||
-          flipSearch && (QuickCompare(flipBoard, minSought, maxSought) ||
-                               appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse))
-         ) {
-           static int lastCounts[EmptySquare+1];
-           int i;
-           if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
-           if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
-       } else stretch = 0;
-       if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
        move++;
     } while(1);
 }
@@ -13951,6 +14007,7 @@ ExitEvent (int status)
       return;
     }
 
+    if (appData.icsActive) printf("\n"); // [HGM] end on new line after closing XBoard
     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
 
     if (telnetISR != NULL) {
@@ -14971,14 +15028,14 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y)
                        boards[0][y][x] = p;
                    }
                }
-               menuBoard[1][x] = menuBoard[BOARD_HEIGHT-2][x] = p;
            }
            if(gameMode != IcsExamining) { // [HGM] editpos: cycle trough boards
-               for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
-                   ChessSquare p = menuBoard[0][x];
-                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[0][y] == p) menuBoard[0][y] = EmptySquare;
-                   p = menuBoard[BOARD_HEIGHT-1][x];
-                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[BOARD_HEIGHT-1][y] == p) menuBoard[BOARD_HEIGHT-1][y] = EmptySquare;
+               int r;
+               for(r = 0; r < BOARD_HEIGHT; r++) {
+                 for(x = BOARD_LEFT; x < BOARD_RGHT; x++) { // create 'menu board' by removing duplicates 
+                   ChessSquare p = menuBoard[r][x];
+                   for(y = x + 1; y < BOARD_RGHT; y++) if(menuBoard[r][y] == p) menuBoard[r][y] = EmptySquare;
+                 }
                }
                DisplayMessage("Clicking clock again restores position", "");
                if(gameInfo.variant != lastVariant) lastVariant = gameInfo.variant, CopyBoard(erasedBoard, boards[0]);
@@ -15240,7 +15297,8 @@ ClockClick (int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetBlackToPlayEvent();
-         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+                     gameMode == MachinePlaysBlack && PosFlags(0) & F_NULL_MOVE && !blackFlag && !shiftKey) && WhiteOnMove(currentMove)) {
           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move
          } else if (shiftKey) {
            AdjustClock(which, -1);
@@ -15252,7 +15310,8 @@ ClockClick (int which)
          if (gameMode == EditPosition || gameMode == IcsExamining) {
            if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
            SetWhiteToPlayEvent();
-         } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) {
+         } else if ((gameMode == AnalyzeMode || gameMode == EditGame ||
+                     gameMode == MachinePlaysWhite && PosFlags(0) & F_NULL_MOVE && !whiteFlag && !shiftKey) && !WhiteOnMove(currentMove)) {
           UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move
          } else if (shiftKey) {
            AdjustClock(which, -1);
@@ -17597,12 +17656,12 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                 if(PieceToChar(piece) == '+') {
                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
                     *p++ = '+';
-                    piece = (ChessSquare)(DEMOTED piece);
+                    piece = (ChessSquare)(CHUDEMOTED piece);
                 }
                 *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
-                    p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
+                    p[-1] = PieceToChar((ChessSquare)(CHUDEMOTED piece));
                     *p++ = '~';
                 }
            }
@@ -17780,7 +17839,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
                    appData.NrRanks = gameInfo.boardHeight - i; i=0;
                 }
                break;
-#if(BOARD_FILES >= 10)
+#if(BOARD_FILES >= 10)*0
             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
                 p++; emptycount=10;
                 if (j + emptycount > gameInfo.boardWidth) return FALSE;