X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=13afbc52f435b666fe21ec76ee1c3997661edd45;hb=8baea05c6e9e63f8b1b57891b67b5ac0e1961b81;hp=3f6229da29fab8839781c7a70822004c2e7b732d;hpb=fa8cdd39eca80f4bdbf6e8e8a290fa1b8979224e;p=xboard.git diff --git a/backend.c b/backend.c index 3f6229d..13afbc5 100644 --- a/backend.c +++ b/backend.c @@ -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,7 @@ 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)); #ifdef WIN32 extern void ConsoleCreate(); @@ -270,6 +271,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 +424,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; @@ -450,15 +452,15 @@ int adjudicateLossPlies = 6; char white_holding[64], black_holding[64]; TimeMark lastNodeCountTime; long lastNodeCount=0; -int shiftKey; // [HGM] set by mouse handler +int shiftKey, controlKey; // [HGM] set by mouse handler 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; +long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime; 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 */ +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; @@ -851,7 +853,7 @@ LoadEngine () SendToProgram("force\n", savCps); DisplayMessage("", ""); if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove); - for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps); + for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps); ThawUI(); SetGNUMode(); } @@ -920,7 +922,7 @@ Load (ChessProgramState *cps, int i) while(q = strchr(p, SLASH)) p = q+1; if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; } if(engineDir[0] != NULLCHAR) { - ASSIGN(appData.directory[i], engineDir); + ASSIGN(appData.directory[i], engineDir); p = engineName; } else if(p != engineName) { // derive directory from engine path, when not given p[-1] = 0; ASSIGN(appData.directory[i], engineName); @@ -1253,7 +1255,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; @@ -1843,6 +1845,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 +1854,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); @@ -2446,6 +2460,7 @@ VariantSwitch (Board board, VariantClass newVariant) board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = board[i][j]; } + board[HOLDINGS_SET] = 0; gameInfo.boardWidth = newWidth; gameInfo.boardHeight = newHeight; gameInfo.holdingsWidth = newHoldingsWidth; @@ -2493,8 +2508,8 @@ PlotSeekAd (int i) xList[i] = yList[i] = -100; // outside graph, so cannot be clicked if(r < minRating+100 && r >=0 ) r = minRating+100; if(r > maxRating) r = maxRating; - if(tc < 1.) tc = 1.; - if(tc > 95.) tc = 95.; + if(tc < 1.f) tc = 1.f; + if(tc > 95.f) tc = 95.f; x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin; y = ((double)r - minRating)/(maxRating - minRating) * (h-vMargin-squareSize/8-1) + vMargin; @@ -2509,6 +2524,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 = ""; @@ -2528,7 +2549,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); } } @@ -2598,7 +2619,7 @@ DrawSeekGraph () for(i=0; i<4000; i+= 100) if(i>=minRating && i= 0) { + int victim = boards[currentMove][toY][toX]; + boards[currentMove][toY][toX] = promoSweep; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][toY][toX] = victim; + } else ChangeDragPiece(promoSweep); } @@ -5410,10 +5481,11 @@ MultiPV (ChessProgramState *cps) } Boolean -LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end) +LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane) { int startPV, multi, lineStart, origIndex = index; char *p, buf2[MSG_SIZ]; + ChessProgramState *cps = (pane ? &second : &first); if(index < 0 || index >= strlen(buf)) return FALSE; // sanity lastX = x; lastY = y; @@ -5425,12 +5497,12 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end) do{ while(buf[index] && buf[index] != '\n') index++; } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line buf[index] = 0; - if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 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 @@ -5834,6 +5906,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_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click return; } - if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { + if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { + if(appData.sweepSelect) { ChessSquare piece = boards[currentMove][fromY][fromX]; - DragPieceBegin(xPix, yPix, TRUE); dragging = 1; promoSweep = defaultPromoChoice; if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece; selectFlag = 0; lastX = xPix; lastY = yPix; Sweep(0); // Pawn that is going to promote: preview promotion piece + sweepSelecting = 1; DisplayMessage("", _("Pull pawn backwards to under-promote")); - DrawPosition(FALSE, boards[currentMove]); - return; + MarkTargetSquares(1); + } + return; // promo popup appears on up-click } /* Finish clickclick move */ if (appData.animate || appData.highlightLastMove) { @@ -7193,12 +7282,15 @@ 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; @@ -7227,6 +7319,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } ClearHighlights(); fromX = fromY = -1; + MarkTargetSquares(1); DrawPosition(TRUE, boards[currentMove]); return; } @@ -7238,6 +7331,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]; @@ -7260,6 +7354,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; @@ -7912,6 +8007,25 @@ SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial) return bookHit; // notify caller of hit, so it can take action to send move to opponent } +int +LoadError (char *errmess, ChessProgramState *cps) +{ // unloads engine and switches back to -ncp mode if it was first + 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; + if(cps == &first) { + appData.noChessProgram = TRUE; + gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu + gameMode = BeginningOfGame; ModeHighlight(); + SetNCPMode(); + } + if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout + DisplayMessage("", ""); // erase waiting message + if(errmess) DisplayError(errmess, 0); // announce reason, if given + return TRUE; +} + char *savedMessage; ChessProgramState *savedState; void @@ -7934,7 +8048,7 @@ HandleMachineMove (char *message, ChessProgramState *cps) ChessMove moveType; char promoChar; char *p, *pv=buf1; - int machineWhite; + int machineWhite, oldError; char *bookHit; if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) { @@ -7948,7 +8062,7 @@ HandleMachineMove (char *message, ChessProgramState *cps) return; // Skim the pairing messages here. } - cps->userError = 0; + oldError = cps->userError; cps->userError = 0; FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit /* @@ -8517,18 +8631,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. _(cps->which), cps->program, cps->host, message); RemoveInputSource(cps->isr); if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else { - cps->isr = NULL; - DestroyChildProcess(cps->pr, 9 ); // just to be sure - cps->pr = NoProc; - if(cps == &first) { - appData.noChessProgram = TRUE; - gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu - gameMode = BeginningOfGame; ModeHighlight(); - SetNCPMode(); - } - if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout - DisplayMessage("", ""); // erase waiting message - DisplayError(buf1, 0); + if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError + if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup } return; } @@ -9266,6 +9370,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 */ @@ -9555,8 +9666,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", @@ -9565,6 +9680,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); @@ -9625,9 +9741,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; } @@ -9636,6 +9749,11 @@ ShowMove (int fromX, int fromY, int toX, int toY) DisplayMove(currentMove - 1); DrawPosition(FALSE, boards[currentMove]); + 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); + } + } DisplayBothClocks(); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); } @@ -9993,6 +10111,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; @@ -10014,6 +10153,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; @@ -10092,6 +10232,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; @@ -10877,11 +11039,16 @@ AutoPlayGameLoop () continue; if (appData.timeDelay < 0) return; - StartLoadGameTimer((long)(1000.0 * appData.timeDelay)); + StartLoadGameTimer((long)(1000.0f * appData.timeDelay)); break; } } +void +AnalyzeNextGame() +{ + ReloadGame(1); // next game +} int AutoPlayOneMove () @@ -10903,7 +11070,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(); @@ -11533,15 +11707,18 @@ QuickScan (Board board, Move *move) 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; + from = pieceList[(++move)->piece]; // for FRC this has to be done here + quickBoard[from] = 0; // rook + quickBoard[to] = piece; + to = move->to; piece = move->piece; + goto aftercastle; } } 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; + aftercastle: quickBoard[to] = piece; pieceList[piece] = to; cnt++; turn ^= 3; @@ -11744,6 +11921,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; } @@ -12137,10 +12317,13 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) if (oldGameMode == AnalyzeFile || oldGameMode == AnalyzeMode) { + appData.loadGameIndex = -1; // [HGM] order auto-stepping through games + keepInfo = 1; AnalyzeFileEvent(); + keepInfo = 0; } - if (!matchMode && pos >= 0) { + if (!matchMode && pos > 0) { ToNrEvent(pos); // [HGM] no autoplay if selected on position } else if (matchMode || appData.timeDelay == 0) { @@ -12491,7 +12674,6 @@ int SaveGamePGN (FILE *f) { int i, offset, linelen, newblock; - time_t tm; // char *movetext; char numtext[32]; int movelen, numlen, blank; @@ -12499,8 +12681,6 @@ SaveGamePGN (FILE *f) offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ - tm = time((time_t *) NULL); - PrintPGNTags(f, &gameInfo); if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag @@ -13234,6 +13414,7 @@ PauseEvent () WhiteOnMove(forwardMostMove))) { StopClocks(); } + case AnalyzeMode: pausing = TRUE; ModeHighlight(); break; @@ -13268,16 +13449,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; @@ -13293,6 +13532,7 @@ AnalyzeModeEvent () StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + return 1; } void @@ -13301,9 +13541,17 @@ 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) { EditGameEvent(); if (gameMode != EditGame) return; + if (!appData.showThinking) ToggleShowThinking(); ResurrectChessProgram(); SendToProgram("analyze\n", &first); first.analyzing = TRUE; @@ -13319,7 +13567,8 @@ AnalyzeFileEvent () StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; - if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay)); + if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay)); + AnalysisPeriodicEvent(1); } void @@ -13591,6 +13840,12 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + + if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) { + DisplayError("second engine does not play this", 0); + return; + } + if(!stalling) { InitChessProgram(&second, FALSE); // unbalances ping of second engine SendToProgram("force\n", &second); @@ -13861,8 +14116,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; } @@ -13877,14 +14132,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; iisr); - if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"), _(cps->which), cps->program); - if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ + if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load + if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program); if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; } @@ -16191,6 +16462,16 @@ DecrementClocks () } if (CheckFlags()) return; + if(twoBoards) { // count down secondary board's clocks as well + activePartnerTime -= lastTickLength; + partnerUp = 1; + if(activePartner == 'W') + DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one! + else + DisplayBlackClock(activePartnerTime, TRUE); + partnerUp = 0; + } + tickStartTM = now; intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge; StartClockTimer(intendedTickLength); @@ -16594,14 +16875,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 */ @@ -16667,7 +16964,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; @@ -16796,17 +17093,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