X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=93f79d37335d0f2314a0c286cd789f3ad34d66a5;hb=3845dc6e578a5e2ca1df4ed90a5db36f7529ac30;hp=f234bebf8b4e7adadfc05e9e0aad0ec49a9627af;hpb=eae7684295f1bd6a48e793a9d0e93f1ad6d6c573;p=xboard.git diff --git a/backend.c b/backend.c index f234beb..93f79d3 100644 --- a/backend.c +++ b/backend.c @@ -232,6 +232,8 @@ int EngineOutputIsUp(); void InitDrawingSizes(int x, int y); 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(); @@ -248,6 +250,7 @@ char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [H void ics_update_width P((int new_width)); extern char installDir[MSG_SIZ]; VariantClass startVariant; /* [HGM] nicks: initial variant */ +Boolean abortMatch; extern int tinyLayout, smallLayout; ChessProgramStats programStats; @@ -415,7 +418,7 @@ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; 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; @@ -505,6 +508,8 @@ ChessMove savedResult[MAX_VARIATIONS]; void PushTail P((int firstMove, int lastMove)); Boolean PopTail P((Boolean annotate)); +void PushInner P((int firstMove, int lastMove)); +void PopInner P((Boolean annotate)); void CleanupTail P((void)); ChessSquare FIDEArray[2][BOARD_FILES] = { @@ -792,7 +797,11 @@ InitEngine(ChessProgramState *cps, int n) /* [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]; @@ -857,16 +866,21 @@ ReplaceEngine(ChessProgramState *cps, int n) LoadEngine(); } -extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName; -extern Boolean isUCI, hasBook, storeVariant, v1, addToList; +extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params; +extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick; + +static char resetOptions[] = + "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 " + "-firstOptions \"\" -firstNPS -1 -fn \"\""; -void Load(ChessProgramState *cps, int i) +void +Load(ChessProgramState *cps, int i) { - char *p, *q, buf[MSG_SIZ]; + char *p, *q, buf[MSG_SIZ], command[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* - ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1"); + ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; ParseArgsFromString(buf); SwapEngines(i); ReplaceEngine(cps, i); @@ -874,29 +888,37 @@ void Load(ChessProgramState *cps, int i) } p = engineName; while(q = strchr(p, SLASH)) p = q+1; - if(*p== NULLCHAR) return; - appData.chessProgram[i] = strdup(p); + if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; } if(engineDir[0] != NULLCHAR) appData.directory[i] = engineDir; else if(p != engineName) { // derive directory from engine path, when not given p[-1] = 0; appData.directory[i] = strdup(engineName); - p[-1] = '/'; + p[-1] = SLASH; } else appData.directory[i] = "."; + if(params[0]) { + snprintf(command, MSG_SIZ, "%s %s", p, params); + p = command; + } + appData.chessProgram[i] = strdup(p); appData.isUCI[i] = isUCI; appData.protocolVersion[i] = v1 ? 1 : PROTOVER; appData.hasOwnBookUCI[i] = hasBook; + if(!nickName[0]) useNick = FALSE; + if(useNick) ASSIGN(appData.pgnName[i], nickName); if(addToList) { int len; 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\n", p, appData.directory[i], + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, 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) : ""); -fprintf(debugFP, "new line: %s", buf); firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1); snprintf(firstChessProgramNames, len, "%s%s", q, buf); if(q) free(q); @@ -905,9 +927,39 @@ fprintf(debugFP, "new line: %s", buf); } void -InitBackEnd1() +InitTimeControls() { int matched, min, sec; + /* + * Parse timeControl resource + */ + if (!ParseTimeControl(appData.timeControl, appData.timeIncrement, + appData.movesPerSession)) { + char buf[MSG_SIZ]; + snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl); + DisplayFatalError(buf, 0, 2); + } + + /* + * Parse searchTime resource + */ + if (*appData.searchTime != NULLCHAR) { + matched = sscanf(appData.searchTime, "%d:%d", &min, &sec); + if (matched == 1) { + searchTime = min * 60; + } else if (matched == 2) { + searchTime = min * 60 + sec; + } else { + char buf[MSG_SIZ]; + snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime); + DisplayFatalError(buf, 0, 2); + } + } +} + +void +InitBackEnd1() +{ ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant @@ -958,31 +1010,7 @@ InitBackEnd1() } } - /* - * Parse timeControl resource - */ - if (!ParseTimeControl(appData.timeControl, appData.timeIncrement, - appData.movesPerSession)) { - char buf[MSG_SIZ]; - snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl); - DisplayFatalError(buf, 0, 2); - } - - /* - * Parse searchTime resource - */ - if (*appData.searchTime != NULLCHAR) { - matched = sscanf(appData.searchTime, "%d:%d", &min, &sec); - if (matched == 1) { - searchTime = min * 60; - } else if (matched == 2) { - searchTime = min * 60 + sec; - } else { - char buf[MSG_SIZ]; - snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime); - DisplayFatalError(buf, 0, 2); - } - } + InitTimeControls(); /* [AS] Adjudication threshold */ adjudicateLossThreshold = appData.adjudicateLossThreshold; @@ -991,6 +1019,13 @@ InitBackEnd1() 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 @@ -1364,13 +1399,40 @@ void MatchEvent(int mode) { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu 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... + return; + } +// if(gameMode != BeginningOfGame) { +// DisplayError(_("You can only start a match from the initial position."), 0); +// return; +// } + abortMatch = FALSE; + 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; @@ -5206,15 +5268,20 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } +Boolean pushed = FALSE; void -ParsePV(char *pv, Boolean storeComments) +ParsePV(char *pv, Boolean storeComments, Boolean atEnd) { // Parse a string of PV moves, and append to current game, behind forwardMostMove int fromX, fromY, toX, toY; char promoChar; ChessMove moveType; Boolean valid; int nr = 0; + if (gameMode == AnalyzeMode && currentMove < forwardMostMove) { + PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position! + pushed = TRUE; + } endPV = forwardMostMove; do { while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace @@ -5253,45 +5320,56 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f endPV++; CopyBoard(boards[endPV], boards[endPV-1]); ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]); - moveList[endPV-1][0] = fromX + AAA; - moveList[endPV-1][1] = fromY + ONE; - moveList[endPV-1][2] = toX + AAA; - moveList[endPV-1][3] = toY + ONE; - moveList[endPV-1][4] = promoChar; - moveList[endPV-1][5] = NULLCHAR; + CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]); strncat(moveList[endPV-1], "\n", MOVE_LEN); - if(storeComments) - CoordsToAlgebraic(boards[endPV - 1], + CoordsToAlgebraic(boards[endPV - 1], PosFlags(endPV - 1), fromY, fromX, toY, toX, promoChar, parseList[endPV - 1]); - else - parseList[endPV-1][0] = NULLCHAR; } while(valid); - currentMove = endPV; + currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1; if(currentMove == forwardMostMove) ClearPremoveHighlights(); else SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE, moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE); DrawPosition(TRUE, boards[currentMove]); } +int +MultiPV(ChessProgramState *cps) +{ // check if engine supports MultiPV, and if so, return the number of the option that sets it + int i; + for(i=0; inrOptions; i++) + if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin) + return i; + return -1; +} + Boolean LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end) { - int startPV; - char *p; + int startPV, multi, lineStart, origIndex = index; + char *p, buf2[MSG_SIZ]; if(index < 0 || index >= strlen(buf)) return FALSE; // sanity lastX = x; lastY = y; while(index > 0 && buf[index-1] != '\n') index--; // beginning of line - startPV = index; + lineStart = startPV = index; while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index; if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3; index = startPV; do{ while(buf[index] && buf[index] != '\n') index++; } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line buf[index] = 0; - ParsePV(buf+startPV, FALSE); + if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) { + int n = first.option[multi].value; + 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 FALSE; + } + ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; return TRUE; } @@ -5301,16 +5379,32 @@ LoadPV(int x, int y) { // called on right mouse click to load PV int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w')); lastX = x; lastY = y; - ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array. + ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array. return TRUE; } void UnLoadPV() { + int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded! if(endPV < 0) return; endPV = -1; + if(gameMode == AnalyzeMode && currentMove > forwardMostMove) { + Boolean saveAnimate = appData.animate; + if(pushed) { + if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space + if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official + } else storedGames--; // abandon shelved tail of original game + } + pushed = FALSE; + forwardMostMove = currentMove; + currentMove = oldFMM; + appData.animate = FALSE; + ToNrEvent(forwardMostMove); + appData.animate = saveAnimate; + } currentMove = forwardMostMove; + if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation ClearPremoveHighlights(); DrawPosition(TRUE, boards[currentMove]); } @@ -7036,6 +7130,52 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp SetProgramStats( &stats ); } +#define MAXPLAYERS 500 + +char * +TourneyStandings(int display) +{ + int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0; + int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS]; + char result, *p, *names[MAXPLAYERS]; + + if(appData.tourneyType < 0) return strdup("Swiss tourney finished"); // standings of Swiss yet TODO + + names[0] = p = strdup(appData.participants); + while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants + + for(i=0; i 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s) + for(w=0; w bScore) bScore = score[i], b = i; + ranking[w] = b; points[w] = bScore; score[b] = -2; + } + p = malloc(nPlayers*34+1); + for(w=0; wuseUsermove ? "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 @@ -7484,6 +7639,8 @@ void DeferredBookMove(void) HandleMachineMove(savedMessage, savedState); } +static int savedWhitePlayer, savedBlackPlayer, pairingReceived; + void HandleMachineMove(message, cps) char *message; @@ -7498,6 +7655,14 @@ HandleMachineMove(message, cps) 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) 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 @@ -9165,6 +9330,7 @@ ShowMove(fromX, fromY, toX, toY) DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); + DisplayBook(currentMove); } void SendEgtPath(ChessProgramState *cps) @@ -9346,9 +9512,14 @@ StartChessProgram(cps) 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; } @@ -9382,32 +9553,94 @@ TwoMachinesEventIfReady P((void)) 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); - 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; @@ -9461,6 +9694,7 @@ void SwapEngines(int n) SWAP(scoreIsAbsolute, h) SWAP(timeOdds, h) SWAP(logo, p) + SWAP(pgnName, p) } void @@ -9468,14 +9702,12 @@ SetPlayer(int player) { // [HGM] find the engine line of the partcipant given by number, and parse its options. int i; char buf[MSG_SIZ], *engineName, *p = appData.participants; - static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" " - "-firstNeedsNoncompliantFEN false -firstNPS -1"; for(i=0; i=0 && !pairingReceived) { + char buf[1<<16]; + if(pairing.pr == NoProc) StartChessProgram(&pairing); + 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 + syncInterval = nPlayers/2; *swapColors = 0; + appData.matchGames = appData.tourneyCycles * syncInterval - 1; + whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1; + matchGame = 1; roundNr = nr / syncInterval + 1; + } else + *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval); if(syncInterval) { p = q = appData.results; @@ -9631,7 +9879,7 @@ GameEnds(result, resultDetails, whosays) { GameMode nextGameMode; int isIcsGame; - char buf[MSG_SIZ], popupRequested = 0, forceUnload; + char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL; if(endingGame) return; /* [HGM] crash: forbid recursion */ endingGame = 1; @@ -9959,12 +10207,12 @@ GameEnds(result, resultDetails, whosays) } if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game - if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result + if(appData.tourneyFile[0] && !abortMatch){ // [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; // tourney is done + if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame - if (nextGame <= appData.matchGames) { + if (nextGame <= appData.matchGames && !abortMatch) { gameMode = nextGameMode; matchGame = nextGame; // this will be overruled in tourney mode! GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause @@ -9994,10 +10242,18 @@ GameEnds(result, resultDetails, whosays) ModeHighlight(); endingGame = 0; /* [HGM] crash */ if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion. - if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else { - matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0; - DisplayNote(buf); + if(matchMode == TRUE) { // match through command line: exit with or without popup + if(ranking) { + if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0); + else ExitEvent(0); + } else DisplayFatalError(buf, 0, 0); + } else { // match through menu; just stop, with or without popup + matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0; + if(ranking){ + if(strcmp(ranking, "busy")) DisplayNote(ranking); + } else DisplayNote(buf); } + if(ranking) free(ranking); } } @@ -12121,6 +12377,9 @@ ExitEvent(status) RemoveInputSource(second.isr); } + if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing); + if (pairing.isr != NULL) RemoveInputSource(pairing.isr); + ShutDownFrontEnd(); exit(status); } @@ -12211,6 +12470,7 @@ void EditTagsEvent() { char *tags = PGNTags(&gameInfo); + bookUp = FALSE; EditTagsPopUp(tags, NULL); free(tags); } @@ -12431,6 +12691,12 @@ DisplayTwoMachinesTitle() { 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, @@ -13391,6 +13657,7 @@ ForwardInner(target) if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } + DisplayBook(currentMove); } @@ -13493,6 +13760,7 @@ BackwardInner(target) HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); // [HGM] PV info: routine tests if comment empty DisplayComment(currentMove - 1, commentList[currentMove]); + DisplayBook(currentMove); } void @@ -13858,11 +14126,11 @@ SetGameInfo() gameInfo.round = StrSave("-"); } if (first.twoMachinesColor[0] == 'w') { - gameInfo.white = StrSave(first.tidy); - gameInfo.black = StrSave(second.tidy); + gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy); + gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy); } else { - gameInfo.white = StrSave(second.tidy); - gameInfo.black = StrSave(first.tidy); + gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy); + gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy); } gameInfo.timeControl = TimeControlTagValue(); break; @@ -14179,6 +14447,7 @@ ReceiveFromProgram(isr, closure, message, count, error) 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 */ @@ -15999,16 +16268,10 @@ int wrap(char *dest, char *src, int count, int width, int *lp) // [HGM] vari: routines for shelving variations void -PushTail(int firstMove, int lastMove) +PushInner(int firstMove, int lastMove) { int i, j, nrMoves = lastMove - firstMove; - if(appData.icsActive) { // only in local mode - forwardMostMove = currentMove; // mimic old ICS behavior - return; - } - if(storedGames >= MAX_VARIATIONS-1) return; - // push current tail of game on stack savedResult[storedGames] = gameInfo.result; savedDetails[storedGames] = gameInfo.resultDetails; @@ -16033,19 +16296,27 @@ PushTail(int firstMove, int lastMove) storedGames++; forwardMostMove = firstMove; // truncate game so we can start variation +} + +void +PushTail(int firstMove, int lastMove) +{ + if(appData.icsActive) { // only in local mode + forwardMostMove = currentMove; // mimic old ICS behavior + return; + } + if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk + + PushInner(firstMove, lastMove); if(storedGames == 1) GreyRevert(FALSE); } -Boolean -PopTail(Boolean annotate) +void +PopInner(Boolean annotate) { int i, j, nrMoves; char buf[8000], moveBuf[20]; - if(appData.icsActive) return FALSE; // only in local mode - if(!storedGames) return FALSE; // sanity - CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open - storedGames--; ToNrEvent(savedFirst[storedGames]); // sets currentMove nrMoves = savedLast[storedGames] - currentMove; @@ -16085,6 +16356,17 @@ PopTail(Boolean annotate) } gameInfo.resultDetails = savedDetails[storedGames]; forwardMostMove = currentMove + nrMoves; +} + +Boolean +PopTail(Boolean annotate) +{ + if(appData.icsActive) return FALSE; // only in local mode + if(!storedGames) return FALSE; // sanity + CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open + + PopInner(annotate); + if(storedGames == 0) GreyRevert(TRUE); return TRUE; } @@ -16131,7 +16413,7 @@ LoadVariation(int index, char *text) PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game // kludge: use ParsePV() to append variation to game move = currentMove; - ParsePV(start, TRUE); + ParsePV(start, TRUE, TRUE); forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did ClearPremoveHighlights(); CommentPopDown();