{ "eloThresholdBoth", ArgInt, (void *) &appData.eloThreshold2, FALSE, (ArgIniType) 0 },
{ "dateThreshold", ArgInt, (void *) &appData.dateThreshold, FALSE, (ArgIniType) 0 },
{ "searchMode", ArgInt, (void *) &appData.searchMode, FALSE, (ArgIniType) 1 },
+ { "stretch", ArgInt, (void *) &appData.stretch, FALSE, (ArgIniType) 1 },
+ { "ignoreColors", ArgBoolean, (void *) &appData.ignoreColors, FALSE, FALSE },
#if ZIPPY
{ "zippyTalk", ArgBoolean, (void *) &appData.zippyTalk, FALSE, (ArgIniType) ZIPPY_TALK },
#define Q_PROMO 4
#define Q_EP 3
-#define Q_WCASTL 2
-#define Q_BCASTL 1
+#define Q_BCASTL 2
+#define Q_WCASTL 1
int pieceList[256], quickBoard[256];
ChessSquare pieceType[256] = { EmptySquare };
Board soughtBoard, reverseBoard;
-int counts[EmptySquare], soughtCounts[EmptySquare], reverseCounts[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
+int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare];
+Boolean epOK;
typedef struct {
unsigned char piece, to;
} Move;
-Move moveDatabase[4000000];
-int movePtr = 0;
+#define DATABASESIZE 10000000 /* good for 100k games */
+Move moveDatabase[DATABASESIZE];
+int movePtr;
void MakePieceList(Board board, int *counts)
{
pieceList[n] = sq;
pieceType[n] = board[r][f];
counts[board[r][f]]++;
- if(board[r][f] == WhiteKing) pieceList[1] = sq; else
- if(board[r][f] == BlackKing) pieceList[2] = sq; // remember where Kings start, for castling
+ if(board[r][f] == WhiteKing) pieceList[1] = n; else
+ if(board[r][f] == BlackKing) pieceList[2] = n; // remember which are Kings, for castling
}
}
+ epOK = gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina;
}
-void PackMove(int fromX, int fromY, int toX, int toY, char promoChar)
+void PackMove(int fromX, int fromY, int toX, int toY, ChessSquare promoPiece)
{
int sq = fromX + (fromY<<4);
int piece = quickBoard[sq];
quickBoard[sq] = 0;
moveDatabase[movePtr].to = pieceList[piece] = sq = toX + (toY<<4);
- if(promoChar) {
-
+ if(piece == pieceList[1] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+ int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT;
+ moveDatabase[movePtr++].piece = Q_WCASTL;
+ quickBoard[sq] = piece;
+ piece = quickBoard[from]; quickBoard[from] = 0;
+ moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+ } else
+ if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) {
+ int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4);
+ moveDatabase[movePtr++].piece = Q_BCASTL;
+ quickBoard[sq] = piece;
+ piece = quickBoard[from]; quickBoard[from] = 0;
+ moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1;
+ } else
+ if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) {
+ quickBoard[(fromY<<4)+toX] = 0;
+ moveDatabase[movePtr].piece = Q_EP;
+ moveDatabase[movePtr++].to = (fromY<<4)+toX;
+ moveDatabase[movePtr].to = sq;
+ } else
+ if(promoPiece != pieceType[piece]) {
+ moveDatabase[movePtr++].piece = Q_PROMO;
+ moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece;
}
moveDatabase[movePtr].piece = piece;
quickBoard[sq] = piece;
int PackGame(Board board)
{
- moveDatabase[movePtr++].piece = 0; // terminate previous game
+ moveDatabase[movePtr].piece = 0; // terminate previous game
+ if(movePtr > DATABASESIZE - 500) return 0; // gamble on that game will not be more than 250 moves
+ movePtr++;
MakePieceList(board, counts);
return movePtr;
}
-int QuickCompare(Board board, int *counts, int *maxCounts)
+int QuickCompare(Board board, int *minCounts, int *maxCounts)
{ // compare according to search mode
int r, f;
switch(appData.searchMode)
if(board[r][f] != pieceType[quickBoard[(r<<4)+f]]) return FALSE;
} // fall through to material comparison
case 4: // exact material
- for(r=0; r<EmptySquare; r++) if(counts[r] != soughtCounts[r]) return FALSE;
+ for(r=0; r<EmptySquare; r++) if(counts[r] != maxCounts[r]) return FALSE;
return TRUE;
- case 5: // material range with given imbalance
- for(r=0; r<EmptySquare; r++) if(counts[r] < soughtCounts[r] || counts[r] > maxCounts[r]) return FALSE;
- case 6: // material range
- for(r=0; r<EmptySquare; r++) if(counts[r] < soughtCounts[r] || counts[r] > maxCounts[r]) return FALSE;
+ case 6: // material range with given imbalance
+ for(r=0; r<BlackPawn; r++) if(counts[r] - minCounts[r] != counts[r+BlackPawn] - minCounts[r+BlackPawn]) return FALSE;
+ // fall through to range comparison
+ case 5: // material range
+ for(r=0; r<EmptySquare; r++) if(counts[r] < minCounts[r] || counts[r] > maxCounts[r]) return FALSE;
return TRUE;
}
}
int QuickScan(Board board, Move *move)
{ // reconstruct game,and compare all positions in it
+ int cnt=0, stretch=0;
MakePieceList(board, counts);
do {
int piece = move->piece;
int to = move->to, from = pieceList[piece];
if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4
- if(!piece) return FALSE;
+ if(!piece) return -1;
if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType)
piece = (++move)->piece;
from = pieceList[piece];
move++;
continue;
} else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to)
- from = pieceList[piece]; // first two elements of pieceList contain initial King positions
- piece = quickBoard[from]; // so this must be King
+ piece = pieceList[piece]; // first two elements of pieceList contain King numbers
+ from = pieceList[piece]; // so this must be King
quickBoard[from] = 0;
quickBoard[to] = piece;
pieceList[piece] = to;
+ move++;
continue;
}
}
quickBoard[from] = 0;
quickBoard[to] = piece;
pieceList[piece] = to;
- if(QuickCompare(soughtBoard, soughtCounts, maxSought)) return TRUE;
+ cnt++;
+ if(QuickCompare(soughtBoard, minSought, maxSought) ||
+ appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse)) {
+ static int lastCounts[EmptySquare+1];
+ int i;
+ if(stretch) for(i=0; i<EmptySquare; i++) if(lastCounts[i] != counts[i]) { stretch = 0; break; } // reset if material changes
+ if(stretch++ == 0) for(i=0; i<EmptySquare; i++) lastCounts[i] = counts[i]; // remember actual material
+ } else stretch = 0;
+ if(stretch && (appData.searchMode == 1 || stretch >= appData.stretch)) return cnt + 1 - stretch;
move++;
} while(1);
}
+InitSearch()
+{
+ int r, f;
+ CopyBoard(soughtBoard, boards[currentMove]);
+ MakePieceList(soughtBoard, maxSought);
+ CopyBoard(reverseBoard, boards[currentMove]);
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ int piece = boards[currentMove][BOARD_HEIGHT-1-r][f];
+ if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip
+ reverseBoard[r][f] = piece;
+ }
+ for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6];
+ for(r=0; r<BlackPawn; r++) maxReverse[r] = maxSought[r+BlackPawn], maxReverse[r+BlackPawn] = maxSought[r];
+ if(appData.searchMode >= 5) {
+ for(r=BOARD_HEIGHT/2; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) soughtBoard[r][f] = EmptySquare;
+ MakePieceList(soughtBoard, minSought);
+ for(r=0; r<BlackPawn; r++) minReverse[r] = minSought[r+BlackPawn], minReverse[r+BlackPawn] = minSought[r];
+ }
+}
+
GameInfo dummyInfo;
int GameContainsPosition(FILE *f, ListGame *lg)
}
if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen);
else CopyBoard(boards[scratch], initialPosition); // default start position
- if(lg->moves && !QuickScan( boards[scratch], &moveDatabase[lg->moves] )) return -1; // quick scan rules out it is there
+ if(lg->moves) {
+ if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there
+ if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan.
+ }
if(btm) plyNr++;
if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
fseek(f, lg->offset, 0);
plyNr++;
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]);
if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr;
+ if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr;
}
}
int eloThreshold2;
int dateThreshold;
int searchMode;
+ int stretch;
+ Boolean ignoreColors;
char *userName;
int rewindIndex; /* [HGM] autoinc */
int sameColorGames; /* [HGM] alternate */
GameListFree(&gameList);
yynewfile(f);
gameNumber = 0;
+ movePtr = 0;
lastStart = (ChessMove) 0;
yyskipmoves = FALSE;
}
currentListGame->number = ++gameNumber;
currentListGame->offset = offset;
+ if(1) { CopyBoard(boards[scratch], initialPosition); plyNr = 0; currentListGame->moves = PackGame(boards[scratch]); }
if (currentListGame->gameInfo.event != NULL) {
free(currentListGame->gameInfo.event);
}
}
currentListGame->number = ++gameNumber;
currentListGame->offset = offset;
+ if(1) { CopyBoard(boards[scratch], initialPosition); plyNr = 0; currentListGame->moves = PackGame(boards[scratch]); }
lastStart = cm;
break;
default:
ParsePGNTag(yy_text, ¤tListGame->gameInfo);
}
} while (cm == PGNTag || cm == Comment);
- if(1) { CopyBoard(boards[scratch], initialPosition); plyNr = 0; currentListGame->moves = PackGame(boards[scratch]); }
+ if(1) {
+ int btm=0;
+ if(currentListGame->gameInfo.fen) ParseFEN(boards[scratch], &btm, currentListGame->gameInfo.fen);
+ else CopyBoard(boards[scratch], initialPosition);
+ plyNr = (btm != 0);
+ currentListGame->moves = PackGame(boards[scratch]);
+ }
if(cm != NormalMove) break;
case IllegalMove:
if(appData.testLegality) break;
}
currentListGame->number = ++gameNumber;
currentListGame->offset = offset;
+ if(1) { CopyBoard(boards[scratch], initialPosition); plyNr = 0; currentListGame->moves = PackGame(boards[scratch]); }
lastStart = MoveNumberOne;
}
case WhiteCapturesEnPassant:
toY = currentMoveString[3] - ONE;
plyNr++;
ApplyMove(fromX, fromY, toX, toY, currentMoveString[4], boards[scratch]);
- PackMove(fromX, fromY, toX, toY, currentMoveString[4]);
+ if(currentListGame->moves) PackMove(fromX, fromY, toX, toY, boards[scratch][toY][toX]);
break;
case WhiteWins: // [HGM] rescom: save last comment as result details
case BlackWins:
}
while (cm != (ChessMove) 0);
+ if(!currentListGame->moves) DisplayError("Game cache overflowed\nPosition-searching might not work properly", 0);
+
if (appData.debugMode) {
for (currentListGame = (ListGame *) gameList.head;
currentListGame->node.succ;
}
GetTimeMark(&t2);printf("GameListBuild %d msec\n", SubtractTimeMarks(&t2,&t));
quickFlag = 0;
+ PackGame(boards[scratch]); // for appending end-of-game marker.
DisplayTitle("WinBoard");
rewind(f);
yyskipmoves = FALSE;
#define OPT_Subset 1966\r
#define OPT_Struct 1967\r
#define OPT_Material 1968\r
+#define OPT_Range 1969\r
+#define OPT_Difference 1970\r
+#define OPT_Stretch 1971\r
+#define OPT_Stretcht 1972\r
+#define OPT_Reversed 1973\r
+#define OPT_SearchMode 1974\r
#define OPT_Bitmaps 1976\r
#define OPT_PieceFont 1977\r
#define OPT_MessageFont8 1978\r
}\r
}\r
\r
+ if(byPos) InitSearch();\r
+\r
for (nItem = 0; nItem < ((ListGame *) gameList.tailPred)->number; nItem++){\r
char * st = NULL;\r
BOOL skip = FALSE;\r
PUSHBUTTON "Cancel",IDCANCEL,195,190,40,14\r
END\r
\r
-DLG_LoadOptions DIALOG DISCARDABLE 10, 18, 166, 184\r
+DLG_LoadOptions DIALOG DISCARDABLE 10, 18, 170, 248\r
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU\r
CAPTION "Load Game Options"\r
FONT 8, "MS Sans Serif"\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
+ EDITTEXT OPT_Stretch,16,110,28,14,ES_AUTOHSCROLL\r
+ LTEXT "consecutive positions",OPT_Stretcht,46,114,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
+ BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,6,136,159,10\r
CONTROL "Match if position is subset",OPT_Subset,"Button",\r
- BS_AUTORADIOBUTTON | WS_TABSTOP,4,124,159,10\r
+ BS_AUTORADIOBUTTON | WS_TABSTOP,6,149,159,10\r
CONTROL "Match material with exact pawn structure",OPT_Struct,"Button",\r
- BS_AUTORADIOBUTTON | WS_TABSTOP,4,137,159,10\r
+ BS_AUTORADIOBUTTON | WS_TABSTOP,6,162,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
+ BS_AUTORADIOBUTTON | WS_TABSTOP,6,175,159,10\r
+ CONTROL "Material range (upper board-half is optional)",OPT_Range,"Button",\r
+ BS_AUTORADIOBUTTON | WS_TABSTOP,6,188,159,10\r
+ CONTROL "Material difference (optional material balanced)",OPT_Difference,"Button",\r
+ BS_AUTORADIOBUTTON | WS_TABSTOP,6,201,159,10\r
+ GROUPBOX "Search Mode: ",OPT_SearchMode,3,126,164,87,WS_GROUP\r
+ CONTROL "Also match reversed colors",OPT_Reversed,"Button",\r
+ BS_AUTOCHECKBOX | WS_GROUP | WS_TABSTOP,4,214,160,10\r
+ PUSHBUTTON "OK",IDOK,56,229,50,14,WS_GROUP\r
+ PUSHBUTTON "Cancel",IDCANCEL,112,229,50,14\r
END\r
\r
DLG_SaveOptions DIALOG DISCARDABLE 6, 17, 178, 119\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
+ (IsDlgButtonChecked(hDlg, OPT_Material) ? 4 :\r
+ (IsDlgButtonChecked(hDlg, OPT_Range) ? 5 :\r
+ (IsDlgButtonChecked(hDlg, OPT_Difference) ? 6 : -1))))));\r
}\r
\r
VOID\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
+ SetDlgItemInt(hDlg, OPT_Stretch, appData.stretch, FALSE);\r
+ CheckDlgButton(hDlg, OPT_Reversed, appData.ignoreColors);\r
switch (appData.searchMode) {\r
case 1:\r
CheckDlgButton(hDlg, OPT_Exact, TRUE);\r
case 4:\r
CheckDlgButton(hDlg, OPT_Material, TRUE);\r
break;\r
+ case 5:\r
+ CheckDlgButton(hDlg, OPT_Range, TRUE);\r
+ break;\r
+ case 6:\r
+ CheckDlgButton(hDlg, OPT_Difference, TRUE);\r
+ break;\r
}\r
return TRUE;\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.stretch = GetDlgItemInt(hDlg, OPT_Stretch, &ok, FALSE);\r
appData.searchMode = LoadOptionsWhichRadio(hDlg);\r
+ appData.ignoreColors = IsDlgButtonChecked(hDlg, OPT_Reversed);\r
EndDialog(hDlg, TRUE);\r
return TRUE;\r
\r
st = glc->strings;
lg = (ListGame *) gameList.head;
listLength = wins = losses = draws = 0;
- if(byPos) MakePieceList(boards[currentMove], soughtCounts), CopyBoard(soughtBoard, boards[currentMove]);
+ if(byPos) InitSearch();
while (nstrings--) {
int pos = -1;
line = GameListLine(lg->number, &lg->gameInfo);
{ 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 *modeNames[] = { N_("Exact position match"), N_("Shown position is subset"), N_("Same material with exactly same Pawn chain"),
+ N_("Same material"), N_("Material range (top board half optional)"), N_("Material difference (optional stuff balanced)"), NULL };
+char *modeValues[] = { "1", "2", "3", "4", "5", "6" };
char *searchMode;
int LoadOptionsOK()
{ 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*) &appData.ignoreColors, "", NULL, CheckBox, N_("Also match reversed colors") },
+{ 0, 1, 50, NULL, (void*) &appData.stretch, "", NULL, Spin, N_("Minimum nr consecutive positions:") },
{ 0, 0, 0, NULL, (void*) &LoadOptionsOK, "", NULL, EndMark , "" }
};