X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=61a9f1ecabecb9279912f3482c68a55db8d96eb9;hb=1bb92a334cb80297755d51dcbb3847273b49226e;hp=0a8b6823fc63cc2535ba7764ebbdd0ab82cd3f17;hpb=19ba39ea3fba5ff50a6624575b262db203ada9d4;p=xboard.git diff --git a/backend.c b/backend.c index 0a8b682..61a9f1e 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, 2011, 2012 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -129,6 +129,7 @@ extern int gettimeofday(struct timeval *, struct timezone *); # include "zippy.h" #endif #include "backendz.h" +#include "evalgraph.h" #include "gettext.h" #ifdef ENABLE_NLS @@ -152,7 +153,6 @@ void read_from_player P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); void read_from_ics P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); -void ics_printf P((char *format, ...)); void SendToICS P((char *s)); void SendToICSDelayed P((char *s, long msdelay)); void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)); @@ -223,6 +223,8 @@ int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync)); FILE *WriteTourneyFile P((char *results, FILE *f)); void DisplayTwoMachinesTitle P(()); static void ExcludeClick P((int index)); +void ToggleSecond P((void)); +void PauseEngine P((ChessProgramState *cps)); #ifdef WIN32 extern void ConsoleCreate(); @@ -270,6 +272,7 @@ char lastMsg[MSG_SIZ]; ChessSquare pieceSweep = EmptySquare; ChessSquare promoSweep = EmptySquare, defaultPromoChoice; int promoDefaultAltered; +int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */ /* States for ics_getting_history */ #define H_FALSE 0 @@ -422,7 +425,7 @@ Boolean alarmSounded; /* end premove variables */ char *ics_prefix = "$"; -int ics_type = ICS_GENERIC; +enum ICS_TYPE ics_type = ICS_GENERIC; int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0; int pauseExamForwardMostMove = 0; @@ -461,7 +464,7 @@ long timeControl_2; /* [AS] Allow separate time controls */ char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; int matchGame = 0, nextGame = 0, roundNr = 0; -Boolean waitingForGame = FALSE; +Boolean waitingForGame = FALSE, startingEngine = FALSE; TimeMark programStartTime, pauseStart; char ics_handle[MSG_SIZ]; int have_set_title = 0; @@ -783,6 +786,7 @@ InitEngine (ChessProgramState *cps, int n) cps->analysisSupport = 2; /* detect */ cps->analyzing = FALSE; cps->initDone = FALSE; + cps->reload = FALSE; /* New features added by Tord: */ cps->useFEN960 = FALSE; @@ -836,6 +840,8 @@ InitEngine (ChessProgramState *cps, int n) ChessProgramState *savCps; +GameMode oldMode; + void LoadEngine () { @@ -845,21 +851,25 @@ LoadEngine () 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 + oldMode = BeginningOfGame; // to prevent restoring old mode } InitChessProgram(savCps, FALSE); - SendToProgram("force\n", savCps); + if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode DisplayMessage("", ""); if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove); for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps); ThawUI(); SetGNUMode(); + if(oldMode == AnalyzeMode) AnalyzeModeEvent(); } void ReplaceEngine (ChessProgramState *cps, int n) { - EditGameEvent(); + oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete + keepInfo = 1; + if(oldMode != BeginningOfGame) EditGameEvent(); + keepInfo = 0; UnloadEngine(cps); appData.noChessProgram = FALSE; appData.clockMode = TRUE; @@ -873,7 +883,7 @@ ReplaceEngine (ChessProgramState *cps, int n) extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params; extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick; -static char resetOptions[] = +static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 " "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" " "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 " @@ -945,7 +955,7 @@ Load (ChessProgramState *cps, int i) if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR; quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n", - quote, p, quote, appData.directory[i], + quote, p, quote, appData.directory[i], useNick ? " -fn \"" : "", useNick ? nickName : "", useNick ? "\"" : "", @@ -1253,7 +1263,7 @@ GetTimeQuota (int movenr, int lastUsed, char *tcString) long time, increment; char *s = tcString; - if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version + if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version do { if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType); nextSession = s; suddenDeath = moves == 0 && increment == 0; @@ -1282,16 +1292,16 @@ ParseTimeControl (char *tc, float ti, int 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; } @@ -1843,6 +1853,7 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count { int outError, outCount; static int gotEof = 0; + static FILE *ini; /* Pass data read from player on to ICS */ if (count > 0) { @@ -1851,6 +1862,17 @@ read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count if (outCount < count) { DisplayFatalError(_("Error writing to ICS"), outError, 1); } + if(have_sent_ICS_logon == 2) { + if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file + fprintf(ini, "%s", message); + have_sent_ICS_logon = 3; + } else + have_sent_ICS_logon = 1; + } else if(have_sent_ICS_logon == 3) { + fprintf(ini, "%s", message); + fclose(ini); + have_sent_ICS_logon = 1; + } } else if (count < 0) { RemoveInputSource(isr); DisplayFatalError(_("Error reading from keyboard"), error, 1); @@ -2510,6 +2532,12 @@ PlotSeekAd (int i) } void +PlotSingleSeekAd (int i) +{ + PlotSeekAd(i); +} + +void AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot) { char buf[MSG_SIZ], *ext = ""; @@ -2529,7 +2557,7 @@ AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, i seekNrList[nrOfSeekAds] = nr; zList[nrOfSeekAds] = 0; seekAdList[nrOfSeekAds++] = StrSave(buf); - if(plot) PlotSeekAd(nrOfSeekAds-1); + if(plot) PlotSingleSeekAd(nrOfSeekAds-1); } } @@ -2599,7 +2627,7 @@ DrawSeekGraph () for(i=0; i<4000; i+= 100) if(i>=minRating && i= 0) { - int n = first.option[multi].value; + if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(cps)) >= 0) { + int n = cps->option[multi].value; if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++; snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n); - if(first.option[multi].value != n) SendToProgram(buf2, &first); - first.option[multi].value = n; + if(cps->option[multi].value != n) SendToProgram(buf2, cps); + cps->option[multi].value = n; *start = *end = 0; return FALSE; } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked @@ -5472,6 +5525,7 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end) } ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; + extendGame = (gameMode == AnalyzeMode && appData.autoExtend); return TRUE; } @@ -5481,7 +5535,7 @@ PvToSAN (char *pv) static char buf[10*MSG_SIZ]; int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove; *buf = NULLCHAR; - if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); + if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it for(i = forwardMostMove; i forwardMostMove) { + if(extendGame && currentMove > forwardMostMove) { Boolean saveAnimate = appData.animate; if(pushed) { if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space @@ -5867,6 +5923,7 @@ InitPosition (int redraw) case VariantSChess: SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek"); gameInfo.holdingsSize = 7; + for(i=0; i 18) { // tail if(exclusionHeader[19] == '-') { // tail was excluded - SendToProgram("include all\n", &first); + SendToBoth("include all\n"); WriteMap(0); // clear map completely // now re-exclude selected moves for(i=0; i= BOARD_RGHT) return; @@ -7202,9 +7269,9 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(!sweepSelecting) { toX = x; toY = y; - saveAnimate = appData.animate; } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep + saveAnimate = appData.animate; if (clickType == Press) { if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { // must be Edit Position mode with empty-square selected @@ -7232,17 +7299,19 @@ LeftClick (ClickType clickType, int xPix, int yPix) ClearHighlights(); } } else { +#if 0 +// [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square /* Finish drag move */ if (appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); } else { ClearHighlights(); } +#endif DragPieceEnd(xPix, yPix); dragging = 0; /* Don't animate move and drag both */ appData.animate = FALSE; } - MarkTargetSquares(1); // moves into holding are invalid for now (except in EditPosition, adapting to-square) if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) { @@ -7267,6 +7336,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } ClearHighlights(); fromX = fromY = -1; + MarkTargetSquares(1); DrawPosition(TRUE, boards[currentMove]); return; } @@ -7278,6 +7348,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { SetHighlights(fromX, fromY, toX, toY); + MarkTargetSquares(1); if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) { // [HGM] super: promotion to captured piece selected from holdings ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX]; @@ -7300,6 +7371,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed Explode(boards[currentMove-1], fromX, fromY, toX, toY)) DrawPosition(TRUE, boards[currentMove]); + MarkTargetSquares(1); fromX = fromY = -1; } appData.animate = saveAnimate; @@ -7556,6 +7628,9 @@ MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisCo || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon + } else if(v == VariantKnightmate) { + if(nMine == 1) return FALSE; + if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side]; @@ -7608,7 +7683,7 @@ Adjudicate (ChessProgramState *cps) // In any case it determnes if the game is a claimable draw (filling in EP_STATUS). // Actually ending the game is now based on the additional internal condition canAdjudicate. // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed. - int k, count = 0; static int bare = 1; + int k, drop, count = 0; static int bare = 1; ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first)); Boolean canAdjudicate = !appData.icsActive; @@ -7692,13 +7767,21 @@ Adjudicate (ChessProgramState *cps) boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE : ((nrW < nrB) != WhiteOnMove(forwardMostMove) ? EP_CHECKMATE : EP_WINS); - else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) + else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses } break; case MT_CHECKMATE: reason = "Xboard adjudication: Checkmate"; boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); + if(gameInfo.variant == VariantShogi) { + if(forwardMostMove > backwardMostMove + && moveList[forwardMostMove-1][1] == '@' + && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) { + reason = "XBoard adjudication: pawn-drop mate"; + boards[forwardMostMove][EP_STATUS] = EP_WINS; + } + } break; } @@ -7763,10 +7846,12 @@ Adjudicate (ChessProgramState *cps) /* Check for rep-draws */ count = 0; + drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess + && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand); for(k = forwardMostMove-2; - k>=backwardMostMove && k>=forwardMostMove-100 && + k>=backwardMostMove && k>=forwardMostMove-100 && (drop || (signed char)boards[k][EP_STATUS] < EP_UNKNOWN && - (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE; + (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE); k-=2) { int rights=0; if(CompareBoards(boards[k], boards[forwardMostMove])) { @@ -7792,7 +7877,7 @@ Adjudicate (ChessProgramState *cps) /* adjudicate after user-specified nr of repeats */ int result = GameIsDrawn; char *details = "XBoard adjudication: repetition draw"; - if(gameInfo.variant == VariantXiangqi && appData.testLegality) { + if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) { // [HGM] xiangqi: check for forbidden perpetuals int m, ourPerpetual = 1, hisPerpetual = 1; for(m=forwardMostMove; m>k; m-=2) { @@ -7810,6 +7895,12 @@ Adjudicate (ChessProgramState *cps) if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet break; // (or we would have caught him before). Abort repetition-checking loop. } else + if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws + if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins! + result = BlackWins; + details = "Xboard adjudication: repetition"; + } + } else // it must be XQ // Now check for perpetual chases if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase hisPerpetual = PerpetualChase(k, forwardMostMove); @@ -7958,7 +8049,7 @@ LoadError (char *errmess, ChessProgramState *cps) if(cps->initDone) return FALSE; cps->isr = NULL; // this should suppress further error popups from breaking pipes DestroyChildProcess(cps->pr, 9 ); // just to be sure - cps->pr = NoProc; + cps->pr = NoProc; if(cps == &first) { appData.noChessProgram = TRUE; gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu @@ -7983,6 +8074,8 @@ DeferredBookMove (void) } static int savedWhitePlayer, savedBlackPlayer, pairingReceived; +static ChessProgramState *stalledEngine; +static char stashedInputMove[MSG_SIZ]; void HandleMachineMove (char *message, ChessProgramState *cps) @@ -8048,6 +8141,22 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) { + if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume. + if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which); + safeStrCpy(stashedInputMove, message, MSG_SIZ); + stalledEngine = cps; + if(appData.ponderNextMove) { // bring opponent out of ponder + if(gameMode == TwoMachinesPlay) { + if(cps->other->pause) + PauseEngine(cps->other); + else + SendToProgram("easy\n", cps->other); + } + } + StopClocks(); + return; + } + /* This method is only useful on engines that support ping */ if (cps->lastPing != cps->lastPong) { if (gameMode == BeginningOfGame) { @@ -8118,7 +8227,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h /* Machine move could not be parsed; ignore it. */ snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"), machineMove, _(cps->which)); - DisplayError(buf1, 0); + DisplayMoveError(buf1); 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); if (gameMode == TwoMachinesPlay) { @@ -8315,7 +8424,7 @@ 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 || gameInfo.variant == VariantFairy) && + 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) return; @@ -8383,7 +8492,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11); SendToICS(buf1); } - } + } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment return; } if (!strncmp(message, "tellall ", 8)) { @@ -9315,6 +9424,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece } + if(gameInfo.variant == VariantSChess) { // update virginity + if(fromY == 0) board[VIRGIN][fromX] &= ~VIRGIN_W; // loss by moving + if(fromY == BOARD_HEIGHT-1) board[VIRGIN][fromX] &= ~VIRGIN_B; + if(toY == 0) board[VIRGIN][toX] &= ~VIRGIN_W; // loss by capture + if(toY == BOARD_HEIGHT-1) board[VIRGIN][toX] &= ~VIRGIN_B; + } + if (fromX == toX && fromY == toY) return; piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */ @@ -9364,11 +9480,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); - if(gameInfo.variant==VariantBughouse || - gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY >= BOARD_HEIGHT>>1) + && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -9425,11 +9541,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); - if(gameInfo.variant==VariantBughouse || - gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY < BOARD_HEIGHT>>1) + && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -9604,8 +9720,12 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) && fromX != toX && fromY != toY) fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY); // promotion suffix - if(promoChar != NULLCHAR) - fprintf(serverMoves, ":%c:%c%c", ToLower(promoChar), AAA+toX, ONE+toY); + if(promoChar != NULLCHAR) { + if(fromY == 0 || fromY == BOARD_HEIGHT-1) + fprintf(serverMoves, ":%c%c:%c%c", WhiteOnMove(forwardMostMove) ? 'w' : 'b', + ToLower(promoChar), AAA+fromX, ONE+fromY); // Seirawan gating + else 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", @@ -9614,6 +9734,7 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) else timeLeft = blackTimeRemaining/1000; fprintf(serverMoves, "/%d", timeLeft); strncpy(buf, parseList[forwardMostMove], MOVE_LEN*2); + if(p = strchr(buf, '/')) *p = NULLCHAR; else 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); @@ -9674,9 +9795,6 @@ ShowMove (int fromX, int fromY, int toX, int toY) AnimateMove(boards[forwardMostMove - 1], fromX, fromY, toX, toY); } - if (appData.highlightLastMove) { - SetHighlights(fromX, fromY, toX, toY); - } } currentMove = forwardMostMove; } @@ -9684,6 +9802,11 @@ ShowMove (int fromX, int fromY, int toX, int toY) if (instant) return; DisplayMove(currentMove - 1); + if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { + if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board + SetHighlights(fromX, fromY, toX, toY); + } + } DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); @@ -9846,6 +9969,33 @@ InitChessProgram (ChessProgramState *cps, int setup) void +ResendOptions (ChessProgramState *cps) +{ // send the stored value of the options + int i; + char buf[MSG_SIZ]; + Option *opt = cps->option; + for(i=0; inrOptions; i++, opt++) { + switch(opt->type) { + case Spin: + case Slider: + case CheckBox: + snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value); + break; + case ComboBox: + snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]); + break; + default: + snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue); + break; + case Button: + case SaveButton: + continue; + } + SendToProgram(buf, cps); + } +} + +void StartChessProgram (ChessProgramState *cps) { char buf[MSG_SIZ]; @@ -9885,9 +10035,12 @@ StartChessProgram (ChessProgramState *cps) cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps); if (cps->protocolVersion > 1) { snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion); - cps->nrOptions = 0; // [HGM] options: clear all engine-specific options - cps->comboCnt = 0; // and values of combo boxes + if(!cps->reload) { // do not clear options when reloading because of -xreuse + cps->nrOptions = 0; // [HGM] options: clear all engine-specific options + cps->comboCnt = 0; // and values of combo boxes + } SendToProgram(buf, cps); + if(cps->reload) ResendOptions(cps); } else { SendToProgram("xboard\n", cps); } @@ -9908,7 +10061,6 @@ TwoMachinesEventIfReady P((void)) return; } DisplayMessage("", ""); curMess = 0; - ThawUI(); TwoMachinesEvent(); } @@ -9968,7 +10120,15 @@ WriteTourneyFile (char *results, FILE *f) fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile); fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); + fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false"); + fprintf(f, "-polyglotBook %s\n", appData.polyglotBook); + fprintf(f, "-bookDepth %d\n", appData.bookDepth); + fprintf(f, "-bookVariation %d\n", appData.bookStrength); fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false"); + fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize); + fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB); + fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false"); + fprintf(f, "-smpCores %d\n", appData.smpCores); if(searchTime > 0) fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60); else { @@ -10042,6 +10202,27 @@ Substitute (char *participants, int expunge) } int +CheckPlayers (char *participants) +{ + int i; + char buf[MSG_SIZ], *p; + NamesToList(firstChessProgramNames, command, mnemonic, "all"); + while(p = strchr(participants, '\n')) { + *p = NULLCHAR; + for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break; + if(!mnemonic[i]) { + snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants); + *p = '\n'; + DisplayError(buf, 0); + return 1; + } + *p = '\n'; + participants = p + 1; + } + return 0; +} + +int CreateTourney (char *name) { FILE *f; @@ -10063,6 +10244,7 @@ CreateTourney (char *name) DisplayError(_("Not enough participants"), 0); return 0; } + if(CheckPlayers(appData.participants)) return 0; ASSIGN(appData.tourneyFile, name); if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1 if((f = WriteTourneyFile("", NULL)) == NULL) return 0; @@ -10141,6 +10323,28 @@ SwapEngines (int n) } int +GetEngineLine (char *s, int n) +{ + int i; + char buf[MSG_SIZ]; + extern char *icsNames; + if(!s || !*s) return 0; + NamesToList(n >= 10 ? icsNames : firstChessProgramNames, command, mnemonic, "all"); + for(i=1; mnemonic[i]; i++) if(!strcmp(s, mnemonic[i])) break; + if(!mnemonic[i]) return 0; + if(n == 11) return 1; // just testing if there was a match + snprintf(buf, MSG_SIZ, "-%s %s", n == 10 ? "icshost" : "fcp", command[i]); + if(n == 1) SwapEngines(n); + ParseArgsFromString(buf); + if(n == 1) SwapEngines(n); + if(n == 0 && *appData.secondChessProgram == NULLCHAR) { + SwapEngines(1); // set second same as first if not yet set (to suppress WB startup dialog) + ParseArgsFromString(buf); + } + return 1; +} + +int SetPlayer (int player, char *p) { // [HGM] find the engine line of the partcipant given by number, and parse its options. int i; @@ -10153,6 +10357,13 @@ SetPlayer (int player, char *p) ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE; appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER; ParseArgsFromString(buf); + } else { // no engine with this nickname is installed! + snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName); + ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error + matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0; + ModeHighlight(); + DisplayError(buf, 0); + return 0; } free(engineName); return i; @@ -10218,7 +10429,7 @@ Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInte *blackPlayer = curRound + appData.tourneyType; } - // take care of white/black alternation per round. + // 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; } @@ -10227,7 +10438,7 @@ 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; + int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1; FILE *tf; if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game tf = fopen(appData.tourneyFile, "r"); @@ -10267,7 +10478,7 @@ NextTourneyGame (int nr, int *swapColors) SendToProgram(buf, &pairing); return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again... } - pairingReceived = 0; // ... so we continue here + pairingReceived = 0; // ... so we continue here *swapColors = 0; appData.matchGames = appData.tourneyCycles * syncInterval - 1; whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1; @@ -10279,18 +10490,18 @@ NextTourneyGame (int nr, int *swapColors) // redefine engines, engine dir, etc. NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines if(first.pr == NoProc) { - SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line + if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. } if(second.pr == NoProc) { SwapEngines(1); - SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line + if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line SwapEngines(1); // and make that valid for second engine by swapping InitEngine(&second, 1); } CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes UpdateLogos(FALSE); // leave display to ModeHiglight() - return 1; + return OK; } void @@ -10391,6 +10602,8 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) fromX = fromY = -1; // [HGM] abort any move the user is entering. + if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit) + if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) { /* If we are playing on ICS, the server decides when the game is over, but the engine can offer to draw, claim @@ -10483,6 +10696,10 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) resultDetails = buf; } /* (Claiming a loss is accepted no questions asked!) */ + } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) { + forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving + result = GameUnfinished; + if(!*appData.tourneyFile) matchGame--; // replay even in plain match } /* [HGM] bare: don't allow bare King to win */ if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper @@ -10527,6 +10744,9 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates ) { if (*appData.saveGameFile != NULLCHAR) { + if(result == GameUnfinished && matchMode && *appData.tourneyFile) + AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead + else SaveGameToFile(appData.saveGameFile, TRUE); } else if (appData.autoSaveGames) { AutoSaveGame(); @@ -10534,6 +10754,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) if (*appData.savePositionFile != NULLCHAR) { SavePositionToFile(appData.savePositionFile); } + AddGameToBook(FALSE); // Only does something during Monte-Carlo book building } } @@ -10650,6 +10871,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) SendToProgram("quit\n", &first); DoSleep( appData.delayAfterQuit ); DestroyChildProcess(first.pr, first.useSigterm); + first.reload = TRUE; } first.pr = NoProc; } @@ -10675,11 +10897,12 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) SendToProgram("quit\n", &second); DoSleep( appData.delayAfterQuit ); DestroyChildProcess(second.pr, second.useSigterm); + second.reload = TRUE; } second.pr = NoProc; } - if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) { + if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) { char resChar = '='; switch (result) { case WhiteWins: @@ -10704,7 +10927,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) break; } - if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game + if(exiting) 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 @@ -10803,8 +11026,8 @@ ResurrectChessProgram () if (appData.noChessProgram) return 1; - 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(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, because we started engine 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 { @@ -10931,6 +11154,11 @@ AutoPlayGameLoop () } } +void +AnalyzeNextGame() +{ + ReloadGame(1); // next game +} int AutoPlayOneMove () @@ -10952,7 +11180,14 @@ AutoPlayOneMove () } if (currentMove >= forwardMostMove) { - if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); } + if(gameMode == AnalyzeFile) { + if(appData.loadGameIndex == -1) { + GameEnds(EndOfFile, NULL, GE_FILE); + ScheduleDelayedEvent(AnalyzeNextGame, 10); + } else { + ExitAnalyzeMode(); SendToProgram("force\n", &first); + } + } // gameMode = EndOfGame; // ModeHighlight(); @@ -11627,12 +11862,12 @@ InitSearch () if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip reverseBoard[r][f] = piece; } - reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; + reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6]; if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights - || (boards[currentMove][CASTLING][2] == NoRights || + || (boards[currentMove][CASTLING][2] == NoRights || boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights ) - && (boards[currentMove][CASTLING][5] == NoRights || + && (boards[currentMove][CASTLING][5] == NoRights || boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) ) ) { flipSearch = TRUE; @@ -11654,6 +11889,7 @@ InitSearch () } GameInfo dummyInfo; +static int creatingBook; int GameContainsPosition (FILE *f, ListGame *lg) @@ -11796,6 +12032,9 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) gn = 1; } else { + if(gameMode == AnalyzeFile && appData.loadGameIndex == -1) + appData.loadGameIndex = 0; // [HGM] suppress error message if we reach file end after auto-stepping analysis + else DisplayError(_("Game number out of range"), 0); return FALSE; } @@ -12114,6 +12353,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) cm = (ChessMove) Myylex(); } + if(!creatingBook) { if (first.pr == NoProc) { StartChessProgram(&first); } @@ -12126,6 +12366,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) } DisplayBothClocks(); } + } /* [HGM] server: flag to write setup moves in broadcast file as one */ loadFlag = appData.suppressLoadMoves; @@ -12189,9 +12430,11 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) if (oldGameMode == AnalyzeFile || oldGameMode == AnalyzeMode) { + appData.loadGameIndex = -1; // [HGM] order auto-stepping through games AnalyzeFileEvent(); } + if(creatingBook) return TRUE; if (!matchMode && pos > 0) { ToNrEvent(pos); // [HGM] no autoplay if selected on position } else @@ -13226,6 +13469,20 @@ ExitEvent (int status) } void +PauseEngine (ChessProgramState *cps) +{ + SendToProgram("pause\n", cps); + cps->pause = 2; +} + +void +UnPauseEngine (ChessProgramState *cps) +{ + SendToProgram("resume\n", cps); + cps->pause = 1; +} + +void PauseEvent () { if (appData.debugMode) @@ -13233,8 +13490,24 @@ PauseEvent () if (pausing) { pausing = FALSE; ModeHighlight(); + if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move + StartClocks(); + if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering + if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other); + else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other); + } + if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine); + HandleMachineMove(stashedInputMove, stalledEngine); + stalledEngine = NULL; + return; + } if (gameMode == MachinePlaysWhite || - gameMode == MachinePlaysBlack) { + gameMode == TwoMachinesPlay || + gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine + if(first.pause) UnPauseEngine(&first); + else if(appData.ponderNextMove) SendToProgram("hard\n", &first); + if(second.pause) UnPauseEngine(&second); + else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second); StartClocks(); } else { DisplayBothClocks(); @@ -13246,7 +13519,7 @@ PauseEvent () Reset(FALSE, TRUE); SendToICS(ics_prefix); SendToICS("refresh\n"); - } else if (currentMove < forwardMostMove) { + } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) { ForwardInner(forwardMostMove); } pauseExamInvalid = FALSE; @@ -13277,12 +13550,30 @@ PauseEvent () case TwoMachinesPlay: if (forwardMostMove == 0) return; /* don't pause if no one has moved */ - if ((gameMode == MachinePlaysWhite && - !WhiteOnMove(forwardMostMove)) || - (gameMode == MachinePlaysBlack && - WhiteOnMove(forwardMostMove))) { + if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately + ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second); + if(onMove->pause) { // thinking engine can be paused + PauseEngine(onMove); // do it + if(onMove->other->pause) // pondering opponent can always be paused immediately + PauseEngine(onMove->other); + else + SendToProgram("easy\n", onMove->other); + StopClocks(); + } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder + } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move + if(first.pause) { + PauseEngine(&first); + StopClocks(); + } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder + } else { // human on move, pause pondering by either method + if(first.pause) + PauseEngine(&first); + else if(appData.ponderNextMove) + SendToProgram("easy\n", &first); StopClocks(); } + // if no immediate pausing is possible, wait for engine to move, and stop clocks then + case AnalyzeMode: pausing = TRUE; ModeHighlight(); break; @@ -13317,16 +13608,74 @@ EditTagsEvent () } void +ToggleSecond () +{ + if(second.analyzing) { + SendToProgram("exit\n", &second); + second.analyzing = FALSE; + } else { + if (second.pr == NoProc) StartChessProgram(&second); + InitChessProgram(&second, FALSE); + FeedMovesToProgram(&second, currentMove); + + SendToProgram("analyze\n", &second); + second.analyzing = TRUE; + } +} + +/* Toggle ShowThinking */ +void +ToggleShowThinking() +{ + appData.showThinking = !appData.showThinking; + ShowThinkingEvent(); +} + +int AnalyzeModeEvent () { + char buf[MSG_SIZ]; + + if (!first.analysisSupport) { + snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy); + DisplayError(buf, 0); + return 0; + } + /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */ + if (appData.icsActive) { + if (gameMode != IcsObserving) { + snprintf(buf, MSG_SIZ, _("You are not observing a game")); + DisplayError(buf, 0); + /* secure check */ + if (appData.icsEngineAnalyze) { + if (appData.debugMode) + fprintf(debugFP, _("Found unexpected active ICS engine analyze \n")); + ExitAnalyzeMode(); + ModeHighlight(); + } + return 0; + } + /* if enable, user wants to disable icsEngineAnalyze */ + if (appData.icsEngineAnalyze) { + ExitAnalyzeMode(); + ModeHighlight(); + return 0; + } + appData.icsEngineAnalyze = TRUE; + if (appData.debugMode) + fprintf(debugFP, _("ICS engine analyze starting... \n")); + } + + if (gameMode == AnalyzeMode) { ToggleSecond(); return 0; } if (appData.noChessProgram || gameMode == AnalyzeMode) - return; + return 0; if (gameMode != AnalyzeFile) { if (!appData.icsEngineAnalyze) { EditGameEvent(); - if (gameMode != EditGame) return; + if (gameMode != EditGame) return 0; } + if (!appData.showThinking) ToggleShowThinking(); ResurrectChessProgram(); SendToProgram("analyze\n", &first); first.analyzing = TRUE; @@ -13342,6 +13691,7 @@ AnalyzeModeEvent () StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + return 1; } void @@ -13350,9 +13700,19 @@ AnalyzeFileEvent () if (appData.noChessProgram || gameMode == AnalyzeFile) return; + if (!first.analysisSupport) { + char buf[MSG_SIZ]; + snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy); + DisplayError(buf, 0); + return; + } + if (gameMode != AnalyzeMode) { + keepInfo = 1; // mere annotating should not alter PGN tags EditGameEvent(); + keepInfo = 0; if (gameMode != EditGame) return; + if (!appData.showThinking) ToggleShowThinking(); ResurrectChessProgram(); SendToProgram("analyze\n", &first); first.analyzing = TRUE; @@ -13363,12 +13723,12 @@ AnalyzeFileEvent () gameMode = AnalyzeFile; pausing = FALSE; ModeHighlight(); - SetGameInfo(); StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay)); + AnalysisPeriodicEvent(1); } void @@ -13538,7 +13898,7 @@ DisplayTwoMachinesTitle () gameInfo.white, _("vs."), gameInfo.black, nextGame+1, appData.matchGames+1, appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr"); - } else + } else if (first.twoMachinesColor[0] == 'w') { snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", gameInfo.white, _("vs."), gameInfo.black, @@ -13577,9 +13937,10 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry) StartChessProgram(cps); if (cps->protocolVersion == 1) { retry(); + ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry' } else { /* kludge: allow timeout for initial "feature" command */ - FreezeUI(); + if(retry != TwoMachinesEventIfReady) FreezeUI(); snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which)); DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); @@ -13632,16 +13993,19 @@ TwoMachinesEvent P((void)) // forwardMostMove = currentMove; TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... + startingEngine = TRUE; 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.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) { + startingEngine = FALSE; DisplayError("second engine does not play this", 0); return; } @@ -13675,7 +14039,7 @@ TwoMachinesEvent P((void)) } gameMode = TwoMachinesPlay; - pausing = FALSE; + pausing = startingEngine = FALSE; ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); @@ -13916,8 +14280,8 @@ ExitAnalyzeMode () DisplayMessage("",_("Close ICS engine analyze...")); } if (first.analysisSupport && first.analyzing) { - SendToProgram("exit\n", &first); - first.analyzing = FALSE; + SendToBoth("exit\n"); + first.analyzing = second.analyzing = FALSE; } thinkOutput[0] = NULLCHAR; } @@ -13932,14 +14296,22 @@ EditPositionDone (Boolean fakeRights) if(fakeRights) { // [HGM] suppress this if we just pasted a FEN. boards[0][EP_STATUS] = EP_NONE; boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1; - if(boards[0][0][BOARD_WIDTH>>1] == king) { - boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights; + if(boards[0][0][BOARD_WIDTH>>1] == king) { + boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights; boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights; } else boards[0][CASTLING][2] = NoRights; - if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { - boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights; + if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { + boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights; boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights; } else boards[0][CASTLING][5] = NoRights; + if(gameInfo.variant == VariantSChess) { + int i; + for(i=BOARD_LEFT; ibackwardMostMove; i--) { // seek back to start or previous null move if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break; } - SendBoard(&first, i); - for(currentMove=i; currentMovenumber <= 0 ) { + DisplayError(_("Game list not loaded or empty"), 0); + return; + } + + if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) { + fclose(f); + secondTime++; + DisplayNote(_("Book file exists! Try again for overwrite.")); + return; + } + + creatingBook = TRUE; + secondTime = FALSE; + + /* Get list size */ + for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){ + LoadGame(f, nItem, "", TRUE); + AddGameToBook(TRUE); + lg = (ListGame *) lg->node.succ; + } + + creatingBook = FALSE; + FlushBook(); +} + +void BookEvent () { if (appData.noChessProgram) return; @@ -14946,6 +15358,8 @@ SetGameInfo () ChessMove r = GameUnfinished; char *p = NULL; + if(keepInfo) return; + if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame r = gameInfo.result; p = gameInfo.resultDetails; @@ -15043,7 +15457,7 @@ ReplaceComment (int index, char *text) char *p; float score; - if(index && sscanf(text, "%f/%d", &score, &len) == 2 && + 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 @@ -15356,7 +15770,7 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou 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, "hint: %c", &c)!=1 && sscanf(message, "pong %c", &c)!=1 && start != '#') { quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### "; print = (appData.engineComments >= 2); @@ -15650,6 +16064,7 @@ FeatureDone (ChessProgramState *cps, int val) ScheduleDelayedEvent(cb, val ? 1 : 3600000); } cps->initDone = val; + if(val) cps->reload = FALSE; } /* Parse feature command from engine */ @@ -15694,7 +16109,7 @@ ParseFeatures (char *args, ChessProgramState *cps) if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue; if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue; if (BoolFeature(&p, "name", &cps->sendName, cps)) continue; - if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */ + if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause if (IntFeature(&p, "done", &val, cps)) { FeatureDone(cps, val); continue; @@ -15712,6 +16127,7 @@ ParseFeatures (char *args, ChessProgramState *cps) if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue; if (StringFeature(&p, "option", buf, cps)) { + if(cps->reload) continue; // we are reloading because of xreuse FREE(cps->option[cps->nrOptions].name); cps->option[cps->nrOptions].name = malloc(MSG_SIZ); safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ); @@ -15838,9 +16254,9 @@ AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which) void TypeInEvent (char firstChar) { - if ((gameMode == BeginningOfGame && !appData.icsActive) || + if ((gameMode == BeginningOfGame && !appData.icsActive) || gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || - gameMode == AnalyzeMode || gameMode == EditGame || + 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 @@ -15876,16 +16292,16 @@ TypeInDoneEvent (char *move) return; } - if (gameMode != EditGame && currentMove != forwardMostMove && + if (gameMode != EditGame && currentMove != forwardMostMove && gameMode != Training) { DisplayMoveError(_("Displayed move is not current")); } else { - int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + 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, + if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { - UserMoveEvent(fromX, fromY, toX, toY, promoChar); + UserMoveEvent(fromX, fromY, toX, toY, promoChar); } else { DisplayMoveError(_("Could not parse move")); } @@ -16659,14 +17075,30 @@ PositionToFEN (int move, char *overrideCastling) /* [HGM] write true castling rights */ if( nrCastlingRights == 6 ) { + int q, k=0; if(boards[move][CASTLING][0] == BOARD_RGHT-1 && - boards[move][CASTLING][2] != NoRights ) *p++ = 'K'; - if(boards[move][CASTLING][1] == BOARD_LEFT && - boards[move][CASTLING][2] != NoRights ) *p++ = 'Q'; + boards[move][CASTLING][2] != NoRights ) k = 1, *p++ = 'K'; + q = (boards[move][CASTLING][1] == BOARD_LEFT && + boards[move][CASTLING][2] != NoRights ); + if(gameInfo.variant == VariantSChess) { // for S-Chess, indicate all vrgin backrank pieces + for(i=j=0; i=BOARD_LEFT+q && j; i--) + if((boards[move][0][i] != WhiteKing || k+q == 0) && + boards[move][VIRGIN][i] & VIRGIN_W) *p++ = i + AAA + 'A' - 'a'; + } + if(q) *p++ = 'Q'; + k = 0; if(boards[move][CASTLING][3] == BOARD_RGHT-1 && - boards[move][CASTLING][5] != NoRights ) *p++ = 'k'; - if(boards[move][CASTLING][4] == BOARD_LEFT && - boards[move][CASTLING][5] != NoRights ) *p++ = 'q'; + boards[move][CASTLING][5] != NoRights ) k = 1, *p++ = 'k'; + q = (boards[move][CASTLING][4] == BOARD_LEFT && + boards[move][CASTLING][5] != NoRights ); + if(gameInfo.variant == VariantSChess) { + for(i=j=0; i=BOARD_LEFT+q && j; i--) + if((boards[move][BOARD_HEIGHT-1][i] != BlackKing || k+q == 0) && + boards[move][VIRGIN][i] & VIRGIN_B) *p++ = i + AAA; + } + if(q) *p++ = 'q'; } } if (q == p) *p++ = '-'; /* No castling rights */ @@ -16732,7 +17164,7 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen) { int i, j; char *p, c; - int emptycount; + int emptycount, virgin[BOARD_FILES]; ChessSquare piece; p = fen; @@ -16861,17 +17293,18 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen) while(*p==' ') p++; if(nrCastlingRights) { - if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') { + if(gameInfo.variant == VariantSChess) for(i=0; i= 'A' && *p <= 'Z' || *p >= 'a' && *p <= 'z' || *p=='-') { /* castling indicator present, so default becomes no castlings */ for(i=0; i= 'a' && *p < 'a' + gameInfo.boardWidth) || ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) { - char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights; + int c = *p++, whiteKingFile=NoRights, blackKingFile=NoRights; for(i=BOARD_LEFT; iwhiteKingFile; i--); board[CASTLING][0] = i != whiteKingFile ? i : NoRights; board[CASTLING][2] = whiteKingFile; + if(board[CASTLING][0] != NoRights) virgin[board[CASTLING][0]] |= VIRGIN_W; + if(board[CASTLING][2] != NoRights) virgin[board[CASTLING][2]] |= VIRGIN_W; break; case'Q': for(i=BOARD_LEFT; iblackKingFile; i--); board[CASTLING][3] = i != blackKingFile ? i : NoRights; board[CASTLING][5] = blackKingFile; + if(board[CASTLING][3] != NoRights) virgin[board[CASTLING][3]] |= VIRGIN_B; + if(board[CASTLING][5] != NoRights) virgin[board[CASTLING][5]] |= VIRGIN_B; break; case'q': for(i=BOARD_LEFT; i= 'a') { /* black rights */ + if(gameInfo.variant == VariantSChess) { virgin[c-AAA] |= VIRGIN_B; break; } // in S-Chess castlings are always kq, so just virginity for(i=BOARD_LEFT; i