Suppress testing for availability in bughouse drops
[xboard.git] / backend.c
index 5715e5d..bc25c5f 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -450,15 +450,15 @@ int adjudicateLossPlies = 6;
 char white_holding[64], black_holding[64];
 TimeMark lastNodeCountTime;
 long lastNodeCount=0;
-int shiftKey; // [HGM] set by mouse handler
+int shiftKey, controlKey; // [HGM] set by mouse handler
 
 int have_sent_ICS_logon = 0;
 int movesPerSession;
 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
+long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
 Boolean adjustedClock;
 long timeControl_2; /* [AS] Allow separate time controls */
-char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
+char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
 long timeRemaining[2][MAX_MOVES];
 int matchGame = 0, nextGame = 0, roundNr = 0;
 Boolean waitingForGame = FALSE;
@@ -851,7 +851,7 @@ LoadEngine ()
     SendToProgram("force\n", savCps);
     DisplayMessage("", "");
     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
-    for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
+    for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
     ThawUI();
     SetGNUMode();
 }
@@ -920,7 +920,7 @@ Load (ChessProgramState *cps, int i)
     while(q = strchr(p, SLASH)) p = q+1;
     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
     if(engineDir[0] != NULLCHAR) {
-       ASSIGN(appData.directory[i], engineDir);
+       ASSIGN(appData.directory[i], engineDir); p = engineName;
     } else if(p != engineName) { // derive directory from engine path, when not given
        p[-1] = 0;
        ASSIGN(appData.directory[i], engineName);
@@ -2446,6 +2446,7 @@ VariantSwitch (Board board, VariantClass newVariant)
           board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
             board[i][j];
      }
+     board[HOLDINGS_SET] = 0;
      gameInfo.boardWidth  = newWidth;
      gameInfo.boardHeight = newHeight;
      gameInfo.holdingsWidth = newHoldingsWidth;
@@ -4244,6 +4245,7 @@ ParseBoard12 (char *string)
     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
         && 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 */
+      int fac = strchr(elapsed_time, '.') ? 1 : 1000;
       char *toSqr;
       for (k = 0; k < ranks; k++) {
         for (j = 0; j < files; j++)
@@ -4266,11 +4268,13 @@ ParseBoard12 (char *string)
       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
       if(partnerUp) DrawPosition(FALSE, partnerBoard);
       if(twoBoards) {
-         DisplayWhiteClock(white_time, to_play == 'W');
-         DisplayBlackClock(black_time, to_play != 'W');
+         DisplayWhiteClock(white_time*fac, to_play == 'W');
+         DisplayBlackClock(black_time*fac, to_play != 'W');
+         activePartner = to_play;
+         activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac;
                      partnerUp = 0; flipView = !flipView; } // [HGM] dual
-      snprintf(partnerStatus, MSG_SIZ,"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);
+      snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000,
+                (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play);
       DisplayMessage(partnerStatus, "");
        partnerBoardValid = TRUE;
       return;
@@ -4310,6 +4314,7 @@ ParseBoard12 (char *string)
     }
 
    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
+                                       move_str[1] == '@' && !gameInfo.holdingsWidth ||
                                        weird && (int)gameInfo.variant < (int)VariantShogi) {
      /* [HGM] We seem to have switched variant unexpectedly
       * Try to guess new variant from board size
@@ -4319,7 +4324,8 @@ ParseBoard12 (char *string)
          if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
          if(ranks == 8 && files == 12) newVariant = VariantCourier; else
          if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
-         if(!weird) newVariant = VariantNormal;
+         if(ranks == 10 && files == 10) newVariant = VariantGrand; else
+         if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal;
           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
          /* Get a move list just to see the header, which
             will tell us whether this is really bug or zh */
@@ -5170,6 +5176,12 @@ Sweep (int step)
     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
            appData.testLegality && (promoSweep == king ||
            gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
+    if(toX >= 0) {
+       int victim = boards[currentMove][toY][toX];
+       boards[currentMove][toY][toX] = promoSweep;
+       DrawPosition(FALSE, boards[currentMove]);
+       boards[currentMove][toY][toX] = victim;
+    } else
     ChangeDragPiece(promoSweep);
 }
 
@@ -6664,7 +6676,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar)
                    if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
                }
            } else
-           boards[0][fromY][fromX] = EmptySquare;
+           boards[0][fromY][fromX] = gatingPiece;
            DrawPosition(FALSE, boards[currentMove]);
            return;
        }
@@ -6916,11 +6928,11 @@ void
 MarkTargetSquares (int clear)
 {
   int x, y;
+  if(clear) // no reason to ever suppress clearing
+    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
   if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
      !appData.testLegality || gameMode == EditPosition) return;
-  if(clear) {
-    for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
-  } else {
+  if(!clear) {
     int capt = 0;
     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
@@ -6929,7 +6941,7 @@ MarkTargetSquares (int clear)
       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
     }
   }
-  DrawPosition(TRUE, NULL);
+  DrawPosition(FALSE, NULL);
 }
 
 int
@@ -6968,7 +6980,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
     Boolean saveAnimate;
-    static int second = 0, promotionChoice = 0, clearFlag = 0;
+    static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
     static TimeMark lastClickTime, prevClickTime;
@@ -6992,7 +7004,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        defaultPromoChoice = promoSweep;
        promoSweep = EmptySquare;   // terminate sweep
        promoDefaultAltered = TRUE;
-       if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
+       if(!selectFlag && !sweepSelecting && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
     }
 
     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
@@ -7060,6 +7072,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if (OKToStartUserMove(fromX, fromY)) {
                second = 0;
                MarkTargetSquares(0);
+               if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX];
                DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
                if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
                    promoSweep = defaultPromoChoice;
@@ -7069,6 +7082,8 @@ LeftClick (ClickType clickType, int xPix, int yPix)
                }
                if (appData.highlightDragging) {
                    SetHighlights(fromX, fromY, -1, -1);
+               } else {
+                   ClearHighlights();
                }
            } else fromX = fromY = -1;
            return;
@@ -7148,9 +7163,10 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            /* Undo animation damage if any */
            DrawPosition(FALSE, NULL);
        }
-       if (second) {
+       if (second || sweepSelecting) {
            /* Second up/down in same square; just abort move */
-           second = 0;
+           if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]);
+           second = sweepSelecting = 0;
            fromX = fromY = -1;
            gatingPiece = EmptySquare;
            ClearHighlights();
@@ -7167,10 +7183,12 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     /* we now have a different from- and (possibly off-board) to-square */
     /* Completed move */
-    toX = x;
-    toY = y;
-    saveAnimate = appData.animate;
-    MarkTargetSquares(1);
+    if(!sweepSelecting) {
+       toX = x;
+       toY = y;
+       saveAnimate = appData.animate;
+    } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep
+
     if (clickType == Press) {
        if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
            // must be Edit Position mode with empty-square selected
@@ -7178,16 +7196,18 @@ LeftClick (ClickType clickType, int xPix, int yPix)
            if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
            return;
        }
-       if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+       if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+         if(appData.sweepSelect) {
            ChessSquare piece = boards[currentMove][fromY][fromX];
-           DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
            promoSweep = defaultPromoChoice;
            if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
            selectFlag = 0; lastX = xPix; lastY = yPix;
            Sweep(0); // Pawn that is going to promote: preview promotion piece
+           sweepSelecting = 1;
            DisplayMessage("", _("Pull pawn backwards to under-promote"));
-           DrawPosition(FALSE, boards[currentMove]);
-           return;
+           MarkTargetSquares(1);
+         }
+         return; // promo popup appears on up-click
        }
        /* Finish clickclick move */
        if (appData.animate || appData.highlightLastMove) {
@@ -7206,6 +7226,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
        /* Don't animate move and drag both */
        appData.animate = FALSE;
     }
+    MarkTargetSquares(1);
 
     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
@@ -7915,6 +7936,25 @@ SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial)
     return bookHit; // notify caller of hit, so it can take action to send move to opponent
 }
 
+int
+LoadError (char *errmess, ChessProgramState *cps)
+{   // unloads engine and switches back to -ncp mode if it was first
+    if(cps->initDone) return FALSE;
+    cps->isr = NULL; // this should suppress further error popups from breaking pipes
+    DestroyChildProcess(cps->pr, 9 ); // just to be sure
+    cps->pr = NoProc; 
+    if(cps == &first) {
+       appData.noChessProgram = TRUE;
+       gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
+       gameMode = BeginningOfGame; ModeHighlight();
+       SetNCPMode();
+    }
+    if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
+    DisplayMessage("", ""); // erase waiting message
+    if(errmess) DisplayError(errmess, 0); // announce reason, if given
+    return TRUE;
+}
+
 char *savedMessage;
 ChessProgramState *savedState;
 void
@@ -7937,7 +7977,7 @@ HandleMachineMove (char *message, ChessProgramState *cps)
     ChessMove moveType;
     char promoChar;
     char *p, *pv=buf1;
-    int machineWhite;
+    int machineWhite, oldError;
     char *bookHit;
 
     if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
@@ -7951,7 +7991,7 @@ HandleMachineMove (char *message, ChessProgramState *cps)
        return; // Skim the pairing messages here.
     }
 
-    cps->userError = 0;
+    oldError = cps->userError; cps->userError = 0;
 
 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
     /*
@@ -8520,18 +8560,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                _(cps->which), cps->program, cps->host, message);
        RemoveInputSource(cps->isr);
        if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
-           cps->isr = NULL;
-           DestroyChildProcess(cps->pr, 9 ); // just to be sure
-           cps->pr = NoProc; 
-           if(cps == &first) {
-               appData.noChessProgram = TRUE;
-               gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu
-               gameMode = BeginningOfGame; ModeHighlight();
-               SetNCPMode();
-           }
-           if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout
-           DisplayMessage("", ""); // erase waiting message
-           DisplayError(buf1, 0);
+           if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError
+           if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup
        }
        return;
     }
@@ -11515,7 +11545,7 @@ 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, total = MakePieceList(board, counts), delayedKing = -1;
     do {
        int piece = move->piece;
        int to = move->to, from = pieceList[piece];
@@ -11533,18 +11563,22 @@ QuickScan (Board board, Move *move)
            move++;
            continue;
          } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
+           int rook;
            piece = pieceList[piece]; // first two elements of pieceList contain King numbers
            from  = pieceList[piece]; // so this must be King
            quickBoard[from] = 0;
-           quickBoard[to] = piece;
            pieceList[piece] = to;
-           move++;
-           continue;
+           from = pieceList[(++move)->piece]; // for FRC this has to be done here
+           quickBoard[from] = 0; // rook
+           quickBoard[to] = piece;
+           to = move->to; piece = move->piece;
+           goto aftercastle;
          }
        }
        if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture
        if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for
        quickBoard[from] = 0;
+      aftercastle:
        quickBoard[to] = piece;
        pieceList[piece] = to;
        cnt++; turn ^= 3;
@@ -11559,7 +11593,7 @@ QuickScan (Board board, Move *move)
            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++;
+       move++; delayedKing = -1;
     } while(1);
 }
 
@@ -12143,7 +12177,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList)
       AnalyzeFileEvent();
     }
 
-    if (!matchMode && pos >= 0) {
+    if (!matchMode && pos > 0) {
        ToNrEvent(pos); // [HGM] no autoplay if selected on position
     } else
     if (matchMode || appData.timeDelay == 0) {
@@ -15257,10 +15291,10 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou
     if (count <= 0) {
        if (count == 0) {
            RemoveInputSource(cps->isr);
-           if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
            snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
                    _(cps->which), cps->program);
-        if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+           if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load
+           if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
                    if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
@@ -16199,6 +16233,16 @@ DecrementClocks ()
     }
     if (CheckFlags()) return;
 
+    if(twoBoards) { // count down secondary board's clocks as well
+       activePartnerTime -= lastTickLength;
+       partnerUp = 1;
+       if(activePartner == 'W')
+           DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one!
+       else
+           DisplayBlackClock(activePartnerTime, TRUE);
+       partnerUp = 0;
+    }
+
     tickStartTM = now;
     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);