Allow second engine to analyze too
[xboard.git] / backend.c
index 20e5efc..a463fa7 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -222,6 +222,7 @@ int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
 FILE *WriteTourneyFile P((char *results, FILE *f));
 void DisplayTwoMachinesTitle P(());
 static void ExcludeClick P((int index));
+void ToggleSecond P((void));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -421,7 +422,7 @@ Boolean alarmSounded;
 /* end premove variables */
 
 char *ics_prefix = "$";
-int ics_type = ICS_GENERIC;
+enum ICS_TYPE ics_type = ICS_GENERIC;
 
 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
 int pauseExamForwardMostMove = 0;
@@ -4905,6 +4906,7 @@ AnalysisPeriodicEvent (int force)
 
     /* Send . command to Crafty to collect stats */
     SendToProgram(".\n", &first);
+    if(second.analyzing) SendToProgram(".\n", &second);
 
     /* Don't send another until we get a response (this makes
        us stop sending to old Crafty's which don't understand
@@ -5135,6 +5137,11 @@ UploadGameEvent ()
     for(i = backwardMostMove; i<last; i++) {
        char buf[20];
        snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
+       if((*buf == 'b' || *buf == 'B') && buf[1] == 'x') { // work-around for stupid FICS bug, which thinks bxc3 can be a Bishop move
+           int len = strlen(moveList[i]);
+           snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s", moveList[i]); // use long algebraic
+           if(!isdigit(buf[len-2])) snprintf(buf+len-2, 20-len, "=%c\n", ToUpper(buf[len-2])); // promotion must have '=' in ICS format
+       }
        SendToICS(buf);
     }
     SendToICS(ics_prefix);
@@ -5442,10 +5449,11 @@ MultiPV (ChessProgramState *cps)
 }
 
 Boolean
-LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
+LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane)
 {
        int startPV, multi, lineStart, origIndex = index;
        char *p, buf2[MSG_SIZ];
+       ChessProgramState *cps = (pane ? &second : &first);
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
@@ -5457,12 +5465,12 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end)
        do{ while(buf[index] && buf[index] != '\n') index++;
        } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
        buf[index] = 0;
-       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
-               int n = first.option[multi].value;
+       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) {
+               int n = cps->option[multi].value;
                if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
                snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
-               if(first.option[multi].value != n) SendToProgram(buf2, &first);
-               first.option[multi].value = n;
+               if(cps->option[multi].value != n) SendToProgram(buf2, cps);
+               cps->option[multi].value = n;
                *start = *end = 0;
                return FALSE;
        } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked
@@ -5866,6 +5874,7 @@ InitPosition (int redraw)
     case VariantSChess:
       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
       gameInfo.holdingsSize = 7;
+      for(i=0; i<BOARD_FILES; i++) initialPosition[VIRGIN][i] = VIRGIN_W | VIRGIN_B;
       break;
     case VariantJanus:
       pieces = JanusArray;
@@ -6846,6 +6855,7 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom
   }
 
   /* Relay move to ICS or chess engine */
+  if(second.analyzing) ToggleSecond(); // for now, we just stop second analyzing engine
   if (appData.icsActive) {
     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
        gameMode == IcsExamining) {
@@ -9314,6 +9324,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board)
              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
        }
 
+       if(gameInfo.variant == VariantSChess) { // update virginity
+          if(fromY == 0)              board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving
+          if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B;
+          if(toY == 0)                board[VIRGIN][toX]   &= ~VIRGIN_W; // loss by capture
+          if(toY == BOARD_HEIGHT-1)   board[VIRGIN][toX]   &= ~VIRGIN_B;
+       }
+
      if (fromX == toX && fromY == toY) return;
 
      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
@@ -9603,8 +9620,12 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
              && fromX != toX && fromY != toY)
                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
         // promotion suffix
-        if(promoChar != NULLCHAR)
-                fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
+        if(promoChar != NULLCHAR) {
+            if(fromY == 0 || fromY == BOARD_HEIGHT-1)
+                 fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b',
+                                                ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating
+            else fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY);
+       }
         if(!loadFlag) {
                char buf[MOVE_LEN*2], *p; int len;
             fprintf(serverMoves, "/%d/%d",
@@ -9613,6 +9634,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar)
             else                      timeLeft = blackTimeRemaining/1000;
             fprintf(serverMoves, "/%d", timeLeft);
                strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2);
+               if(p = strchr(buf, '/')) *p = NULLCHAR; else
                if(p = strchr(buf, '=')) *p = NULLCHAR;
                len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square
             fprintf(serverMoves, "/%s", buf);
@@ -10041,6 +10063,27 @@ Substitute (char *participants, int expunge)
 }
 
 int
+CheckPlayers (char *participants)
+{
+       int i;
+       char buf[MSG_SIZ], *p;
+       NamesToList(firstChessProgramNames, command, mnemonic, "all");
+       while(p = strchr(participants, '\n')) {
+           *p = NULLCHAR;
+           for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break;
+           if(!mnemonic[i]) {
+               snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants);
+               *p = '\n';
+               DisplayError(buf, 0);
+               return 1;
+           }
+           *p = '\n';
+           participants = p + 1;
+       }
+       return 0;
+}
+
+int
 CreateTourney (char *name)
 {
        FILE *f;
@@ -10062,6 +10105,7 @@ CreateTourney (char *name)
                DisplayError(_("Not enough participants"), 0);
                return 0;
            }
+           if(CheckPlayers(appData.participants)) return 0;
            ASSIGN(appData.tourneyFile, name);
            if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
            if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
@@ -10140,6 +10184,27 @@ SwapEngines (int n)
 }
 
 int
+GetEngineLine (char *s, int n)
+{
+    int i;
+    char buf[MSG_SIZ];
+    extern char *icsNames;
+    if(!s || !*s) return 0;
+    NamesToList(n == 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all");
+    for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break;
+    if(!mnemonic[i]) return 0;
+    snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]);
+    if(n == 1) SwapEngines(n);
+    ParseArgsFromString(buf);
+    if(n == 1) SwapEngines(n);
+    if(n == 0 && *appData.secondChessProgram == NULLCHAR) {
+       SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog)
+       ParseArgsFromString(buf);
+    }
+    return 1;
+}
+
+int
 SetPlayer (int player, char *p)
 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
     int i;
@@ -13316,8 +13381,25 @@ EditTagsEvent ()
 }
 
 void
+ToggleSecond ()
+{
+  if(second.analyzing) {
+    SendToProgram("exit\n", &second);
+    second.analyzing = FALSE;
+  } else {
+    if (second.pr == NoProc) StartChessProgram(&second);
+    InitChessProgram(&second, FALSE);
+    FeedMovesToProgram(&second, currentMove);
+
+    SendToProgram("analyze\n", &second);
+    second.analyzing = TRUE;
+  }
+}
+
+void
 AnalyzeModeEvent ()
 {
+    if (gameMode == AnalyzeMode) { ToggleSecond(); return; }
     if (appData.noChessProgram || gameMode == AnalyzeMode)
       return;
 
@@ -13918,6 +14000,10 @@ ExitAnalyzeMode ()
       SendToProgram("exit\n", &first);
       first.analyzing = FALSE;
     }
+    if (second.analyzing) {
+      SendToProgram("exit\n", &second);
+      second.analyzing = FALSE;
+    }
     thinkOutput[0] = NULLCHAR;
 }
 
@@ -13931,14 +14017,22 @@ EditPositionDone (Boolean fakeRights)
     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
       boards[0][EP_STATUS] = EP_NONE;
       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
-    if(boards[0][0][BOARD_WIDTH>>1] == king) {
+      if(boards[0][0][BOARD_WIDTH>>1] == king) {
        boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights;
        boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
       } else boards[0][CASTLING][2] = NoRights;
-    if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
+      if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
        boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights;
        boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
       } else boards[0][CASTLING][5] = NoRights;
+      if(gameInfo.variant == VariantSChess) {
+       int i;
+       for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // pieces in their original position are assumed virgin
+         boards[0][VIRGIN][i] = 0;
+         if(boards[0][0][i]              == FIDEArray[0][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_W;
+         if(boards[0][BOARD_HEIGHT-1][i] == FIDEArray[1][i-BOARD_LEFT]) boards[0][VIRGIN][i] |= VIRGIN_B;
+       }
+      }
     }
     SendToProgram("force\n", &first);
     if (blackPlaysFirst) {
@@ -14492,6 +14586,7 @@ ForwardInner (int target)
     if (gameMode == EditGame || gameMode == AnalyzeMode ||
        gameMode == Training || gameMode == PlayFromGameFile ||
        gameMode == AnalyzeFile) {
+      if(target != currentMove && second.analyzing) ToggleSecond(); // for now, we just stop second analyzing engine
        while (currentMove < target) {
            SendMoveToProgram(currentMove++, &first);
        }
@@ -14596,6 +14691,7 @@ BackwardInner (int target)
     }
     if (gameMode == EditGame || gameMode==AnalyzeMode ||
        gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+      if(target != currentMove && second.analyzing) ToggleSecond(); // for now, we just stop second analyzing engine
        while (currentMove > target) {
            if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') {
                // null move cannot be undone. Reload program with move history before it.
@@ -16659,14 +16755,30 @@ PositionToFEN (int move, char *overrideCastling)
 
         /* [HGM] write true castling rights */
         if( nrCastlingRights == 6 ) {
+            int q, k=0;
             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
-               boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
-            if(boards[move][CASTLING][1] == BOARD_LEFT &&
-               boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
+               boards[move][CASTLING][2] != NoRights  ) k = 1, *p++ = 'K';
+            q = (boards[move][CASTLING][1] == BOARD_LEFT &&
+                 boards[move][CASTLING][2] != NoRights  );
+            if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces
+               for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_RGHT]; // count white held pieces
+                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+                    if((boards[move][0][i] != WhiteKing || k+q == 0) &&
+                        boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a';
+            }
+           if(q) *p++ = 'Q';
+            k = 0;
             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
-               boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
-            if(boards[move][CASTLING][4] == BOARD_LEFT &&
-               boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
+               boards[move][CASTLING][5] != NoRights  ) k = 1, *p++ = 'k';
+            q = (boards[move][CASTLING][4] == BOARD_LEFT &&
+                 boards[move][CASTLING][5] != NoRights  );
+            if(gameInfo.variant == VariantSChess) {
+               for(i=j=0; i<BOARD_HEIGHT; i++) j += boards[move][i][BOARD_LEFT-1]; // count black held pieces
+                for(i=BOARD_RGHT-1-k; i>=BOARD_LEFT+q && j; i--)
+                    if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) &&
+                        boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA;
+            }
+            if(q) *p++ = 'q';
         }
      }
      if (q == p) *p++ = '-'; /* No castling rights */
@@ -16732,7 +16844,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
 {
     int i, j;
     char *p, c;
-    int emptycount;
+    int emptycount, virgin[BOARD_FILES];
     ChessSquare piece;
 
     p = fen;
@@ -16861,14 +16973,15 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
 
     while(*p==' ') p++;
     if(nrCastlingRights) {
-      if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
+      if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) virgin[i] = 0;
+      if(*p >= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') {
           /* castling indicator present, so default becomes no castlings */
           for(i=0; i<nrCastlingRights; i++ ) {
                  board[CASTLING][i] = NoRights;
           }
       }
       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
-             (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+             (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess) &&
              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
         int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights;
@@ -16888,25 +17001,34 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
+             if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W;
+             if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
               break;
           case'Q':
               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
               board[CASTLING][2] = whiteKingFile;
+             if(board[CASTLING][1] != NoRights) virgin[board[CASTLING][1]] |= VIRGIN_W;
+             if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W;
               break;
           case'k':
               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
+             if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B;
+             if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
               break;
           case'q':
               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
               board[CASTLING][5] = blackKingFile;
+             if(board[CASTLING][4] != NoRights) virgin[board[CASTLING][4]] |= VIRGIN_B;
+             if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B;
           case '-':
               break;
           default: /* FRC castlings */
               if(c >= 'a') { /* black rights */
+                 if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity
                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
                   if(i == BOARD_RGHT) break;
@@ -16919,6 +17041,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
                   else
                       board[CASTLING][4] = c;
               } else { /* white rights */
+                 if(gameInfo.variant == VariantSChess) { virgin[c-AAA-'A'+'a'] |= VIRGIN_W; break; } // in S-Chess castlings are always KQ
                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
                     if(board[0][i] == WhiteKing) break;
                   if(i == BOARD_RGHT) break;
@@ -16934,6 +17057,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
       }
       for(i=0; i<nrCastlingRights; i++)
         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
+      if(gameInfo.variant == VariantSChess) for(i=0; i<BOARD_FILES; i++) board[VIRGIN][i] = virgin[i];
     if (appData.debugMode) {
         fprintf(debugFP, "FEN castling rights:");
         for(i=0; i<nrCastlingRights; i++)