X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=ca3e342c89807ee7a89750aeef68c9ff008f2ac3;hb=0c082fce711802c67feae5dd1ddadad022a48147;hp=b313fa5ad100e04f3a64c5edffb841dbfe94654d;hpb=98a0bc48e8cc10b05bd9eea57bfc204b62e9aeb6;p=xboard.git diff --git a/backend.c b/backend.c index b313fa5..ca3e342 100644 --- a/backend.c +++ b/backend.c @@ -55,8 +55,6 @@ #ifdef WIN32 #include -#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); - int flock(int f, int code); #define LOCK_EX 2 #define SLASH '\\' @@ -64,7 +62,6 @@ int flock(int f, int code); #else #include -#define DoSleep( n ) if( (n) >= 0) sleep(n) #define SLASH '/' #endif @@ -171,8 +168,6 @@ int LoadGameOneMove P((ChessMove readAhead)); int LoadGameFromFile P((char *filename, int n, char *title, int useList)); int LoadPositionFromFile P((char *filename, int n, char *title)); int SavePositionToFile P((char *filename)); -void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar, - Board board)); void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar)); void ShowMove P((int fromX, int fromY, int toX, int toY)); int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, @@ -207,7 +202,6 @@ void StopClocks P((void)); void ResetClocks P((void)); char *PGNDate P((void)); void SetGameInfo P((void)); -Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen)); int RegisterMove P((void)); void MakeRegisteredMove P((void)); void TruncateGame P((void)); @@ -234,7 +228,7 @@ void InitDrawingSizes(int x, int y); void NextMatchGame P((void)); int NextTourneyGame P((int nr, int *swap)); int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync)); -FILE *WriteTourneyFile P((char *results)); +FILE *WriteTourneyFile P((char *results, FILE *f)); void DisplayTwoMachinesTitle P(()); #ifdef WIN32 @@ -468,6 +462,7 @@ int have_sent_ICS_logon = 0; int movesPerSession; int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */ long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack; +Boolean adjustedClock; long timeControl_2; /* [AS] Allow separate time controls */ char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; @@ -621,10 +616,10 @@ ChessSquare GothicArray[2][BOARD_FILES] = { #ifdef FALCON ChessSquare FalconArray[2][BOARD_FILES] = { - { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, - WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, - BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook } + { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen, + WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen, + BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook } }; #else // !FALCON #define FalconArray CapablancaArray @@ -839,6 +834,7 @@ InitEngine(ChessProgramState *cps, int n) } InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard + ParseFeatures(appData.featureDefaults, cps); } ChessProgramState *savCps; @@ -882,16 +878,17 @@ extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick; static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 " + "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" " "-firstOptions \"\" -firstNPS -1 -fn \"\""; void Load(ChessProgramState *cps, int i) { char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ]; - if(engineLine[0]) { // an engine was selected from the combo box + if(engineLine && engineLine[0]) { // an engine was selected from the combo box snprintf(buf, MSG_SIZ, "-fcp %s", engineLine); SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second* - ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; + ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE; ParseArgsFromString(buf); SwapEngines(i); ReplaceEngine(cps, i); @@ -907,8 +904,8 @@ Load(ChessProgramState *cps, int i) appData.directory[i] = strdup(engineName); p[-1] = SLASH; } else appData.directory[i] = "."; - if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces if(params[0]) { + if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces snprintf(command, MSG_SIZ, "%s %s", p, params); p = command; } @@ -981,6 +978,7 @@ InitBackEnd1() GetTimeMark(&programStartTime); srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level + appData.seedBase = random() + (random()<<15); pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended ClearProgramStats(); @@ -1436,7 +1434,7 @@ MatchEvent(int mode) if(strchr(appData.results, '*') == NULL) { FILE *f; appData.tourneyCycles++; - if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles + if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles fclose(f); NextTourneyGame(-1, &dummy); ReserveGame(-1, 0); @@ -1664,6 +1662,18 @@ InitBackEnd3 P((void)) } } +void +HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current ) +{ + DisplayBook(current+1); + + MoveHistorySet( movelist, first, last, current, pvInfoList ); + + EvalGraphSet( first, last, current, pvInfoList ); + + MakeEngineOutputTitle(); +} + /* * Establish will establish a contact to a remote host.port. * Sets icsPR to a ProcRef for a process (or pseudo-process) @@ -1856,7 +1866,7 @@ SendToICS(s) { int count, outCount, outError; - if (icsPR == NULL) return; + if (icsPR == NoProc) return; count = strlen(s); outCount = OutputMaybeTelnet(icsPR, s, count, &outError); @@ -1875,7 +1885,7 @@ SendToICSDelayed(s,msdelay) { int count, outCount, outError; - if (icsPR == NULL) return; + if (icsPR == NoProc) return; count = strlen(s); if (appData.debugMode) { @@ -3888,7 +3898,7 @@ read_from_ics(isr, closure, data, count, error) #if ZIPPY if (appData.zippyPlay && first.initDone) { ZippyGameEnd(endtype, why); - if (first.pr == NULL) { + if (first.pr == NoProc) { /* Start the next process early so that we'll be ready for the next challenge */ StartChessProgram(&first); @@ -5127,7 +5137,7 @@ Sweep(int step) 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; + 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)); @@ -5139,7 +5149,7 @@ 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(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE; if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; if(!step) return FALSE; lastX = x; lastY = y; @@ -5414,7 +5424,7 @@ char * PvToSAN(char *pv) { static char buf[10*MSG_SIZ]; - int i, k=0, savedEnd=endPV; + int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove; *buf = NULLCHAR; if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it @@ -5424,7 +5434,7 @@ PvToSAN(char *pv) k += strlen(buf+k); } snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim - if(forwardMostMove < savedEnd) PopInner(0); + if(forwardMostMove < savedEnd) { PopInner(0); forwardMostMove = saveFMM; } // PopInner would set fmm to endPV! endPV = savedEnd; return buf; } @@ -5467,11 +5477,11 @@ UnLoadPV() void MovePV(int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) - int margin = h>>3, step = 0; + int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15); // we must somehow check if right button is still down (might be released off board!) if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-( - if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return; + if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return; if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; if(!step) return; lastX = x; lastY = y; @@ -7617,8 +7627,10 @@ Adjudicate(ChessProgramState *cps) hisPerpetual = PerpetualChase(k, forwardMostMove); ourPerpetual = PerpetualChase(k+1, forwardMostMove); if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit + static char resdet[MSG_SIZ]; result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; - details = "Xboard adjudication: perpetual chasing"; + details = resdet; + snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255); } else if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet break; // Abort repetition-checking loop. @@ -8107,10 +8119,12 @@ 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 + if ((!appData.testLegality || gameInfo.variant == VariantFairy) && + !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(appData.icsActive || forwardMostMove != 0 || cps != &first) return; if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); + if(startedFromSetupPosition) return; ParseFEN(boards[0], &dummy, message+s); DrawPosition(TRUE, boards[0]); startedFromSetupPosition = TRUE; @@ -8267,7 +8281,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (StrStr(message, "analyze")) { cps->analysisSupport = FALSE; cps->analyzing = FALSE; - Reset(FALSE, TRUE); +// Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state! + EditGameEvent(); // [HGM] try to preserve loaded game snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy); DisplayError(buf2, 0); return; @@ -8632,6 +8647,19 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1); + if(serverMoves && (time > 100 || time == 0 && plylev > 7)) { + char buf[MSG_SIZ]; + FILE *f; + snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName); + buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' : + gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0]; + if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf); + if(f = fopen(buf, "w")) { // export PV to applicable PV file + fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv); + fclose(f); + } else DisplayError("failed writing PV", 0); + } + tempStats.depth = plylev; tempStats.nodes = nodes; tempStats.time = time; @@ -8917,6 +8945,7 @@ ParseGameHistory(game) break; case WhiteDrop: case BlackDrop: + if(currentMoveString[0] == '@') continue; // no null moves in ICS mode! fromX = moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); @@ -9341,6 +9370,11 @@ MakeMove(fromX, fromY, toX, toY, promoChar) { // forwardMostMove++; // [HGM] bare: moved downstream + (void) CoordsToAlgebraic(boards[forwardMostMove], + PosFlags(forwardMostMove), + fromY, fromX, toY, toX, promoChar, + parseList[forwardMostMove]); + if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */ int timeLeft; static int lastLoadFlag=0; int king, piece; piece = boards[forwardMostMove][fromY][fromX]; @@ -9348,10 +9382,14 @@ MakeMove(fromX, fromY, toX, toY, promoChar) if(gameInfo.variant == VariantKnightmate) king += (int) WhiteUnicorn - (int) WhiteKing; if(forwardMostMove == 0) { - if(blackPlaysFirst) + if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame) + fprintf(serverMoves, "%s;", UserName()); + else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') fprintf(serverMoves, "%s;", second.tidy); fprintf(serverMoves, "%s;", first.tidy); - if(!blackPlaysFirst) + if(gameMode == MachinePlaysWhite) + fprintf(serverMoves, "%s;", UserName()); + else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w') fprintf(serverMoves, "%s;", second.tidy); } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";"); lastLoadFlag = loadFlag; @@ -9372,20 +9410,24 @@ MakeMove(fromX, fromY, toX, toY, promoChar) fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY); // promotion suffix if(promoChar != NULLCHAR) - fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY); + fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY); if(!loadFlag) { + char buf[MOVE_LEN*2], *p; int len; fprintf(serverMoves, "/%d/%d", pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score); if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000; else timeLeft = blackTimeRemaining/1000; fprintf(serverMoves, "/%d", timeLeft); + strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2); + if(p = strchr(buf, '=')) *p = NULLCHAR; + len = strlen(buf); if(len > 1 && buf[len-2] != '-') buf[len-2] = NULLCHAR; // strip to-square + fprintf(serverMoves, "/%s", buf); } fflush(serverMoves); } - if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations - DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), - 0, 1); + if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations.. + GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD); return; } UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this @@ -9399,6 +9441,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar) SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; + adjustedClock = FALSE; gameInfo.result = GameUnfinished; if (gameInfo.resultDetails != NULL) { free(gameInfo.resultDetails); @@ -9406,10 +9449,6 @@ MakeMove(fromX, fromY, toX, toY, promoChar) } CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[forwardMostMove - 1]); - (void) CoordsToAlgebraic(boards[forwardMostMove - 1], - PosFlags(forwardMostMove - 1), - fromY, fromX, toY, toX, promoChar, - parseList[forwardMostMove - 1]); switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) { case MT_NONE: case MT_STALEMATE: @@ -9456,7 +9495,6 @@ ShowMove(fromX, fromY, toX, toY) DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); - DisplayBook(currentMove); } void SendEgtPath(ChessProgramState *cps) @@ -9502,6 +9540,7 @@ InitChessProgram(cps, setup) hintRequested = FALSE; bookRequested = FALSE; + ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */ /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */ if(cps->memSize) { /* [HGM] memory */ @@ -9720,12 +9759,13 @@ CountPlayers(char *p) } FILE * -WriteTourneyFile(char *results) +WriteTourneyFile(char *results, FILE *f) { // write tournament parameters on tourneyFile; on success return the stream pointer for closing - FILE *f = fopen(appData.tourneyFile, "w"); + if(f == NULL) f = fopen(appData.tourneyFile, "w"); if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else { // create a file with tournament description fprintf(f, "-participants {%s}\n", appData.participants); + fprintf(f, "-seedBase %d\n", appData.seedBase); fprintf(f, "-tourneyType %d\n", appData.tourneyType); fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles); fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames); @@ -9737,6 +9777,7 @@ WriteTourneyFile(char *results) fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile); fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); + fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false"); if(searchTime > 0) fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60); else { @@ -9749,10 +9790,73 @@ WriteTourneyFile(char *results) return f; } +#define MAXENGINES 1000 +char *command[MAXENGINES], *mnemonic[MAXENGINES]; + +void Substitute(char *participants, int expunge) +{ + int i, changed, changes=0, nPlayers=0; + char *p, *q, *r, buf[MSG_SIZ]; + if(participants == NULL) return; + if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; } + r = p = participants; q = appData.participants; + while(*p && *p == *q) { + if(*p == '\n') r = p+1, nPlayers++; + p++; q++; + } + if(*p) { // difference + while(*p && *p++ != '\n'); + while(*q && *q++ != '\n'); + changed = nPlayers; + changes = 1 + (strcmp(p, q) != 0); + } + if(changes == 1) { // a single engine mnemonic was changed + q = r; while(*q) nPlayers += (*q++ == '\n'); + p = buf; while(*r && (*p = *r++) != '\n') p++; + *p = NULLCHAR; + NamesToList(firstChessProgramNames, command, mnemonic); + for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break; + if(mnemonic[i]) { // The substitute is valid + FILE *f; + if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) { + flock(fileno(f), LOCK_EX); + ParseArgsFromFile(f); + fseek(f, 0, SEEK_SET); + FREE(appData.participants); appData.participants = participants; + if(expunge) { // erase results of replaced engine + int len = strlen(appData.results), w, b, dummy; + for(i=0; i 1) DisplayError(_("You can only change one engine at the time"), 0); + free(participants); + return; +} + int CreateTourney(char *name) { FILE *f; + if(matchMode && strcmp(name, appData.tourneyFile)) { + ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing + } if(name[0] == NULLCHAR) { if(appData.participants[0]) DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0); @@ -9770,7 +9874,7 @@ CreateTourney(char *name) } ASSIGN(appData.tourneyFile, name); if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1 - if((f = WriteTourneyFile("")) == NULL) return 0; + if((f = WriteTourneyFile("", NULL)) == NULL) return 0; } fclose(f); appData.noChessProgram = FALSE; @@ -9779,9 +9883,6 @@ CreateTourney(char *name) return 1; } -#define MAXENGINES 1000 -char *command[MAXENGINES], *mnemonic[MAXENGINES]; - void NamesToList(char *names, char **engineList, char **engineMnemonic) { char buf[MSG_SIZ], *p, *q; @@ -9804,7 +9905,7 @@ void NamesToList(char *names, char **engineList, char **engineMnemonic) names = p; i++; if(i > MAXENGINES - 2) break; } - engineList[i] = NULL; + engineList[i] = engineMnemonic[i] = NULL; } // following implemented as macro to avoid type limitations @@ -9826,6 +9927,7 @@ void SwapEngines(int n) SWAP(logo, p) SWAP(pgnName, p) SWAP(pvSAN, h) + SWAP(engOptions, p) } void @@ -9838,7 +9940,8 @@ SetPlayer(int player) for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break; if(mnemonic[i]) { snprintf(buf, MSG_SIZ, "-fcp %s", command[i]); - ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; + ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE; + appData.firstHasOwnBookUCI = !appData.defNoBook; ParseArgsFromString(buf); } free(engineName); @@ -9941,7 +10044,7 @@ NextTourneyGame(int nr, int *swapColors) matchGame = 1; roundNr = nr / syncInterval + 1; } - if(first.pr != NoProc) return 1; // engines already loaded + if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded // redefine engines, engine dir, etc. NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines @@ -9959,16 +10062,18 @@ NextTourneyGame(int nr, int *swapColors) void NextMatchGame() { // performs game initialization that does not invoke engines, and then tries to start the game - int firstWhite, swapColors = 0; + int res, 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 + if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening Reset(FALSE, first.pr != NoProc); - appData.noChessProgram = FALSE; - if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file + res = LoadGameOrPosition(matchGame); // setup game + appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too! + if(!res) return; // abort when bad game/pos file TwoMachinesEvent(); } @@ -10155,7 +10260,7 @@ GameEnds(result, resultDetails, whosays) if(result==WhiteWins) c = '+'; if(result==BlackWins) c = '-'; if(resultDetails != NULL) - fprintf(serverMoves, ";%c;%s\n", c, resultDetails); + fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves); } if (resultDetails != NULL) { gameInfo.result = result; @@ -10349,6 +10454,7 @@ GameEnds(result, resultDetails, whosays) if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result + if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame); ReserveGame(nextGame, resChar); // sets nextGame if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done else ranking = strdup("busy"); //suppress popup when aborted but not finished @@ -10546,7 +10652,7 @@ Reset(redraw, init) timeRemaining[0][0] = whiteTimeRemaining; timeRemaining[1][0] = blackTimeRemaining; - if (first.pr == NULL) { + if (first.pr == NoProc) { StartChessProgram(&first); } if (init) { @@ -11071,6 +11177,231 @@ PositionMatches(Board b1, Board b2) return TRUE; } +#define Q_PROMO 4 +#define Q_EP 3 +#define Q_BCASTL 2 +#define Q_WCASTL 1 + +int pieceList[256], quickBoard[256]; +ChessSquare pieceType[256] = { EmptySquare }; +Board soughtBoard, reverseBoard, flipBoard, rotateBoard; +int counts[EmptySquare], minSought[EmptySquare], minReverse[EmptySquare], maxSought[EmptySquare], maxReverse[EmptySquare]; +int soughtTotal, turn; +Boolean epOK, flipSearch; + +typedef struct { + unsigned char piece, to; +} Move; + +#define DSIZE (250000) + +Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 moves +Move *moveDatabase = initialSpace; +unsigned int movePtr, dataSize = DSIZE; + +int MakePieceList(Board board, int *counts) +{ + int r, f, n=Q_PROMO, total=0; + for(r=0;r fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { + int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT; + moveDatabase[movePtr++].piece = Q_WCASTL; + quickBoard[sq] = piece; + piece = quickBoard[from]; quickBoard[from] = 0; + moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1; + } else + if(piece == pieceList[2] && fromY == toY && (toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { + int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4); + moveDatabase[movePtr++].piece = Q_BCASTL; + quickBoard[sq] = piece; + piece = quickBoard[from]; quickBoard[from] = 0; + moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1; + } else + if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) { + quickBoard[(fromY<<4)+toX] = 0; + moveDatabase[movePtr].piece = Q_EP; + moveDatabase[movePtr++].to = (fromY<<4)+toX; + moveDatabase[movePtr].to = sq; + } else + if(promoPiece != pieceType[piece]) { + moveDatabase[movePtr++].piece = Q_PROMO; + moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece; + } + moveDatabase[movePtr].piece = piece; + quickBoard[sq] = piece; + movePtr++; +} + +int PackGame(Board board) +{ + Move *newSpace = NULL; + moveDatabase[movePtr].piece = 0; // terminate previous game + if(movePtr > dataSize) { + if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128); + dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB) + if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move)); + if(newSpace) { + int i; + Move *p = moveDatabase, *q = newSpace; + for(i=0; i 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated) + moveDatabase = newSpace; + } else { // calloc failed, we must be out of memory. Too bad... + dataSize = 0; // prevent calloc events for all subsequent games + return 0; // and signal this one isn't cached + } + } + movePtr++; + MakePieceList(board, counts); + return movePtr; +} + +int QuickCompare(Board board, int *minCounts, int *maxCounts) +{ // compare according to search mode + int r, f; + switch(appData.searchMode) + { + case 1: // exact position match + if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move + for(r=0; r maxCounts[r]) return FALSE; + } + return TRUE; +} + +int QuickScan(Board board, Move *move) +{ // reconstruct game,and compare all positions in it + int cnt=0, stretch=0, total = MakePieceList(board, counts); + do { + int piece = move->piece; + int to = move->to, from = pieceList[piece]; + if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4 + if(!piece) return -1; + if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType) + piece = (++move)->piece; + from = pieceList[piece]; + counts[pieceType[piece]]--; + pieceType[piece] = (ChessSquare) move->to; + counts[move->to]++; + } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to) + counts[pieceType[quickBoard[to]]]--; + quickBoard[to] = 0; total--; + move++; + continue; + } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to) + piece = pieceList[piece]; // first two elements of pieceList contain King numbers + from = pieceList[piece]; // so this must be King + quickBoard[from] = 0; + quickBoard[to] = piece; + pieceList[piece] = to; + move++; + continue; + } + } + if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture + if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for + quickBoard[from] = 0; + quickBoard[to] = piece; + pieceList[piece] = to; + cnt++; turn ^= 3; + if(QuickCompare(soughtBoard, minSought, maxSought) || + appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) || + flipSearch && (QuickCompare(flipBoard, minSought, maxSought) || + appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse)) + ) { + static int lastCounts[EmptySquare+1]; + int i; + if(stretch) for(i=0; i= appData.stretch)) return cnt + 1 - stretch; + move++; + } while(1); +} + +void InitSearch() +{ + int r, f; + flipSearch = FALSE; + CopyBoard(soughtBoard, boards[currentMove]); + soughtTotal = MakePieceList(soughtBoard, maxSought); + soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1; + if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!) + CopyBoard(reverseBoard, boards[currentMove]); + for(r=0; r= 5) { + for(r=BOARD_HEIGHT/2; rgameInfo.variant != gameInfo.variant) return -1; // wrong variant + if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1; + if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1; + if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1; if(!initDone) { - for(next = WhitePawn; next>8 ^ rand()<<6 ^rand()<<20; + for(next = WhitePawn; next>8 ^ random()<<6 ^random()<<20; initDone = TRUE; } - dummyInfo.variant = VariantNormal; - FREE(dummyInfo.fen); dummyInfo.fen = NULL; - dummyInfo.whiteRating = 0; - dummyInfo.blackRating = 0; - FREE(dummyInfo.date); dummyInfo.date = NULL; + if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen); + else CopyBoard(boards[scratch], initialPosition); // default start position + if(lg->moves) { + turn = btm + 1; + if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there + if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan. + } + if(btm) plyNr++; + if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr; fseek(f, lg->offset, 0); yynewfile(f); - CopyBoard(boards[scratch], initialPosition); // default start position while(1) { - yyboardindex = scratch + (plyNr&1); - quickFlag = 1; + yyboardindex = scratch; + quickFlag = plyNr+1; next = Myylex(); - quickFlag = 0; + quickFlag = 0; switch(next) { case PGNTag: if(plyNr) return -1; // after we have seen moves, any tags will be start of next game -#if 0 - ParsePGNTag(yy_text, &dummyInfo); // this has a bad memory leak... - if(dummyInfo.fen) ParseFEN(boards[scratch], &btm, dummyInfo.fen), free(dummyInfo.fen), dummyInfo.fen = NULL; -#else - // do it ourselves avoiding malloc - { char *p = yy_text+1, *q; - while(!isdigit(*p) && !isalpha(*p)) p++; - q = p; while(*p != ' ' && *p != '\t' && *p != '\n') p++; - *p = NULLCHAR; - if(!StrCaseCmp(q, "Date") && (p = strchr(p+1, '"'))) { if(atoi(p+1) < appData.dateThreshold) return -1; } else - if(!StrCaseCmp(q, "Variant") && (p = strchr(p+1, '"'))) dummyInfo.variant = StringToVariant(p+1); else - if(!StrCaseCmp(q, "WhiteElo") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else - if(!StrCaseCmp(q, "BlackElo") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else - if(!StrCaseCmp(q, "WhiteUSCF") && (p = strchr(p+1, '"'))) dummyInfo.whiteRating = atoi(p+1); else - if(!StrCaseCmp(q, "BlackUSCF") && (p = strchr(p+1, '"'))) dummyInfo.blackRating = atoi(p+1); else - if(!StrCaseCmp(q, "FEN") && (p = strchr(p+1, '"'))) ParseFEN(boards[scratch], &btm, p+1); - } -#endif default: continue; @@ -11169,21 +11490,18 @@ int GameContainsPosition(FILE *f, ListGame *lg) fromY = DROP_RANK; toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; + promoChar = 0; break; } // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move - if(plyNr == 0) { // but first figure out variant and initial position - if(dummyInfo.variant != gameInfo.variant) return -1; // wrong variant - if(appData.eloThreshold1 && (dummyInfo.whiteRating < appData.eloThreshold1 && dummyInfo.blackRating < appData.eloThreshold1)) return -1; - if(appData.eloThreshold2 && (dummyInfo.whiteRating < appData.eloThreshold2 || dummyInfo.blackRating < appData.eloThreshold2)) return -1; - if(appData.dateThreshold && (!dummyInfo.date || atoi(dummyInfo.date) < appData.dateThreshold)) return -1; - if(btm) CopyBoard(boards[scratch+1], boards[scratch]), plyNr++; - if(PositionMatches(boards[scratch + plyNr], boards[currentMove])) return plyNr; - } - CopyBoard(boards[scratch + (plyNr+1&1)], boards[scratch + (plyNr&1)]); plyNr++; - ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch + (plyNr&1)]); - if(PositionMatches(boards[scratch + (plyNr&1)], boards[currentMove])) return plyNr; + ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]); + if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr; + if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr; + if(appData.findMirror) { + if(PositionMatches(boards[scratch], flipBoard)) return plyNr; + if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr; + } } } @@ -11708,7 +12026,7 @@ LoadPosition(f, positionNumber, title) lastLoadPositionFP = f; lastLoadPositionNumber = positionNumber; safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0])); - if (first.pr == NoProc) { + if (first.pr == NoProc && !appData.noChessProgram) { StartChessProgram(&first); InitChessProgram(&first, FALSE); } @@ -11780,7 +12098,6 @@ LoadPosition(f, positionNumber, title) } startedFromSetupPosition = TRUE; - SendToProgram("force\n", &first); CopyBoard(boards[0], initial_position); if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; @@ -11793,7 +12110,10 @@ LoadPosition(f, positionNumber, title) DisplayMessage("", _("White to play")); } initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */ - SendBoard(&first, forwardMostMove); + if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed + SendToProgram("force\n", &first); + SendBoard(&first, forwardMostMove); + } if (appData.debugMode) { int i, j; for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");} @@ -11859,12 +12179,18 @@ SaveGameToFile(filename, append) { FILE *f; char buf[MSG_SIZ]; - int result; + int result, i, t,tot=0; if (strcmp(filename, "-") == 0) { return SaveGame(stdout, 0, NULL); } else { - f = fopen(filename, append ? "a" : "w"); + for(i=0; i<10; i++) { // upto 10 tries + f = fopen(filename, append ? "a" : "w"); + if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot); + if(f || errno != 13) break; + DoSleep(t = 5 + random()%11); // wait 5-15 msec + tot += t; + } if (f == NULL) { snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename); DisplayError(buf, errno); @@ -12817,6 +13143,7 @@ AnalyzeFileEvent() StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay)); } void @@ -13021,7 +13348,7 @@ int WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry) { char buf[MSG_SIZ]; - if (cps->pr == NULL) { + if (cps->pr == NoProc) { StartChessProgram(cps); if (cps->protocolVersion == 1) { retry(); @@ -13306,9 +13633,11 @@ EditGameEvent() SendToProgram("undo\n", &first); i--; } + if(!adjustedClock) { whiteTimeRemaining = timeRemaining[0][currentMove]; blackTimeRemaining = timeRemaining[1][currentMove]; DisplayBothClocks(); + } if (whiteFlag || blackFlag) { whiteFlag = blackFlag = 0; } @@ -13901,6 +14230,8 @@ ForwardInner(target) if (gameMode == EditPosition) return; + MarkTargetSquares(1); + if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); @@ -13951,7 +14282,6 @@ ForwardInner(target) if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } - DisplayBook(currentMove); } @@ -14007,6 +14337,7 @@ BackwardInner(target) target, currentMove, forwardMostMove); if (gameMode == EditPosition) return; + MarkTargetSquares(1); if (currentMove <= backwardMostMove) { ClearHighlights(); DrawPosition(full_redraw, boards[currentMove]); @@ -14064,7 +14395,6 @@ BackwardInner(target) HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); // [HGM] PV info: routine tests if comment empty DisplayComment(currentMove - 1, commentList[currentMove]); - DisplayBook(currentMove); } void @@ -14557,6 +14887,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); if (len == 0) return; if (commentList[index] != NULL) { + Boolean addClosingBrace = addBraces; old = commentList[index]; oldlen = strlen(old); while(commentList[index][oldlen-1] == '\n') @@ -14573,7 +14904,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n"); else strcat(commentList[index], "\n"); strcat(commentList[index], text); - if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n"); + if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n"); else strcat(commentList[index], "\n"); } else { commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4... @@ -14673,7 +15004,7 @@ char *GetInfoFromComment( int index, char * text ) while( *++sep >= '0' && *sep <= '9'); // strip seconds if(deci >= 0) while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds - while(*sep == ' ') sep++; + while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++; } if( depth <= 0 ) { @@ -14701,7 +15032,7 @@ SendToProgram(message, cps) int count, outCount, error; char buf[MSG_SIZ]; - if (cps->pr == NULL) return; + if (cps->pr == NoProc) return; Attention(cps); if (appData.debugMode) { @@ -15631,9 +15962,11 @@ NextTickLength(timeRemaining) void AdjustClock(Boolean which, int dir) { + if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; } if(which) blackTimeRemaining += 60000*dir; else whiteTimeRemaining += 60000*dir; DisplayBothClocks(); + adjustedClock = TRUE; } /* Stop clocks and reset to a fresh time control */ @@ -15657,6 +15990,7 @@ ResetClocks() } lastWhite = lastBlack = whiteStartMove = blackStartMove = 0; DisplayBothClocks(); + adjustedClock = FALSE; } #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */