Let XBoard propose name of tourney file
[xboard.git] / backend.c
index 8dcfbd6..c73d6e7 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -233,6 +233,7 @@ void InitDrawingSizes(int x, int y);
 void NextMatchGame P((void));
 int NextTourneyGame P((int nr, int *swap));
 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
+FILE *WriteTourneyFile P((char *results));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -417,7 +418,7 @@ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
 char thinkOutput1[MSG_SIZ*10];
 
-ChessProgramState first, second;
+ChessProgramState first, second, pairing;
 
 /* premove variables */
 int premoveToX = 0;
@@ -796,7 +797,11 @@ InitEngine(ChessProgramState *cps, int n)
 
     /* [HGM] debug */
     cps->debug = FALSE;
+
     cps->supportsNPS = UNKNOWN;
+    cps->memSize = FALSE;
+    cps->maxCores = FALSE;
+    cps->egtFormats[0] = NULLCHAR;
 
     /* [HGM] options */
     cps->optionSettings  = appData.engOptions[n];
@@ -911,7 +916,7 @@ Load(ChessProgramState *cps, int i)
                        useNick ? "\"" : "",
                        v1 ? " -firstProtocolVersion 1" : "",
                        hasBook ? "" : " -fNoOwnBookUCI",
-                       isUCI ? " -fUCI" : "",
+                       isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
                        storeVariant ? " -variant " : "",
                        storeVariant ? VariantName(gameInfo.variant) : "");
        firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
@@ -1014,6 +1019,13 @@ InitBackEnd1()
     InitEngine(&second, 1);
     CommonEngineInit();
 
+    pairing.which = "pairing"; // pairing engine
+    pairing.pr = NoProc;
+    pairing.isr = NULL;
+    pairing.program = appData.pairingEngine;
+    pairing.host = "localhost";
+    pairing.dir = ".";
+
     if (appData.icsActive) {
         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
@@ -1398,14 +1410,29 @@ MatchEvent(int mode)
 //         return;
 //     }
        abortMatch = FALSE;
-       appData.matchGames = appData.defaultMatchGames;
+       if(mode == 2) appData.matchGames = appData.defaultMatchGames;
        /* Set up machine vs. machine match */
        nextGame = 0;
-       NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
+       NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
        if(appData.tourneyFile[0]) {
            ReserveGame(-1, 0);
            if(nextGame > appData.matchGames) {
                char buf[MSG_SIZ];
+               if(strchr(appData.results, '*') == NULL) {
+                   FILE *f;
+                   appData.tourneyCycles++;
+                   if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+                       fclose(f);
+                       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"));
+                           matchMode = mode;
+                           ScheduleDelayedEvent(NextMatchGame, 10000);
+                           return;
+                       }
+                   }
+               }
                snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
                DisplayError(buf, 0);
                appData.tourneyFile[0] = 0;
@@ -5244,7 +5271,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
 Boolean pushed = FALSE;
 
 void
-ParsePV(char *pv, Boolean storeComments)
+ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
   int fromX, fromY, toX, toY; char promoChar;
   ChessMove moveType;
@@ -5293,45 +5320,56 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f
     endPV++;
     CopyBoard(boards[endPV], boards[endPV-1]);
     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
-    moveList[endPV-1][0] = fromX + AAA;
-    moveList[endPV-1][1] = fromY + ONE;
-    moveList[endPV-1][2] = toX + AAA;
-    moveList[endPV-1][3] = toY + ONE;
-    moveList[endPV-1][4] = promoChar;
-    moveList[endPV-1][5] = NULLCHAR;
+    CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
     strncat(moveList[endPV-1], "\n", MOVE_LEN);
-    if(storeComments)
-       CoordsToAlgebraic(boards[endPV - 1],
+    CoordsToAlgebraic(boards[endPV - 1],
                             PosFlags(endPV - 1),
                             fromY, fromX, toY, toX, promoChar,
                             parseList[endPV - 1]);
-    else
-       parseList[endPV-1][0] = NULLCHAR;
   } while(valid);
-  currentMove = endPV;
+  currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
   DrawPosition(TRUE, boards[currentMove]);
 }
 
+int
+MultiPV(ChessProgramState *cps)
+{      // check if engine supports MultiPV, and if so, return the number of the option that sets it
+       int i;
+       for(i=0; i<cps->nrOptions; i++)
+           if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
+               return i;
+       return -1;
+}
+
 Boolean
 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
 {
-       int startPV;
-       char *p;
+       int startPV, multi, lineStart, origIndex = index;
+       char *p, buf2[MSG_SIZ];
 
        if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
        lastX = x; lastY = y;
        while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
-       startPV = index;
+       lineStart = startPV = index;
        while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
        if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
        index = startPV;
        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;
-       ParsePV(buf+startPV, FALSE);
+       if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
+               int n = first.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;
+               *start = *end = 0;
+               return FALSE;
+       }
+       ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
        *start = startPV; *end = index-1;
        return TRUE;
 }
@@ -5341,17 +5379,32 @@ LoadPV(int x, int y)
 { // called on right mouse click to load PV
   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
   lastX = x; lastY = y;
-  ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
+  ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
   return TRUE;
 }
 
 void
 UnLoadPV()
 {
+  int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
   if(endPV < 0) return;
   endPV = -1;
+  if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
+       Boolean saveAnimate = appData.animate;
+       if(pushed) {
+           if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
+               if(storedGames == 1) GreyRevert(FALSE);      // we already pushed the tail, so just make it official
+           } else storedGames--; // abandon shelved tail of original game
+       }
+       pushed = FALSE;
+       forwardMostMove = currentMove;
+       currentMove = oldFMM;
+       appData.animate = FALSE;
+       ToNrEvent(forwardMostMove);
+       appData.animate = saveAnimate;
+  }
   currentMove = forwardMostMove;
-  if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
+  if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
   ClearPremoveHighlights();
   DrawPosition(TRUE, boards[currentMove]);
 }
@@ -7086,6 +7139,8 @@ TourneyStandings(int display)
     int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
     char result, *p, *names[MAXPLAYERS];
 
+    if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
+
     names[0] = p = strdup(appData.participants);
     while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
 
@@ -7544,8 +7599,23 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
     if(bookHit) {
        // after a book hit we never send 'go', and the code after the call to this routine
        // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
-       char buf[MSG_SIZ];
-       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
+       char buf[MSG_SIZ], *move = bookHit;
+       if(cps->useSAN) {
+           int fromX, fromY, toX, toY;
+           char promoChar;
+           ChessMove moveType;
+           move = buf + 30;
+           if (ParseOneMove(bookHit, forwardMostMove, &moveType,
+                                &fromX, &fromY, &toX, &toY, &promoChar)) {
+               (void) CoordsToAlgebraic(boards[forwardMostMove],
+                                   PosFlags(forwardMostMove),
+                                   fromY, fromX, toY, toX, promoChar, move);
+           } else {
+               if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
+               bookHit = NULL;
+           }
+       }
+       snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
        SendToProgram(buf, cps);
        if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
@@ -7569,6 +7639,8 @@ void DeferredBookMove(void)
        HandleMachineMove(savedMessage, savedState);
 }
 
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
 void
 HandleMachineMove(message, cps)
      char *message;
@@ -7583,6 +7655,14 @@ HandleMachineMove(message, cps)
     int machineWhite;
     char *bookHit;
 
+    if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
+       // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
+       if(savedWhitePlayer == 0 || savedBlackPlayer == 0) return;
+       pairingReceived = 1;
+       NextMatchGame();
+       return; // Skim the pairing messages here.
+    }
+
     cps->userError = 0;
 
 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
@@ -9432,9 +9512,14 @@ StartChessProgram(cps)
 
     if (err != 0) {
       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
-       DisplayFatalError(buf, err, 1);
-       cps->pr = NoProc;
-       cps->isr = NULL;
+       DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
+       if(cps != &first) return;
+       appData.noChessProgram = TRUE;
+       ThawUI();
+       SetNCPMode();
+//     DisplayFatalError(buf, err, 1);
+//     cps->pr = NoProc;
+//     cps->isr = NULL;
        return;
     }
 
@@ -9468,39 +9553,94 @@ TwoMachinesEventIfReady P((void))
   TwoMachinesEvent();
 }
 
+char *
+MakeName(char *template)
+{
+    time_t clock;
+    struct tm *tm;
+    static char buf[MSG_SIZ];
+    char *p = buf;
+    int i;
+
+    clock = time((time_t *)NULL);
+    tm = localtime(&clock);
+
+    while(*p++ = *template++) if(p[-1] == '%') {
+       switch(*template++) {
+         case 0:   *p = 0; return buf;
+         case 'Y': i = tm->tm_year+1900; break;
+         case 'y': i = tm->tm_year-100; break;
+         case 'M': i = tm->tm_mon+1; break;
+         case 'd': i = tm->tm_mday; break;
+         case 'h': i = tm->tm_hour; break;
+         case 'm': i = tm->tm_min; break;
+         case 's': i = tm->tm_sec; break;
+         default:  i = 0;
+       }
+       snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
+    }
+    return buf;
+}
+
+int
+CountPlayers(char *p)
+{
+    int n = 0;
+    while(p = strchr(p, '\n')) p++, n++; // count participants
+    return n;
+}
+
+FILE *
+WriteTourneyFile(char *results)
+{   // write tournament parameters on tourneyFile; on success return the stream pointer for closing
+    FILE *f = fopen(appData.tourneyFile, "w");
+    if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
+       // create a file with tournament description
+       fprintf(f, "-participants {%s}\n", appData.participants);
+       fprintf(f, "-tourneyType %d\n", appData.tourneyType);
+       fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
+       fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
+       fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
+       fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
+       fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
+       fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
+       fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
+       fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
+       fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
+       fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
+       if(searchTime > 0)
+               fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
+       else {
+               fprintf(f, "-mps %d\n", appData.movesPerSession);
+               fprintf(f, "-tc %s\n", appData.timeControl);
+               fprintf(f, "-inc %.2f\n", appData.timeIncrement);
+       }
+       fprintf(f, "-results \"%s\"\n", results);
+    }
+    return f;
+}
+
 int
 CreateTourney(char *name)
 {
        FILE *f;
-       if(name[0] == NULLCHAR) return 0;
-       f = fopen(appData.tourneyFile, "r");
+       if(name[0] == NULLCHAR) {
+           if(appData.participants[0])
+               DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
+           return 0;
+       }
+       f = fopen(name, "r");
        if(f) { // file exists
+           ASSIGN(appData.tourneyFile, name);
            ParseArgsFromFile(f); // parse it
        } else {
-           f = fopen(appData.tourneyFile, "w");
-           if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
-               // create a file with tournament description
-               fprintf(f, "-participants {%s}\n", appData.participants);
-               fprintf(f, "-tourneyType %d\n", appData.tourneyType);
-               fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
-               fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
-               fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
-               fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
-               fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
-               fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
-               fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
-               fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
-               fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
-               fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
-               if(searchTime > 0)
-                       fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
-               else {
-                       fprintf(f, "-mps %d\n", appData.movesPerSession);
-                       fprintf(f, "-tc %s\n", appData.timeControl);
-                       fprintf(f, "-inc %.2f\n", appData.timeIncrement);
-               }
-               fprintf(f, "-results \"\"\n");
+           if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
+           if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
+               DisplayError(_("Not enough participants"), 0);
+               return 0;
            }
+           ASSIGN(appData.tourneyFile, name);
+           if((f = WriteTourneyFile("")) == NULL) return 0;
        }
        fclose(f);
        appData.noChessProgram = FALSE;
@@ -9576,7 +9716,7 @@ SetPlayer(int player)
 int
 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
 {   // determine players from game number
-    int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
+    int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
 
     if(appData.tourneyType == 0) {
        roundsPerCycle = (nPlayers - 1) | 1;
@@ -9623,7 +9763,7 @@ int
 NextTourneyGame(int nr, int *swapColors)
 {   // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
     char *p, *q;
-    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
+    int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
     FILE *tf;
     if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
     tf = fopen(appData.tourneyFile, "r");
@@ -9631,9 +9771,24 @@ NextTourneyGame(int nr, int *swapColors)
     ParseArgsFromFile(tf); fclose(tf);
     InitTimeControls(); // TC might be altered from tourney file
 
-    p = appData.participants;
-    while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
-    *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
+    nPlayers = CountPlayers(appData.participants); // count participants
+    if(appData.tourneyType < 0 && appData.pairingEngine[0]) {
+       if(nr>=0 && !pairingReceived) {
+           char buf[1<<16];
+           if(pairing.pr == NoProc) StartChessProgram(&pairing);
+           snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
+           SendToProgram(buf, &pairing);
+           snprintf(buf, 1<<16, "pairing %d\n", nr+1);
+           SendToProgram(buf, &pairing);
+           return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
+       }
+       pairingReceived = 0;                              // ... so we continue here 
+       syncInterval = nPlayers/2; *swapColors = 0;
+       appData.matchGames = appData.tourneyCycles * syncInterval - 1;
+       whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
+       matchGame = 1; roundNr = nr / syncInterval + 1;
+    } else
+    *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
 
     if(syncInterval) {
        p = q = appData.results;
@@ -12222,6 +12377,9 @@ ExitEvent(status)
        RemoveInputSource(second.isr);
     }
 
+    if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+    if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
     ShutDownFrontEnd();
     exit(status);
 }
@@ -14283,6 +14441,7 @@ ReceiveFromProgram(isr, closure, message, count, error)
     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 */
@@ -16248,7 +16407,7 @@ LoadVariation(int index, char *text)
        PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
        // kludge: use ParsePV() to append variation to game
        move = currentMove;
-       ParsePV(start, TRUE);
+       ParsePV(start, TRUE, TRUE);
        forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
        ClearPremoveHighlights();
        CommentPopDown();