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));
#ifdef WIN32
extern void ConsoleCreate();
char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
char thinkOutput1[MSG_SIZ*10];
-ChessProgramState first, second;
+ChessProgramState first, second, pairing;
/* premove variables */
int premoveToX = 0;
/* [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];
void
Load(ChessProgramState *cps, int i)
{
- char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
+ char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[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*
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]) {
snprintf(command, MSG_SIZ, "%s %s", p, params);
p = command;
if(useNick) ASSIGN(appData.pgnName[i], nickName);
if(addToList) {
int len;
+ char quote;
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%s%s%s\n", p, appData.directory[i],
+ quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
+ snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
+ quote, p, quote, 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) : "");
firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
InitEngine(&second, 1);
CommonEngineInit();
+ pairing.which = "pairing"; // pairing engine
+ pairing.pr = NoProc;
+ pairing.isr = NULL;
+ pairing.program = appData.pairingEngine;
+ pairing.host = "localhost";
+ pairing.dir = ".";
+
if (appData.icsActive) {
appData.clockMode = TRUE; /* changes dynamically in ICS mode */
} else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
safeStrCpy(q, p, strlen(p) + 2);
if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
- if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
+ if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
q[nextGame] = '*';
}
fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
DisplayMessage(buf, "");
free(p); appData.results = q;
- if(nextGame <= appData.matchGames && resChar != ' ' &&
+ if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
(gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
UnloadEngine(&first); // next game belongs to other pairing;
UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
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...
+ if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
return;
}
// if(gameMode != BeginningOfGame) {
// return;
// }
abortMatch = FALSE;
- appData.matchGames = appData.defaultMatchGames;
+ if(mode == 2) 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
+ NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
if(appData.tourneyFile[0]) {
ReserveGame(-1, 0);
if(nextGame > appData.matchGames) {
char buf[MSG_SIZ];
+ if(strchr(appData.results, '*') == NULL) {
+ FILE *f;
+ appData.tourneyCycles++;
+ if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
+ fclose(f);
+ NextTourneyGame(-1, &dummy);
+ ReserveGame(-1, 0);
+ if(nextGame <= appData.matchGames) {
+ DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
+ matchMode = mode;
+ ScheduleDelayedEvent(NextMatchGame, 10000);
+ return;
+ }
+ }
+ }
snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
DisplayError(buf, 0);
appData.tourneyFile[0] = 0;
if(f = fopen(appData.tourneyFile, "r")) {
ParseArgsFromFile(f); // make sure tourney parmeters re known
fclose(f);
+ appData.clockMode = TRUE;
+ SetGNUMode();
} else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
}
MatchEvent(TRUE);
buf[index] = 0;
if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
int n = first.option[multi].value;
- if(origIndex < 10) { if(n>1) n--; } else if(origIndex > index - 6) n++;
+ 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 TRUE;
+ return FALSE;
}
ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
*start = startPV; *end = index-1;
games[b]++;
nr++;
}
+ if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO
if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
for(w=0; w<nPlayers; w++) {
bScore = -1;
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
HandleMachineMove(savedMessage, savedState);
}
+static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
+
void
HandleMachineMove(message, cps)
char *message;
int machineWhite;
char *bookHit;
+ if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
+ // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
+ if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
+ DisplayError(_("Invalid pairing from pairing engine"), 0);
+ return;
+ }
+ pairingReceived = 1;
+ NextMatchGame();
+ return; // Skim the pairing messages here.
+ }
+
cps->userError = 0;
FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
if (err != 0) {
snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
- DisplayFatalError(buf, err, 1);
- cps->pr = NoProc;
- cps->isr = NULL;
+ DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
+ if(cps != &first) return;
+ appData.noChessProgram = TRUE;
+ ThawUI();
+ SetNCPMode();
+// DisplayFatalError(buf, err, 1);
+// cps->pr = NoProc;
+// cps->isr = NULL;
return;
}
TwoMachinesEvent();
}
+char *
+MakeName(char *template)
+{
+ time_t clock;
+ struct tm *tm;
+ static char buf[MSG_SIZ];
+ char *p = buf;
+ int i;
+
+ clock = time((time_t *)NULL);
+ tm = localtime(&clock);
+
+ while(*p++ = *template++) if(p[-1] == '%') {
+ switch(*template++) {
+ case 0: *p = 0; return buf;
+ case 'Y': i = tm->tm_year+1900; break;
+ case 'y': i = tm->tm_year-100; break;
+ case 'M': i = tm->tm_mon+1; break;
+ case 'd': i = tm->tm_mday; break;
+ case 'h': i = tm->tm_hour; break;
+ case 'm': i = tm->tm_min; break;
+ case 's': i = tm->tm_sec; break;
+ default: i = 0;
+ }
+ snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
+ }
+ return buf;
+}
+
+int
+CountPlayers(char *p)
+{
+ int n = 0;
+ while(p = strchr(p, '\n')) p++, n++; // count participants
+ return n;
+}
+
+FILE *
+WriteTourneyFile(char *results)
+{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing
+ FILE *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, "-tourneyType %d\n", appData.tourneyType);
+ fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
+ fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
+ fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
+ fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
+ fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
+ fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
+ fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
+ 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 \"%s\"\n", results);
+ }
+ return f;
+}
+
int
CreateTourney(char *name)
{
FILE *f;
- if(name[0] == NULLCHAR) return 0;
- f = fopen(appData.tourneyFile, "r");
+ if(name[0] == NULLCHAR) {
+ if(appData.participants[0])
+ DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
+ return 0;
+ }
+ f = fopen(name, "r");
if(f) { // file exists
+ ASSIGN(appData.tourneyFile, name);
ParseArgsFromFile(f); // parse it
} else {
- f = fopen(appData.tourneyFile, "w");
- if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
- // create a file with tournament description
- fprintf(f, "-participants {%s}\n", appData.participants);
- fprintf(f, "-tourneyType %d\n", appData.tourneyType);
- fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
- fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
- fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
- fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
- fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
- fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
- fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
- 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");
+ if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
+ if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
+ DisplayError(_("Not enough participants"), 0);
+ return 0;
}
+ ASSIGN(appData.tourneyFile, name);
+ if((f = WriteTourneyFile("")) == NULL) return 0;
}
fclose(f);
appData.noChessProgram = FALSE;
int
Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
{ // determine players from game number
- int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
+ int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
if(appData.tourneyType == 0) {
roundsPerCycle = (nPlayers - 1) | 1;
*whitePlayer = curRound;
*blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
} else {
- *whitePlayer = curRound - pairingsPerRound + curPairing;
+ *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
- *blackPlayer = curRound + pairingsPerRound - curPairing;
+ *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
}
} else if(appData.tourneyType > 0) {
NextTourneyGame(int nr, int *swapColors)
{ // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
char *p, *q;
- int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
+ int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
FILE *tf;
if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
tf = fopen(appData.tourneyFile, "r");
ParseArgsFromFile(tf); fclose(tf);
InitTimeControls(); // TC might be altered from tourney file
- p = appData.participants;
- while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
- *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
+ nPlayers = CountPlayers(appData.participants); // count participants
+ if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
+ *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
if(syncInterval) {
p = q = appData.results;
waitingForGame = FALSE;
}
+ if(appData.tourneyType < 0) {
+ if(nr>=0 && !pairingReceived) {
+ char buf[1<<16];
+ if(pairing.pr == NoProc) {
+ if(!appData.pairingEngine[0]) {
+ DisplayFatalError(_("No pairing engine specified"), 0, 1);
+ return 0;
+ }
+ StartChessProgram(&pairing); // starts the pairing engine
+ }
+ snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
+ SendToProgram(buf, &pairing);
+ snprintf(buf, 1<<16, "pairing %d\n", nr+1);
+ SendToProgram(buf, &pairing);
+ return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
+ }
+ pairingReceived = 0; // ... so we continue here
+ *swapColors = 0;
+ appData.matchGames = appData.tourneyCycles * syncInterval - 1;
+ whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
+ matchGame = 1; roundNr = nr / syncInterval + 1;
+ }
+
if(first.pr != NoProc) return 1; // engines already loaded
// redefine engines, engine dir, etc.
}
if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
- if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
+ if(appData.tourneyFile[0]){ // [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, ranking = TourneyStandings(3); // tourney is done
+ else ranking = strdup("busy"); //suppress popup when aborted but not finished
} else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
if (nextGame <= appData.matchGames && !abortMatch) {
if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
if(matchMode == TRUE) { // match through command line: exit with or without popup
if(ranking) {
+ ToNrEvent(forwardMostMove);
if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
else ExitEvent(0);
} else DisplayFatalError(buf, 0, 0);
RemoveInputSource(second.isr);
}
+ if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
+ if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
+
ShutDownFrontEnd();
exit(status);
}
{
char buf[MSG_SIZ];
if (appData.matchGames > 0) {
+ if(appData.tourneyFile[0]) {
+ snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
+ gameInfo.white, gameInfo.black,
+ nextGame+1, appData.matchGames+1,
+ appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
+ } else
if (first.twoMachinesColor[0] == 'w') {
snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
gameInfo.white, gameInfo.black,
if (count <= 0) {
if (count == 0) {
RemoveInputSource(cps->isr);
+ if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
_(cps->which), cps->program);
if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */