Automaticaly install Java engines
[xboard.git] / backend.c
index b8ebf8f..ab84abe 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -925,7 +925,7 @@ char *insert, *wbOptions; // point in ChessProgramNames were we should insert ne
 void
 Load (ChessProgramState *cps, int i)
 {
-    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
+    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
        snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
        SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
@@ -949,11 +949,13 @@ Load (ChessProgramState *cps, int i)
        p[-1] = SLASH;
        if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
     } else { ASSIGN(appData.directory[i], "."); }
+    jar = (strstr(p, ".jar") == p + strlen(p) - 4);
     if(params[0]) {
        if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
        snprintf(command, MSG_SIZ, "%s %s", p, params);
        p = command;
     }
+    if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
     ASSIGN(appData.chessProgram[i], p);
     appData.isUCI[i] = isUCI;
     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
@@ -1496,7 +1498,7 @@ MatchEvent (int mode)
                        NextTourneyGame(-1, &dummy);
                        ReserveGame(-1, 0);
                        if(nextGame <= appData.matchGames) {
-                           DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+                           DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
                            matchMode = mode;
                            ScheduleDelayedEvent(NextMatchGame, 10000);
                            return;
@@ -2006,10 +2008,12 @@ StripHighlight (char *s)
     return retbuf;
 }
 
+char engineVariant[MSG_SIZ];
 char *variantNames[] = VARIANT_NAMES;
 char *
 VariantName (VariantClass v)
 {
+    if(v == VariantUnknown || *engineVariant) return engineVariant;
     return variantNames[v];
 }
 
@@ -3025,8 +3029,10 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int
                        } else {
                            char tmp[MSG_SIZ];
                            if(gameMode == IcsObserving) // restore original ICS messages
+                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
                              snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
                            else
+                           /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
                            snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
                            SendToPlayer(tmp, strlen(tmp));
                        }
@@ -5239,7 +5245,8 @@ ProcessICSInitScript (FILE *f)
 }
 
 
-static int lastX, lastY, selectFlag, dragging;
+static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging;
+static ClickType lastClickType;
 
 void
 Sweep (int step)
@@ -7020,16 +7027,23 @@ 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++) marker[r][f] = legal[r][f] = 0;
+       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;
        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] = 1; 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
            if(*fen == 'Y') marker[r][f++] = 1; else
+           if(*fen == 'G') marker[r][f++] = 3; else
+           if(*fen == 'B') marker[r][f++] = 4; else
+           if(*fen == 'C') marker[r][f++] = 5; else
+           if(*fen == 'M') marker[r][f++] = 6; else
+           if(*fen == 'W') marker[r][f++] = 7; else
+           if(*fen == 'D') marker[r][f++] = 8; else
            if(*fen == 'R') marker[r][f++] = 2; else {
                while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0';
              f += s; fen -= s>0;
@@ -7105,12 +7119,34 @@ CanPromote (ChessSquare piece, int y)
                piece == WhiteLance && y == BOARD_HEIGHT-2 );
 }
 
+void
+HoverEvent (int hiX, int hiY, int x, int y)
+{
+       static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES];
+       int r, f;
+       if(!first.highlight) return;
+       if(hiX == -1 && hiY == -1 && x == fromX && y == fromY) // record markings 
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+           baseMarker[r][f] = marker[r][f], baseLegal[r][f] = legal[r][f];
+       else if(hiX != x || hiY != y) {
+         // [HGM] lift: entered new to-square; redraw arrow, and inform engine
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++)
+           marker[r][f] = baseMarker[r][f], legal[r][f] = baseLegal[r][f];
+         if((marker[y][x] == 2 || marker[y][x] == 6) && legal[y][x]) {
+           char buf[MSG_SIZ];
+           snprintf(buf, MSG_SIZ, "hover %c%d\n", x + AAA, y + ONE - '0');
+           SendToProgram(buf, &first);
+         }
+         SetHighlights(fromX, fromY, x, y);
+       }
+}
+
 void ReportClick(char *action, int x, int y)
 {
        char buf[MSG_SIZ]; // Inform engine of what user does
        int r, f;
-       if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square
-         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1;
+       if(action[0] == 'l') // mark any target square of a lifted piece as legal to-square, clear markers
+         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) legal[r][f] = 1, marker[r][f] = 0;
        if(!first.highlight || gameMode == EditPosition) return;
        snprintf(buf, MSG_SIZ, "%s %c%d%s\n", action, x+AAA, y+ONE-'0', controlKey && action[0]=='p' ? "," : "");
        SendToProgram(buf, &first);
@@ -7131,6 +7167,7 @@ LeftClick (ClickType clickType, int xPix, int yPix)
     prevClickTime = lastClickTime; GetTimeMark(&lastClickTime);
 
     if (clickType == Press) ErrorPopDown();
+    lastClickType = clickType, lastLeftX = xPix, lastLeftY = yPix; // [HGM] alien: remember state
 
     x = EventToSquare(xPix, BOARD_WIDTH);
     y = EventToSquare(yPix, BOARD_HEIGHT);
@@ -7350,7 +7387,7 @@ 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(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
+       if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
          if(appData.sweepSelect) {
            ChessSquare piece = boards[currentMove][fromY][fromX];
            promoSweep = defaultPromoChoice;
@@ -8509,13 +8546,24 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
     }
 
     if (!strncmp(message, "setup ", 6) && 
-       (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize())
+       (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize())
                                        ) { // [HGM] allow first engine to define opening position
-      int dummy, s=6; char buf[MSG_SIZ];
+      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(startedFromSetupPosition) return;
-      if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition
+      dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName);
+      if(dummy >= 3) {
+        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
+           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
+          if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition
+        }
+      }
       ParseFEN(boards[0], &dummy, message+s);
       DrawPosition(TRUE, boards[0]);
       startedFromSetupPosition = TRUE;
@@ -8636,6 +8684,23 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
        MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares
        return;
     }
+    if(!strncmp(message, "click ", 6)) {
+       char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving)
+       if(appData.testLegality || !appData.oneClick) return;
+       sscanf(message+6, "%c%d%c", &f, &y, &c);
+       x = f - 'a' + BOARD_LEFT, y -= ONE - '0';
+       if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y;
+       x = x*squareSize + (x+1)*lineGap + squareSize/2;
+       y = y*squareSize + (y+1)*lineGap + squareSize/2;
+       f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks
+       if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click
+           LeftClick(Release, lastLeftX, lastLeftY);
+       controlKey  = (c == ',');
+       LeftClick(Press, x, y);
+       LeftClick(Release, x, y);
+       first.highlight = f;
+       return;
+    }
     /*
      * If the move is illegal, cancel it and redraw the board.
      * Also deal with other error cases.  Matching is rather loose
@@ -8976,7 +9041,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
            DisplayInformation(_("Machine accepts your draw offer"));
            GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
          } else {
-            DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
+            DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept."));
          }
        }
     }
@@ -9055,7 +9120,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.
                        if(f = fopen(buf, "w")) { // export PV to applicable PV file
                                fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv);
                                fclose(f);
-                       } else DisplayError(_("failed writing PV"), 0);
+                       }
+                       else
+                         /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */
+                         DisplayError(_("failed writing PV"), 0);
                }
 
                tempStats.depth = plylev;
@@ -9942,6 +10010,7 @@ NonStandardBoardSize ()
 {
       /* [HGM] Awkward testing. Should really be a table */
       int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+      if( gameInfo.variant == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix
       if( gameInfo.variant == VariantXiangqi )
            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
       if( gameInfo.variant == VariantShogi )
@@ -11178,6 +11247,7 @@ Reset (int redraw, int init)
     lastHint[0] = NULLCHAR;
     ClearGameInfo(&gameInfo);
     gameInfo.variant = StringToVariant(appData.variant);
+    if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown;
     ics_user_moved = ics_clock_paused = FALSE;
     ics_getting_history = H_FALSE;
     ics_gamenum = -1;
@@ -16018,6 +16088,27 @@ SendTimeRemaining (ChessProgramState *cps, int machineWhite)
     SendToProgram(message, cps);
 }
 
+char *
+EngineDefinedVariant (ChessProgramState *cps, int n)
+{   // return name of n-th unknown variant that engine supports
+    static char buf[MSG_SIZ];
+    char *p, *s = cps->variants;
+    if(!s) return NULL;
+    do { // parse string from variants feature
+      VariantClass v;
+       p = strchr(s, ',');
+       if(p) *p = NULLCHAR;
+      v = StringToVariant(s);
+      if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal
+       if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants
+           if(--n < 0) safeStrCpy(buf, s, MSG_SIZ);
+       }
+       if(p) *p++ = ',';
+       if(n < 0) return buf;
+    } while(s = p);
+    return NULL;
+}
+
 int
 BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps)
 {
@@ -17120,7 +17211,7 @@ PositionToFEN (int move, char *overrideCastling, int moveCounts)
                     *p++ = '+';
                     piece = (ChessSquare)(DEMOTED piece);
                 }
-                *p++ = PieceToChar(piece);
+                *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece));
                 if(p[-1] == '~') {
                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
@@ -17311,6 +17402,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen)
                 while (emptycount--)
                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
 #endif
+            } else if (*p == '*') {
+               board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++;
             } else if (isdigit(*p)) {
                emptycount = *p++ - '0';
                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */