Add secondry adapter command for UCCI or USI
[xboard.git] / backend.c
index f234beb..e56bbb1 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -232,6 +232,7 @@ int EngineOutputIsUp();
 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));
 
 #ifdef WIN32
        extern void ConsoleCreate();
@@ -248,6 +249,7 @@ char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [H
 void ics_update_width P((int new_width));
 extern char installDir[MSG_SIZ];
 VariantClass startVariant; /* [HGM] nicks: initial variant */
+Boolean abortMatch;
 
 extern int tinyLayout, smallLayout;
 ChessProgramStats programStats;
@@ -505,6 +507,8 @@ ChessMove savedResult[MAX_VARIATIONS];
 
 void PushTail P((int firstMove, int lastMove));
 Boolean PopTail P((Boolean annotate));
+void PushInner P((int firstMove, int lastMove));
+void PopInner P((Boolean annotate));
 void CleanupTail P((void));
 
 ChessSquare  FIDEArray[2][BOARD_FILES] = {
@@ -792,7 +796,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];
@@ -857,16 +865,21 @@ ReplaceEngine(ChessProgramState *cps, int n)
     LoadEngine();
 }
 
-extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName;
-extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
+extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
+extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
+
+static char resetOptions[] = 
+       "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
+       "-firstOptions \"\" -firstNPS -1 -fn \"\"";
 
-void Load(ChessProgramState *cps, int i)
+void
+Load(ChessProgramState *cps, int i)
 {
-    char *p, *q, buf[MSG_SIZ];
+    char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
     if(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*
-       ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
        ParseArgsFromString(buf);
        SwapEngines(i);
        ReplaceEngine(cps, i);
@@ -874,29 +887,37 @@ void Load(ChessProgramState *cps, int i)
     }
     p = engineName;
     while(q = strchr(p, SLASH)) p = q+1;
-    if(*p== NULLCHAR) return;
-    appData.chessProgram[i] = strdup(p);
+    if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
     if(engineDir[0] != NULLCHAR)
        appData.directory[i] = engineDir;
     else if(p != engineName) { // derive directory from engine path, when not given
        p[-1] = 0;
        appData.directory[i] = strdup(engineName);
-       p[-1] = '/';
+       p[-1] = SLASH;
     } else appData.directory[i] = ".";
+    if(params[0]) {
+       snprintf(command, MSG_SIZ, "%s %s", p, params);
+       p = command;
+    }
+    appData.chessProgram[i] = strdup(p);
     appData.isUCI[i] = isUCI;
     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
     appData.hasOwnBookUCI[i] = hasBook;
+    if(!nickName[0]) useNick = FALSE;
+    if(useNick) ASSIGN(appData.pgnName[i], nickName);
     if(addToList) {
        int len;
        q = firstChessProgramNames;
        if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
-       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i], 
+       snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i], 
+                       useNick ? " -fn \"" : "",
+                       useNick ? nickName : "",
+                       useNick ? "\"" : "",
                        v1 ? " -firstProtocolVersion 1" : "",
                        hasBook ? "" : " -fNoOwnBookUCI",
-                       isUCI ? " -fUCI" : "",
+                       isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
                        storeVariant ? " -variant " : "",
                        storeVariant ? VariantName(gameInfo.variant) : "");
-fprintf(debugFP, "new line: %s", buf);
        firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
        snprintf(firstChessProgramNames, len, "%s%s", q, buf);
        if(q)   free(q);
@@ -905,9 +926,39 @@ fprintf(debugFP, "new line: %s", buf);
 }
 
 void
-InitBackEnd1()
+InitTimeControls()
 {
     int matched, min, sec;
+    /*
+     * Parse timeControl resource
+     */
+    if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
+                         appData.movesPerSession)) {
+       char buf[MSG_SIZ];
+       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
+       DisplayFatalError(buf, 0, 2);
+    }
+
+    /*
+     * Parse searchTime resource
+     */
+    if (*appData.searchTime != NULLCHAR) {
+       matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
+       if (matched == 1) {
+           searchTime = min * 60;
+       } else if (matched == 2) {
+           searchTime = min * 60 + sec;
+       } else {
+           char buf[MSG_SIZ];
+           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
+           DisplayFatalError(buf, 0, 2);
+       }
+    }
+}
+
+void
+InitBackEnd1()
+{
 
     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
@@ -958,31 +1009,7 @@ InitBackEnd1()
         }
     }
 
-    /*
-     * Parse timeControl resource
-     */
-    if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
-                         appData.movesPerSession)) {
-       char buf[MSG_SIZ];
-       snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
-       DisplayFatalError(buf, 0, 2);
-    }
-
-    /*
-     * Parse searchTime resource
-     */
-    if (*appData.searchTime != NULLCHAR) {
-       matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
-       if (matched == 1) {
-           searchTime = min * 60;
-       } else if (matched == 2) {
-           searchTime = min * 60 + sec;
-       } else {
-           char buf[MSG_SIZ];
-           snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
-           DisplayFatalError(buf, 0, 2);
-       }
-    }
+    InitTimeControls();
 
     /* [AS] Adjudication threshold */
     adjudicateLossThreshold = appData.adjudicateLossThreshold;
@@ -1364,6 +1391,18 @@ void
 MatchEvent(int mode)
 {      // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
        int dummy;
+       if(matchMode) { // already in match mode: switch it off
+           abortMatch = TRUE;
+           appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
+           ModeHighlight(); // kludgey way to remove checkmark...
+           return;
+       }
+//     if(gameMode != BeginningOfGame) {
+//         DisplayError(_("You can only start a match from the initial position."), 0);
+//         return;
+//     }
+       abortMatch = FALSE;
+       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
@@ -5206,15 +5245,20 @@ 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;
   Boolean valid;
   int nr = 0;
 
+  if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+    PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
+    pushed = TRUE;
+  }
   endPV = forwardMostMove;
   do {
     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
@@ -5253,45 +5297,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;
 }
@@ -5301,16 +5356,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 continuation
   ClearPremoveHighlights();
   DrawPosition(TRUE, boards[currentMove]);
 }
@@ -7036,6 +7107,50 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp
     SetProgramStats( &stats );
 }
 
+#define MAXPLAYERS 500
+
+char *
+TourneyStandings(int display)
+{
+    int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
+    int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
+    char result, *p, *names[MAXPLAYERS];
+
+    names[0] = p = strdup(appData.participants);
+    while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
+
+    for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
+
+    while(result = appData.results[nr]) {
+       color = Pairing(nr, nPlayers, &w, &b, &dummy);
+       if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
+       wScore = bScore = 0;
+       switch(result) {
+         case '+': wScore = 2; break;
+         case '-': bScore = 2; break;
+         case '=': wScore = bScore = 1; break;
+         case ' ':
+         case '*': return strdup("busy"); // tourney not finished
+       }
+       score[w] += wScore;
+       score[b] += bScore;
+       games[w]++;
+       games[b]++;
+       nr++;
+    }
+    if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
+    for(w=0; w<nPlayers; w++) {
+       bScore = -1;
+       for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
+       ranking[w] = b; points[w] = bScore; score[b] = -2;
+    }
+    p = malloc(nPlayers*34+1);
+    for(w=0; w<nPlayers && w<display; w++)
+       sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
+    free(names[0]);
+    return p;
+}
+
 void
 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
 {      // count all piece types
@@ -7459,8 +7574,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
@@ -9165,6 +9295,7 @@ ShowMove(fromX, fromY, toX, toY)
     DrawPosition(FALSE, boards[currentMove]);
     DisplayBothClocks();
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+    DisplayBook(currentMove);
 }
 
 void SendEgtPath(ChessProgramState *cps)
@@ -9406,6 +9537,13 @@ CreateTourney(char *name)
                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");
            }
        }
@@ -9461,6 +9599,7 @@ void SwapEngines(int n)
     SWAP(scoreIsAbsolute, h)
     SWAP(timeOdds, h)
     SWAP(logo, p)
+    SWAP(pgnName, p)
 }
 
 void
@@ -9468,14 +9607,12 @@ SetPlayer(int player)
 {   // [HGM] find the engine line of the partcipant given by number, and parse its options.
     int i;
     char buf[MSG_SIZ], *engineName, *p = appData.participants;
-    static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
-                                "-firstNeedsNoncompliantFEN false -firstNPS -1";
     for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
     engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
     for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
     if(mnemonic[i]) {
        snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
-       ParseArgsFromString(resetOptions);
+       ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
        ParseArgsFromString(buf);
     }
     free(engineName);
@@ -9537,6 +9674,7 @@ NextTourneyGame(int nr, int *swapColors)
     tf = fopen(appData.tourneyFile, "r");
     if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
     ParseArgsFromFile(tf); fclose(tf);
+    InitTimeControls(); // TC might be altered from tourney file
 
     p = appData.participants;
     while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
@@ -9631,7 +9769,7 @@ GameEnds(result, resultDetails, whosays)
 {
     GameMode nextGameMode;
     int isIcsGame;
-    char buf[MSG_SIZ], popupRequested = 0, forceUnload;
+    char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
 
     if(endingGame) return; /* [HGM] crash: forbid recursion */
     endingGame = 1;
@@ -9959,12 +10097,12 @@ GameEnds(result, resultDetails, whosays)
        }
 
        if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
-       if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
+       if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
            ReserveGame(nextGame, resChar); // sets nextGame
-           if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0; // tourney is done
+           if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
        } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
 
-       if (nextGame <= appData.matchGames) {
+       if (nextGame <= appData.matchGames && !abortMatch) {
            gameMode = nextGameMode;
            matchGame = nextGame; // this will be overruled in tourney mode!
            GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
@@ -9994,10 +10132,18 @@ GameEnds(result, resultDetails, whosays)
     ModeHighlight();
     endingGame = 0;  /* [HGM] crash */
     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
-      if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
-       matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
-       DisplayNote(buf);
+       if(matchMode == TRUE) { // match through command line: exit with or without popup
+           if(ranking) {
+               if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
+               else ExitEvent(0);
+           } else DisplayFatalError(buf, 0, 0);
+       } else { // match through menu; just stop, with or without popup
+           matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
+           if(ranking){
+               if(strcmp(ranking, "busy")) DisplayNote(ranking);
+           } else DisplayNote(buf);
       }
+      if(ranking) free(ranking);
     }
 }
 
@@ -12211,6 +12357,7 @@ void
 EditTagsEvent()
 {
     char *tags = PGNTags(&gameInfo);
+    bookUp = FALSE;
     EditTagsPopUp(tags, NULL);
     free(tags);
 }
@@ -13391,6 +13538,7 @@ ForwardInner(target)
     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
        DisplayComment(currentMove - 1, commentList[currentMove]);
     }
+    DisplayBook(currentMove);
 }
 
 
@@ -13493,6 +13641,7 @@ BackwardInner(target)
     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
     // [HGM] PV info: routine tests if comment empty
     DisplayComment(currentMove - 1, commentList[currentMove]);
+    DisplayBook(currentMove);
 }
 
 void
@@ -13858,11 +14007,11 @@ SetGameInfo()
            gameInfo.round = StrSave("-");
        }
        if (first.twoMachinesColor[0] == 'w') {
-           gameInfo.white = StrSave(first.tidy);
-           gameInfo.black = StrSave(second.tidy);
+           gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
+           gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
        } else {
-           gameInfo.white = StrSave(second.tidy);
-           gameInfo.black = StrSave(first.tidy);
+           gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
+           gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
        }
        gameInfo.timeControl = TimeControlTagValue();
        break;
@@ -15999,16 +16148,10 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 // [HGM] vari: routines for shelving variations
 
 void
-PushTail(int firstMove, int lastMove)
+PushInner(int firstMove, int lastMove)
 {
        int i, j, nrMoves = lastMove - firstMove;
 
-       if(appData.icsActive) { // only in local mode
-               forwardMostMove = currentMove; // mimic old ICS behavior
-               return;
-       }
-       if(storedGames >= MAX_VARIATIONS-1) return;
-
        // push current tail of game on stack
        savedResult[storedGames] = gameInfo.result;
        savedDetails[storedGames] = gameInfo.resultDetails;
@@ -16033,19 +16176,27 @@ PushTail(int firstMove, int lastMove)
 
        storedGames++;
        forwardMostMove = firstMove; // truncate game so we can start variation
+}
+
+void
+PushTail(int firstMove, int lastMove)
+{
+       if(appData.icsActive) { // only in local mode
+               forwardMostMove = currentMove; // mimic old ICS behavior
+               return;
+       }
+       if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
+
+       PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
 }
 
-Boolean
-PopTail(Boolean annotate)
+void
+PopInner(Boolean annotate)
 {
        int i, j, nrMoves;
        char buf[8000], moveBuf[20];
 
-       if(appData.icsActive) return FALSE; // only in local mode
-       if(!storedGames) return FALSE; // sanity
-       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
-
        storedGames--;
        ToNrEvent(savedFirst[storedGames]); // sets currentMove
        nrMoves = savedLast[storedGames] - currentMove;
@@ -16085,6 +16236,17 @@ PopTail(Boolean annotate)
       }
        gameInfo.resultDetails = savedDetails[storedGames];
        forwardMostMove = currentMove + nrMoves;
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+       if(appData.icsActive) return FALSE; // only in local mode
+       if(!storedGames) return FALSE; // sanity
+       CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
+
+       PopInner(annotate);
+
        if(storedGames == 0) GreyRevert(TRUE);
        return TRUE;
 }
@@ -16131,7 +16293,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();