X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=9204e1b042eaf78f73fa45654bfdd01f49abe0b4;hb=4099d0667f611390759e74b42cadf4325ee1f2cd;hp=6bd4418d5daed9d96f238e5d1d1e7361916b6c02;hpb=4d6bd700a90b76e2b34fbfba71e6f0fb61c556ae;p=xboard.git diff --git a/backend.c b/backend.c index 6bd4418..9204e1b 100644 --- a/backend.c +++ b/backend.c @@ -5,7 +5,7 @@ * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -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,9 @@ 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)); #ifdef WIN32 extern void ConsoleCreate(); @@ -241,6 +249,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,6 +276,10 @@ 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; +int promoDefaultAltered; /* States for ics_getting_history */ #define H_FALSE 0 @@ -311,23 +324,19 @@ char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ char* safeStrCpy( char *dst, const char *src, size_t count ) -{ - /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html - * - * usage: safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]); - */ - +{ // [HGM] made safe + int i; assert( dst != NULL ); assert( src != NULL ); assert( count > 0 ); - strncpy( dst, src, count ); - if( dst[ count-1 ] != '\0' ) + for(i=0; i 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; + + /* [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; + +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("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1"); + 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(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], + v1 ? " -firstProtocolVersion 1" : "", + hasBook ? "" : " -fNoOwnBookUCI", + isUCI ? " -fUCI" : "", + 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 InitBackEnd1() { int matched, min, sec; @@ -664,6 +922,7 @@ InitBackEnd1() 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; @@ -736,137 +995,12 @@ InitBackEnd1() /* [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"); - - DisplayFatalError(buf, 0, 2); - } - else - { - second.protocolVersion = appData.secondProtocolVersion; - } + InitEngine(&first, 0); + InitEngine(&second, 1); + CommonEngineInit(); 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; @@ -882,15 +1016,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; @@ -960,12 +1085,12 @@ InitBackEnd1() case VariantJanus: /* should work */ case VariantSuper: /* experimental */ case VariantGreat: /* experimental, requires legality testing to be off */ + case VariantSChess: /* S-Chess, should work */ + case VariantSpartan: /* should work */ break; } } - InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard - InitEngineUCI( installDir, &second ); } int NextIntegerFromString( char ** str, long * value ) @@ -1083,7 +1208,6 @@ ParseTimeControl(tc, ti, mps) long tc2; char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc; int min, sec=0; - int len; if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0; if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1) @@ -1092,16 +1216,16 @@ ParseTimeControl(tc, ti, mps) if(mps) snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti); - else + else snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti); } else { if(mps) snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc); - else + else snprintf(buf, MSG_SIZ, ":%s", mytc); } fullTimeControlString = StrSave(buf); // this should now be in PGN format - + if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) { return FALSE; } @@ -1170,6 +1294,120 @@ 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; + 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 + if(appData.tourneyFile[0]) { + ReserveGame(-1, 0); + if(nextGame > appData.matchGames) { + char buf[MSG_SIZ]; + 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)) { @@ -1230,6 +1468,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) { @@ -1256,34 +1500,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); @@ -2859,7 +3083,7 @@ read_from_ics(isr, closure, data, count, error) if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel for(p=0; p= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) { talker[0] = '['; strcat(talker, "] "); Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE); chattingPartner = p; break; @@ -2914,9 +3138,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) || @@ -3081,6 +3305,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) { @@ -3119,23 +3345,8 @@ read_from_ics(isr, closure, data, count, error) if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) { ICSInitScript(); have_sent_ICS_logon = 1; - /* if we don't send the login/password via icsLogon, use special readline - code for it */ - if (strlen(appData.icsLogon)==0) - { - sending_ICS_password = 0; // in case we come back to login - sending_ICS_login = 1; - }; continue; } - /* need to shadow the password */ - if (!sending_ICS_password && looking_at(buf, &i, "password:")) { - /* if we don't send the login/password via icsLogon, use special readline - code for it */ - if (strlen(appData.icsLogon)==0) - sending_ICS_password = 1; - continue; - } if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && (looking_at(buf, &i, "\n<12> ") || @@ -3858,7 +4069,7 @@ ParseBoard12(string) int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback; int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count; char to_play, board_chars[200]; - char move_str[500], str[500], elapsed_time[500]; + char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ]; char black[32], white[32]; Board board; int prevMove = currentMove; @@ -3899,7 +4110,7 @@ ParseBoard12(string) &ticking); if (n < 21) { - snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string); + snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string); DisplayError(str, 0); return; } @@ -4348,7 +4559,7 @@ ParseBoard12(string) if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!) if(appData.debugMode) fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf); - safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0])); + safeStrCpy(move_str, buf, MSG_SIZ); } valid = ParseOneMove(move_str, moveNum - 1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar) @@ -4377,11 +4588,12 @@ ParseBoard12(string) strcat(parseList[moveNum - 1], " "); strcat(parseList[moveNum - 1], elapsed_time); /* currentMoveString is set as a side-effect of ParseOneMove */ - if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '+'; + if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^'; safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0])); strcat(moveList[moveNum - 1], "\n"); - if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board + if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper + && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board for(k=0; k= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn; + if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare; + if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare; + do { + promoSweep -= step; + if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap + else if((int)promoSweep == -1) promoSweep = WhiteKing; + else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn; + else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing; + if(!step) step = 1; + } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || + appData.testLegality && (promoSweep == king || + gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep)); + ChangeDragPiece(promoSweep); +} + +int PromoScroll(int x, int y) +{ + int step = 0; + + if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE; + 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 FALSE; + lastX = x; lastY = y; + if((promoSweep < BlackPawn) == flipView) step = -step; + if(step > 0) selectFlag = 1; + if(!selectFlag) Sweep(step); + return FALSE; +} + +void +NextPiece(int step) +{ + ChessSquare piece = boards[currentMove][toY][toX]; + do { + pieceSweep -= step; + if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap + if((int)pieceSweep == -1) pieceSweep = BlackKing; + if(!step) step = -1; + } while(PieceToChar(pieceSweep) == '.'); + boards[currentMove][toY][toX] = pieceSweep; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][toY][toX] = piece; +} /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */ void AlphaRank(char *move, int n) @@ -4884,9 +5149,6 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) int *fromX, *fromY, *toX, *toY; char *promoChar; { - if (appData.debugMode) { - fprintf(debugFP, "move to parse: %s\n", move); - } *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr); switch (*moveType) { @@ -4964,6 +5226,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } +Boolean pushed = FALSE; void ParsePV(char *pv, Boolean storeComments) @@ -4973,6 +5236,10 @@ ParsePV(char *pv, Boolean storeComments) 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 @@ -5015,6 +5282,9 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f 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; + strncat(moveList[endPV-1], "\n", MOVE_LEN); if(storeComments) CoordsToAlgebraic(boards[endPV - 1], PosFlags(endPV - 1), @@ -5030,8 +5300,6 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f DrawPosition(TRUE, boards[currentMove]); } -static int lastX, lastY; - Boolean LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end) { @@ -5068,6 +5336,7 @@ UnLoadPV() if(endPV < 0) return; endPV = -1; currentMove = forwardMostMove; + if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation ClearPremoveHighlights(); DrawPosition(TRUE, boards[currentMove]); } @@ -5077,13 +5346,17 @@ MovePV(int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) int margin = h>>3, step = 0; - if(endPV < 0) return; // we must somehow check if right button is still down (might be released off board!) - if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else - if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else - if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1; + 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; lastX = x; lastY = y; + + if(pieceSweep != EmptySquare) { NextPiece(step); return; } + if(endPV < 0) return; + if(y < margin) step = 1; else + if(y > h - margin) step = -1; if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0; currentMove += step; if(currentMove == forwardMostMove) ClearPremoveHighlights(); else @@ -5316,8 +5589,8 @@ InitPosition(redraw) int i, j, pawnRow, overrule, oldx = gameInfo.boardWidth, oldy = gameInfo.boardHeight, - oldh = gameInfo.holdingsWidth, - oldv = gameInfo.variant; + oldh = gameInfo.holdingsWidth; + static int oldv; if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request @@ -5355,12 +5628,12 @@ InitPosition(redraw) if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant SetCharTable(pieceNickName, appData.pieceNickNames); else SetCharTable(pieceNickName, "............"); + pieces = FIDEArray; switch (gameInfo.variant) { case VariantFischeRandom: shuffleOpenings = TRUE; default: - pieces = FIDEArray; break; case VariantShatranj: pieces = ShatranjArray; @@ -5388,6 +5661,10 @@ InitPosition(redraw) gameInfo.boardWidth = 10; SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); break; + case VariantSChess: + SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek"); + gameInfo.holdingsSize = 7; + break; case VariantJanus: pieces = JanusArray; gameInfo.boardWidth = 10; @@ -5430,6 +5707,10 @@ InitPosition(redraw) pieces = KnightmateArray; SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); break; + case VariantSpartan: + pieces = SpartanArray; + SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k"); + break; case VariantFairy: pieces = fairyArray; SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk"); @@ -5500,7 +5781,7 @@ InitPosition(redraw) if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue; initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth]; initialPosition[pawnRow][j] = WhitePawn; - initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn; + initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn; if(gameInfo.variant == VariantXiangqi) { if(j&1) { initialPosition[pawnRow][j] = @@ -5544,6 +5825,14 @@ InitPosition(redraw) initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan; initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9; } + if( gameInfo.variant == VariantSChess ) { + initialPosition[1][0] = BlackMarshall; + initialPosition[2][0] = BlackAngel; + initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall; + initialPosition[5][BOARD_WIDTH-1] = WhiteAngel; + initialPosition[1][1] = initialPosition[2][1] = + initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1; + } if (appData.debugMode) { fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings); } @@ -5563,18 +5852,12 @@ InitPosition(redraw) if(oldx != gameInfo.boardWidth || oldy != gameInfo.boardHeight || + oldv != gameInfo.variant || oldh != gameInfo.holdingsWidth -#ifdef GOTHIC - || oldv == VariantGothic || // For licensing popups - gameInfo.variant == VariantGothic -#endif -#ifdef FALCON - || oldv == VariantFalcon || - gameInfo.variant == VariantFalcon -#endif ) InitDrawingSizes(-2 ,0); + oldv = gameInfo.variant; if (redraw) DrawPosition(TRUE, boards[currentMove]); } @@ -5598,7 +5881,8 @@ SendBoard(cps, moveNum) /* Kludge to set black to move, avoiding the troublesome and now * deprecated "black" command. */ - if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps); + if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move... + SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps); SendToProgram("edit\n", cps); SendToProgram("#\n", cps); @@ -5649,6 +5933,21 @@ SendBoard(cps, moveNum) setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } +ChessSquare +DefaultPromoChoice(int white) +{ + ChessSquare result; + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) + result = WhiteFerz; // no choice + else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) + result= WhiteKing; // in Suicide Q is the last thing we want + else if(gameInfo.variant == VariantSpartan) + result = white ? WhiteQueen : WhiteAngel; + else result = WhiteQueen; + if(!white) result = WHITE_TO_BLACK result; + return result; +} + static int autoQueen; // [HGM] oneclick int @@ -5676,6 +5975,12 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) promotionZoneSize = 3; } + // Treat Lance as Pawn when it is not representing Amazon + if(gameInfo.variant != VariantSuper) { + if(piece == WhiteLance) piece = WhitePawn; else + if(piece == BlackLance) piece = BlackPawn; + } + // next weed out all moves that do not touch the promotion zone at all if((int)piece >= BlackPawn) { if(toY >= promotionZoneSize && fromY >= promotionZoneSize) @@ -5721,19 +6026,25 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) *promoChoice = PieceToChar(BlackFerz); // no choice return FALSE; } - if(autoQueen) { // predetermined - if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers) - *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want - else *promoChoice = PieceToChar(BlackQueen); + // no sense asking what we must promote to if it is going to explode... + if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) { + *promoChoice = PieceToChar(BlackQueen); // Queen as good as any 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 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) || gameMode == IcsPlayingBlack && WhiteOnMove(currentMove); if(appData.testLegality && !premove) { moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), - fromY, fromX, toY, toX, NULLCHAR); + fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR); if(moveType != WhitePromotion && moveType != BlackPromotion) return FALSE; } @@ -5875,6 +6186,8 @@ OnlyMove(int *x, int *y, Boolean captures) { case IcsPlayingBlack: if(WhiteOnMove(currentMove)) return FALSE; break; + case EditGame: + break; default: return FALSE; } @@ -5974,6 +6287,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) case BeginningOfGame: case AnalyzeMode: case Training: + if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn && (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) { /* User is moving for Black */ @@ -6068,15 +6382,15 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) pup = boards[currentMove][toY][toX]; /* [HGM] If move started in holdings, it means a drop. Convert to standard form */ - if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { + if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) { if( pup != EmptySquare ) return; moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop; - if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", + if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]); // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings - while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; + while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; fromY = DROP_RANK; } @@ -6161,7 +6475,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) the previous line in Analysis Mode */ if ((gameMode == AnalyzeMode || gameMode == EditGame) && currentMove < forwardMostMove) { - PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game + if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game + else forwardMostMove = currentMove; } /* If we need the chess program but it's dead, restart it */ @@ -6175,7 +6490,10 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/ - if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end + if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + return 1; + } if (gameMode == BeginningOfGame) { if (appData.noChessProgram) { @@ -6230,6 +6548,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) switch (gameMode) { case EditGame: + if(appData.testLegality) switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) { case MT_NONE: case MT_CHECK: @@ -6259,6 +6578,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw + promoDefaultAltered = FALSE; // [HGM] fall back on default choice if(bookHit) { // [HGM] book: simulate book reply static char bookMove[MSG_SIZ]; // a bit generous? @@ -6311,12 +6631,43 @@ MarkTargetSquares(int clear) DrawPosition(TRUE, NULL); } +int +Explode(Board board, int fromX, int fromY, int toX, int toY) +{ + if(gameInfo.variant == VariantAtomic && + (board[toY][toX] != EmptySquare || // capture? + toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ? + board[fromY][fromX] == BlackPawn ) + )) { + AnimateAtomicCapture(board, fromX, fromY, toX, toY); + return TRUE; + } + return FALSE; +} + +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 || + piece == BlackLance && y == 1 || + piece == WhiteLance && y == BOARD_HEIGHT-2 ); +} + void LeftClick(ClickType clickType, int xPix, int yPix) { int x, y; Boolean saveAnimate; - static int second = 0, promotionChoice = 0, dragging = 0; + static int second = 0, promotionChoice = 0, clearFlag = 0; char promoChoice = NULLCHAR; + ChessSquare piece; if(appData.seekGraph && appData.icsActive && loggedOn && (gameMode == BeginningOfGame || gameMode == IcsIdle)) { @@ -6336,6 +6687,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix) x = BOARD_WIDTH - 1 - x; } + if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece + defaultPromoChoice = promoSweep; + promoSweep = EmptySquare; // terminate sweep + promoDefaultAltered = TRUE; + 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 if(clickType == Release) return; // ignore upclick of click-click destination promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel @@ -6364,28 +6722,48 @@ void LeftClick(ClickType clickType, int xPix, int yPix) || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) ) return; + if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered) + 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 + int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack || + gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove)); + defaultPromoChoice = DefaultPromoChoice(side); + } + autoQueen = appData.alwaysPromoteToQueen; if (fromX == -1) { - if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) { - if (clickType == Press) { + int originalY = y; + gatingPiece = EmptySquare; + if (clickType != Press) { + if(dragging) { // [HGM] from-square must have been reset due to game end since last press + DragPieceEnd(xPix, yPix); dragging = 0; + DrawPosition(FALSE, NULL); + } + return; + } + 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)) { - fromX = x; - fromY = y; + if (OKToStartUserMove(fromX, fromY)) { second = 0; MarkTargetSquares(0); DragPieceBegin(xPix, yPix); dragging = 1; + if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { + promoSweep = defaultPromoChoice; + selectFlag = 0; lastX = xPix; lastY = yPix; + Sweep(0); // Pawn that is going to promote: preview promotion piece + DisplayMessage("", _("Pull pawn backwards to under-promote")); + } if (appData.highlightDragging) { - SetHighlights(x, y, -1, -1); + SetHighlights(fromX, fromY, -1, -1); } - } - } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press - DragPieceEnd(xPix, yPix); dragging = 0; - DrawPosition(FALSE, NULL); + } else fromX = fromY = -1; + return; } - return; - } } /* fromX != -1 */ @@ -6400,7 +6778,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) /* Check if clicking again on the same color piece */ fromP = boards[currentMove][fromY][fromX]; toP = boards[currentMove][y][x]; - frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom; + frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess; if ((WhitePawn <= fromP && fromP <= WhiteKing && WhitePawn <= toP && toP <= WhiteKing && !(fromP == WhiteKing && toP == WhiteRook && frc) && @@ -6411,6 +6789,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) !(fromP == BlackKing && toP == BlackRook && frc))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); + promoDefaultAltered = FALSE; if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) { if (appData.highlightDragging) { SetHighlights(x, y, -1, -1); @@ -6418,13 +6797,24 @@ void LeftClick(ClickType clickType, int xPix, int yPix) ClearHighlights(); } if (OKToStartUserMove(x, y)) { + if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating + (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && + y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1)) + gatingPiece = boards[currentMove][fromY][fromX]; + else gatingPiece = EmptySquare; fromX = x; fromY = y; dragging = 1; MarkTargetSquares(0); DragPieceBegin(xPix, yPix); + if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { + promoSweep = defaultPromoChoice; + selectFlag = 0; lastX = xPix; lastY = yPix; + Sweep(0); // Pawn that is going to promote: preview promotion piece + } } - return; } + if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on + second = FALSE; } // ignore clicks on holdings if(x < BOARD_LEFT || x >= BOARD_RGHT) return; @@ -6432,6 +6822,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); @@ -6440,6 +6838,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) /* Second up/down in same square; just abort move */ second = 0; fromX = fromY = -1; + gatingPiece = EmptySquare; ClearHighlights(); gotPremove = 0; ClearPremoveHighlights(); @@ -6450,12 +6849,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); @@ -6502,7 +6909,9 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } // off-board moves should not be highlighted - if(x < 0 || x < 0) ClearHighlights(); + if(x < 0 || y < 0) ClearHighlights(); + + if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece)); if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { SetHighlights(fromX, fromY, toX, toY); @@ -6521,9 +6930,13 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } PromotionPopUp(); } else { + int oldMove = currentMove; UserMoveEvent(fromX, fromY, toX, toY, promoChoice); if (!appData.highlightLastMove || gotPremove) ClearHighlights(); if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY); + if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed + Explode(boards[currentMove-1], fromX, fromY, toX, toY)) + DrawPosition(TRUE, boards[currentMove]); fromX = fromY = -1; } appData.animate = saveAnimate; @@ -6563,16 +6976,25 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) xSqr = EventToSquare(x, BOARD_WIDTH); ySqr = EventToSquare(y, BOARD_HEIGHT); - if (action == Release) UnLoadPV(); // [HGM] pv + if (action == Release) { + if(pieceSweep != EmptySquare) { + EditPositionMenuEvent(pieceSweep, toX, toY); + pieceSweep = EmptySquare; + } else UnLoadPV(); // [HGM] pv + } if (action != Press) return -2; // return code to be ignored switch (gameMode) { case IcsExamining: if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; case EditPosition: if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; - if (xSqr < 0 || ySqr < 0) return -1; - whichMenu = 0; // edit-position menu - break; + if (xSqr < 0 || ySqr < 0) return -1; + if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu + pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep + toX = xSqr; toY = ySqr; lastX = x, lastY = y; + if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY; + NextPiece(0); + return -2; case IcsObserving: if(!appData.icsEngineAnalyze) return -1; case IcsPlayingWhite: @@ -6640,6 +7062,50 @@ 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]; + + 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; w 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn, "Xboard adjudication: Bare king", GE_XBOARD ); return 1; @@ -6854,7 +7317,6 @@ Adjudicate(ChessProgramState *cps) if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested if(engineOpponent) SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( result, reason, GE_XBOARD ); return 1; } @@ -6873,7 +7335,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD ); return 1; } @@ -6894,7 +7355,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); return 1; } @@ -6977,7 +7437,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( result, details, GE_XBOARD ); return 1; } @@ -7014,7 +7473,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); return 1; } @@ -7037,7 +7495,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, p, GE_XBOARD ); return 1; } @@ -7048,7 +7505,6 @@ Adjudicate(ChessProgramState *cps) SendToProgram("force\n", engineOpponent); // suppress reply SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */ } - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); return 1; } @@ -7227,7 +7683,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h &fromX, &fromY, &toX, &toY, &promoChar)) { /* Machine move could not be parsed; ignore it. */ snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"), - machineMove, cps->which); + machineMove, _(cps->which)); DisplayError(buf1, 0); snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType); @@ -7284,11 +7740,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if (cps->sendTime == 2) cps->sendTime = 1; if (cps->offeredDraw) cps->offeredDraw--; - /* currentMoveString is set as a side-effect of ParseOneMove */ - safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0])); - strcat(machineMove, "\n"); - safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0])); - /* [AS] Save move info*/ pvInfoList[ forwardMostMove ].score = programStats.score; pvInfoList[ forwardMostMove ].depth = programStats.depth; @@ -7325,7 +7776,10 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h } } - if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends + if(Adjudicate(cps)) { + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + return; // [HGM] adjudicate: for all automatic game ends + } #if ZIPPY if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) && @@ -7433,6 +7887,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands! } + if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position + int dummy, s=6; char buf[MSG_SIZ]; + if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return; + if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); + ParseFEN(boards[0], &dummy, message+s); + DrawPosition(TRUE, boards[0]); + startedFromSetupPosition = TRUE; + return; + } /* [HGM] Allow engine to set up a position. Don't ask me why one would * want this, I was asked to put it in, and obliged. */ @@ -7459,13 +7922,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. * Look for communication commands */ if (!strncmp(message, "telluser ", 9)) { - EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box + if(message[9] == '\\' && message[10] == '\\') + EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box DisplayNote(message + 9); return; } if (!strncmp(message, "tellusererror ", 14)) { cps->userError = 1; - EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box + if(message[14] == '\\' && message[15] == '\\') + EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box DisplayError(message + 14, 0); return; } @@ -7636,21 +8101,21 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. gameMode = EditGame; ModeHighlight(); } + /* [HGM] illegal-move claim should forfeit game when Xboard */ + /* only passes fully legal moves */ + if( appData.testLegality && gameMode == TwoMachinesPlay ) { + GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, + "False illegal-move claim", GE_XBOARD ); + return; // do not take back move we tested as valid + } currentMove = forwardMostMove-1; DisplayMove(currentMove-1); /* before DisplayMoveError */ SwitchClocks(forwardMostMove-1); // [HGM] race DisplayBothClocks(); snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"), - parseList[currentMove], cps->which); + parseList[currentMove], _(cps->which)); DisplayMoveError(buf1); DrawPosition(FALSE, boards[currentMove]); - - /* [HGM] illegal-move claim should forfeit game when Xboard */ - /* only passes fully legal moves */ - if( appData.testLegality && gameMode == TwoMachinesPlay ) { - GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, - "False illegal-move claim", GE_XBOARD ); - } return; } if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) { @@ -7673,9 +8138,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. cps->maybeThinking = FALSE; snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"), - cps->which, cps->program, cps->host, message); + _(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; } @@ -7696,7 +8164,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* Hint move could not be parsed!? */ snprintf(buf2, sizeof(buf2), _("Illegal hint move \"%s\"\nfrom %s chess program"), - buf1, cps->which); + buf1, _(cps->which)); DisplayError(buf2, 0); } } else { @@ -8189,7 +8657,7 @@ ParseGameHistory(game) yynewstr(game); for (;;) { yyboardindex = boardIndex; - moveType = (ChessMove) yylex(); + moveType = (ChessMove) Myylex(); switch (moveType) { case IllegalMove: /* maybe suicide chess, etc. */ if (appData.debugMode) { @@ -8300,6 +8768,7 @@ ParseGameHistory(game) if (q != NULL) *q = NULLCHAR; p++; } + while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message gameInfo.resultDetails = StrSave(p); continue; } @@ -8355,16 +8824,16 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) if( board[toY][toX] != EmptySquare ) board[EP_STATUS] = EP_CAPTURE; - /* [HGM] In Shatranj and Courier all promotions are to Ferz */ - if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk) - && promoChar != 0) promoChar = PieceToChar(WhiteFerz); - if (fromY == DROP_RANK) { /* must be first */ piece = board[toY][toX] = (ChessSquare) fromX; } else { int i; + if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) { + if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi ) + board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants + } else if( board[fromY][fromX] == WhitePawn ) { if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers board[EP_STATUS] = EP_PAWN_MOVE; @@ -8404,21 +8873,21 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) king += (int) WhiteUnicorn - (int) WhiteKing; /* Code added by Tord: */ - /* FRC castling assumed when king captures friendly rook. */ - if (board[fromY][fromX] == WhiteKing && - board[toY][toX] == WhiteRook) { + /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */ + if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook || + board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) { board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; - if(toX > fromX) { + if((toX > fromX) != (piece == WhiteRook)) { board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook; } else { board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook; } - } else if (board[fromY][fromX] == BlackKing && - board[toY][toX] == BlackRook) { + } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook || + board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) { board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; - if(toX > fromX) { + if((toX > fromX) != (piece == BlackRook)) { board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook; } else { board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook; @@ -8439,9 +8908,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = king; board[toY][toX+1] = board[fromY][BOARD_LEFT]; board[fromY][BOARD_LEFT] = EmptySquare; - } else if (board[fromY][fromX] == WhitePawn + } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi || + board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) && toY >= BOARD_HEIGHT-promoRank - && gameInfo.variant != VariantXiangqi ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); @@ -8503,9 +8972,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = BlackKing; board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; - } else if (board[fromY][fromX] == BlackPawn + } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi || + board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) && toY < promoRank - && gameInfo.variant != VariantXiangqi ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); @@ -8551,10 +9020,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) /* [HGM] OK, so I have written it. Holdings are stored in the */ /* penultimate board files, so they are automaticlly stored */ /* in the game history. */ - if (fromY == DROP_RANK) { + if (fromY == DROP_RANK || gameInfo.variant == VariantSChess + && promoChar && piece != WhitePawn && piece != BlackPawn) { /* Delete from holdings, by decreasing count */ /* and erasing image if necessary */ - p = (int) fromX; + p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar)); if(p < (int) BlackPawn) { /* white drop */ p -= (int)WhitePawn; p = PieceToNumber((ChessSquare)p); @@ -8574,7 +9044,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) } } if (captured != EmptySquare && gameInfo.holdingsSize > 0 - && gameInfo.variant != VariantBughouse ) { + && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) { /* [HGM] holdings: Add to holdings, if holdings exist */ if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip @@ -8618,14 +9088,17 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = EmptySquare; } } + if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) { + board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating + } else if(promoChar == '+') { /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */ board[toY][toX] = (ChessSquare) (PROMOTED piece); - } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar - board[toY][toX] = CharToPiece(promoChar); + } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar + board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); } - if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) - && promoChar != NULLCHAR && gameInfo.holdingsSize) { + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) + && promoChar != NULLCHAR && gameInfo.holdingsSize) { // [HGM] superchess: take promotion piece out of holdings int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); if((int)piece < (int)BlackPawn) { // determine stm from piece color @@ -8842,7 +9315,7 @@ InitChessProgram(cps, setup) if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse ) overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5; if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || - gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon ) + gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus ) overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; if( gameInfo.variant == VariantCourier ) overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; @@ -8850,6 +9323,8 @@ InitChessProgram(cps, setup) overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; if( gameInfo.variant == VariantGreat ) overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; + if( gameInfo.variant == VariantSChess ) + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7; if(overruled) { snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, @@ -8958,49 +9433,225 @@ 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)) +int +CreateTourney(char *name) { - 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; + FILE *f; + if(name[0] == NULLCHAR) return 0; + f = fopen(appData.tourneyFile, "r"); + if(f) { // file exists + 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"); + } + } + 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, ")"); } - LoadPositionFromFile(appData.loadPositionFile, - index, - appData.loadPositionFile); + engineMnemonic[i] = strdup(buf); + names = p; i++; + if(i > MAXENGINES - 2) break; } - TwoMachinesEventIfReady(); + 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) +} + +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; + static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" " + "-firstNeedsNoncompliantFEN false -firstNPS -1"; + 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=0; + 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); + + p = appData.participants; + while(p = strchr(p, '\n')) p++, nPlayers++; // count participants + *swapColors = Pairing(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 ) @@ -9050,7 +9701,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; @@ -9352,9 +10003,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 { @@ -9362,27 +10015,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 { @@ -9392,6 +10048,13 @@ GameEnds(result, resultDetails, whosays) first.matchWins, second.matchWins, appData.matchGames - (first.matchWins + second.matchWins)); popupRequested++; // [HGM] crash: postpone to after resetting endingGame + if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match + first.twoMachinesColor = "black\n"; + second.twoMachinesColor = "white\n"; + } else { + first.twoMachinesColor = "white\n"; + second.twoMachinesColor = "black\n"; + } } } if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && @@ -9401,10 +10064,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); } } @@ -9443,15 +10114,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); @@ -9468,6 +10147,7 @@ ResurrectChessProgram() SendToProgram("analyze\n", &first); first.analyzing = TRUE; } + return 1; } /* @@ -9541,6 +10221,7 @@ Reset(redraw, init) ResetClocks(); timeRemaining[0][0] = whiteTimeRemaining; timeRemaining[1][0] = blackTimeRemaining; + if (first.pr == NULL) { StartChessProgram(&first); } @@ -9561,7 +10242,7 @@ AutoPlayGameLoop() return; if (matchMode || appData.timeDelay == 0) continue; - if (appData.timeDelay < 0 || gameMode == AnalyzeFile) + if (appData.timeDelay < 0) return; StartLoadGameTimer((long)(1000.0 * appData.timeDelay)); break; @@ -9578,10 +10259,18 @@ AutoPlayOneMove() fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove); } - if (gameMode != PlayFromGameFile) + if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile) return FALSE; + if (gameMode == AnalyzeFile && currentMove > backwardMostMove) { + pvInfoList[currentMove].depth = programStats.depth; + pvInfoList[currentMove].score = programStats.score; + pvInfoList[currentMove].time = 0; + if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2); + } + if (currentMove >= forwardMostMove) { + if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); } gameMode = EditGame; ModeHighlight(); @@ -9642,7 +10331,7 @@ LoadGameOneMove(readAhead) } else { if (gameFileFP == NULL) return FALSE; - moveType = (ChessMove) yylex(); + moveType = (ChessMove) Myylex(); } done = FALSE; @@ -9714,6 +10403,7 @@ LoadGameOneMove(readAhead) if (q != NULL) *q = NULLCHAR; p++; } + while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message GameEnds(moveType, p, GE_FILE); done = TRUE; if (cmailMsgLoaded) { @@ -9846,8 +10536,6 @@ LoadGameOneMove(readAhead) return FALSE; } else { /* currentMoveString is set as a side-effect of yylex */ - strcat(currentMoveString, "\n"); - safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0])); thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); @@ -10137,7 +10825,7 @@ LoadGame(f, gameNumber, title, useList) cm = lastLoadGameStart = EndOfFile; while (gn > 0) { yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); switch (cm) { case EndOfFile: if (cmailMsgLoaded) { @@ -10190,7 +10878,7 @@ LoadGame(f, gameNumber, title, useList) if (gn > 0) { do { yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } while (cm == PGNTag || cm == Comment); } break; @@ -10229,7 +10917,7 @@ LoadGame(f, gameNumber, title, useList) /* Skip any header junk before position diagram and/or move 1 */ for (;;) { yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); if (cm == EndOfFile || cm == GNUChessGame || cm == XBoardGame) { @@ -10302,7 +10990,7 @@ LoadGame(f, gameNumber, title, useList) } yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); /* Handle comments interspersed among the tags */ while (cm == Comment) { @@ -10312,7 +11000,7 @@ LoadGame(f, gameNumber, title, useList) p = yy_text; AppendComment(currentMove, p, FALSE); yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } } @@ -10352,6 +11040,7 @@ LoadGame(f, gameNumber, title, useList) for (i = BOARD_HEIGHT - 1; i >= 0; i--) for (j = BOARD_LEFT; j < BOARD_RGHT; p++) switch (*p) { + case '{': case '[': case '-': case ' ': @@ -10389,7 +11078,7 @@ LoadGame(f, gameNumber, title, useList) } } yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } if (first.pr == NoProc) { @@ -10415,7 +11104,7 @@ LoadGame(f, gameNumber, title, useList) p = yy_text; AppendComment(currentMove, p, FALSE); yyboardindex = forwardMostMove; - cm = (ChessMove) yylex(); + cm = (ChessMove) Myylex(); } if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) || @@ -10702,6 +11391,7 @@ SaveGameToFile(filename, append) { FILE *f; char buf[MSG_SIZ]; + int result; if (strcmp(filename, "-") == 0) { return SaveGame(stdout, 0, NULL); @@ -10712,7 +11402,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; } } } @@ -11074,7 +11771,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; } } @@ -11586,7 +12289,7 @@ void EditTagsEvent() { char *tags = PGNTags(&gameInfo); - EditTagsPopUp(tags); + EditTagsPopUp(tags, NULL); free(tags); } @@ -11824,12 +12527,48 @@ DisplayTwoMachinesTitle() } void +SettingsMenuIfReady() +{ + if (second.lastPing != second.lastPong) { + DisplayMessage("", _("Waiting for second chess program")); + ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000 + return; + } + ThawUI(); + DisplayMessage("", ""); + SettingsPopUp(&second); +} + +int +WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry) +{ + char buf[MSG_SIZ]; + if (cps->pr == NULL) { + StartChessProgram(cps); + if (cps->protocolVersion == 1) { + retry(); + } else { + /* kludge: allow timeout for initial "feature" command */ + FreezeUI(); + snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which); + DisplayMessage("", buf); + ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); + } + return 1; + } + return 0; +} + +void TwoMachinesEvent P((void)) { int i; char buf[MSG_SIZ]; ChessProgramState *onmove; char *bookHit = NULL; + static int stalling = 0; + TimeMark now; + long wait; if (appData.noChessProgram) return; @@ -11863,23 +12602,31 @@ TwoMachinesEvent P((void)) // forwardMostMove = currentMove; TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... - ResurrectChessProgram(); /* in case first program isn't running */ - if (second.pr == NULL) { - StartChessProgram(&second); - if (second.protocolVersion == 1) { - TwoMachinesEventIfReady(); - } else { - /* kludge: allow timeout for initial "feature" command */ - FreezeUI(); - DisplayMessage("", _("Starting second chess program")); - ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT); - } + 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(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("", ""); - InitChessProgram(&second, FALSE); - SendToProgram("force\n", &second); if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); if (appData.debugMode) { @@ -11901,7 +12648,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); @@ -12515,6 +13262,32 @@ CallFlagEvent() } void +ClockClick(int which) +{ // [HGM] code moved to back-end from winboard.c + if(which) { // black clock + if (gameMode == EditPosition || gameMode == IcsExamining) { + if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); + SetBlackToPlayEvent(); + } else if (gameMode == EditGame || shiftKey) { + AdjustClock(which, -1); + } else if (gameMode == IcsPlayingWhite || + gameMode == MachinePlaysBlack) { + CallFlagEvent(); + } + } else { // white clock + if (gameMode == EditPosition || gameMode == IcsExamining) { + if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); + SetWhiteToPlayEvent(); + } else if (gameMode == EditGame || shiftKey) { + AdjustClock(which, -1); + } else if (gameMode == IcsPlayingBlack || + gameMode == MachinePlaysWhite) { + CallFlagEvent(); + } + } +} + +void DrawEvent() { /* Offer draw or accept pending draw offer from opponent */ @@ -13155,9 +13928,9 @@ 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("-"); @@ -13218,7 +13991,13 @@ ReplaceComment(index, text) char *text; { int len; + char *p; + float score; + if(index && sscanf(text, "%f/%d", &score, &len) == 2 && + pvInfoList[index-1].depth == len && + fabs(pvInfoList[index-1].score - score*100.) < 0.5 && + (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any while (*text == '\n') text++; len = strlen(text); while (len > 0 && text[len - 1] == '\n') len--; @@ -13289,27 +14068,27 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); while(commentList[index][oldlen-1] == '\n') commentList[index][--oldlen] = NULLCHAR; commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4 - safeStrCpy(commentList[index], old, oldlen); + safeStrCpy(commentList[index], old, oldlen + len + 6); free(old); // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}" - if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) { - if(addBraces) addBraces = FALSE; else { text++; len--; } + if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) { + if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; } while (*text == '\n') { text++; len--; } commentList[index][--oldlen] = NULLCHAR; } - if(addBraces) strcat(commentList[index], "\n{\n"); + if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n"); else strcat(commentList[index], "\n"); strcat(commentList[index], text); - if(addBraces) strcat(commentList[index], "\n}\n"); + if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n"); else strcat(commentList[index], "\n"); } else { commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4... if(addBraces) - safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0])); + safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3); else commentList[index][0] = NULLCHAR; strcat(commentList[index], text); - strcat(commentList[index], "\n"); - if(addBraces) strcat(commentList[index], "}\n"); + strcat(commentList[index], addBraces == 2 ? ")\n" : "\n"); + if(addBraces == TRUE) strcat(commentList[index], "}\n"); } } @@ -13328,7 +14107,7 @@ static char * FindStr( char * text, char * sub_text ) /* [HGM] PV time: and then remove it, to prevent it appearing twice */ char *GetInfoFromComment( int index, char * text ) { - char * sep = text; + char * sep = text, *p; if( text != NULL && index > 0 ) { int score = 0; @@ -13366,11 +14145,20 @@ char *GetInfoFromComment( int index, char * text ) return text; } + p = text; + if(p[1] == '(') { // comment starts with PV + p = strchr(p, ')'); // locate end of PV + if(p == NULL || sep < p+5) return text; + // at this point we have something like "{(.*) +0.23/6 ..." + p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')'; + *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{'; + // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..." + } time = -1; sec = -1; deci = -1; - if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 && - sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 && - sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 && - sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { + if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 && + sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 && + sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 && + sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { return text; } @@ -13386,7 +14174,7 @@ char *GetInfoFromComment( int index, char * text ) /* [HGM] PV time: now locate end of PV info */ while( *++sep >= '0' && *sep <= '9'); // strip depth if(time >= 0) - while( *++sep >= '0' && *sep <= '9'); // strip time + while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time if(sec >= 0) while( *++sep >= '0' && *sep <= '9'); // strip seconds if(deci >= 0) @@ -13406,6 +14194,7 @@ char *GetInfoFromComment( int index, char * text ) pvInfoList[index-1].score = score; pvInfoList[index-1].time = 10*time; // centi-sec if(*sep == '}') *sep = 0; else *--sep = '{'; + if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both } return sep; } @@ -13433,16 +14222,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 */ - snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which); + 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) { + 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 */ - snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program); } 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; } } @@ -13462,22 +14256,26 @@ ReceiveFromProgram(isr, closure, message, count, error) if (isr != cps->isr) return; /* Killed intentionally */ if (count <= 0) { if (count == 0) { + RemoveInputSource(cps->isr); snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"), - cps->which, cps->program); + _(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) { + 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 */ - snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program); } 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)"), - cps->which, cps->program); + _(cps->which), cps->program); RemoveInputSource(cps->isr); /* [AS] Program is misbehaving badly... kill it */ @@ -13509,6 +14307,8 @@ ReceiveFromProgram(isr, closure, message, count, error) sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 && sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && + sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 && + sscanf(message, "hint: %c", &c)!=1 && sscanf(message, "pong %c", &c)!=1 && start != '#') { quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### "; print = (appData.engineComments >= 2); @@ -13574,10 +14374,10 @@ SendTimeControl(cps, mps, tc, inc, sd, st) /* Note old gnuchess bug -- minutes:seconds used to not work. Fixed in later versions, but still avoid :seconds when seconds is 0. */ - snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000); + snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.); } else { snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000, - seconds, inc/1000); + seconds, inc/1000.); } } SendToProgram(buf, cps); @@ -13593,7 +14393,7 @@ SendTimeControl(cps, mps, tc, inc, sd, st) SendToProgram(buf, cps); } - if(cps->nps > 0) { /* [HGM] nps */ + if(cps->nps >= 0) { /* [HGM] nps */ if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported! else { @@ -13746,11 +14546,11 @@ ParseOption(Option *opt, ChessProgramState *cps) } else if((p = strstr(opt->name, " -file "))) { // for now -file is a synonym for -string, to already provide compatibility with future polyglots opt->textValue = p+7; - opt->type = TextBox; // FileName; + opt->type = FileName; // FileName; } else if((p = strstr(opt->name, " -path "))) { // for now -file is a synonym for -string, to already provide compatibility with future polyglots opt->textValue = p+7; - opt->type = TextBox; // PathName; + opt->type = PathName; // PathName; } else if(p = strstr(opt->name, " -check ")) { if(sscanf(p, " -check %d", &def) < 1) return FALSE; opt->value = (def != 0); @@ -13784,6 +14584,20 @@ ParseOption(Option *opt, ChessProgramState *cps) if(p && (p == cps->optionSettings || p[-1] == ',')) { snprintf(buf, MSG_SIZ, "option %s", p); if(p = strstr(buf, ",")) *p = 0; + if(q = strchr(buf, '=')) switch(opt->type) { + case ComboBox: + for(n=0; nmax; n++) + if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n; + break; + case TextBox: + safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name)); + break; + case Spin: + case CheckBox: + opt->value = atoi(q+1); + default: + break; + } strcat(buf, "\n"); SendToProgram(buf, cps); } @@ -13797,7 +14611,9 @@ FeatureDone(cps, val) { DelayedEventCallback cb = GetDelayedEvent(); if ((cb == InitBackEnd3 && cps == &first) || - (cb == TwoMachinesEventIfReady && cps == &second)) { + (cb == SettingsMenuIfReady && cps == &second) || + (cb == LoadEngine) || + (cb == TwoMachinesEventIfReady)) { CancelDelayedEvent(); ScheduleDelayedEvent(cb, val ? 1 : 3600000); } @@ -13871,7 +14687,7 @@ ParseFeatures(args, cps) } if(cps->nrOptions >= MAX_OPTIONS) { cps->nrOptions--; - snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which); + snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which)); DisplayError(buf, 0); } continue; @@ -13990,6 +14806,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; { @@ -14407,15 +15272,15 @@ SwitchClocks(int newMoveNr) if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ -// if(pvInfoList[forwardMostMove-1].time == -1) - pvInfoList[forwardMostMove-1].time = // use GUI time +// if(pvInfoList[forwardMostMove].time == -1) + pvInfoList[forwardMostMove].time = // use GUI time (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10; } else { if(whiteNPS >= 0) lastTickLength = 0; whiteTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ -// if(pvInfoList[forwardMostMove-1].time == -1) - pvInfoList[forwardMostMove-1].time = +// if(pvInfoList[forwardMostMove].time == -1) + pvInfoList[forwardMostMove].time = (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10; } flagged = CheckFlags(); @@ -15212,16 +16077,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; @@ -15246,19 +16105,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; @@ -15298,6 +16165,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; }