X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=a8e342d3920c42bdc049f8c1bfbf604d1109510e;hb=ebbd3101235b8a04c34b30eb9a037bb6bae71123;hp=636a1a56f10803fce4ddbad295bb0ab0e954a4f1;hpb=faf295a6b4e7a09ae9214bf1ec4df2062b6a47ee;p=xboard.git diff --git a/backend.c b/backend.c index 636a1a5..a8e342d 100644 --- a/backend.c +++ b/backend.c @@ -152,7 +152,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)); @@ -222,6 +221,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 +392,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 @@ -448,15 +449,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; @@ -733,8 +734,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 @@ -845,7 +850,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(); } @@ -870,7 +875,29 @@ 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) +{ + char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf; + int i=0; + if(appData.recentEngines <= 0) return; + TidyProgramName(engineLine, "localhost", tidy+1); + tidy[0] = buf[0] = '\n'; strcat(tidy, "\n"); + strncpy(buf+1, *list, MSG_SIZ-50); + if(p = strstr(buf, tidy)) { // tidy name appears in list + q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch + while(*p++ = *++q); // squeeze out + } + strcat(tidy, buf+1); // put list behind tidy name + p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list + if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last + ASSIGN(*list, tidy+1); +} + +char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine void Load (ChessProgramState *cps, int i) @@ -879,28 +906,32 @@ 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); ReplaceEngine(cps, i); + FloatToFront(&appData.recentEngineList, engineLine); return; } p = engineName; while(q = strchr(p, SLASH)) p = q+1; if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; } - if(engineDir[0] != NULLCHAR) - appData.directory[i] = engineDir; - else if(p != engineName) { // derive directory from engine path, when not given + if(engineDir[0] != NULLCHAR) { + ASSIGN(appData.directory[i], engineDir); p = engineName; + } 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; @@ -922,9 +953,12 @@ 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); } ReplaceEngine(cps, i); } @@ -1219,11 +1253,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; @@ -1303,6 +1335,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) { @@ -1396,9 +1429,13 @@ ReserveGame (int gameNr, char resChar) free(p); appData.results = q; if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch && (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) { + int round = appData.defaultMatchGames * appData.tourneyType; + if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine + appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round 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\n", nextGame, gameNr); } void @@ -1455,6 +1492,8 @@ MatchEvent (int mode) NextMatchGame(); } +char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line + void InitBackEnd3 P((void)) { @@ -1468,6 +1507,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, comboLine ? comboLine : appData.firstChessProgram); } if (appData.icsActive) { @@ -2405,6 +2445,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; @@ -2452,8 +2493,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; @@ -2582,6 +2623,14 @@ int 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; @@ -2929,6 +2978,9 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int OutputKibitz(suppressKibitz, parse); } else { char tmp[MSG_SIZ]; + if(gameMode == IcsObserving) // restore original ICS messages + snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse); + else snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse); SendToPlayer(tmp, strlen(tmp)); } @@ -3062,7 +3114,8 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int if (appData.autoKibitz && started == STARTED_NONE && !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) { - if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) && + if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") || + looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) && (StrStr(star_match[0], gameInfo.white) == star_match[0] || StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent suppressKibitz = TRUE; @@ -3524,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(); @@ -3601,8 +3654,8 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int flipView = appData.flipView; DrawPosition(TRUE, boards[currentMove]); DisplayBothClocks(); - snprintf(str, MSG_SIZ, _("%s vs. %s"), - gameInfo.white, gameInfo.black); + snprintf(str, MSG_SIZ, "%s %s %s", + gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(str); gameMode = IcsIdle; } else { @@ -4019,8 +4072,8 @@ read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh, gameInfo.white, gameInfo.black); } else { - snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"), - gameInfo.white, white_holding, + snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]", + gameInfo.white, white_holding, _("vs."), gameInfo.black, black_holding); } if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured... @@ -4172,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; @@ -4187,9 +4241,12 @@ ParseBoard12 (char *string) break; } - if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) + if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || + gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) { // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */ + int fac = strchr(elapsed_time, '.') ? 1 : 1000; + static int lastBgGame = -1; char *toSqr; for (k = 0; k < ranks; k++) { for (j = 0; j < files; j++) @@ -4211,14 +4268,35 @@ ParseBoard12 (char *string) if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); } if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual if(partnerUp) DrawPosition(FALSE, partnerBoard); - if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual - snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000, - (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play); + if(twoBoards) { + DisplayWhiteClock(white_time*fac, to_play == 'W'); + DisplayBlackClock(black_time*fac, to_play != 'W'); + activePartner = to_play; + if(gamenum != lastBgGame) { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black); + DisplayTitle(buf); + } + lastBgGame = gamenum; + activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac; + partnerUp = 0; flipView = !flipView; } // [HGM] dual + snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000, + (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play); DisplayMessage(partnerStatus, ""); partnerBoardValid = TRUE; return; } + if(appData.dualBoard && appData.bgObserve) { + if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1) + SendToICS(ics_prefix), SendToICS("pobserve\n"); + else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white); + SendToICS(buf); + } + } + /* Modify behavior for initial board display on move listing of wild games. */ @@ -4253,6 +4331,7 @@ ParseBoard12 (char *string) } if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || + move_str[1] == '@' && !gameInfo.holdingsWidth || weird && (int)gameInfo.variant < (int)VariantShogi) { /* [HGM] We seem to have switched variant unexpectedly * Try to guess new variant from board size @@ -4262,7 +4341,8 @@ ParseBoard12 (char *string) if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else if(ranks == 8 && files == 12) newVariant = VariantCourier; else if(ranks == 9 && files == 9) newVariant = VariantShogi; else - if(!weird) newVariant = VariantNormal; + if(ranks == 10 && files == 10) newVariant = VariantGrand; else + if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal; VariantSwitch(boards[currentMove], newVariant); /* temp guess */ /* Get a move list just to see the header, which will tell us whether this is really bug or zh */ @@ -4471,7 +4551,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 || @@ -4750,12 +4833,12 @@ ParseBoard12 (char *string) basetime, increment, (int) gameInfo.variant); } else { if(gameInfo.variant == VariantNormal) - snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d}"), - gameInfo.white, white_stren, gameInfo.black, black_stren, + snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d}", + gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren, basetime, increment); else - snprintf(str, MSG_SIZ, _("%s (%d) vs. %s (%d) {%d %d %s}"), - gameInfo.white, white_stren, gameInfo.black, black_stren, + snprintf(str, MSG_SIZ, "%s (%d) %s %s (%d) {%d %d %s}", + gameInfo.white, white_stren, _("vs."), gameInfo.black, black_stren, basetime, increment, VariantName(gameInfo.variant)); } DisplayTitle(str); @@ -5110,6 +5193,12 @@ Sweep (int step) } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || appData.testLegality && (promoSweep == king || gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep)); + if(toX >= 0) { + int victim = boards[currentMove][toY][toX]; + boards[currentMove][toY][toX] = promoSweep; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][toY][toX] = victim; + } else ChangeDragPiece(promoSweep); } @@ -5297,9 +5386,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 @@ -5379,6 +5465,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; @@ -5418,6 +5507,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; @@ -6056,6 +6146,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) { @@ -6357,12 +6556,14 @@ 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; + ChessSquare 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 @@ -6491,7 +6692,7 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; } } else - boards[0][fromY][fromX] = EmptySquare; + boards[0][fromY][fromX] = gatingPiece; DrawPosition(FALSE, boards[currentMove]); return; } @@ -6499,7 +6700,6 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) } if(toX < 0 || toY < 0) return; - pdown = boards[currentMove][fromY][fromX]; pup = boards[currentMove][toY][toX]; /* [HGM] If move started in holdings, it means a drop. Convert to standard form */ @@ -6529,6 +6729,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); } @@ -6600,6 +6807,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(); @@ -6625,7 +6834,7 @@ FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int prom gameMode = MachinePlaysBlack; StartClocks(); SetGameInfo(); - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); if (first.sendName) { snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white); @@ -6734,11 +6943,11 @@ void MarkTargetSquares (int clear) { int x, y; + if(clear) // no reason to ever suppress clearing + for(x=0; x= 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 @@ -6864,6 +7078,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 @@ -6872,6 +7087,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) if (OKToStartUserMove(fromX, fromY)) { second = 0; MarkTargetSquares(0); + if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX]; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { promoSweep = defaultPromoChoice; @@ -6881,6 +7097,8 @@ LeftClick (ClickType clickType, int xPix, int yPix) } if (appData.highlightDragging) { SetHighlights(fromX, fromY, -1, -1); + } else { + ClearHighlights(); } } else fromX = fromY = -1; return; @@ -6910,6 +7128,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)) { @@ -6923,7 +7145,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); @@ -6956,9 +7178,10 @@ LeftClick (ClickType clickType, int xPix, int yPix) /* Undo animation damage if any */ DrawPosition(FALSE, NULL); } - if (second) { + if (second || sweepSelecting) { /* Second up/down in same square; just abort move */ - second = 0; + if(sweepSelecting) DrawPosition(FALSE, boards[currentMove]); + second = sweepSelecting = 0; fromX = fromY = -1; gatingPiece = EmptySquare; ClearHighlights(); @@ -6975,10 +7198,12 @@ LeftClick (ClickType clickType, int xPix, int yPix) /* we now have a different from- and (possibly off-board) to-square */ /* Completed move */ - toX = x; - toY = y; + if(!sweepSelecting) { + toX = x; + toY = y; + } else sweepSelecting = 0; // this must be the up-click corresponding to the down-click that started the sweep + saveAnimate = appData.animate; - MarkTargetSquares(1); if (clickType == Press) { if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { // must be Edit Position mode with empty-square selected @@ -6986,16 +7211,18 @@ LeftClick (ClickType clickType, int xPix, int yPix) if(x >= 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) { @@ -7014,6 +7241,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) /* 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) { @@ -7045,7 +7273,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); @@ -7529,14 +7757,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 @@ -7731,6 +7951,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 @@ -7753,7 +7992,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) { @@ -7767,7 +8006,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 /* @@ -7872,11 +8111,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)) { @@ -7902,12 +8136,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); @@ -8333,7 +8561,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) @@ -8347,8 +8575,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 { - if(cps == &first) appData.noChessProgram = TRUE; - 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; } @@ -9307,10 +9535,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) { @@ -9426,9 +9657,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]); - } } @@ -9752,7 +9980,6 @@ WriteTourneyFile (char *results, FILE *f) return f; } -#define MAXENGINES 1000 char *command[MAXENGINES], *mnemonic[MAXENGINES]; void @@ -9777,7 +10004,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; @@ -9846,19 +10073,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, " ("); @@ -9866,10 +10103,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 @@ -9893,23 +10130,47 @@ 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) } -void -SetPlayer (int player) +int +SetPlayer (int player, char *p) { // [HGM] find the engine line of the partcipant given by number, and parse its options. int i; - char buf[MSG_SIZ], *engineName, *p = appData.participants; + char buf[MSG_SIZ], *engineName; for(i=0; i= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1); } + } else if(appData.tourneyType > 1) { + *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round + *whitePlayer = curRound + appData.tourneyType; } else if(appData.tourneyType > 0) { *whitePlayer = curPairing; *blackPlayer = curRound + appData.tourneyType; @@ -10009,16 +10273,20 @@ 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 - SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line - SwapEngines(1); - SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line - SwapEngines(1); // and make that valid for second engine by swapping - InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. - InitEngine(&second, 1); + 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. + } + if(second.pr == NoProc) { + SwapEngines(1); + SetPlayer(blackPlayer, appData.participants); // 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; @@ -10029,6 +10297,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 @@ -10626,6 +10912,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 @@ -10638,7 +10925,7 @@ AutoPlayGameLoop () continue; if (appData.timeDelay < 0) return; - StartLoadGameTimer((long)(1000.0 * appData.timeDelay)); + StartLoadGameTimer((long)(1000.0f * appData.timeDelay)); break; } } @@ -11294,15 +11581,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; @@ -11529,7 +11819,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) yynewfile(f); if (lg && lg->gameInfo.white && lg->gameInfo.black) { - snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white, + snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."), lg->gameInfo.black); DisplayTitle(buf); } else if (*title != NULLCHAR) { @@ -11901,7 +12191,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) AnalyzeFileEvent(); } - if (!matchMode && pos >= 0) { + if (!matchMode && pos > 0) { ToNrEvent(pos); // [HGM] no autoplay if selected on position } else if (matchMode || appData.timeDelay == 0) { @@ -12252,7 +12542,6 @@ int SaveGamePGN (FILE *f) { int i, offset, linelen, newblock; - time_t tm; // char *movetext; char numtext[32]; int movelen, numlen, blank; @@ -12260,10 +12549,10 @@ 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 + if (backwardMostMove > 0 || startedFromSetupPosition) { char *fen = PositionToFEN(backwardMostMove, NULL); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); @@ -13078,7 +13367,7 @@ 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)); } void @@ -13122,7 +13411,7 @@ MachineWhiteEvent () pausing = FALSE; ModeHighlight(); SetGameInfo(); - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); if (first.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black); @@ -13199,7 +13488,7 @@ MachineBlackEvent () pausing = FALSE; ModeHighlight(); SetGameInfo(); - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); if (first.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white); @@ -13244,24 +13533,24 @@ DisplayTwoMachinesTitle () char buf[MSG_SIZ]; if (appData.matchGames > 0) { if(appData.tourneyFile[0]) { - snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"), - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)", + gameInfo.white, _("vs."), gameInfo.black, nextGame+1, appData.matchGames+1, appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr"); } else if (first.twoMachinesColor[0] == 'w') { - snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"), - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", + gameInfo.white, _("vs."), gameInfo.black, first.matchWins, second.matchWins, matchGame - 1 - (first.matchWins + second.matchWins)); } else { - snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"), - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", + gameInfo.white, _("vs."), gameInfo.black, second.matchWins, first.matchWins, matchGame - 1 - (first.matchWins + second.matchWins)); } } else { - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); } DisplayTitle(buf); } @@ -13290,7 +13579,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); } @@ -13350,6 +13639,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); @@ -13365,6 +13660,7 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait); return; } + // we are now committed to starting the game stalling = 0; DisplayMessage("", ""); if (startedFromSetupPosition) { @@ -13606,6 +13902,7 @@ EditPositionEvent () currentMove = forwardMostMove = backwardMostMove = 0; HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); DisplayMove(-1); + if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), ""); } void @@ -13852,6 +14149,8 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) } else boards[0][y][x] = selection; DrawPosition(TRUE, boards[0]); + ClearHighlights(); + fromX = fromY = -1; } break; } @@ -14148,7 +14447,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", @@ -14157,6 +14456,7 @@ ForwardInner (int target) if (gameMode == EditPosition) return; + seekGraphUp = FALSE; MarkTargetSquares(1); if (gameMode == PlayFromGameFile && !pausing) @@ -14204,11 +14504,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 } @@ -14263,6 +14564,7 @@ BackwardInner (int target) target, currentMove, forwardMostMove); if (gameMode == EditPosition) return; + seekGraphUp = FALSE; MarkTargetSquares(1); if (currentMove <= backwardMostMove) { ClearHighlights(); @@ -14321,6 +14623,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 @@ -14591,7 +14894,7 @@ PrintOpponents (FILE *fp) void TidyProgramName (char *prog, char *host, char buf[MSG_SIZ]) { - char *p, *q; + char *p, *q, c; int local = (strcmp(host, "localhost") == 0); while (!local && (p = strchr(prog, ';')) != NULL) { p++; @@ -14608,7 +14911,8 @@ TidyProgramName (char *prog, char *host, char buf[MSG_SIZ]) while (p >= prog && *p != '/' && *p != '\\') p--; p++; if(p == prog && *p == '"') p++; - if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4; + c = *q; *q = 0; + if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c; memcpy(buf, p, q - p); buf[q - p] = NULLCHAR; if (!local) { @@ -14959,6 +15263,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); @@ -14995,10 +15303,10 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou if (count <= 0) { if (count == 0) { RemoveInputSource(cps->isr); - 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; } @@ -15060,6 +15368,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); } } @@ -15168,9 +15481,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; @@ -15278,8 +15588,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; @@ -15366,7 +15676,7 @@ ParseFeatures (char *args, ChessProgramState *cps) continue; } if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue; - if (StringFeature(&p, "myname", &cps->tidy, cps)) { + if (StringFeature(&p, "myname", cps->tidy, cps)) { if (gameMode == TwoMachinesPlay) { DisplayTwoMachinesTitle(); } else { @@ -15374,12 +15684,13 @@ ParseFeatures (char *args, ChessProgramState *cps) } continue; } - if (StringFeature(&p, "variants", &cps->variants, cps)) continue; + if (StringFeature(&p, "variants", cps->variants, cps)) continue; if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue; if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue; 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 */ @@ -15398,8 +15709,11 @@ ParseFeatures (char *args, ChessProgramState *cps) if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue; 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, "egt", cps->egtFormats, cps)) continue; + 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); @@ -15552,6 +15866,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) { @@ -15923,6 +16245,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);