X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=3f6229da29fab8839781c7a70822004c2e7b732d;hb=02de46755f727ffb565f7c855f37c344eee925ff;hp=92a228dc457f39038a932ca9f1bb0bf243763380;hpb=53c1e5069c952812b429d7c141cf201edf6afaae;p=xboard.git diff --git a/backend.c b/backend.c index 92a228d..3f6229d 100644 --- a/backend.c +++ b/backend.c @@ -222,6 +222,7 @@ 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 *f)); void DisplayTwoMachinesTitle P(()); +static void ExcludeClick P((int index)); #ifdef WIN32 extern void ConsoleCreate(); @@ -392,7 +393,8 @@ PosFlags (index) return flags; } -FILE *gameFileFP, *debugFP; +FILE *gameFileFP, *debugFP, *serverFP; +char *currentDebugFile; // [HGM] debug split: to remember name /* [AS] Note: sometimes, the sscanf() function is used to parse the input @@ -733,8 +735,12 @@ ClearOptions (ChessProgramState *cps) } char *engineNames[] = { -"first", -"second" + /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings + such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */ +N_("first"), + /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings + such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */ +N_("second") }; void @@ -870,7 +876,8 @@ 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 \"\""; + "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 " + "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false"; void FloatToFront(char **list, char *engineLine) @@ -891,6 +898,8 @@ FloatToFront(char **list, char *engineLine) ASSIGN(*list, tidy+1); } +char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine + void Load (ChessProgramState *cps, int i) { @@ -898,7 +907,8 @@ Load (ChessProgramState *cps, int i) 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; appData.pvSAN[0] = FALSE; + ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE; + FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL; appData.firstProtocolVersion = PROTOVER; ParseArgsFromString(buf); SwapEngines(i); @@ -909,19 +919,20 @@ Load (ChessProgramState *cps, int i) p = engineName; while(q = strchr(p, SLASH)) p = q+1; if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; } - if(engineDir[0] != NULLCHAR) - appData.directory[i] = engineDir; - else if(p != engineName) { // derive directory from engine path, when not given + if(engineDir[0] != NULLCHAR) { + ASSIGN(appData.directory[i], engineDir); + } else if(p != engineName) { // derive directory from engine path, when not given p[-1] = 0; - appData.directory[i] = strdup(engineName); + ASSIGN(appData.directory[i], engineName); p[-1] = SLASH; - } else appData.directory[i] = "."; + if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split! + } else { ASSIGN(appData.directory[i], "."); } 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; } - appData.chessProgram[i] = strdup(p); + ASSIGN(appData.chessProgram[i], p); appData.isUCI[i] = isUCI; appData.protocolVersion[i] = v1 ? 1 : PROTOVER; appData.hasOwnBookUCI[i] = hasBook; @@ -943,8 +954,10 @@ Load (ChessProgramState *cps, int i) isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "", storeVariant ? " -variant " : "", storeVariant ? VariantName(gameInfo.variant) : ""); + if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions); firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1); - snprintf(firstChessProgramNames, len, "%s%s", q, buf); + if(insert != q) insert[-1] = NULLCHAR; + snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert); if(q) free(q); FloatToFront(&appData.recentEngineList, buf); } @@ -1241,11 +1254,9 @@ GetTimeQuota (int movenr, int lastUsed, char *tcString) char *s = tcString; if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version - if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString); do { if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType); nextSession = s; suddenDeath = moves == 0 && increment == 0; - if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment); if(movenr == -1) return time; /* last move before new session */ if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking if(incType == '!' && lastUsed < increment) increment = lastUsed; @@ -1325,6 +1336,7 @@ InitBackEnd2 () if (appData.debugMode) { fprintf(debugFP, "%s\n", programVersion); } + ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use set_cont_sequence(appData.wrapContSeq); if (appData.matchGames > 0) { @@ -1424,7 +1436,7 @@ ReserveGame (int gameNr, char resChar) UnloadEngine(&first); // next game belongs to other pairing; UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones. } - if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr); + if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr); } void @@ -1481,6 +1493,8 @@ MatchEvent (int mode) NextMatchGame(); } +char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line + void InitBackEnd3 P((void)) { @@ -1494,7 +1508,7 @@ InitBackEnd3 P((void)) free(programVersion); programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); - FloatToFront(&appData.recentEngineList, appData.firstChessProgram); + FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram); } if (appData.icsActive) { @@ -2610,6 +2624,13 @@ SeekGraphClick (ClickType click, int x, int y, int moving) { static int lastDown = 0, displayed = 0, lastSecond; if(y < 0) return FALSE; + if(!(appData.seekGraph && appData.icsActive && loggedOn && + (gameMode == BeginningOfGame || gameMode == IcsIdle))) { + if(!seekGraphUp) return FALSE; + seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down + DrawPosition(TRUE, NULL); + return TRUE; + } if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list if(click == Release || moving) return FALSE; nrOfSeekAds = 0; @@ -3556,7 +3577,7 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book - if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line + if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line soughtPending = FALSE; seekGraphUp = TRUE; DrawSeekGraph(); @@ -4204,6 +4225,7 @@ ParseBoard12 (char *string) newGameMode = ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ? IcsPlayingWhite : IcsPlayingBlack; + soughtPending =FALSE; // [HGM] seekgraph: solve race condition break; case RELATION_EXAMINING: newGameMode = IcsExamining; @@ -4503,7 +4525,10 @@ ParseBoard12 (char *string) r = boards[moveNum][CASTLING][5] = initialRights[5]; } /* [HGM] e.p. rights. Assume that ICS sends file number here? */ - boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT; + boards[moveNum][EP_STATUS] = EP_NONE; + if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE; + if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE; + if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT; if (ics_getting_history == H_GOT_REQ_HEADER || @@ -5329,9 +5354,6 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses lastParseAttempt = pv; valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar); -if(appData.debugMode){ -fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv); -} if(!valid && nr == 0 && ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ nr++; moveType = Comment; // First move has been played; kludge to make sure we continue @@ -5411,6 +5433,9 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end) first.option[multi].value = n; *start = *end = 0; return FALSE; + } else if(strstr(buf+lineStart, "exclude:") == buf+lineStart) { // exclude moves clicked + ExcludeClick(origIndex - lineStart); + return FALSE; } ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; @@ -5450,6 +5475,7 @@ UnLoadPV () { int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded! if(endPV < 0) return; + if(appData.autoCopyPV) CopyFENToClipboard(); endPV = -1; if(gameMode == AnalyzeMode && currentMove > forwardMostMove) { Boolean saveAnimate = appData.animate; @@ -6088,6 +6114,115 @@ SendBoard (ChessProgramState *cps, int moveNum) setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } +char exclusionHeader[MSG_SIZ]; +int exCnt, excludePtr; +typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion; +static Exclusion excluTab[200]; +static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves + +static void +WriteMap (int s) +{ + int j; + for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s; + exclusionHeader[19] = s ? '-' : '+'; // update tail state +} + +static void +ClearMap () +{ + safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ); + excludePtr = 24; exCnt = 0; + WriteMap(0); +} + +static void +UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state) +{ // search given move in table of header moves, to know where it is listed (and add if not there), and update state + char buf[2*MOVE_LEN], *p; + Exclusion *e = excluTab; + int i; + for(i=0; i>= 3; + if(state == '*') state = (excludeMap[k] & 1< 18) { // tail + if(exclusionHeader[19] == '-') { // tail was excluded + SendToProgram("include all\n", &first); + WriteMap(0); // clear map completely + // now re-exclude selected moves + for(i=0; i index) { + char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!) + ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+'); + break; + } + } +} + ChessSquare DefaultPromoChoice (int white) { @@ -6389,12 +6524,15 @@ int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = EndOfFile; +int doubleClick; void UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) { ChessMove moveType; ChessSquare pdown, pup; + int ff=fromX, rf=fromY, ft=toX, rt=toY; + /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he @@ -6561,6 +6699,13 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) } } + if(doubleClick) { // [HGM] exclude: move entered with double-click on from square is for exclusion, not playing + if(ExcludeOneMove(fromY, fromX, toY, toX, promoChar, '*')) // toggle + ClearPremoveHighlights(); // was included + else ClearHighlights(), SetPremoveHighlights(ff, rf, ft, rt); // exclusion indicated by premove highlights + return; + } + FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } @@ -6632,6 +6777,8 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom else forwardMostMove = currentMove; } + ClearMap(); + /* If we need the chess program but it's dead, restart it */ ResurrectChessProgram(); @@ -6821,12 +6968,11 @@ LeftClick (ClickType clickType, int xPix, int yPix) static int second = 0, promotionChoice = 0, clearFlag = 0; char promoChoice = NULLCHAR; ChessSquare piece; + static TimeMark lastClickTime, prevClickTime; - if(appData.seekGraph && appData.icsActive && loggedOn && - (gameMode == BeginningOfGame || gameMode == IcsIdle)) { - SeekGraphClick(clickType, xPix, yPix, 0); - return; - } + if(SeekGraphClick(clickType, xPix, yPix, 0)) return; + + prevClickTime = lastClickTime; GetTimeMark(&lastClickTime); if (clickType == Press) ErrorPopDown(); @@ -6875,7 +7021,13 @@ LeftClick (ClickType clickType, int xPix, int yPix) || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) ) return; - if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered) + if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) { + // could be static click on premove from-square: abort premove + gotPremove = 0; + ClearPremoveHighlights(); + } + + if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200) fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for @@ -6896,6 +7048,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } return; } + doubleClick = FALSE; fromX = x; fromY = y; toX = toY = -1; if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection @@ -6942,6 +7095,10 @@ LeftClick (ClickType clickType, int xPix, int yPix) !(fromP == BlackKing && toP == BlackRook && frc))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); + if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) { + second = FALSE; // first double-click rather than scond click + doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves + } promoDefaultAltered = FALSE; MarkTargetSquares(1); if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) { @@ -6955,7 +7112,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1)) gatingPiece = boards[currentMove][fromY][fromX]; - else gatingPiece = EmptySquare; + else gatingPiece = doubleClick ? fromP : EmptySquare; fromX = x; fromY = y; dragging = 1; MarkTargetSquares(0); @@ -7077,7 +7234,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) // off-board moves should not be highlighted if(x < 0 || y < 0) ClearHighlights(); - if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece)); + if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece)); if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { SetHighlights(fromX, fromY, toX, toY); @@ -7561,14 +7718,6 @@ Adjudicate (ChessProgramState *cps) } } else moveCount = 6; } - if (appData.debugMode) { int i; - fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", - forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS], - appData.drawRepeats); - for( i=forwardMostMove; i>=backwardMostMove; i-- ) - fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]); - - } // Repetition draws and 50-move rule can be applied independently of legality testing @@ -7904,11 +8053,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h return; } - if (appData.debugMode) { int f = forwardMostMove; - fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, - boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2], - boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]); - } if(cps->alphaRank) AlphaRank(machineMove, 4); if (!ParseOneMove(machineMove, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { @@ -7934,12 +8078,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h ChessMove moveType; moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), fromY, fromX, toY, toX, promoChar); - if (appData.debugMode) { - int i; - for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", - boards[forwardMostMove][CASTLING][i], castlingRank[i]); - fprintf(debugFP, "castling rights\n"); - } if(moveType == IllegalMove) { snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); @@ -8365,7 +8503,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* * If chess program startup fails, exit with an error message. - * Attempts to recover here are futile. + * Attempts to recover here are futile. [HGM] Well, we try anyway */ if ((StrStr(message, "unknown host") != NULL) || (StrStr(message, "No remote directory") != NULL) @@ -8379,7 +8517,17 @@ 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 { - if(cps == &first) appData.noChessProgram = TRUE; + 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); } return; @@ -9339,10 +9487,13 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating } else if(promoChar == '+') { - /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */ + /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */ board[toY][toX] = (ChessSquare) (PROMOTED piece); } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar - board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); + ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); + if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified + && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available + board[toY][toX] = newPiece; } if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR && gameInfo.holdingsSize) { @@ -9458,9 +9609,6 @@ MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) strcat(parseList[forwardMostMove - 1], "#"); break; } - if (appData.debugMode) { - fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]); - } } @@ -9808,7 +9956,7 @@ Substitute (char *participants, int expunge) q = r; while(*q) nPlayers += (*q++ == '\n'); p = buf; while(*r && (*p = *r++) != '\n') p++; *p = NULLCHAR; - NamesToList(firstChessProgramNames, command, mnemonic); + NamesToList(firstChessProgramNames, command, mnemonic, "all"); for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break; if(mnemonic[i]) { // The substitute is valid FILE *f; @@ -9877,19 +10025,29 @@ CreateTourney (char *name) return 1; } -void -NamesToList (char *names, char **engineList, char **engineMnemonic) +int +NamesToList (char *names, char **engineList, char **engineMnemonic, char *group) { char buf[MSG_SIZ], *p, *q; - int i=1; - while(*names) { - p = names; q = buf; + int i=1, header, skip, all = !strcmp(group, "all"), depth = 0; + insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names' + skip = !all && group[0]; // if group requested, we start in skip mode + for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) { + p = names; q = buf; header = 0; while(*p && *p != '\n') *q++ = *p++; *q = 0; + if(*p == '\n') p++; + if(buf[0] == '#') { + if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label + depth++; // we must be entering a new group + if(all) continue; // suppress printing group headers when complete list requested + header = 1; + if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group + } + if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header) if(engineList[i]) free(engineList[i]); engineList[i] = strdup(buf); - if(*p == '\n') p++; - TidyProgramName(engineList[i], "localhost", buf); + if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied if(engineMnemonic[i]) free(engineMnemonic[i]); if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) { strcat(buf, " ("); @@ -9897,10 +10055,10 @@ NamesToList (char *names, char **engineList, char **engineMnemonic) strcat(buf, ")"); } engineMnemonic[i] = strdup(buf); - names = p; i++; - if(i > MAXENGINES - 2) break; + i++; } engineList[i] = engineMnemonic[i] = NULL; + return i; } // following implemented as macro to avoid type limitations @@ -9924,6 +10082,13 @@ SwapEngines (int n) SWAP(pgnName, p) SWAP(pvSAN, h) SWAP(engOptions, p) + SWAP(engInitString, p) + SWAP(computerString, p) + SWAP(features, p) + SWAP(fenOverride, p) + SWAP(NPS, h) + SWAP(accumulateTC, h) + SWAP(host, p) } int @@ -9952,7 +10117,7 @@ RecentEngineEvent (int nr) int n; // SwapEngines(1); // bump first to second // ReplaceEngine(&second, 1); // and load it there - NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines + NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines n = SetPlayer(nr, recentEngines); // select new (using original menu order!) if(mnemonic[n]) { // if somehow the engine with the selected nickname is no longer found in the list, we skip ReplaceEngine(&first, 0); @@ -10060,11 +10225,11 @@ NextTourneyGame (int nr, int *swapColors) matchGame = 1; roundNr = nr / syncInterval + 1; } - if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded + if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded // redefine engines, engine dir, etc. - NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines - if(first.pr == NoProc || nr < 0) { + 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 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. } @@ -10084,6 +10249,24 @@ NextMatchGame () { // performs game initialization that does not invoke engines, and then tries to start the game int res, firstWhite, swapColors = 0; if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed + if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it + if(strcmp(buf, currentDebugFile)) { // name has changed + FILE *f = fopen(buf, "w"); + if(f) { // if opening the new file failed, just keep using the old one + ASSIGN(currentDebugFile, buf); + fclose(debugFP); + debugFP = f; + } + if(appData.serverFileName) { + if(serverFP) fclose(serverFP); + serverFP = fopen(appData.serverFileName, "w"); + if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy); + if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy); + } + } + } 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 @@ -10681,6 +10864,7 @@ Reset (int redraw, int init) DisplayMessage("", ""); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved + ClearMap(); // [HGM] exclude: invalidate map } void @@ -12319,6 +12503,8 @@ SaveGamePGN (FILE *f) PrintPGNTags(f, &gameInfo); + if(appData.numberTag && matchMode) fprintf(f, "[Number \"%d\"]\n", nextGame+1); // [HGM] number tag + if (backwardMostMove > 0 || startedFromSetupPosition) { char *fen = PositionToFEN(backwardMostMove, NULL); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); @@ -13345,7 +13531,7 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry) } else { /* kludge: allow timeout for initial "feature" command */ FreezeUI(); - snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which); + snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which)); DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); } @@ -13420,6 +13606,7 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait); return; } + // we are now committed to starting the game stalling = 0; DisplayMessage("", ""); if (startedFromSetupPosition) { @@ -13661,6 +13848,7 @@ EditPositionEvent () currentMove = forwardMostMove = backwardMostMove = 0; HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); DisplayMove(-1); + if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), ""); } void @@ -13907,6 +14095,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) } else boards[0][y][x] = selection; DrawPosition(TRUE, boards[0]); + ClearHighlights(); + fromX = fromY = -1; } break; } @@ -14203,7 +14393,7 @@ StopExaminingEvent () void ForwardInner (int target) { - int limit; + int limit; int oldSeekGraphUp = seekGraphUp; if (appData.debugMode) fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n", @@ -14260,11 +14450,12 @@ ForwardInner (int target) } DisplayBothClocks(); DisplayMove(currentMove - 1); - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(oldSeekGraphUp, boards[currentMove]); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } + ClearMap(); // [HGM] exclude: invalidate map } @@ -14378,6 +14569,7 @@ BackwardInner (int target) HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); // [HGM] PV info: routine tests if comment empty DisplayComment(currentMove - 1, commentList[currentMove]); + ClearMap(); // [HGM] exclude: invalidate map } void @@ -15017,6 +15209,10 @@ SendToProgram (char *message, ChessProgramState *cps) fprintf(debugFP, "%ld >%-6s: %s", SubtractTimeMarks(&now, &programStartTime), cps->which, message); + if(serverFP) + fprintf(serverFP, "%ld >%-6s: %s", + SubtractTimeMarks(&now, &programStartTime), + cps->which, message), fflush(serverFP); } count = strlen(message); @@ -15118,6 +15314,11 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou SubtractTimeMarks(&now, &programStartTime), cps->which, quote, message); + if(serverFP) + fprintf(serverFP, "%ld <%-6s: %s%s\n", + SubtractTimeMarks(&now, &programStartTime), cps->which, + quote, + message), fflush(serverFP); } } @@ -15226,9 +15427,6 @@ SendTimeRemaining (ChessProgramState *cps, int machineWhite) } /* [HGM] translate opponent's time by time-odds factor */ otime = (otime * cps->other->timeOdds) / cps->timeOdds; - if (appData.debugMode) { - fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds); - } if (time <= 0) time = 1; if (otime <= 0) otime = 1; @@ -15336,8 +15534,8 @@ ParseOption (Option *opt, ChessProgramState *cps) if(sscanf(p, " -check %d", &def) < 1) return FALSE; opt->value = (def != 0); opt->type = CheckBox; - } else if(p = strstr(opt->name, " -combo ")) { - opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type + } else if(p = strstr(opt->name, " -combo ")) { + opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices if(*q == '*') cps->comboList[cps->comboCnt-1]++; opt->value = n = 0; @@ -15438,6 +15636,7 @@ ParseFeatures (char *args, ChessProgramState *cps) if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue; if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue; if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue; + 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 */ @@ -15457,7 +15656,10 @@ ParseFeatures (char *args, ChessProgramState *cps) if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue; if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue; - if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) { + if (StringFeature(&p, "option", buf, cps)) { + FREE(cps->option[cps->nrOptions].name); + cps->option[cps->nrOptions].name = malloc(MSG_SIZ); + safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ); if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name); SendToProgram(buf, cps); @@ -15610,6 +15812,14 @@ TypeInDoneEvent (char *move) ToNrEvent(2*n-1); return; } + // undocumented kludge: allow command-line option to be typed in! + // (potentially fatal, and does not implement the effect of the option.) + // should only be used for options that are values on which future decisions will be made, + // and definitely not on options that would be used during initialization. + if(strstr(move, "!!! -") == move) { + ParseArgsFromString(move+4); + return; + } if (gameMode != EditGame && currentMove != forwardMostMove && gameMode != Training) {