Implement searching games in Game List for a position
[xboard.git] / backend.c
index a304ca5..804da7d 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -6231,7 +6231,6 @@ OKToStartUserMove(x, y)
       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6259,6 +6258,8 @@ OKToStartUserMove(x, y)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode
       case EditGame:
        if (!white_piece && WhiteOnMove(currentMove)) {
            DisplayMoveError(_("It is White's turn"));
@@ -6302,6 +6303,7 @@ OKToStartUserMove(x, y)
     }
     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
        && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
+       && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line
        && gameMode != AnalyzeFile && gameMode != Training) {
        DisplayMoveError(_("Displayed position is not current"));
        return FALSE;
@@ -6393,7 +6395,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        */
 
     switch (gameMode) {
-      case PlayFromGameFile:
       case AnalyzeFile:
       case TwoMachinesPlay:
       case EndOfGame:
@@ -6419,6 +6420,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar)
        }
        break;
 
+      case PlayFromGameFile:
+           if(!shiftKey ||!appData.variations) return; // [HGM] only variations
       case EditGame:
       case IcsExamining:
       case BeginningOfGame:
@@ -6613,7 +6616,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
 
   /* Ok, now we know that the move is good, so we can kill
      the previous line in Analysis Mode */
-  if ((gameMode == AnalyzeMode || gameMode == EditGame)
+  if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey)
                                && currentMove < forwardMostMove) {
     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
     else forwardMostMove = currentMove;
@@ -7371,6 +7374,28 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol
 }
 
 int
+CompareWithRights(Board b1, Board b2)
+{
+    int rights = 0;
+    if(!CompareBoards(b1, b2)) return FALSE;
+    if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE;
+    /* compare castling rights */
+    if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) )
+           rights++; /* King lost rights, while rook still had them */
+    if( b1[CASTLING][2] != NoRights ) { /* king has rights */
+        if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] )
+           rights++; /* but at least one rook lost them */
+    }
+    if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) )
+           rights++;
+    if( b1[CASTLING][5] != NoRights ) {
+        if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] )
+           rights++;
+    }
+    return rights == 0;
+}
+
+int
 Adjudicate(ChessProgramState *cps)
 {      // [HGM] some adjudications useful with buggy engines
        // [HGM] adjudicate: made into separate routine, which now can be called after every move
@@ -10462,6 +10487,7 @@ Reset(redraw, init)
                redraw, init, gameMode);
     }
     CleanupTail(); // [HGM] vari: delete any stored variations
+    CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on
     pausing = pauseExamInvalid = FALSE;
     startedFromSetupPosition = blackPlaysFirst = FALSE;
     firstMove = TRUE;
@@ -10569,8 +10595,8 @@ AutoPlayOneMove()
 
     if (currentMove >= forwardMostMove) {
       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
-      gameMode = EditGame;
-      ModeHighlight();
+//      gameMode = EndOfGame;
+//      ModeHighlight();
 
       /* [AS] Clear current move marker at the end of a game */
       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
@@ -11017,7 +11043,133 @@ ReloadGame(offset)
     }
 }
 
+int keys[EmptySquare+1];
+
+int
+PositionMatches(Board b1, Board b2)
+{
+    int r, f, sum=0;
+    switch(appData.searchMode) {
+       case 1: return CompareWithRights(b1, b2);
+       case 2:
+           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+               if(b2[r][f] != EmptySquare && b1[r][f] != b2[r][f]) return FALSE;
+           }
+           return TRUE;
+       case 3:
+           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+             if((b2[r][f] == WhitePawn || b2[r][f] == BlackPawn) && b1[r][f] != b2[r][f]) return FALSE;
+               sum += keys[b1[r][f]] - keys[b2[r][f]];
+           }
+           return sum==0;
+       case 4:
+           for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+               sum += keys[b1[r][f]] - keys[b2[r][f]];
+           }
+           return sum==0;
+    }
+    return TRUE;
+}
+
+GameInfo dummyInfo;
 
+int GameContainsPosition(FILE *f, ListGame *lg)
+{
+    int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1;
+    int fromX, fromY, toX, toY;
+    char promoChar;
+    static int initDone=FALSE;
+
+    if(!initDone) {
+       for(next = WhitePawn; next<EmptySquare; next++) keys[next] = rand()>>8 ^ rand()<<6 ^rand()<<20;
+       initDone = TRUE;
+    }
+    dummyInfo.variant = VariantNormal;
+    FREE(dummyInfo.fen); dummyInfo.fen = NULL;
+    dummyInfo.whiteRating = 0;
+    dummyInfo.blackRating = 0;
+    FREE(dummyInfo.date); dummyInfo.date = NULL;
+    fseek(f, lg->offset, 0);
+    yynewfile(f);
+    CopyBoard(boards[scratch], initialPosition); // default start position
+    while(1) {
+       yyboardindex = scratch + (plyNr&1);
+      quickFlag = 1;
+       next = Myylex();
+      quickFlag = 0;
+       switch(next) {
+           case PGNTag:
+               if(plyNr) return -1; // after we have seen moves, any tags will be start of next game
+               ParsePGNTag(yy_text, &dummyInfo);
+           if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
+           default:
+               continue;
+
+           case XBoardGame:
+           case GNUChessGame:
+               if(plyNr) return -1; // after we have seen moves, this is for new game
+             continue;
+
+           case AmbiguousMove: // we cannot reconstruct the game beyond these two
+           case ImpossibleMove:
+           case WhiteWins: // game ends here with these four
+           case BlackWins:
+           case GameIsDrawn:
+           case GameUnfinished:
+               return -1;
+
+           case IllegalMove:
+               if(appData.testLegality) return -1;
+           case WhiteCapturesEnPassant:
+           case BlackCapturesEnPassant:
+           case WhitePromotion:
+           case BlackPromotion:
+           case WhiteNonPromotion:
+           case BlackNonPromotion:
+           case NormalMove:
+           case WhiteKingSideCastle:
+           case WhiteQueenSideCastle:
+           case BlackKingSideCastle:
+           case BlackQueenSideCastle:
+           case WhiteKingSideCastleWild:
+           case WhiteQueenSideCastleWild:
+           case BlackKingSideCastleWild:
+           case BlackQueenSideCastleWild:
+           case WhiteHSideCastleFR:
+           case WhiteASideCastleFR:
+           case BlackHSideCastleFR:
+           case BlackASideCastleFR:
+               fromX = currentMoveString[0] - AAA;
+               fromY = currentMoveString[1] - ONE;
+               toX = currentMoveString[2] - AAA;
+               toY = currentMoveString[3] - ONE;
+               promoChar = currentMoveString[4];
+               break;
+           case WhiteDrop:
+           case BlackDrop:
+               fromX = next == WhiteDrop ?
+                 (int) CharToPiece(ToUpper(currentMoveString[0])) :
+                 (int) CharToPiece(ToLower(currentMoveString[0]));
+               fromY = DROP_RANK;
+               toX = currentMoveString[2] - AAA;
+               toY = currentMoveString[3] - ONE;
+               break;
+       }
+       // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move
+       if(plyNr == 0) { // but first figure out variant and initial position
+           if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant
+           if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1;
+           if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1;
+           if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1;
+           if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++;
+           if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr;
+       }
+       CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]);
+       plyNr++;
+       ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]);
+       if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr;
+    }
+}
 
 /* Load the nth game from open file f */
 int
@@ -11032,7 +11184,7 @@ LoadGame(f, gameNumber, title, useList)
     int gn = gameNumber;
     ListGame *lg = NULL;
     int numPGNTags = 0;
-    int err;
+    int err, pos = -1;
     GameMode oldGameMode;
     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
 
@@ -11058,6 +11210,7 @@ LoadGame(f, gameNumber, title, useList)
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
+           pos = lg->position;
            gn = 1;
        }
        else {
@@ -11457,10 +11610,11 @@ LoadGame(f, gameNumber, title, useList)
       AnalyzeFileEvent();
     }
 
+    if (!matchMode && pos >= 0) {
+       ToNrEvent(pos); // [HGM] no autoplay if selected on position
+    } else
     if (matchMode || appData.timeDelay == 0) {
       ToEndEvent();
-      gameMode = EditGame;
-      ModeHighlight();
     } else if (appData.timeDelay > 0) {
       AutoPlayGameLoop();
     }
@@ -16391,6 +16545,7 @@ int wrap(char *dest, char *src, int count, int width, int *lp)
 }
 
 // [HGM] vari: routines for shelving variations
+Boolean modeRestore = FALSE;
 
 void
 PushInner(int firstMove, int lastMove)
@@ -16434,6 +16589,7 @@ PushTail(int firstMove, int lastMove)
 
        PushInner(firstMove, lastMove);
        if(storedGames == 1) GreyRevert(FALSE);
+       if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
 }
 
 void
@@ -16442,8 +16598,8 @@ PopInner(Boolean annotate)
        int i, j, nrMoves;
        char buf[8000], moveBuf[20];
 
-       storedGames--;
-       ToNrEvent(savedFirst[storedGames]); // sets currentMove
+       ToNrEvent(savedFirst[storedGames-1]); // sets currentMove
+       storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns
        nrMoves = savedLast[storedGames] - currentMove;
        if(annotate) {
                int cnt = 10;
@@ -16491,8 +16647,10 @@ PopTail(Boolean annotate)
        CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
 
        PopInner(annotate);
+       if(currentMove < forwardMostMove) ForwardEvent(); else
+       HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
 
-       if(storedGames == 0) GreyRevert(TRUE);
+       if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; }
        return TRUE;
 }
 
@@ -16519,7 +16677,7 @@ LoadVariation(int index, char *text)
        char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
        int level = 0, move;
 
-       if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+       if(gameMode != EditGame && gameMode != AnalyzeMode && gameMode != PlayFromGameFile) return;
        // first find outermost bracketing variation
        while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
            if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}