Fix multi-leg promotions
[xboard.git] / backend.c
index 83034f4..79d04fb 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -261,6 +261,7 @@ void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 VariantClass startVariant; /* [HGM] nicks: initial variant */
 Boolean abortMatch;
+int deadRanks;
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -5162,14 +5163,14 @@ SendMoveToProgram (int moveNum, ChessProgramState *cps)
       if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square
        char *m = moveList[moveNum];
        static char c[2];
-       *c = m[7]; // promoChar
+       *c = m[7]; if(*c == '\n') *c = NULLCHAR; // promoChar
        if((boards[moveNum][m[6]-ONE][m[5]-AAA] < BlackPawn) == (boards[moveNum][m[1]-ONE][m[0]-AAA] < BlackPawn)) // move is kludge to indicate castling
          snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", m[0], m[1] - '0', // convert to two moves
                                               m[2], m[3] - '0',
                                               m[5], m[6] - '0',
                                               m[2] + (m[0] > m[5] ? 1 : -1), m[3] - '0');
-       else if(*c && m[8]) { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
-         *c = m[9];
+       else if(*c && m[8] != '\n') { // kill square followed by 2 characters: 2nd kill square rather than promo suffix
+         *c = m[9]; if(*c == '\n') *c = NULLCHAR;
          snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d,%c%d%c%d%s\n", m[0], m[1] - '0', // convert to three moves
                                               m[7], m[8] - '0',
                                               m[7], m[8] - '0',
@@ -5385,7 +5386,7 @@ CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char
            sprintf(move, "%c%c%c%c%c\n",
                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
          if(killX >= 0 && killY >= 0) {
-           sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY);
+           sprintf(move+4, ";%c%c%c\n", AAA + killX, ONE + killY, promoChar);
            if(kill2X >= 0 && kill2Y >= 0) sprintf(move+7, "%c%c%c\n", AAA + kill2X, ONE + kill2Y, promoChar);
          }
        }
@@ -6701,9 +6702,12 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i
       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
        return FALSE;
 
+    if(legal[toY][toX] == 4) return FALSE;
+
     piece = boards[currentMove][fromY][fromX];
     if(gameInfo.variant == VariantChu) {
         promotionZoneSize = BOARD_HEIGHT/3;
+        if(legal[toY][toX] == 6) return FALSE; // no promotion if highlights deny it
         highestPromotingPiece = (PieceToChar(piece) == '+' || PieceToChar(CHUPROMOTED(piece)) != '+') ? WhitePawn : WhiteKing;
     } else if(gameInfo.variant == VariantShogi) {
         promotionZoneSize = BOARD_HEIGHT/3 +(BOARD_HEIGHT == 8);
@@ -7396,13 +7400,13 @@ MarkByFEN(char *fen)
 {
        int r, f;
        if(!appData.markers || !appData.highlightDragging) return;
-       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 0;
-       r=BOARD_HEIGHT-1; f=BOARD_LEFT;
+       for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = marker[r][f] = 0;
+       r=BOARD_HEIGHT-1-deadRanks; f=BOARD_LEFT;
        while(*fen) {
            int s = 0;
-           marker[r][f] = 0;
            if(*fen == 'M') legal[r][f] = 2; else // request promotion choice
-           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 3; else
+           if(*fen == 'B') legal[r][f] = 4; else // request auto-promotion to victim
+           if(*fen >= 'A' && *fen <= 'Z') legal[r][f] = 6; else
            if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a';
            if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else
            if(*fen == 'T') marker[r][f++] = 0; else
@@ -7536,12 +7540,13 @@ void ReportClick(char *action, int x, int y)
 }
 
 Boolean right; // instructs front-end to use button-1 events as if they were button 3
+Boolean deferChoice;
 
 void
 LeftClick (ClickType clickType, int xPix, int yPix)
 {
     int x, y;
-    Boolean saveAnimate;
+    static Boolean saveAnimate;
     static int second = 0, promotionChoice = 0, clearFlag = 0, sweepSelecting = 0, flashing = 0, saveFlash;
     char promoChoice = NULLCHAR;
     ChessSquare piece;
@@ -7549,6 +7554,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
 
     if(flashing) return;
 
+  if(!deferChoice) { // when called for a retry, skip everything to the point where we left off
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
     if (!flipView && y >= 0) {
@@ -7896,8 +7902,19 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     else ReportClick("put", x, y);
 
     if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece));
-
-    if(legal[toY][toX] == 2) promoChoice = ToLower(PieceToChar(defaultPromoChoice)); // highlight-induced promotion
+ }
+
+    if(legal[toY][toX] == 2) { // highlight-induced promotion
+       if(piece == defaultPromoChoice) promoChoice = NULLCHAR; // deferral
+       else promoChoice = ToLower(PieceToChar(defaultPromoChoice));
+    } else if(legal[toY][toX] == 4) { // blue target square: engine must supply promotion choice
+      if(!*promoRestrict) {           // but has not done that yet
+       deferChoice = TRUE;           // set up retry for when it does
+       return;                       // and wait for that
+      }
+      promoChoice = ToLower(*promoRestrict); // force engine's choice
+      deferChoice = FALSE;
+    }
 
     if (legal[toY][toX] == 2 && !appData.sweepSelect || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
        SetHighlights(fromX, fromY, toX, toY);
@@ -9130,6 +9147,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
         while(message[s] && message[s++] != ' ');
         if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand ||
            dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant
+           if(hand <= h) deadRanks = 0; else deadRanks = hand - h, h = hand; // adapt board to over-sized holdings
            appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand;
            if(dummy == 4) gameInfo.variant = StringToVariant(varName);     // parent variant
           InitPosition(1); // calls InitDrawingSizes to let new parameters take effect
@@ -9165,9 +9183,13 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h
       }
       return;
     }
-    if(sscanf(message, "choice %s", promoRestrict) == 1 && promoSweep != EmptySquare) {
-      promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
-      Sweep(0);
+    if(sscanf(message, "choice %s", promoRestrict) == 1) {
+      if(deferChoice) {
+        LeftClick(Press, 0, 0); // finish the click that was interrupted
+      } else if(promoSweep != EmptySquare) {
+        promoSweep = CharToPiece(currentMove&1 ? ToLower(*promoRestrict) : ToUpper(*promoRestrict));
+        if(strlen(promoRestrict) > 1) Sweep(0);
+      }
       return;
     }
     /* [HGM] Allow engine to set up a position. Don't ask me why one would
@@ -12015,6 +12037,7 @@ Reset (int redraw, int init)
                redraw, init, gameMode);
     }
     pieceDefs = FALSE; // [HGM] gen: reset engine-defined piece moves
+    deadRanks = 0; // assume entire board is used
     for(i=0; i<EmptySquare; i++) { FREE(pieceDesc[i]); pieceDesc[i] = NULL; }
     CleanupTail(); // [HGM] vari: delete any stored variations
     CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
@@ -17810,11 +17833,13 @@ ResetClocks ()
 
 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
 
+static int timeSuffix; // [HGM] This should realy be a passed parameter, but it has to pass through too many levels for my laziness...
+
 /* Decrement running clock by amount of time that has passed */
 void
 DecrementClocks ()
 {
-    long timeRemaining;
+    long tRemaining;
     long lastTickLength, fudge;
     TimeMark now;
 
@@ -17831,28 +17856,32 @@ DecrementClocks ()
 
     if (WhiteOnMove(forwardMostMove)) {
        if(whiteNPS >= 0) lastTickLength = 0;
-       timeRemaining = whiteTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0 && !appData.icsActive) {
+        tRemaining = whiteTimeRemaining -= lastTickLength;
+        if( tRemaining < 0 && !appData.icsActive) {
             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
-                lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
+                lastWhite=  tRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
             }
         }
+       if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[0][forwardMostMove-1] - tRemaining;
        DisplayWhiteClock(whiteTimeRemaining - fudge,
                          WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+       timeSuffix = 0;
     } else {
        if(blackNPS >= 0) lastTickLength = 0;
-       timeRemaining = blackTimeRemaining -= lastTickLength;
-        if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
+        tRemaining = blackTimeRemaining -= lastTickLength;
+        if( tRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
             if(suddenDeath) {
                 blackStartMove = forwardMostMove;
-                lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
+                lastBlack =  tRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
             }
         }
+       if(forwardMostMove && appData.moveTime) timeSuffix = timeRemaining[1][forwardMostMove-1] - tRemaining;
        DisplayBlackClock(blackTimeRemaining - fudge,
                          !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
+       timeSuffix = 0;
     }
     if (CheckFlags()) return;
 
@@ -17867,7 +17896,7 @@ DecrementClocks ()
     }
 
     tickStartTM = now;
-    intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
+    intendedTickLength = NextTickLength( tRemaining - fudge) + fudge;
     StartClockTimer(intendedTickLength);
 
     /* if the time remaining has fallen below the alarm threshold, sound the
@@ -17883,9 +17912,9 @@ DecrementClocks ()
               ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
           )) return;
 
-       if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
+       if (alarmSounded && ( tRemaining > appData.icsAlarmTime)) {
            alarmSounded = FALSE;
-       } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+       } else if (!alarmSounded && ( tRemaining <= appData.icsAlarmTime)) {
            PlayAlarmSound();
            alarmSounded = TRUE;
        }
@@ -18026,7 +18055,7 @@ TimeString (long ms)
 {
     long second, minute, hour, day;
     char *sign = "";
-    static char buf[32];
+    static char buf[40], moveTime[8];
 
     if (ms > 0 && ms <= 9900) {
       /* convert milliseconds to tenths, rounding up */
@@ -18053,13 +18082,16 @@ TimeString (long ms)
     minute = second / 60;
     second = second % 60;
 
+    if(timeSuffix) snprintf(moveTime, 8, " (%d)", timeSuffix/1000); // [HGM] kludge alert; fraction contains move time
+    else *moveTime = NULLCHAR;
+
     if (day > 0)
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
-             sign, day, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld%s ",
+             sign, day, hour, minute, second, moveTime);
     else if (hour > 0)
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld%s ", sign, hour, minute, second, moveTime);
     else
-      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
+      snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld%s ", sign, minute, second, moveTime);
 
     return buf;
 }
@@ -18184,7 +18216,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
     p = buf;
 
     /* Piece placement data */
-    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
        if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
        emptycount = 0;
         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
@@ -18400,8 +18432,10 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize)
 
     p = fen;
 
+    for(i=1; i<=deadRanks; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) board[BOARD_HEIGHT-i][j] = DarkSquare;
+
     /* Piece placement data */
-    for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+    for (i = BOARD_HEIGHT - 1 - deadRanks; i >= 0; i--) {
        j = 0;
        for (;;) {
             if (*p == '/' || *p == ' ' || *p == '[' ) {
@@ -19024,6 +19058,8 @@ LoadVariation (int index, char *text)
        ToNrEvent(currentMove+1);
 }
 
+int transparency[2];
+
 void
 LoadTheme ()
 {
@@ -19046,9 +19082,13 @@ LoadTheme ()
                appData.liteBackTextureMode,
                appData.darkBackTextureMode );
       } else {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s",
-               Col2Text(2),   // lightSquareColor
-               Col2Text(3) ); // darkSquareColor
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false");
+      }
+      if(!appData.useBitmaps || transparency[0]) {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -lsc %s", Col2Text(2) ); // lightSquareColor
+      }
+      if(!appData.useBitmaps || transparency[1]) {
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -dsc %s", Col2Text(3) ); // darkSquareColor
       }
       if(appData.useBorder) {
        snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"",
@@ -19063,9 +19103,13 @@ LoadTheme ()
                Col2Text(9),    // appData.fontBackColorWhite
                Col2Text(10) ); // appData.fontForeColorBlack
       } else {
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"",
-               appData.pieceDirectory);
-       if(!appData.pieceDirectory[0])
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false");
+       if(appData.pieceDirectory[0]) {
+         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -pid \"%s\"", appData.pieceDirectory);
+         if(appData.trueColors != 2) // 2 is a kludge to suppress this in WinBoard
+           snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -trueColors %s", appData.trueColors ? "true" : "false");
+       }
+       if(!appData.pieceDirectory[0] || !appData.trueColors)
          snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s",
                Col2Text(0),   // whitePieceColor
                Col2Text(1) ); // blackPieceColor