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));
+FILE *WriteTourneyFile P((char *results, FILE *f));
void DisplayTwoMachinesTitle P(());
#ifdef WIN32
appData.directory[i] = strdup(engineName);
p[-1] = SLASH;
} else appData.directory[i] = ".";
- if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
if(params[0]) {
+ if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
snprintf(command, MSG_SIZ, "%s %s", p, params);
p = command;
}
GetTimeMark(&programStartTime);
srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
+ appData.seedBase = random() + (random()<<15);
pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
ClearProgramStats();
if(strchr(appData.results, '*') == NULL) {
FILE *f;
appData.tourneyCycles++;
- if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+ if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
fclose(f);
NextTourneyGame(-1, &dummy);
ReserveGame(-1, 0);
(int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
switch (gameMode) {
- case PlayFromGameFile:
case AnalyzeFile:
case TwoMachinesPlay:
case EndOfGame:
}
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"));
}
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;
*/
switch (gameMode) {
- case PlayFromGameFile:
case AnalyzeFile:
case TwoMachinesPlay:
case EndOfGame:
}
break;
+ case PlayFromGameFile:
+ if(!shiftKey ||!appData.variations) return; // [HGM] only variations
case EditGame:
case IcsExamining:
case BeginningOfGame:
/* 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;
}
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
if (StrStr(message, "analyze")) {
cps->analysisSupport = FALSE;
cps->analyzing = FALSE;
- Reset(FALSE, TRUE);
+// Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state!
+ EditGameEvent(); // [HGM] try to preserve loaded game
snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
DisplayError(buf2, 0);
return;
break;
case WhiteDrop:
case BlackDrop:
+ if(currentMoveString[0] == '@') continue; // no null moves in ICS mode!
fromX = moveType == WhiteDrop ?
(int) CharToPiece(ToUpper(currentMoveString[0])) :
(int) CharToPiece(ToLower(currentMoveString[0]));
}
FILE *
-WriteTourneyFile(char *results)
+WriteTourneyFile(char *results, FILE *f)
{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing
- FILE *f = fopen(appData.tourneyFile, "w");
+ if(f == NULL) 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, "-seedBase %d\n", appData.seedBase);
fprintf(f, "-tourneyType %d\n", appData.tourneyType);
fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
return f;
}
+#define MAXENGINES 1000
+char *command[MAXENGINES], *mnemonic[MAXENGINES];
+
+void Substitute(char *participants, int expunge)
+{
+ int i, changed, changes=0, nPlayers=0;
+ char *p, *q, *r, buf[MSG_SIZ];
+ if(participants == NULL) return;
+ if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; }
+ r = p = participants; q = appData.participants;
+ while(*p && *p == *q) {
+ if(*p == '\n') r = p+1, nPlayers++;
+ p++; q++;
+ }
+ if(*p) { // difference
+ while(*p && *p++ != '\n');
+ while(*q && *q++ != '\n');
+ changed = nPlayers;
+ changes = 1 + (strcmp(p, q) != 0);
+ }
+ if(changes == 1) { // a single engine mnemonic was changed
+ q = r; while(*q) nPlayers += (*q++ == '\n');
+ p = buf; while(*r && (*p = *r++) != '\n') p++;
+ *p = NULLCHAR;
+ NamesToList(firstChessProgramNames, command, mnemonic);
+ for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break;
+ if(mnemonic[i]) { // The substitute is valid
+ FILE *f;
+ if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) {
+ flock(fileno(f), LOCK_EX);
+ ParseArgsFromFile(f);
+ fseek(f, 0, SEEK_SET);
+ FREE(appData.participants); appData.participants = participants;
+ if(expunge) { // erase results of replaced engine
+ int len = strlen(appData.results), w, b, dummy;
+ for(i=0; i<len; i++) {
+ Pairing(i, nPlayers, &w, &b, &dummy);
+ if((w == changed || b == changed) && appData.results[i] == '*') {
+ DisplayError(_("You cannot replace an engine while it is engaged!\nTerminate its game first."), 0);
+ fclose(f);
+ return;
+ }
+ }
+ for(i=0; i<len; i++) {
+ Pairing(i, nPlayers, &w, &b, &dummy);
+ if(w == changed || b == changed) appData.results[i] = ' '; // mark as not played
+ }
+ }
+ WriteTourneyFile(appData.results, f);
+ fclose(f); // release lock
+ return;
+ }
+ } else DisplayError(_("No engine with the name you gave is installed"), 0);
+ }
+ if(changes == 0) DisplayError(_("First change an engine by editing the participants list\nof the Tournament Options dialog"), 0);
+ if(changes > 1) DisplayError(_("You can only change one engine at the time"), 0);
+ free(participants);
+ return;
+}
+
int
CreateTourney(char *name)
{
FILE *f;
+ if(matchMode && strcmp(name, appData.tourneyFile)) {
+ ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing
+ }
if(name[0] == NULLCHAR) {
if(appData.participants[0])
DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
}
ASSIGN(appData.tourneyFile, name);
if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
- if((f = WriteTourneyFile("")) == NULL) return 0;
+ if((f = WriteTourneyFile("", NULL)) == NULL) return 0;
}
fclose(f);
appData.noChessProgram = FALSE;
return 1;
}
-#define MAXENGINES 1000
-char *command[MAXENGINES], *mnemonic[MAXENGINES];
-
void NamesToList(char *names, char **engineList, char **engineMnemonic)
{
char buf[MSG_SIZ], *p, *q;
names = p; i++;
if(i > MAXENGINES - 2) break;
}
- engineList[i] = NULL;
+ engineList[i] = engineMnemonic[i] = NULL;
}
// following implemented as macro to avoid type limitations
first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
+ if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening
Reset(FALSE, first.pr != NoProc);
appData.noChessProgram = FALSE;
if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
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;
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); */
}
}
+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] = random()>>8 ^ random()<<6 ^random()<<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
+#if 0
+ ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak...
+ if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL;
+#else
+ // do it ourselves avoiding malloc
+ { char *p = yy_text+1, *q;
+ while(!isdigit(*p) && !isalpha(*p)) p++;
+ q = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++;
+ *p = NULLCHAR;
+ if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else
+ if(!StrCaseCmp(q, "Variant") && (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else
+ if(!StrCaseCmp(q, "WhiteElo") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
+ if(!StrCaseCmp(q, "BlackElo") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
+ if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else
+ if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else
+ if(!StrCaseCmp(q, "FEN") && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1);
+ }
+#endif
+ 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
int gn = gameNumber;
ListGame *lg = NULL;
int numPGNTags = 0;
- int err;
+ int err, pos = -1;
GameMode oldGameMode;
VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
if (lg) {
fseek(f, lg->offset, 0);
GameListHighlight(gameNumber);
+ pos = lg->position;
gn = 1;
}
else {
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();
}
StartAnalysisClock();
GetTimeMark(&lastNodeCountTime);
lastNodeCount = 0;
+ if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
}
void
while( *++sep >= '0' && *sep <= '9'); // strip seconds
if(deci >= 0)
while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
- while(*sep == ' ') sep++;
+ while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++;
}
if( depth <= 0 ) {
}
// [HGM] vari: routines for shelving variations
+Boolean modeRestore = FALSE;
void
PushInner(int firstMove, int lastMove)
PushInner(firstMove, lastMove);
if(storedGames == 1) GreyRevert(FALSE);
+ if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE;
}
void
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;
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;
}
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 ]}