Implement searching games in Game List for a position
authorH.G. Muller <h.g.muller@hccnet.nl>
Tue, 2 Aug 2011 20:58:34 +0000 (22:58 +0200)
committerH.G. Muller <h.g.muller@hccnet.nl>
Sat, 6 Aug 2011 16:40:52 +0000 (18:40 +0200)
For this it was needed to reconstruct all positions of the games
without loading them, so they could be compared to the currently
displayed position. A new routine GameContainsPosition is made for that.
 In Disambiguate, when the global quickFlag is set, we now do the
disambiguation ignoring check,and only if this leads to an ambiguous
move, we repeat it taking check into account to see if that resolves
the ambiguity. This causes us to miss if unambiguous moves are illegal
because they put their King in check, but who cares?
 We now do not make game-list line when not needed. If we do not filter
by text, but by position, there is no need for preparing the game-list
line for games that are not selected. All for efficiency.

12 files changed:
args.h
backend.c
backend.h
common.h
moves.c
moves.h
winboard/resource.h
winboard/wgamelist.c
winboard/winboard.rc
winboard/woptions.c
xgamelist.c
xoptions.c

diff --git a/args.h b/args.h
index 3a6449f..858ee44 100644 (file)
--- a/args.h
+++ b/args.h
@@ -630,6 +630,10 @@ ArgDescriptor argDescriptors[] = {
   { "sSAN", ArgTrue, (void *) &appData.pvSAN[1], FALSE, FALSE },
   { "pairingEngine", ArgFilename, (void *) &appData.pairingEngine, TRUE, "" },
   { "defaultTourneyName", ArgFilename, (void *) &appData.defName, TRUE, "" },
+  { "eloThresholdAny", ArgInt, (void *) &appData.eloThreshold1, FALSE, (ArgIniType) 0 },
+  { "eloThresholdBoth", ArgInt, (void *) &appData.eloThreshold2, FALSE, (ArgIniType) 0 },
+  { "dateThreshold", ArgInt, (void *) &appData.dateThreshold, FALSE, (ArgIniType) 0 },
+  { "searchMode", ArgInt, (void *) &appData.searchMode, FALSE, (ArgIniType) 1 },
 
 #if ZIPPY
   { "zippyTalk", ArgBoolean, (void *) &appData.zippyTalk, FALSE, (ArgIniType) ZIPPY_TALK },
index a3ce9ed..804da7d 100644 (file)
--- a/backend.c
+++ b/backend.c
@@ -7374,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
@@ -11021,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
@@ -11036,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 */
 
@@ -11062,6 +11210,7 @@ LoadGame(f, gameNumber, title, useList)
        if (lg) {
            fseek(f, lg->offset, 0);
            GameListHighlight(gameNumber);
+           pos = lg->position;
            gn = 1;
        }
        else {
@@ -11461,6 +11610,9 @@ 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();
     } else if (appData.timeDelay > 0) {
index 56c9d3e..4055ba4 100644 (file)
--- a/backend.h
+++ b/backend.h
@@ -279,6 +279,7 @@ char *PGNResult P((ChessMove result));
 typedef struct _ListGame {
     ListNode node;
     int number;
+    int position;
     unsigned long offset;   /*  Byte offset of game within file.     */
     GameInfo gameInfo;      /*  Note that some entries may be NULL. */
 } ListGame;
index 6b3266f..08d45e5 100644 (file)
--- a/common.h
+++ b/common.h
@@ -627,6 +627,10 @@ typedef struct {
     int NPS[ENGINES];
     Boolean autoKibitz;
     int engineComments;
+    int eloThreshold1;  /* [HGM] select   */
+    int eloThreshold2;
+    int dateThreshold;
+    int searchMode;
     char *userName;
     int rewindIndex;    /* [HGM] autoinc   */
     int sameColorGames; /* [HGM] alternate */
diff --git a/moves.c b/moves.c
index 2a8e187..194047d 100644 (file)
--- a/moves.c
+++ b/moves.c
@@ -70,7 +70,7 @@ int SameColor P((ChessSquare, ChessSquare));
 int PosFlags(int index);
 
 extern signed char initialRights[BOARD_FILES]; /* [HGM] all rights enabled, set in InitPosition */
-
+int quickFlag;
 
 int WhitePiece(piece)
      ChessSquare piece;
@@ -1345,6 +1345,15 @@ void Disambiguate(board, flags, closure)
     }
     rFilter = closure->rtIn; // [HGM] speed: only consider moves to given to-square
     fFilter = closure->ftIn;
+    if(quickFlag) { // [HGM] speed: try without check test first, because if that is not ambiguous, we are happy
+        GenLegal(board, flags|F_IGNORE_CHECK, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn);
+        if(closure->count > 1) { // gamble did not pay off. retry with check test to resolve ambiguity
+            closure->count = closure->captures = 0;
+            closure->rf = closure->ff = closure->rt = closure->ft = 0;
+            closure->kind = ImpossibleMove;
+            GenLegal(board, flags, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn); // [HGM] speed: only pieces of requested type
+        }
+    } else
     GenLegal(board, flags, DisambiguateCallback, (VOIDSTAR) closure, closure->pieceIn); // [HGM] speed: only pieces of requested type
     if (closure->count == 0) {
        /* See if it's an illegal move due to check */
diff --git a/moves.h b/moves.h
index 105906b..d8f3953 100644 (file)
--- a/moves.h
+++ b/moves.h
@@ -171,3 +171,5 @@ void Disambiguate P((Board board, int flags, DisambiguateClosure *closure));
 ChessMove CoordsToAlgebraic P((Board board, int flags,
                               int rf, int ff, int rt, int ft,
                               int promoChar, char out[MOVE_LEN]));
+
+extern int quickFlag;
index 91b0b3a..f124467 100644 (file)
 #define OPT_MessageFont7                1955\r
 #define OPT_SampleGameListFont          1956\r
 #define OPT_ChooseGameListFont          1957\r
+#define OPT_elo1                        1958\r
+#define OPT_elo2                        1959\r
+#define OPT_date                        1960\r
+#define OPT_elo1t                       1961\r
+#define OPT_elo2t                       1962\r
+#define OPT_datet                       1963\r
+#define OPT_thresholds                  1964\r
+#define OPT_Exact                       1965\r
+#define OPT_Subset                      1966\r
+#define OPT_Struct                      1967\r
+#define OPT_Material                    1968\r
 \r
 \r
 // Next default values for new objects\r
index 863725a..c5a25f5 100644 (file)
@@ -57,10 +57,11 @@ struct GameListStats
 };\r
 \r
 /* [AS] Setup the game list according to the specified filter */\r
-static int GameListToListBox( HWND hDlg, BOOL boReset, char * pszFilter, struct GameListStats * stats )\r
+static int GameListToListBox( HWND hDlg, BOOL boReset, char * pszFilter, struct GameListStats * stats, BOOL byPos )\r
 {\r
     ListGame * lg = (ListGame *) gameList.head;\r
     int nItem;\r
+    char buf[MSG_SIZ];\r
     BOOL hasFilter = FALSE;\r
     int count = 0;\r
     struct GameListStats dummy;\r
@@ -86,16 +87,23 @@ static int GameListToListBox( HWND hDlg, BOOL boReset, char * pszFilter, struct
     }\r
 \r
     for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){\r
-        char * st = GameListLine(lg->number, &lg->gameInfo);\r
+        char * st = NULL;\r
         BOOL skip = FALSE;\r
+       int pos = -1;\r
 \r
         if( hasFilter ) {\r
-            if( ! SearchPattern( st, pszFilter ) ) {\r
-                skip = TRUE;\r
-            }\r
+            st = GameListLine(lg->number, &lg->gameInfo);\r
+           if( !SearchPattern( st, pszFilter) ) skip = TRUE;\r
+        }\r
+\r
+        if( !skip && byPos) {\r
+            if( (pos = GameContainsPosition(gameFile, lg)) < 0) skip = TRUE;\r
         }\r
 \r
+       lg->position = pos;\r
+\r
         if( ! skip ) {\r
+            if(!st) st = GameListLine(lg->number, &lg->gameInfo);\r
             SendDlgItemMessage(hDlg, OPT_GameListText, LB_ADDSTRING, 0, (LPARAM) st);\r
             count++;\r
 \r
@@ -110,7 +118,7 @@ static int GameListToListBox( HWND hDlg, BOOL boReset, char * pszFilter, struct
                 stats->unfinished++;\r
         }\r
 \r
-        free(st);\r
+        if(st) free(st);\r
         lg = (ListGame *) lg->node.succ;\r
     }\r
 \r
@@ -168,7 +176,7 @@ GameListDialog(HWND hDlg, UINT message,     WPARAM wParam, LPARAM lParam)
     /* Set font */\r
     SendDlgItemMessage( hDlg, OPT_GameListText, WM_SETFONT, (WPARAM)font[boardSize][GAMELIST_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
 \r
-    count = GameListToListBox( hDlg, gameListDialog ? TRUE : FALSE, NULL, &stats );\r
+    count = GameListToListBox( hDlg, gameListDialog ? TRUE : FALSE, NULL, &stats, FALSE         );\r
 \r
     SendDlgItemMessage( hDlg, IDC_GameListFilter, WM_SETTEXT, 0, (LPARAM) "" );\r
     SendDlgItemMessage( hDlg, IDC_GameListFilter, EM_SETLIMITTEXT, MAX_FILTER_LENGTH, 0 );\r
@@ -262,8 +270,10 @@ GameListDialog(HWND hDlg, UINT message,    WPARAM wParam, LPARAM lParam)
       /* [AS] End command replacement */\r
 \r
     switch (LOWORD(wParam)) {\r
-    case IDOK:\r
     case OPT_GameListLoad:\r
+      LoadOptionsPopup(hDlg);\r
+      return TRUE;\r
+    case IDOK:\r
       nItem = SendDlgItemMessage(hDlg, OPT_GameListText, LB_GETCURSEL, 0, 0);\r
       if (nItem < 0) {\r
        /* is this possible? */\r
@@ -283,7 +293,7 @@ GameListDialog(HWND hDlg, UINT message,     WPARAM wParam, LPARAM lParam)
       SendDlgItemMessage(hDlg, OPT_GameListText, LB_SETCURSEL, nItem, 0);\r
       break; /* load the game*/\r
 \r
-    case OPT_GameListPrev:\r
+//    case OPT_GameListPrev:\r
       nItem = SendDlgItemMessage(hDlg, OPT_GameListText, LB_GETCURSEL, 0, 0);\r
       nItem--;\r
       if (nItem < 0) {\r
@@ -295,13 +305,14 @@ GameListDialog(HWND hDlg, UINT message,   WPARAM wParam, LPARAM lParam)
       break; /* load the game*/\r
 \r
     /* [AS] */\r
+    case OPT_GameListPrev:\r
     case IDC_GameListDoFilter:\r
         {\r
             char filter[MAX_FILTER_LENGTH+1];\r
 \r
             if( GetDlgItemText( hDlg, IDC_GameListFilter, filter, sizeof(filter) ) >= 0 ) {\r
                 filter[ sizeof(filter)-1 ] = '\0';\r
-                count = GameListToListBox( hDlg, TRUE, filter, &stats );\r
+                count = GameListToListBox( hDlg, TRUE, filter, &stats, LOWORD(wParam)!=IDC_GameListDoFilter );\r
                 GameListUpdateTitle( hDlg, _("Game List"), count, ((ListGame *) gameList.tailPred)->number, &stats );\r
             }\r
         }\r
index aedb1c1..29b1f91 100644 (file)
@@ -90,7 +90,7 @@ BEGIN
     PUSHBUTTON      "Cancel",IDCANCEL,195,190,40,14\r
 END\r
 \r
-DLG_LoadOptions DIALOG DISCARDABLE  10, 18, 166, 55\r
+DLG_LoadOptions DIALOG DISCARDABLE  10, 18, 166, 184\r
 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU\r
 CAPTION "Load Game Options"\r
 FONT 8, "MS Sans Serif"\r
@@ -99,8 +99,23 @@ BEGIN
                     BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,4,4,160,10\r
     EDITTEXT        OPT_ASTimeDelay,16,16,28,14,ES_AUTOHSCROLL\r
     LTEXT           "seconds per move",OPT_AStext1,46,20,90,8,NOT WS_GROUP\r
-    PUSHBUTTON      "OK",IDOK,56,36,50,14,WS_GROUP\r
-    PUSHBUTTON      "Cancel",IDCANCEL,112,36,50,14\r
+    LTEXT           "when filtering game list on position, use thresholds:",OPT_thresholds,4,36,160,8,NOT WS_GROUP\r
+    EDITTEXT        OPT_elo1,16,50,28,14,ES_AUTOHSCROLL\r
+    LTEXT           "Elo for at least one player",OPT_elo1t,46,54,90,8,NOT WS_GROUP\r
+    EDITTEXT        OPT_elo2,16,70,28,14,ES_AUTOHSCROLL\r
+    LTEXT           "Elo for both players",OPT_elo2t,46,74,90,8,NOT WS_GROUP\r
+    EDITTEXT        OPT_date,16,90,28,14,ES_AUTOHSCROLL\r
+    LTEXT           "or later year",OPT_datet,46,94,94,8,NOT WS_GROUP\r
+    CONTROL         "Match exact position",OPT_Exact,"Button",\r
+                    BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,4,111,159,10\r
+    CONTROL         "Match if position is subset",OPT_Subset,"Button",\r
+                    BS_AUTORADIOBUTTON | WS_TABSTOP,4,124,159,10\r
+    CONTROL         "Match material with exact pawn structure",OPT_Struct,"Button",\r
+                    BS_AUTORADIOBUTTON | WS_TABSTOP,4,137,159,10\r
+    CONTROL         "Match material",OPT_Material,"Button",\r
+                    BS_AUTORADIOBUTTON | WS_TABSTOP,4,150,159,10\r
+    PUSHBUTTON      "OK",IDOK,56,165,50,14,WS_GROUP\r
+    PUSHBUTTON      "Cancel",IDCANCEL,112,165,50,14\r
 END\r
 \r
 DLG_SaveOptions DIALOG DISCARDABLE  6, 17, 178, 119\r
@@ -240,11 +255,10 @@ FONT 8, "MS Sans Serif"
 BEGIN\r
     LISTBOX         OPT_GameListText,2,2,254,130,LBS_NOINTEGRALHEIGHT | \r
                     WS_VSCROLL | WS_HSCROLL | WS_TABSTOP\r
-    PUSHBUTTON      "&Load",OPT_GameListLoad,2,135,32,15\r
-    PUSHBUTTON      "&<",OPT_GameListPrev,38,135,22,15\r
-    PUSHBUTTON      "&>",OPT_GameListNext,64,135,24,15\r
-    PUSHBUTTON      "&Close",OPT_GameListClose,92,135,32,15\r
-    PUSHBUTTON      "Filter",IDC_GameListDoFilter,144,136,30,14\r
+    PUSHBUTTON      "&Thresholds",OPT_GameListLoad,2,136,40,15\r
+    PUSHBUTTON      "&Find Position",OPT_GameListPrev,45,136,48,15\r
+    PUSHBUTTON      "&Close",OPT_GameListClose,96,136,30,15\r
+    PUSHBUTTON      "&Filter",IDC_GameListDoFilter,149,136,25,14\r
     EDITTEXT        IDC_GameListFilter,178,136,78,14,ES_AUTOHSCROLL\r
 END\r
 \r
index a48b924..94da5b8 100644 (file)
@@ -2391,6 +2391,15 @@ CommPortOptionsPopup(HWND hwnd)
  *\r
 \*---------------------------------------------------------------------------*/\r
 \r
+int\r
+LoadOptionsWhichRadio(HWND hDlg)\r
+{\r
+  return (IsDlgButtonChecked(hDlg, OPT_Exact) ? 1 :\r
+         (IsDlgButtonChecked(hDlg, OPT_Subset) ? 2 :\r
+         (IsDlgButtonChecked(hDlg, OPT_Struct) ? 3 :\r
+         (IsDlgButtonChecked(hDlg, OPT_Material) ? 4 : -1))));\r
+}\r
+\r
 VOID\r
 SetLoadOptionEnables(HWND hDlg)\r
 {\r
@@ -2406,6 +2415,7 @@ LoadOptions(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
 {\r
   char buf[MSG_SIZ];\r
   float fnumber;\r
+  int ok;\r
 \r
   switch (message) {\r
   case WM_INITDIALOG: /* message: initialize dialog box */\r
@@ -2421,6 +2431,23 @@ LoadOptions(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
       CheckDlgButton(hDlg, OPT_Autostep, FALSE);\r
     }\r
     SetLoadOptionEnables(hDlg);\r
+    SetDlgItemInt(hDlg, OPT_elo1, appData.eloThreshold1, FALSE);\r
+    SetDlgItemInt(hDlg, OPT_elo2, appData.eloThreshold2, FALSE);\r
+    SetDlgItemInt(hDlg, OPT_date, appData.dateThreshold, FALSE);\r
+    switch (appData.searchMode) {\r
+    case 1:\r
+      CheckDlgButton(hDlg, OPT_Exact, TRUE);\r
+      break;\r
+    case 2:\r
+      CheckDlgButton(hDlg, OPT_Subset, TRUE);\r
+      break;\r
+    case 3:\r
+      CheckDlgButton(hDlg, OPT_Struct, TRUE);\r
+      break;\r
+    case 4:\r
+      CheckDlgButton(hDlg, OPT_Material, TRUE);\r
+      break;\r
+    }\r
     return TRUE;\r
 \r
   case WM_COMMAND: /* message: received a command */\r
@@ -2438,6 +2465,10 @@ LoadOptions(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
       } else {\r
        appData.timeDelay = (float) -1.0;\r
       }\r
+      appData.eloThreshold1 = GetDlgItemInt(hDlg, OPT_elo1, &ok, FALSE);\r
+      appData.eloThreshold2 = GetDlgItemInt(hDlg, OPT_elo2, &ok, FALSE);\r
+      appData.dateThreshold = GetDlgItemInt(hDlg, OPT_date, &ok, FALSE);\r
+      appData.searchMode = LoadOptionsWhichRadio(hDlg);\r
       EndDialog(hDlg, TRUE);\r
       return TRUE;\r
 \r
index 46200c5..d68c00b 100644 (file)
@@ -186,7 +186,7 @@ GameListCreate(name, callback, client_data)
     XtSetArg(args[j], XtNleft, XtChainLeft); j++;
     XtSetArg(args[j], XtNright, XtChainLeft); j++;
     b_load =
-      XtCreateManagedWidget(_("load"), commandWidgetClass, form, args, j);
+      XtCreateManagedWidget(_("thresholds"), commandWidgetClass, form, args, j);
     XtAddCallback(b_load, XtNcallback, callback, client_data);
 
     j = 0;
@@ -197,9 +197,9 @@ GameListCreate(name, callback, client_data)
     XtSetArg(args[j], XtNleft, XtChainLeft); j++;
     XtSetArg(args[j], XtNright, XtChainLeft); j++;
     b_loadprev =
-      XtCreateManagedWidget(_("prev"), commandWidgetClass, form, args, j);
+      XtCreateManagedWidget(_("find position"), commandWidgetClass, form, args, j);
     XtAddCallback(b_loadprev, XtNcallback, callback, client_data);
-
+#if 1
     j = 0;
     XtSetArg(args[j], XtNfromVert, viewport);  j++;
     XtSetArg(args[j], XtNfromHoriz, b_loadprev);  j++;
@@ -210,7 +210,9 @@ GameListCreate(name, callback, client_data)
     b_loadnext =
       XtCreateManagedWidget(_("next"), commandWidgetClass, form, args, j);
     XtAddCallback(b_loadnext, XtNcallback, callback, client_data);
-
+#else
+    b_loadnext = b_loadprev;
+#endif
     j = 0;
     XtSetArg(args[j], XtNfromVert, viewport);  j++;
     XtSetArg(args[j], XtNfromHoriz, b_loadnext);  j++;
@@ -240,7 +242,7 @@ GameListCreate(name, callback, client_data)
     XtSetArg(args[j], XtNbottom, XtChainBottom); j++;
     XtSetArg(args[j], XtNleft, XtChainLeft); j++;
     XtSetArg(args[j], XtNright, XtChainRight); j++;
-    XtSetArg(args[j], XtNwidth, fw_width - 225 - squareSize); j++;
+    XtSetArg(args[j], XtNwidth, fw_width - 275 - squareSize); j++;
     XtSetArg(args[j], XtNstring, filterString);  j++;
     XtSetArg(args[j], XtNdisplayCaret, False);  j++;
     XtSetArg(args[j], XtNresizable, True);  j++;
@@ -321,7 +323,7 @@ GameListCreate(name, callback, client_data)
 }
 
 static int
-GameListPrepare()
+GameListPrepare(int byPos)
 {   // [HGM] filter: put in separate routine, to make callable from call-back
     int nstrings;
     ListGame *lg;
@@ -333,11 +335,13 @@ GameListPrepare()
     lg = (ListGame *) gameList.head;
     listLength = 0;
     while (nstrings--) {
+       int pos = -1;
        line = GameListLine(lg->number, &lg->gameInfo);
-       if(filterString[0] == NULLCHAR || SearchPattern( line, filterString ) ) {
+       if((filterString[0] == NULLCHAR || SearchPattern( line, filterString )) && (!byPos || (pos=GameContainsPosition(glc->fp, lg)) >= 0) ) {
            *st++ = line; // [HGM] filter: make adding line conditional
            listLength++;
        }
+       lg->position = pos;
        lg = (ListGame *) lg->node.succ;
      }
     *st = NULL;
@@ -376,6 +380,14 @@ GameListCallback(w, client_data, call_data)
        GameListPopDown();
        return;
     }
+    if (strcmp(name, _("thresholds")) == 0) {
+       LoadOptionsProc();
+       return;
+    }
+    if (strcmp(name, _("find position")) == 0) {
+        if(GameListPrepare(True)) GameListReplace(); // crashes on empty list...
+       return;
+    }
     listwidg = XtNameToWidget(glc->shell, "*form.viewport.list");
     rs = XawListShowCurrent(listwidg);
     if (strcmp(name, _("load")) == 0) {
@@ -405,10 +417,10 @@ GameListCallback(w, client_data, call_data)
        XtGetValues(filterText, args, j);
         safeStrCpy(filterString, name, sizeof(filterString)/sizeof(filterString[0]));
        XawListHighlight(listwidg, 0);
-        if(GameListPrepare()) GameListReplace(); // crashes on empty list...
+        if(GameListPrepare(False)) GameListReplace(); // crashes on empty list...
         return;
     }
-#if 0
+#if 1
     index = atoi(glc->strings[index])-1; // [HGM] filter: read true index from sequence nr of line
     if (cmailMsgLoaded) {
        CmailLoadGame(glc->fp, index + 1, glc->filename, True);
@@ -442,7 +454,7 @@ GameListPopUp(fp, filename)
        free(glc->strings);
     }
 
-    GameListPrepare(); // [HGM] filter: code put in separate routine
+    GameListPrepare(False); // [HGM] filter: code put in separate routine
 
     glc->fp = fp;
 
@@ -558,7 +570,7 @@ SetFilterProc(w, event, prms, nprms)
         XtSetArg(args[j], XtNstring, &name);  j++;
        XtGetValues(filterText, args, j);
         safeStrCpy(filterString, name, sizeof(filterString)/sizeof(filterString[0]));
-        if(GameListPrepare()) GameListReplace(); // crashes on empty list...
+        if(GameListPrepare(False)) GameListReplace(); // crashes on empty list...
        list = XtNameToWidget(glc->shell, "*form.viewport.list");
        XawListHighlight(list, 0);
         j = 0;
@@ -612,35 +624,35 @@ GameListIsUp()
     return glc && glc->up;
 }
 
-int SaveGameListAsText(FILE *f)\r
-{\r
-    ListGame * lg = (ListGame *) gameList.head;\r
-    int nItem;\r
-\r
-    if( !glc || ((ListGame *) gameList.tailPred)->number <= 0 ) {\r
-        DisplayError("Game list not loaded or empty", 0);\r
-        return False;\r
-    }\r
-\r
-    /* Copy the list into the global memory block */\r
-    if( f != NULL ) {\r
+int SaveGameListAsText(FILE *f)
+{
+    ListGame * lg = (ListGame *) gameList.head;
+    int nItem;
+
+    if( !glc || ((ListGame *) gameList.tailPred)->number <= 0 ) {
+        DisplayError("Game list not loaded or empty", 0);
+        return False;
+    }
+
+    /* Copy the list into the global memory block */
+    if( f != NULL ) {
  
-        lg = (ListGame *) gameList.head;\r
-\r
-        for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){\r
-            char * st = GameListLineFull(lg->number, &lg->gameInfo);\r
+        lg = (ListGame *) gameList.head;
+
+        for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){
+            char * st = GameListLineFull(lg->number, &lg->gameInfo);
            char *line = GameListLine(lg->number, &lg->gameInfo);
            if(filterString[0] == NULLCHAR || SearchPattern( line, filterString ) )
                    fprintf( f, "%s\n", st );
-           free(st); free(line);\r
-            lg = (ListGame *) lg->node.succ;\r
-        }\r
-\r
+           free(st); free(line);
+            lg = (ListGame *) lg->node.succ;
+        }
+
         fclose(f);
-       return True;\r
+       return True;
     }
-    return False;\r
-}\r
+    return False;
+}
 //--------------------------------- Game-List options dialog ------------------------------------------
 
 Widget gameListOptShell, listwidg;
index 437ebae..31750ba 100644 (file)
@@ -554,12 +554,27 @@ Option icsOptions[] = {
 { 0, 0, 0, NULL, (void*) &IcsOptionsOK, "", NULL, EndMark , "" }
 };
 
+char *modeNames[] = { N_("Exact match"), N_("Shown position is subset"), N_("Same material and Pawn chain"), N_("Same material"), NULL };
+char *modeValues[] = { "1", "2", "3", "4" };
+char *searchMode;
+
+int LoadOptionsOK()
+{
+    appData.searchMode = atoi(searchMode);
+    return 1;
+}
+
 Option loadOptions[] = {
 { 0, 0, 0, NULL, (void*) &appData.autoDisplayTags, "", NULL, CheckBox, N_("Auto-Display Tags") },
 { 0, 0, 0, NULL, (void*) &appData.autoDisplayComment, "", NULL, CheckBox, N_("Auto-Display Comment") },
 { 0, 0, 0, NULL, NULL, NULL, NULL, Label, N_("Auto-Play speed of loaded games\n(0 = instant, -1 = off):") },
 { 0, -1, 10000000, NULL, (void*) &appData.timeDelay, "", NULL, Fractional, N_("Seconds per Move:") },
-{ 0,  0, 0, NULL, NULL, "", NULL, EndMark , "" }
+{   0,  0,    0, NULL, NULL, NULL, NULL, Label,  N_("\nThresholds for position filtering in game list:") },
+{ 0, 0, 5000, NULL, (void*) &appData.eloThreshold1, "", NULL, Spin, N_("Elo of strongest player at least:") },
+{ 0, 0, 5000, NULL, (void*) &appData.eloThreshold2, "", NULL, Spin, N_("Elo of weakest player at least:") },
+{ 0, 0, 5000, NULL, (void*) &appData.dateThreshold, "", NULL, Spin, N_("No games before year:") },
+{ 1, 0, 180, NULL, (void*) &searchMode, (char*) modeNames, modeValues, ComboBox, N_("Seach mode:") },
+{ 0,  0, 0, NULL, (void*) &LoadOptionsOK, "", NULL, EndMark , "" }
 };
 
 Option saveOptions[] = {
@@ -1219,6 +1234,7 @@ void LoadOptionsProc(w, event, prms, nprms)
      String *prms;
      Cardinal *nprms;
 {
+   ASSIGN(searchMode, modeValues[appData.searchMode-1]);
    GenericPopUp(loadOptions, _("Load Game Options"), 0);
 }