X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=c73d6e7eff9b70bb985ad9d2080ff6ce12afa659;hb=407cd1126c6c24d890359f1fe1686f6d90c0ad61;hp=c0d36aa364926ad4eb8906b5a24719e9a3e3b2d0;hpb=67e15f886057db1a570ab8e29e7a8a6277736fb3;p=xboard.git diff --git a/backend.c b/backend.c index c0d36aa..c73d6e7 100644 --- a/backend.c +++ b/backend.c @@ -57,9 +57,14 @@ #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); +int flock(int f, int code); +#define LOCK_EX 2 +#define SLASH '\\' + #else #define DoSleep( n ) if( (n) >= 0) sleep(n) +#define SLASH '/' #endif @@ -188,7 +193,7 @@ void SendTimeControl P((ChessProgramState *cps, char *TimeControlTagValue P((void)); void Attention P((ChessProgramState *cps)); void FeedMovesToProgram P((ChessProgramState *cps, int upto)); -void ResurrectChessProgram P((void)); +int ResurrectChessProgram P((void)); void DisplayComment P((int moveNumber, char *text)); void DisplayMove P((int moveNumber)); @@ -225,6 +230,10 @@ void OutputKibitz(int window, char *text); int PerpetualChase(int first, int last); 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(); @@ -241,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; @@ -267,8 +277,9 @@ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ extern int chatCount; int chattingPartner; char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ +char lastMsg[MSG_SIZ]; ChessSquare pieceSweep = EmptySquare; -ChessSquare promoSweep = EmptySquare, defaultPromoChoice, savePiece = EmptySquare; +ChessSquare promoSweep = EmptySquare, defaultPromoChoice; int promoDefaultAltered; /* States for ics_getting_history */ @@ -325,7 +336,7 @@ safeStrCpy( char *dst, const char *src, size_t count ) { dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated if(appData.debugMode) - fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count); + fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count); } return dst; @@ -407,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; @@ -457,8 +468,9 @@ long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhi long timeControl_2; /* [AS] Allow separate time controls */ char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; -int matchGame = 0; -TimeMark programStartTime; +int matchGame = 0, nextGame = 0, roundNr = 0; +Boolean waitingForGame = FALSE; +TimeMark programStartTime, pauseStart; char ics_handle[MSG_SIZ]; int have_set_title = 0; @@ -496,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] = { @@ -659,15 +673,300 @@ ClearProgramStats() } void -InitBackEnd1() +CommonEngineInit() +{ // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings + if (appData.firstPlaysBlack) { + first.twoMachinesColor = "black\n"; + second.twoMachinesColor = "white\n"; + } else { + first.twoMachinesColor = "white\n"; + second.twoMachinesColor = "black\n"; + } + + first.other = &second; + second.other = &first; + + { float norm = 1; + if(appData.timeOddsMode) { + norm = appData.timeOdds[0]; + if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1]; + } + first.timeOdds = appData.timeOdds[0]/norm; + second.timeOdds = appData.timeOdds[1]/norm; + } + + if(programVersion) free(programVersion); + if (appData.noChessProgram) { + programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING)); + sprintf(programVersion, "%s", PACKAGE_STRING); + } else { + /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ + programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); + sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); + } +} + +void +UnloadEngine(ChessProgramState *cps) +{ + /* Kill off first chess program */ + if (cps->isr != NULL) + RemoveInputSource(cps->isr); + cps->isr = NULL; + + if (cps->pr != NoProc) { + ExitAnalyzeMode(); + DoSleep( appData.delayBeforeQuit ); + SendToProgram("quit\n", cps); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(cps->pr, cps->useSigterm); + } + cps->pr = NoProc; + if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which); +} + +void +ClearOptions(ChessProgramState *cps) +{ + int i; + cps->nrOptions = cps->comboCnt = 0; + for(i=0; ioption[i].min = cps->option[i].max = cps->option[i].value = 0; + cps->option[i].textValue = 0; + } +} + +char *engineNames[] = { +"first", +"second" +}; + +void +InitEngine(ChessProgramState *cps, int n) +{ // [HGM] all engine initialiation put in a function that does one engine + + ClearOptions(cps); + + cps->which = engineNames[n]; + cps->maybeThinking = FALSE; + cps->pr = NoProc; + cps->isr = NULL; + cps->sendTime = 2; + cps->sendDrawOffers = 1; + + cps->program = appData.chessProgram[n]; + cps->host = appData.host[n]; + cps->dir = appData.directory[n]; + cps->initString = appData.engInitString[n]; + cps->computerString = appData.computerString[n]; + cps->useSigint = TRUE; + cps->useSigterm = TRUE; + cps->reuse = appData.reuse[n]; + cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second + cps->useSetboard = FALSE; + cps->useSAN = FALSE; + cps->usePing = FALSE; + cps->lastPing = 0; + cps->lastPong = 0; + cps->usePlayother = FALSE; + cps->useColors = TRUE; + cps->useUsermove = FALSE; + cps->sendICS = FALSE; + cps->sendName = appData.icsActive; + cps->sdKludge = FALSE; + cps->stKludge = FALSE; + TidyProgramName(cps->program, cps->host, cps->tidy); + cps->matchWins = 0; + safeStrCpy(cps->variants, appData.variant, MSG_SIZ); + cps->analysisSupport = 2; /* detect */ + cps->analyzing = FALSE; + cps->initDone = FALSE; + + /* New features added by Tord: */ + cps->useFEN960 = FALSE; + cps->useOOCastle = TRUE; + /* End of new features added by Tord. */ + cps->fenOverride = appData.fenOverride[n]; + + /* [HGM] time odds: set factor for each machine */ + cps->timeOdds = appData.timeOdds[n]; + + /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/ + cps->accumulateTC = appData.accumulateTC[n]; + cps->maxNrOfSessions = 1; + + /* [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]; + + cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */ + cps->isUCI = appData.isUCI[n]; /* [AS] */ + cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */ + + if (appData.protocolVersion[n] > PROTOVER + || appData.protocolVersion[n] < 1) + { + char buf[MSG_SIZ]; + int len; + + len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), + appData.protocolVersion[n]); + if( (len > MSG_SIZ) && appData.debugMode ) + fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); + + DisplayFatalError(buf, 0, 2); + } + else + { + cps->protocolVersion = appData.protocolVersion[n]; + } + + InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard +} + +ChessProgramState *savCps; + +void +LoadEngine() +{ + int i; + if(WaitForEngine(savCps, LoadEngine)) return; + CommonEngineInit(); // recalculate time odds + if(gameInfo.variant != StringToVariant(appData.variant)) { + // we changed variant when loading the engine; this forces us to reset + Reset(TRUE, savCps != &first); + EditGameEvent(); // for consistency with other path, as Reset changes mode + } + InitChessProgram(savCps, FALSE); + SendToProgram("force\n", savCps); + DisplayMessage("", ""); + if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove); + for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps); + ThawUI(); + SetGNUMode(); +} + +void +ReplaceEngine(ChessProgramState *cps, int n) +{ + EditGameEvent(); + UnloadEngine(cps); + appData.noChessProgram = FALSE; + appData.clockMode = TRUE; + InitEngine(cps, n); + if(n) return; // only startup first engine immediately; second can wait + savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-( + LoadEngine(); +} + +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) +{ + 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(resetOptions); appData.fenOverride[0] = NULL; + ParseArgsFromString(buf); + SwapEngines(i); + ReplaceEngine(cps, i); + return; + } + p = engineName; + while(q = strchr(p, SLASH)) p = q+1; + 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] = 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%s%s%s\n", p, appData.directory[i], + useNick ? " -fn \"" : "", + useNick ? nickName : "", + useNick ? "\"" : "", + v1 ? " -firstProtocolVersion 1" : "", + hasBook ? "" : " -fNoOwnBookUCI", + isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "", + storeVariant ? " -variant " : "", + storeVariant ? VariantName(gameInfo.variant) : ""); + firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1); + snprintf(firstChessProgramNames, len, "%s%s", q, buf); + if(q) free(q); + } + ReplaceEngine(cps, i); +} + +void +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 GetTimeMark(&programStartTime); srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level + pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended ClearProgramStats(); programStats.ok_to_send = 1; @@ -711,166 +1010,24 @@ 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; - first.which = "first"; - second.which = "second"; - first.maybeThinking = second.maybeThinking = FALSE; - first.pr = second.pr = NoProc; - first.isr = second.isr = NULL; - first.sendTime = second.sendTime = 2; - first.sendDrawOffers = 1; - if (appData.firstPlaysBlack) { - first.twoMachinesColor = "black\n"; - second.twoMachinesColor = "white\n"; - } else { - first.twoMachinesColor = "white\n"; - second.twoMachinesColor = "black\n"; - } - first.program = appData.firstChessProgram; - second.program = appData.secondChessProgram; - first.host = appData.firstHost; - second.host = appData.secondHost; - first.dir = appData.firstDirectory; - second.dir = appData.secondDirectory; - first.other = &second; - second.other = &first; - first.initString = appData.initString; - second.initString = appData.secondInitString; - first.computerString = appData.firstComputerString; - second.computerString = appData.secondComputerString; - first.useSigint = second.useSigint = TRUE; - first.useSigterm = second.useSigterm = TRUE; - first.reuse = appData.reuseFirst; - second.reuse = appData.reuseSecond; - first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second - second.nps = appData.secondNPS; - first.useSetboard = second.useSetboard = FALSE; - first.useSAN = second.useSAN = FALSE; - first.usePing = second.usePing = FALSE; - first.lastPing = second.lastPing = 0; - first.lastPong = second.lastPong = 0; - first.usePlayother = second.usePlayother = FALSE; - first.useColors = second.useColors = TRUE; - first.useUsermove = second.useUsermove = FALSE; - first.sendICS = second.sendICS = FALSE; - first.sendName = second.sendName = appData.icsActive; - first.sdKludge = second.sdKludge = FALSE; - first.stKludge = second.stKludge = FALSE; - TidyProgramName(first.program, first.host, first.tidy); - TidyProgramName(second.program, second.host, second.tidy); - first.matchWins = second.matchWins = 0; - safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0])); - safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0])); - first.analysisSupport = second.analysisSupport = 2; /* detect */ - first.analyzing = second.analyzing = FALSE; - first.initDone = second.initDone = FALSE; - - /* New features added by Tord: */ - first.useFEN960 = FALSE; second.useFEN960 = FALSE; - first.useOOCastle = TRUE; second.useOOCastle = TRUE; - /* End of new features added by Tord. */ - first.fenOverride = appData.fenOverride1; - second.fenOverride = appData.fenOverride2; - - /* [HGM] time odds: set factor for each machine */ - first.timeOdds = appData.firstTimeOdds; - second.timeOdds = appData.secondTimeOdds; - { float norm = 1; - if(appData.timeOddsMode) { - norm = first.timeOdds; - if(norm > second.timeOdds) norm = second.timeOdds; - } - first.timeOdds /= norm; - second.timeOdds /= norm; - } - - /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/ - first.accumulateTC = appData.firstAccumulateTC; - second.accumulateTC = appData.secondAccumulateTC; - first.maxNrOfSessions = second.maxNrOfSessions = 1; - - /* [HGM] debug */ - first.debug = second.debug = FALSE; - first.supportsNPS = second.supportsNPS = UNKNOWN; - - /* [HGM] options */ - first.optionSettings = appData.firstOptions; - second.optionSettings = appData.secondOptions; - - first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */ - second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */ - first.isUCI = appData.firstIsUCI; /* [AS] */ - second.isUCI = appData.secondIsUCI; /* [AS] */ - first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */ - second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */ - - if (appData.firstProtocolVersion > PROTOVER - || appData.firstProtocolVersion < 1) - { - char buf[MSG_SIZ]; - int len; - - len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), - appData.firstProtocolVersion); - if( (len > MSG_SIZ) && appData.debugMode ) - fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); - - DisplayFatalError(buf, 0, 2); - } - else - { - first.protocolVersion = appData.firstProtocolVersion; - } - - if (appData.secondProtocolVersion > PROTOVER - || appData.secondProtocolVersion < 1) - { - char buf[MSG_SIZ]; - int len; - - len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), - appData.secondProtocolVersion); - if( (len > MSG_SIZ) && appData.debugMode ) - fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); + InitEngine(&first, 0); + InitEngine(&second, 1); + CommonEngineInit(); - DisplayFatalError(buf, 0, 2); - } - else - { - second.protocolVersion = appData.secondProtocolVersion; - } + 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.searchTime != NULLCHAR || appData.noChessProgram) { } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode appData.clockMode = FALSE; first.sendTime = second.sendTime = 0; @@ -886,15 +1043,6 @@ InitBackEnd1() } #endif - if (appData.noChessProgram) { - programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING)); - sprintf(programVersion, "%s", PACKAGE_STRING); - } else { - /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ - programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); - sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); - } - if (!appData.icsActive) { char buf[MSG_SIZ]; int len; @@ -970,8 +1118,6 @@ InitBackEnd1() } } - InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard - InitEngineUCI( installDir, &second ); } int NextIntegerFromString( char ** str, long * value ) @@ -1175,6 +1321,135 @@ InitBackEnd2() } } +int +CalculateIndex(int index, int gameNr) +{ // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account) + int res; + if(index > 0) return index; // fixed nmber + if(index == 0) return 1; + res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc + if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind + return res; +} + +int +LoadGameOrPosition(int gameNr) +{ // [HGM] taken out of MatchEvent and NextMatchGame (to combine it) + if (*appData.loadGameFile != NULLCHAR) { + if (!LoadGameFromFile(appData.loadGameFile, + CalculateIndex(appData.loadGameIndex, gameNr), + appData.loadGameFile, FALSE)) { + DisplayFatalError(_("Bad game file"), 0, 1); + return 0; + } + } else if (*appData.loadPositionFile != NULLCHAR) { + if (!LoadPositionFromFile(appData.loadPositionFile, + CalculateIndex(appData.loadPositionIndex, gameNr), + appData.loadPositionFile)) { + DisplayFatalError(_("Bad position file"), 0, 1); + return 0; + } + } + return 1; +} + +void +ReserveGame(int gameNr, char resChar) +{ + FILE *tf = fopen(appData.tourneyFile, "r+"); + char *p, *q, c, buf[MSG_SIZ]; + if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match + safeStrCpy(buf, lastMsg, MSG_SIZ); + DisplayMessage(_("Pick new game"), ""); + flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it + ParseArgsFromFile(tf); + p = q = appData.results; + if(appData.debugMode) { + char *r = appData.participants; + fprintf(debugFP, "results = '%s'\n", p); + while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++; + fprintf(debugFP, "\n"); + } + while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!) + nextGame = q - p; + q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one! + 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(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char + q[nextGame] = '*'; + } + fseek(tf, -(strlen(p)+4), SEEK_END); + c = fgetc(tf); + if(c != '"') // depending on DOS or Unix line endings we can be one off + fseek(tf, -(strlen(p)+2), SEEK_END); + else fseek(tf, -(strlen(p)+3), SEEK_END); + fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing + DisplayMessage(buf, ""); + free(p); appData.results = q; + if(nextGame <= appData.matchGames && resChar != ' ' && + (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. + } +} + +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(-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; + return; + } + } else + if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically + DisplayFatalError(_("Can't have a match with no chess programs"), + 0, 2); + return; + } + matchMode = mode; + matchGame = roundNr = 1; + first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches + NextMatchGame(); +} + void InitBackEnd3 P((void)) { @@ -1235,6 +1510,12 @@ InitBackEnd3 P((void)) DisplayMessage("", ""); if (StrCaseCmp(appData.initialMode, "") == 0) { initialMode = BeginningOfGame; + if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back + gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted + ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it + gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position" + ModeHighlight(); + } } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) { initialMode = TwoMachinesPlay; } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) { @@ -1261,34 +1542,14 @@ InitBackEnd3 P((void)) } if (appData.matchMode) { - /* Set up machine vs. machine match */ - if (appData.noChessProgram) { - DisplayFatalError(_("Can't have a match with no chess programs"), - 0, 2); - return; - } - matchMode = TRUE; - matchGame = 1; - if (*appData.loadGameFile != NULLCHAR) { - int index = appData.loadGameIndex; // [HGM] autoinc - if(index<0) lastIndex = index = 1; - if (!LoadGameFromFile(appData.loadGameFile, - index, - appData.loadGameFile, FALSE)) { - DisplayFatalError(_("Bad game file"), 0, 1); - return; - } - } else if (*appData.loadPositionFile != NULLCHAR) { - int index = appData.loadPositionIndex; // [HGM] autoinc - if(index<0) lastIndex = index = 1; - if (!LoadPositionFromFile(appData.loadPositionFile, - index, - appData.loadPositionFile)) { - DisplayFatalError(_("Bad position file"), 0, 1); - return; - } - } - TwoMachinesEvent(); + if(appData.tourneyFile[0]) { // start tourney from command line + FILE *f; + if(f = fopen(appData.tourneyFile, "r")) { + ParseArgsFromFile(f); // make sure tourney parmeters re known + fclose(f); + } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file + } + MatchEvent(TRUE); } else if (*appData.cmailGameName != NULLCHAR) { /* Set up cmail mode */ ReloadCmailMsgEvent(TRUE); @@ -2919,9 +3180,9 @@ read_from_ics(isr, closure, data, count, error) } } // [HGM] chat: end of patch + backup = i; if (appData.zippyTalk || appData.zippyPlay) { /* [DM] Backup address for color zippy lines */ - backup = i; #if ZIPPY if (loggedOn == TRUE) if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) || @@ -3086,6 +3347,8 @@ read_from_ics(isr, closure, data, count, error) continue; } + if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't + if (looking_at(buf, &i, "\\ ")) { if (prevColor != ColorNormal) { if (oldi > next_out) { @@ -4814,9 +5077,7 @@ ProcessICSInitScript(f) } -static int lastX, lastY, selectFlag, dragging, sweepX, sweepY; -static ChessSquare substitute = EmptySquare; -static char defaultPromoChar; +static int lastX, lastY, selectFlag, dragging; void Sweep(int step) @@ -4826,7 +5087,7 @@ Sweep(int step) if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare; if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn; if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare; - if(toY != BOARD_HEIGHT-1 && toY != 0) pawn = EmptySquare; + if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare; do { promoSweep -= step; if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap @@ -4837,30 +5098,22 @@ Sweep(int step) } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || appData.testLegality && (promoSweep == king || gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep)); - boards[currentMove][sweepY][sweepX] = promoSweep; - DrawPosition(FALSE, boards[currentMove]); + ChangeDragPiece(promoSweep); } int PromoScroll(int x, int y) { int step = 0; + if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE; - if(!selectFlag) { - if(y - lastY < 4 && lastY - y < 4) return FALSE; // assume dragging until significant distance - if(substitute != EmptySquare && ((promoSweep >= BlackPawn) == flipView ? y <= lastY : y >= lastY)) { // we started dragging - defaultPromoChar = ToLower(PieceToChar(promoSweep)); // fix choice - promoSweep = EmptySquare; - return FALSE; - } - DragPieceEnd(x, y); dragging = 0; - selectFlag = 1; // we committed to sweep-selecting - } - if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return TRUE; + if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE; if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; - if(!step) return TRUE; + if(!step) return FALSE; lastX = x; lastY = y; - Sweep(step); - return TRUE; + if((promoSweep < BlackPawn) == flipView) step = -step; + if(step > 0) selectFlag = 1; + if(!selectFlag) Sweep(step); + return FALSE; } void @@ -5015,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 @@ -5062,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; } @@ -5110,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]); } @@ -5127,9 +5412,10 @@ UnLoadPV() void MovePV(int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) - int margin = h>>3, step = 0, dist; + int margin = h>>3, step = 0; // we must somehow check if right button is still down (might be released off board!) + if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-( if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return; if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; if(!step) return; @@ -5813,14 +6099,12 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) *promoChoice = PieceToChar(BlackQueen); // Queen as good as any return FALSE; } - // with sweep-selection we take the selected default - if(appData.sweepSelect) { - *promoChoice = defaultPromoChar; - return FALSE; - } // give caller the default choice even if we will not make it *promoChoice = ToLower(PieceToChar(defaultPromoChoice)); if(gameInfo.variant == VariantShogi) *promoChoice = '+'; + if(appData.sweepSelect && gameInfo.variant != VariantGreat + && gameInfo.variant != VariantShogi + && gameInfo.variant != VariantSuper) return FALSE; if(autoQueen) return FALSE; // predetermined // suppress promotion popup on illegal moves that are not premoves @@ -6433,18 +6717,23 @@ ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging int CanPromote(ChessSquare piece, int y) { + if(gameMode == EditPosition) return FALSE; // no promotions when editing position + // some variants have fixed promotion piece, no promotion at all, or another selection mechanism + if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi || + gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || + gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk) return FALSE; return (piece == BlackPawn && y == 1 || piece == WhitePawn && y == BOARD_HEIGHT-2 || - gameInfo.variant != VariantSuper && - (piece == BlackLance && y == 1 || - piece == WhiteLance && y == BOARD_HEIGHT-2) ); + piece == BlackLance && y == 1 || + piece == WhiteLance && y == BOARD_HEIGHT-2 ); } void LeftClick(ClickType clickType, int xPix, int yPix) { - int x, y, canPromote; + int x, y; Boolean saveAnimate; - static int second = 0, promotionChoice = 0; + static int second = 0, promotionChoice = 0, clearFlag = 0; char promoChoice = NULLCHAR; ChessSquare piece; @@ -6467,21 +6756,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece - defaultPromoChar = ToLower(PieceToChar(defaultPromoChoice = promoSweep)); - if(gameInfo.variant == VariantShogi) defaultPromoChar = (promoSweep == boards[currentMove][fromY][fromX] ? '=' : '+'); + defaultPromoChoice = promoSweep; promoSweep = EmptySquare; // terminate sweep promoDefaultAltered = TRUE; - if(savePiece != EmptySquare) { - boards[currentMove][sweepY][sweepX] = savePiece; savePiece = EmptySquare; - clickType = Press; x = toX; y = toY; // fake up-click on to-square to finish one-click move - } else x = fromX, y = fromY; // and fake up-click on same square otherwise - } - - if(substitute != EmptySquare) { - boards[currentMove][fromY][fromX] = substitute; - substitute = EmptySquare; - DragPieceEnd(xPix, yPix); dragging = 0; - DrawPosition(FALSE, boards[currentMove]); + if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting } if(promotionChoice) { // we are waiting for a click to indicate promotion piece @@ -6513,17 +6791,18 @@ void LeftClick(ClickType clickType, int xPix, int yPix) return; if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered) - fromX = fromY = -1; // second click on piece after altering default treated as first click + fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click - if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for + if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack || gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove)); defaultPromoChoice = DefaultPromoChoice(side); - } + } autoQueen = appData.alwaysPromoteToQueen; if (fromX == -1) { + int originalY = y; gatingPiece = EmptySquare; if (clickType != Press) { if(dragging) { // [HGM] from-square must have been reset due to game end since last press @@ -6532,31 +6811,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } return; } - if(appData.oneClick && OnlyMove(&x, &y, FALSE)) { - if(appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY)) { - promoSweep = defaultPromoChoice; - savePiece = boards[currentMove][sweepY = toY = y][sweepX = toX = x]; - selectFlag = 0; lastX = xPix; lastY = yPix; - Sweep(0); // Pawn that is going to promote: preview promotion piece - return; - } - } else { + fromX = x; fromY = y; + if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || + // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection + appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) { /* First square */ - if (OKToStartUserMove(x, y)) { - sweepX = fromX = x; - sweepY = fromY = y; + if (OKToStartUserMove(fromX, fromY)) { second = 0; MarkTargetSquares(0); - if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { + DragPieceBegin(xPix, yPix); dragging = 1; + if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { promoSweep = defaultPromoChoice; - substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix; + selectFlag = 0; lastX = xPix; lastY = yPix; Sweep(0); // Pawn that is going to promote: preview promotion piece + DisplayMessage("", _("Pull pawn backwards to under-promote")); } - DragPieceBegin(xPix, yPix); dragging = 1; if (appData.highlightDragging) { - SetHighlights(x, y, -1, -1); + SetHighlights(fromX, fromY, -1, -1); } - } + } else fromX = fromY = -1; return; } } @@ -6597,15 +6870,15 @@ void LeftClick(ClickType clickType, int xPix, int yPix) y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1)) gatingPiece = boards[currentMove][fromY][fromX]; else gatingPiece = EmptySquare; - sweepX = fromX = x; - sweepY = fromY = y; dragging = 1; + fromX = x; + fromY = y; dragging = 1; MarkTargetSquares(0); + DragPieceBegin(xPix, yPix); if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { promoSweep = defaultPromoChoice; - substitute = piece; selectFlag = 0; lastX = xPix; lastY = yPix; + selectFlag = 0; lastX = xPix; lastY = yPix; Sweep(0); // Pawn that is going to promote: preview promotion piece } - DragPieceBegin(xPix, yPix); } } if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on @@ -6617,6 +6890,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if (clickType == Release && x == fromX && y == fromY) { DragPieceEnd(xPix, yPix); dragging = 0; + if(clearFlag) { + // a deferred attempt to click-click move an empty square on top of a piece + boards[currentMove][y][x] = EmptySquare; + ClearHighlights(); + DrawPosition(FALSE, boards[currentMove]); + fromX = fromY = -1; clearFlag = 0; + return; + } if (appData.animateDragging) { /* Undo animation damage if any */ DrawPosition(FALSE, NULL); @@ -6636,12 +6917,20 @@ void LeftClick(ClickType clickType, int xPix, int yPix) return; } + clearFlag = 0; + /* we now have a different from- and (possibly off-board) to-square */ /* Completed move */ toX = x; toY = y; saveAnimate = appData.animate; if (clickType == Press) { + if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { + // must be Edit Position mode with empty-square selected + fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag + if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click + return; + } /* Finish clickclick move */ if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); @@ -6707,14 +6996,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) DisplayMessage("Click in holdings to choose piece", ""); return; } - if(appData.sweepSelect && clickType == Press) { - lastX = xPix; lastY = yPix; - ChessSquare piece = boards[currentMove][fromY][fromX]; - promoSweep = defaultPromoChoice; - if(gameInfo.variant == VariantShogi) promoSweep = PROMOTED piece; - sweepX = toX; sweepY = toY; - selectFlag = 1; Sweep(0); - } else PromotionPopUp(); + PromotionPopUp(); } else { int oldMove = currentMove; UserMoveEvent(fromX, fromY, toX, toY, promoChoice); @@ -6848,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 @@ -7296,6 +7639,8 @@ void DeferredBookMove(void) HandleMachineMove(savedMessage, savedState); } +static int savedWhitePlayer, savedBlackPlayer, pairingReceived; + void HandleMachineMove(message, cps) char *message; @@ -7310,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 @@ -7882,7 +8235,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"), _(cps->which), cps->program, cps->host, message); RemoveInputSource(cps->isr); - DisplayFatalError(buf1, 0, 1); + if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else { + if(cps == &first) appData.noChessProgram = TRUE; + DisplayError(buf1, 0); + } return; } @@ -8974,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) @@ -9155,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; } @@ -9172,49 +9534,302 @@ StartChessProgram(cps) } } - void TwoMachinesEventIfReady P((void)) { + static int curMess = 0; if (first.lastPing != first.lastPong) { - DisplayMessage("", _("Waiting for first chess program")); + if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1; ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000 return; } if (second.lastPing != second.lastPong) { - DisplayMessage("", _("Waiting for second chess program")); + if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2; ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000 return; } + DisplayMessage("", ""); curMess = 0; ThawUI(); TwoMachinesEvent(); } -void -NextMatchGame P((void)) +char * +MakeName(char *template) { - int index; /* [HGM] autoinc: step load index during match */ - Reset(FALSE, TRUE); - if (*appData.loadGameFile != NULLCHAR) { - index = appData.loadGameIndex; - if(index < 0) { // [HGM] autoinc - lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1; - if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1; - } - LoadGameFromFile(appData.loadGameFile, - index, - appData.loadGameFile, FALSE); - } else if (*appData.loadPositionFile != NULLCHAR) { - index = appData.loadPositionIndex; - if(index < 0) { // [HGM] autoinc - lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1; - if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1; + 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; } - LoadPositionFromFile(appData.loadPositionFile, - index, - appData.loadPositionFile); + snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p); } - TwoMachinesEventIfReady(); + 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) { + 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 { + 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; + appData.clockMode = TRUE; + SetGNUMode(); + return 1; +} + +#define MAXENGINES 1000 +char *command[MAXENGINES], *mnemonic[MAXENGINES]; + +void NamesToList(char *names, char **engineList, char **engineMnemonic) +{ + char buf[MSG_SIZ], *p, *q; + int i=1; + while(*names) { + p = names; q = buf; + while(*p && *p != '\n') *q++ = *p++; + *q = 0; + if(engineList[i]) free(engineList[i]); + engineList[i] = strdup(buf); + if(*p == '\n') p++; + TidyProgramName(engineList[i], "localhost", buf); + if(engineMnemonic[i]) free(engineMnemonic[i]); + if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) { + strcat(buf, " ("); + sscanf(q + 8, "%s", buf + strlen(buf)); + strcat(buf, ")"); + } + engineMnemonic[i] = strdup(buf); + names = p; i++; + if(i > MAXENGINES - 2) break; + } + engineList[i] = NULL; +} + +// following implemented as macro to avoid type limitations +#define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp; + +void SwapEngines(int n) +{ // swap settings for first engine and other engine (so far only some selected options) + int h; + char *p; + if(n == 0) return; + SWAP(directory, p) + SWAP(chessProgram, p) + SWAP(isUCI, h) + SWAP(hasOwnBookUCI, h) + SWAP(protocolVersion, h) + SWAP(reuse, h) + SWAP(scoreIsAbsolute, h) + SWAP(timeOdds, h) + SWAP(logo, p) + SWAP(pgnName, p) +} + +void +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; + for(i=0; i 0) { + roundsPerCycle = nPlayers - appData.tourneyType; + pairingsPerRound = appData.tourneyType; + } + gamesPerRound = pairingsPerRound * appData.defaultMatchGames; + gamesPerCycle = gamesPerRound * roundsPerCycle; + appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match + curCycle = nr / gamesPerCycle; nr %= gamesPerCycle; + curRound = nr / gamesPerRound; nr %= gamesPerRound; + curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames; + matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file + roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1; + + if(appData.cycleSync) *syncInterval = gamesPerCycle; + if(appData.roundSync) *syncInterval = gamesPerRound; + + if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame); + + if(appData.tourneyType == 0) { + if(curPairing == (nPlayers-1)/2 ) { + *whitePlayer = curRound; + *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd + } else { + *whitePlayer = curRound - pairingsPerRound + curPairing; + if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1); + *blackPlayer = curRound + pairingsPerRound - curPairing; + if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1); + } + } else if(appData.tourneyType > 0) { + *whitePlayer = curPairing; + *blackPlayer = curRound + appData.tourneyType; + } + + // take care of white/black alternation per round. + // For cycles and games this is already taken care of by default, derived from matchGame! + return curRound & 1; +} + +int +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; + FILE *tf; + if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game + tf = fopen(appData.tourneyFile, "r"); + if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; } + ParseArgsFromFile(tf); fclose(tf); + InitTimeControls(); // TC might be altered from tourney file + + nPlayers = CountPlayers(appData.participants); // count participants + if(appData.tourneyType < 0 && appData.pairingEngine[0]) { + if(nr>=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; + while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; } + if(firstBusy/syncInterval < (nextGame/syncInterval)) { + DisplayMessage(_("Waiting for other game(s)"),""); + waitingForGame = TRUE; + ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish + return 0; + } + waitingForGame = FALSE; + } + + if(first.pr != NoProc) return 1; // engines already loaded + + // redefine engines, engine dir, etc. + NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines + SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line + SwapEngines(1); + SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line + SwapEngines(1); // and make that valid for second engine by swapping + InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. + InitEngine(&second, 1); + CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes + return 1; +} + +void +NextMatchGame() +{ // performs game initialization that does not invoke engines, and then tries to start the game + int firstWhite, swapColors = 0; + if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed + firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default + firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round + 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 + Reset(FALSE, first.pr != NoProc); + appData.noChessProgram = FALSE; + if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file + TwoMachinesEvent(); } void UserAdjudicationEvent( int result ) @@ -9264,7 +9879,7 @@ GameEnds(result, resultDetails, whosays) { GameMode nextGameMode; int isIcsGame; - char buf[MSG_SIZ], popupRequested = 0; + char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL; if(endingGame) return; /* [HGM] crash: forbid recursion */ endingGame = 1; @@ -9566,9 +10181,11 @@ GameEnds(result, resultDetails, whosays) second.pr = NoProc; } - if (matchMode && gameMode == TwoMachinesPlay) { + if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) { + char resChar = '='; switch (result) { case WhiteWins: + resChar = '+'; if (first.twoMachinesColor[0] == 'w') { first.matchWins++; } else { @@ -9576,27 +10193,30 @@ GameEnds(result, resultDetails, whosays) } break; case BlackWins: + resChar = '-'; if (first.twoMachinesColor[0] == 'b') { first.matchWins++; } else { second.matchWins++; } break; + case GameUnfinished: + resChar = ' '; default: break; } - if (matchGame < appData.matchGames) { - char *tmp; - if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */ - tmp = first.twoMachinesColor; - first.twoMachinesColor = second.twoMachinesColor; - second.twoMachinesColor = tmp; - } + + 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 + ReserveGame(nextGame, resChar); // sets nextGame + 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 && !abortMatch) { gameMode = nextGameMode; - matchGame++; - if(appData.matchPause>10000 || appData.matchPause<10) - appData.matchPause = 10000; /* [HGM] make pause adjustable */ - ScheduleDelayedEvent(NextMatchGame, appData.matchPause); + matchGame = nextGame; // this will be overruled in tourney mode! + GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause + ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself) endingGame = 0; /* [HGM] crash */ return; } else { @@ -9622,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 = 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); } } @@ -9664,15 +10292,23 @@ FeedMovesToProgram(cps, upto) } -void +int ResurrectChessProgram() { /* The chess program may have exited. If so, restart it and feed it all the moves made so far. */ + static int doInit = 0; - if (appData.noChessProgram || first.pr != NoProc) return; + if (appData.noChessProgram) return 1; - StartChessProgram(&first); + if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?) + if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit + if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort + doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again + } else { + if (first.pr != NoProc) return 1; + StartChessProgram(&first); + } InitChessProgram(&first, FALSE); FeedMovesToProgram(&first, currentMove); @@ -9689,6 +10325,7 @@ ResurrectChessProgram() SendToProgram("analyze\n", &first); first.analyzing = TRUE; } + return 1; } /* @@ -9762,6 +10399,7 @@ Reset(redraw, init) ResetClocks(); timeRemaining[0][0] = whiteTimeRemaining; timeRemaining[1][0] = blackTimeRemaining; + if (first.pr == NULL) { StartChessProgram(&first); } @@ -10931,6 +11569,7 @@ SaveGameToFile(filename, append) { FILE *f; char buf[MSG_SIZ]; + int result; if (strcmp(filename, "-") == 0) { return SaveGame(stdout, 0, NULL); @@ -10941,7 +11580,14 @@ SaveGameToFile(filename, append) DisplayError(buf, errno); return FALSE; } else { - return SaveGame(f, 0, NULL); + safeStrCpy(buf, lastMsg, MSG_SIZ); + DisplayMessage(_("Waiting for access to save file"), ""); + flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing + DisplayMessage(_("Saving game"), ""); + if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry... + result = SaveGame(f, 0, NULL); + DisplayMessage(buf, ""); + return result; } } } @@ -11303,7 +11949,13 @@ SavePositionToFile(filename) DisplayError(buf, errno); return FALSE; } else { + safeStrCpy(buf, lastMsg, MSG_SIZ); + DisplayMessage(_("Waiting for access to save file"), ""); + flock(fileno(f), LOCK_EX); // [HGM] lock + DisplayMessage(_("Saving position"), ""); + lseek(fileno(f), 0, SEEK_END); // better safe than sorry... SavePosition(f, 0, NULL); + DisplayMessage(buf, ""); return TRUE; } } @@ -11725,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); } @@ -11815,6 +12470,7 @@ void EditTagsEvent() { char *tags = PGNTags(&gameInfo); + bookUp = FALSE; EditTagsPopUp(tags, NULL); free(tags); } @@ -12066,16 +12722,18 @@ SettingsMenuIfReady() } int -WaitForSecond(DelayedEventCallback retry) +WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry) { - if (second.pr == NULL) { - StartChessProgram(&second); - if (second.protocolVersion == 1) { + char buf[MSG_SIZ]; + if (cps->pr == NULL) { + StartChessProgram(cps); + if (cps->protocolVersion == 1) { retry(); } else { /* kludge: allow timeout for initial "feature" command */ FreezeUI(); - DisplayMessage("", _("Starting second chess program")); + snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which); + DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); } return 1; @@ -12090,6 +12748,9 @@ TwoMachinesEvent P((void)) char buf[MSG_SIZ]; ChessProgramState *onmove; char *bookHit = NULL; + static int stalling = 0; + TimeMark now; + long wait; if (appData.noChessProgram) return; @@ -12123,16 +12784,31 @@ TwoMachinesEvent P((void)) // forwardMostMove = currentMove; TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... - ResurrectChessProgram(); /* in case first program isn't running */ - if(WaitForSecond(TwoMachinesEventIfReady)) return; - DisplayMessage("", ""); - InitChessProgram(&second, FALSE); - SendToProgram("force\n", &second); + if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */ + + if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position - ScheduleDelayedEvent(TwoMachinesEvent, 10); + ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); + return; + } + if(!stalling) { + InitChessProgram(&second, FALSE); // unbalances ping of second engine + SendToProgram("force\n", &second); + stalling = 1; + ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load + if(appData.matchPause>10000 || appData.matchPause<10) + appData.matchPause = 10000; /* [HGM] make pause adjustable */ + wait = SubtractTimeMarks(&now, &pauseStart); + if(wait < appData.matchPause) { + ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait); + return; + } + stalling = 0; + DisplayMessage("", ""); if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); if (appData.debugMode) { @@ -12154,7 +12830,7 @@ TwoMachinesEvent P((void)) } else { onmove = &second; } - + if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]); SendToProgram(first.computerString, &first); if (first.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", second.tidy); @@ -12975,6 +13651,7 @@ ForwardInner(target) if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } + DisplayBook(currentMove); } @@ -13077,6 +13754,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 @@ -13434,19 +14112,19 @@ SetGameInfo() gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); - if (matchGame > 0) { + if (roundNr > 0) { char buf[MSG_SIZ]; - snprintf(buf, MSG_SIZ, "%d", matchGame); + snprintf(buf, MSG_SIZ, "%d", roundNr); gameInfo.round = StrSave(buf); } else { 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; @@ -13728,16 +14406,21 @@ SendToProgram(message, cps) outCount = OutputToProcess(cps->pr, message, count, &error); if (outCount < count && !exiting && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */ + if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which)); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { - gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program); + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; } + gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ } else { - gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; } + gameInfo.result = res; } gameInfo.resultDetails = StrSave(buf); } + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; } if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1; } } @@ -13757,18 +14440,23 @@ ReceiveFromProgram(isr, closure, message, count, error) if (isr != cps->isr) return; /* Killed intentionally */ 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 */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { - gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program); + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; } + gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ } else { - gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; } + gameInfo.result = res; } gameInfo.resultDetails = StrSave(buf); } - RemoveInputSource(cps->isr); + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; } if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1; } else { snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"), @@ -14109,7 +14797,8 @@ FeatureDone(cps, val) DelayedEventCallback cb = GetDelayedEvent(); if ((cb == InitBackEnd3 && cps == &first) || (cb == SettingsMenuIfReady && cps == &second) || - (cb == TwoMachinesEventIfReady && cps == &second)) { + (cb == LoadEngine) || + (cb == TwoMachinesEventIfReady)) { CancelDelayedEvent(); ScheduleDelayedEvent(cb, val ? 1 : 3600000); } @@ -14302,6 +14991,55 @@ AskQuestionEvent(title, question, replyPrefix, which) } void +TypeInEvent(char firstChar) +{ + if ((gameMode == BeginningOfGame && !appData.icsActive) || + gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || + gameMode == AnalyzeMode || gameMode == EditGame || + gameMode == EditPosition || gameMode == IcsExamining || + gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || + isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes + ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile || + gameMode == IcsObserving || gameMode == TwoMachinesPlay ) || + gameMode == Training) PopUpMoveDialog(firstChar); +} + +void +TypeInDoneEvent(char *move) +{ + Board board; + int n, fromX, fromY, toX, toY; + char promoChar; + ChessMove moveType; + + // [HGM] FENedit + if(gameMode == EditPosition && ParseFEN(board, &n, move) ) { + EditPositionPasteFEN(move); + return; + } + // [HGM] movenum: allow move number to be typed in any mode + if(sscanf(move, "%d", &n) == 1 && n != 0 ) { + ToNrEvent(2*n-1); + return; + } + + if (gameMode != EditGame && currentMove != forwardMostMove && + gameMode != Training) { + DisplayMoveError(_("Displayed move is not current")); + } else { + int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + &moveType, &fromX, &fromY, &toX, &toY, &promoChar); + if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized + if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { + UserMoveEvent(fromX, fromY, toX, toY, promoChar); + } else { + DisplayMoveError(_("Could not parse move")); + } + } +} + +void DisplayMove(moveNumber) int moveNumber; { @@ -15524,16 +16262,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; @@ -15558,19 +16290,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; @@ -15610,6 +16350,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; } @@ -15656,7 +16407,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();