X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=21560911b2317205a0dec289d5a76cc680ca1fe1;hb=cf736a0286ed40b6e671e77fd080812edd75f80b;hp=2bde9f67a79fd572ee644f54521d9502f8fad30e;hpb=5fa05d371b9a6dad9ca959a89db02c4cbc09d92c;p=xboard.git diff --git a/backend.c b/backend.c index 2bde9f6..2156091 100644 --- a/backend.c +++ b/backend.c @@ -5,7 +5,7 @@ * Massachusetts. * * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, - * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc. + * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -129,6 +129,7 @@ extern int gettimeofday(struct timeval *, struct timezone *); # include "zippy.h" #endif #include "backendz.h" +#include "evalgraph.h" #include "gettext.h" #ifdef ENABLE_NLS @@ -152,7 +153,6 @@ void read_from_player P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); void read_from_ics P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); -void ics_printf P((char *format, ...)); void SendToICS P((char *s)); void SendToICSDelayed P((char *s, long msdelay)); void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar)); @@ -223,6 +223,9 @@ int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync)); FILE *WriteTourneyFile P((char *results, FILE *f)); void DisplayTwoMachinesTitle P(()); static void ExcludeClick P((int index)); +void ToggleSecond P((void)); +void PauseEngine P((ChessProgramState *cps)); +static int NonStandardBoardSize P((void)); #ifdef WIN32 extern void ConsoleCreate(); @@ -270,6 +273,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 @@ -325,7 +329,7 @@ safeStrCpy (char *dst, const char *src, size_t count) { dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated if(appData.debugMode) - fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count); + fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count); } return dst; @@ -384,6 +388,7 @@ PosFlags (index) case VariantShatranj: case VariantCourier: case VariantMakruk: + case VariantASEAN: case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; @@ -422,7 +427,7 @@ Boolean alarmSounded; /* end premove variables */ char *ics_prefix = "$"; -int ics_type = ICS_GENERIC; +enum ICS_TYPE ics_type = ICS_GENERIC; int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0; int pauseExamForwardMostMove = 0; @@ -461,7 +466,7 @@ long timeControl_2; /* [AS] Allow separate time controls */ char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; int matchGame = 0, nextGame = 0, roundNr = 0; -Boolean waitingForGame = FALSE; +Boolean waitingForGame = FALSE, startingEngine = FALSE; TimeMark programStartTime, pauseStart; char ics_handle[MSG_SIZ]; int have_set_title = 0; @@ -553,6 +558,13 @@ ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatran BlackKing, BlackMan, BlackKnight, BlackRook } }; +ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */ + { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz, + WhiteKing, WhiteMan, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackMan, BlackFerz, + BlackKing, BlackMan, BlackKnight, BlackRook } +}; + #if (BOARD_FILES>=10) ChessSquare ShogiArray[2][BOARD_FILES] = { @@ -777,12 +789,14 @@ InitEngine (ChessProgramState *cps, int n) cps->sendName = appData.icsActive; cps->sdKludge = FALSE; cps->stKludge = FALSE; + if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ); TidyProgramName(cps->program, cps->host, cps->tidy); cps->matchWins = 0; - safeStrCpy(cps->variants, appData.variant, MSG_SIZ); + ASSIGN(cps->variants, appData.variant); cps->analysisSupport = 2; /* detect */ cps->analyzing = FALSE; cps->initDone = FALSE; + cps->reload = FALSE; /* New features added by Tord: */ cps->useFEN960 = FALSE; @@ -803,7 +817,7 @@ InitEngine (ChessProgramState *cps, int n) cps->supportsNPS = UNKNOWN; cps->memSize = FALSE; cps->maxCores = FALSE; - cps->egtFormats[0] = NULLCHAR; + ASSIGN(cps->egtFormats, ""); /* [HGM] options */ cps->optionSettings = appData.engOptions[n]; @@ -836,6 +850,8 @@ InitEngine (ChessProgramState *cps, int n) ChessProgramState *savCps; +GameMode oldMode; + void LoadEngine () { @@ -845,21 +861,25 @@ LoadEngine () if(gameInfo.variant != StringToVariant(appData.variant)) { // we changed variant when loading the engine; this forces us to reset Reset(TRUE, savCps != &first); - EditGameEvent(); // for consistency with other path, as Reset changes mode + oldMode = BeginningOfGame; // to prevent restoring old mode } InitChessProgram(savCps, FALSE); - SendToProgram("force\n", savCps); + if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode DisplayMessage("", ""); if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove); for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps); ThawUI(); SetGNUMode(); + if(oldMode == AnalyzeMode) AnalyzeModeEvent(); } void ReplaceEngine (ChessProgramState *cps, int n) { - EditGameEvent(); + oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete + keepInfo = 1; + if(oldMode != BeginningOfGame) EditGameEvent(); + keepInfo = 0; UnloadEngine(cps); appData.noChessProgram = FALSE; appData.clockMode = TRUE; @@ -873,7 +893,7 @@ ReplaceEngine (ChessProgramState *cps, int n) extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params; extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick; -static char resetOptions[] = +static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 " "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" " "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 " @@ -945,7 +965,7 @@ Load (ChessProgramState *cps, int i) if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR; quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n", - quote, p, quote, appData.directory[i], + quote, p, quote, appData.directory[i], useNick ? " -fn \"" : "", useNick ? nickName : "", useNick ? "\"" : "", @@ -1146,6 +1166,7 @@ InitBackEnd1 () case Variant3Check: /* should work except for win condition */ case VariantShatranj: /* should work except for all win conditions */ case VariantMakruk: /* should work except for draw countdown */ + case VariantASEAN : /* should work except for draw countdown */ case VariantBerolina: /* might work if TestLegality is off */ case VariantCapaRandom: /* should work */ case VariantJanus: /* should work */ @@ -1253,7 +1274,7 @@ GetTimeQuota (int movenr, int lastUsed, char *tcString) long time, increment; char *s = tcString; - if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version + if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version do { if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType); nextSession = s; suddenDeath = moves == 0 && increment == 0; @@ -1282,16 +1303,16 @@ ParseTimeControl (char *tc, float ti, int mps) if(mps) snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti); - else + else snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti); } else { if(mps) snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc); - else + else snprintf(buf, MSG_SIZ, ":%s", mytc); } fullTimeControlString = StrSave(buf); // this should now be in PGN format - + if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) { return FALSE; } @@ -1334,7 +1355,11 @@ void InitBackEnd2 () { if (appData.debugMode) { - fprintf(debugFP, "%s\n", programVersion); +# ifdef __GIT_VERSION + fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION); +# else + fprintf(debugFP, "Version: %s\n", programVersion); +# endif } ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use @@ -1843,6 +1868,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 +1877,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); @@ -2172,7 +2209,7 @@ StringToVariant (char *e) } } if (appData.debugMode) { - fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"), + fprintf(debugFP, "recognized '%s' (%d) as variant %s\n", e, wnum, VariantName(v)); } return v; @@ -2446,6 +2483,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 +2531,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 +2547,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 +2572,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 +2642,7 @@ DrawSeekGraph () for(i=0; i<4000; i+= 100) if(i>=minRating && i 0) { if (appData.debugMode) { - if (appData.debugMode) { int f = forwardMostMove; - fprintf(debugFP, "parseboard %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]); - } + int f = forwardMostMove; + fprintf(debugFP, "parseboard %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]); fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str); fprintf(debugFP, "moveNum = %d\n", moveNum); fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT); @@ -4877,6 +4949,13 @@ GetMoveListEvent () } void +SendToBoth (char *msg) +{ // to make it easy to keep two engines in step in dual analysis + SendToProgram(msg, &first); + if(second.analyzing) SendToProgram(msg, &second); +} + +void AnalysisPeriodicEvent (int force) { if (((programStats.ok_to_send == 0 || programStats.line_is_book) @@ -4884,7 +4963,7 @@ AnalysisPeriodicEvent (int force) return; /* Send . command to Crafty to collect stats */ - SendToProgram(".\n", &first); + SendToBoth(".\n"); /* Don't send another until we get a response (this makes us stop sending to old Crafty's which don't understand @@ -5020,7 +5099,8 @@ SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char break; case WhitePromotion: case BlackPromotion: - if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, PieceToChar(WhiteFerz)); @@ -5084,7 +5164,7 @@ UploadGameEvent () SendToICS(ics_prefix); SendToICS(buf); if(startedFromSetupPosition || backwardMostMove != 0) { - fen = PositionToFEN(backwardMostMove, NULL); + fen = PositionToFEN(backwardMostMove, NULL, 1); if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything snprintf(buf, MSG_SIZ,"loadfen %s\n", fen); SendToICS(buf); @@ -5115,6 +5195,11 @@ UploadGameEvent () for(i = backwardMostMove; 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); } @@ -5350,7 +5441,8 @@ ParsePV (char *pv, Boolean storeComments, Boolean atEnd) Boolean valid; int nr = 0; - if (gameMode == AnalyzeMode && currentMove < forwardMostMove) { + lastParseAttempt = pv; if(!*pv) return; // turns out we crash when we parse an empty PV + if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && currentMove < forwardMostMove) { PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position! pushed = TRUE; } @@ -5415,11 +5507,14 @@ MultiPV (ChessProgramState *cps) return -1; } +Boolean extendGame; // signals to UnLoadPV() if walked part of PV has to be appended to game + 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; @@ -5431,12 +5526,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 @@ -5445,6 +5540,7 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end) } ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; + extendGame = (gameMode == AnalyzeMode && appData.autoExtend); return TRUE; } @@ -5454,7 +5550,7 @@ PvToSAN (char *pv) static char buf[10*MSG_SIZ]; int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove; *buf = NULLCHAR; - if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); + if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); // shelve PV of PV-walk ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it for(i = forwardMostMove; i forwardMostMove) { + if(extendGame && currentMove > forwardMostMove) { Boolean saveAnimate = appData.animate; if(pushed) { if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space @@ -5811,9 +5909,13 @@ InitPosition (int redraw) case VariantMakruk: pieces = makrukArray; nrCastlingRights = 0; - startedFromSetupPosition = TRUE; SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); break; + case VariantASEAN: + pieces = aseanArray; + nrCastlingRights = 0; + SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk"); + break; case VariantTwoKings: pieces = twoKingsArray; break; @@ -5840,6 +5942,7 @@ InitPosition (int redraw) case VariantSChess: SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek"); gameInfo.holdingsSize = 7; + for(i=0; iuseSetboard) { - char* fen = PositionToFEN(moveNum, cps->fenOverride); + char* fen = PositionToFEN(moveNum, cps->fenOverride, 1); snprintf(message, MSG_SIZ,"setboard %s\n", fen); SendToProgram(message, cps); free(fen); @@ -6166,12 +6269,12 @@ UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, cha } static int -ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, char state) +ExcludeOneMove (int fromY, int fromX, int toY, int toX, char promoChar, char state) { // include or exclude the given move, as specified by state ('+' or '-'), or toggle char buf[MSG_SIZ]; int j, k; ChessMove moveType; - if(promoChar == -1) { // kludge to indicate best move + if((signed char)promoChar == -1) { // kludge to indicate best move if(!ParseOneMove(lastPV[0], currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) // get current best move from last PV return 1; // if unparsable, abort } @@ -6187,7 +6290,7 @@ ExcludeOneMove (int fromY, int fromX, int toY, int toX, signed char promoChar, c // inform engine snprintf(buf, MSG_SIZ, "%sclude ", state == '+' ? "in" : "ex"); CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, buf+8); - SendToProgram(buf, &first); + SendToBoth(buf); return (state == '+'); } @@ -6200,16 +6303,16 @@ ExcludeClick (int index) if(index < 13) { // none: include all WriteMap(0); // clear map for(i=0; i 18) { // tail if(exclusionHeader[19] == '-') { // tail was excluded - SendToProgram("include all\n", &first); + SendToBoth("include all\n"); WriteMap(0); // clear map completely // now re-exclude selected moves for(i=0; i= BOARD_RGHT) return; @@ -7152,9 +7267,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(); @@ -7171,8 +7287,11 @@ 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; if (clickType == Press) { if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { @@ -7181,40 +7300,39 @@ 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; } - /* Finish clickclick move */ - if (appData.animate || appData.highlightLastMove) { - SetHighlights(fromX, fromY, toX, toY); - } else { - ClearHighlights(); - } if(HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { if(appData.sweepSelect) { ChessSquare piece = boards[currentMove][fromY][fromX]; - ChessSquare victim = boards[currentMove][toY][toX]; - boards[currentMove][toY][toX] = piece; // kludge: make sure there is something to grab for drag - DragPieceBegin(xPix, yPix, TRUE); dragging = 1; - boards[currentMove][toY][toX] = victim; 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]); + MarkTargetSquares(1); } return; // promo popup appears on up-click } + /* Finish clickclick move */ + if (appData.animate || appData.highlightLastMove) { + SetHighlights(fromX, fromY, toX, toY); + } else { + ClearHighlights(); + } } else { +#if 0 +// [HGM] this must be done after the move is made, as with arrow it could lead to a board redraw with piece still on from square /* Finish drag move */ if (appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); } else { ClearHighlights(); } +#endif DragPieceEnd(xPix, yPix); dragging = 0; /* Don't animate move and drag both */ appData.animate = FALSE; } - MarkTargetSquares(1); // moves into holding are invalid for now (except in EditPosition, adapting to-square) if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) { @@ -7239,6 +7357,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } ClearHighlights(); fromX = fromY = -1; + MarkTargetSquares(1); DrawPosition(TRUE, boards[currentMove]); return; } @@ -7250,6 +7369,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]; @@ -7272,6 +7392,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; @@ -7528,6 +7649,9 @@ MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisCo || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon + } else if(v == VariantKnightmate) { + if(nMine == 1) return FALSE; + if(nMine == 2 && nHis == 1 && pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side] + pCnt[WhiteKnight+side]) return FALSE; // KBK is only draw } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side]; @@ -7580,7 +7704,7 @@ Adjudicate (ChessProgramState *cps) // In any case it determnes if the game is a claimable draw (filling in EP_STATUS). // Actually ending the game is now based on the additional internal condition canAdjudicate. // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed. - int k, count = 0; static int bare = 1; + int k, drop, count = 0; static int bare = 1; ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first)); Boolean canAdjudicate = !appData.icsActive; @@ -7664,13 +7788,21 @@ Adjudicate (ChessProgramState *cps) boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE : ((nrW < nrB) != WhiteOnMove(forwardMostMove) ? EP_CHECKMATE : EP_WINS); - else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) + else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses } break; case MT_CHECKMATE: reason = "Xboard adjudication: Checkmate"; boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); + if(gameInfo.variant == VariantShogi) { + if(forwardMostMove > backwardMostMove + && moveList[forwardMostMove-1][1] == '@' + && CharToPiece(ToUpper(moveList[forwardMostMove-1][0])) == WhitePawn) { + reason = "XBoard adjudication: pawn-drop mate"; + boards[forwardMostMove][EP_STATUS] = EP_WINS; + } + } break; } @@ -7735,10 +7867,12 @@ Adjudicate (ChessProgramState *cps) /* Check for rep-draws */ count = 0; + drop = gameInfo.holdingsSize && (gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess + && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand); for(k = forwardMostMove-2; - k>=backwardMostMove && k>=forwardMostMove-100 && + k>=backwardMostMove && k>=forwardMostMove-100 && (drop || (signed char)boards[k][EP_STATUS] < EP_UNKNOWN && - (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE; + (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE); k-=2) { int rights=0; if(CompareBoards(boards[k], boards[forwardMostMove])) { @@ -7764,7 +7898,7 @@ Adjudicate (ChessProgramState *cps) /* adjudicate after user-specified nr of repeats */ int result = GameIsDrawn; char *details = "XBoard adjudication: repetition draw"; - if(gameInfo.variant == VariantXiangqi && appData.testLegality) { + if((gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantShogi) && appData.testLegality) { // [HGM] xiangqi: check for forbidden perpetuals int m, ourPerpetual = 1, hisPerpetual = 1; for(m=forwardMostMove; m>k; m-=2) { @@ -7782,6 +7916,12 @@ Adjudicate (ChessProgramState *cps) if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet break; // (or we would have caught him before). Abort repetition-checking loop. } else + if(gameInfo.variant == VariantShogi) { // in Shogi other repetitions are draws + if(BOARD_HEIGHT == 5 && BOARD_RGHT - BOARD_LEFT == 5) { // but in mini-Shogi gote wins! + result = BlackWins; + details = "Xboard adjudication: repetition"; + } + } else // it must be XQ // Now check for perpetual chases if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase hisPerpetual = PerpetualChase(k, forwardMostMove); @@ -7930,7 +8070,7 @@ LoadError (char *errmess, ChessProgramState *cps) if(cps->initDone) return FALSE; cps->isr = NULL; // this should suppress further error popups from breaking pipes DestroyChildProcess(cps->pr, 9 ); // just to be sure - cps->pr = NoProc; + cps->pr = NoProc; if(cps == &first) { appData.noChessProgram = TRUE; gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu @@ -7955,6 +8095,8 @@ DeferredBookMove (void) } static int savedWhitePlayer, savedBlackPlayer, pairingReceived; +static ChessProgramState *stalledEngine; +static char stashedInputMove[MSG_SIZ]; void HandleMachineMove (char *message, ChessProgramState *cps) @@ -8020,6 +8162,22 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) { + if(pausing && !cps->pause) { // for pausing engine that does not support 'pause', we stash its move for processing when we resume. + if(appData.debugMode) fprintf(debugFP, "pause %s engine after move\n", cps->which); + safeStrCpy(stashedInputMove, message, MSG_SIZ); + stalledEngine = cps; + if(appData.ponderNextMove) { // bring opponent out of ponder + if(gameMode == TwoMachinesPlay) { + if(cps->other->pause) + PauseEngine(cps->other); + else + SendToProgram("easy\n", cps->other); + } + } + StopClocks(); + return; + } + /* This method is only useful on engines that support ping */ if (cps->lastPing != cps->lastPong) { if (gameMode == BeginningOfGame) { @@ -8090,7 +8248,7 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h /* Machine move could not be parsed; ignore it. */ snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"), machineMove, _(cps->which)); - DisplayError(buf1, 0); + DisplayMoveError(buf1); snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType); if (gameMode == TwoMachinesPlay) { @@ -8147,6 +8305,17 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ + /* Test suites abort the 'game' after one move */ + if(*appData.finger) { + static FILE *f; + char *fen = PositionToFEN(backwardMostMove, NULL, 0); // no counts in EPD + if(!f) f = fopen(appData.finger, "w"); + if(f) fprintf(f, "%s bm %s;\n", fen, parseList[backwardMostMove]), fflush(f); + else { DisplayFatalError("Bad output file", errno, 0); return; } + free(fen); + GameEnds(GameUnfinished, NULL, GE_XBOARD); + } + /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { int count = 0; @@ -8287,12 +8456,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands! } - if ((!appData.testLegality || gameInfo.variant == VariantFairy) && - !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position + if (!strncmp(message, "setup ", 6) && + (!appData.testLegality || gameInfo.variant == VariantFairy || NonStandardBoardSize()) + ) { // [HGM] allow first engine to define opening position int dummy, s=6; char buf[MSG_SIZ]; if(appData.icsActive || forwardMostMove != 0 || cps != &first) return; if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); if(startedFromSetupPosition) return; + if(sscanf(message+s, "%dx%d+%d", &dummy, &dummy, &dummy) == 3) while(message[s] && message[s++] != ' '); // for compatibility with Alien Edition ParseFEN(boards[0], &dummy, message+s); DrawPosition(TRUE, boards[0]); startedFromSetupPosition = TRUE; @@ -8355,7 +8526,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11); SendToICS(buf1); } - } + } else if(appData.autoComment) AppendComment (forwardMostMove, message + 11, 1); // in local mode, add as move comment return; } if (!strncmp(message, "tellall ", 8)) { @@ -9287,6 +9458,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 */ @@ -9336,11 +9514,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); - if(gameInfo.variant==VariantBughouse || - gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + if(board[toY][toX] < WhiteCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY >= BOARD_HEIGHT>>1) + && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -9397,11 +9575,11 @@ ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); - if(gameInfo.variant==VariantBughouse || - gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + if(board[toY][toX] < BlackCannon && PieceToChar(PROMOTED board[toY][toX]) == '~') /* [HGM] use shadow piece (if available) */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY < BOARD_HEIGHT>>1) + && (oldEP == toX || oldEP == EP_UNKNOWN || appData.testLegality) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -9576,8 +9754,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", @@ -9586,6 +9768,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); @@ -9646,9 +9829,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; } @@ -9656,6 +9836,11 @@ ShowMove (int fromX, int fromY, int toX, int toY) if (instant) return; DisplayMove(currentMove - 1); + if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { + if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board + SetHighlights(fromX, fromY, toX, toY); + } + } DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); @@ -9695,11 +9880,38 @@ SendEgtPath (ChessProgramState *cps) } } +static int +NonStandardBoardSize () +{ + /* [HGM] Awkward testing. Should really be a table */ + int overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantXiangqi ) + overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantShogi ) + overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7; + if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse ) + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5; + if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || + gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus ) + overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantCourier ) + overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantSuper ) + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; + if( gameInfo.variant == VariantGreat ) + overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; + if( gameInfo.variant == VariantSChess ) + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7; + if( gameInfo.variant == VariantGrand ) + overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7; + return overruled; +} + void InitChessProgram (ChessProgramState *cps, int setup) /* setup needed to setup FRC opening position */ { - char buf[MSG_SIZ], b[MSG_SIZ]; int overruled; + char buf[MSG_SIZ], b[MSG_SIZ]; if (appData.noChessProgram) return; hintRequested = FALSE; bookRequested = FALSE; @@ -9731,29 +9943,7 @@ InitChessProgram (ChessProgramState *cps, int setup) return; } - /* [HGM] make prefix for non-standard board size. Awkward testing... */ - overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; - if( gameInfo.variant == VariantXiangqi ) - overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0; - if( gameInfo.variant == VariantShogi ) - overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7; - if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse ) - overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5; - if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || - gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus ) - overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; - if( gameInfo.variant == VariantCourier ) - overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; - if( gameInfo.variant == VariantSuper ) - overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; - if( gameInfo.variant == VariantGreat ) - overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; - if( gameInfo.variant == VariantSChess ) - overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7; - if( gameInfo.variant == VariantGrand ) - overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7; - - if(overruled) { + if(NonStandardBoardSize()) { /* [HGM] make prefix for non-standard board size. */ snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name /* [HGM] varsize: try first if this defiant size variant is specifically known */ @@ -9818,6 +10008,33 @@ InitChessProgram (ChessProgramState *cps, int setup) void +ResendOptions (ChessProgramState *cps) +{ // send the stored value of the options + int i; + char buf[MSG_SIZ]; + Option *opt = cps->option; + for(i=0; inrOptions; i++, opt++) { + switch(opt->type) { + case Spin: + case Slider: + case CheckBox: + snprintf(buf, MSG_SIZ, "option %s=%d\n", opt->name, opt->value); + break; + case ComboBox: + snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->choice[opt->value]); + break; + default: + snprintf(buf, MSG_SIZ, "option %s=%s\n", opt->name, opt->textValue); + break; + case Button: + case SaveButton: + continue; + } + SendToProgram(buf, cps); + } +} + +void StartChessProgram (ChessProgramState *cps) { char buf[MSG_SIZ]; @@ -9857,9 +10074,12 @@ StartChessProgram (ChessProgramState *cps) cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps); if (cps->protocolVersion > 1) { snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion); - cps->nrOptions = 0; // [HGM] options: clear all engine-specific options - cps->comboCnt = 0; // and values of combo boxes + if(!cps->reload) { // do not clear options when reloading because of -xreuse + cps->nrOptions = 0; // [HGM] options: clear all engine-specific options + cps->comboCnt = 0; // and values of combo boxes + } SendToProgram(buf, cps); + if(cps->reload) ResendOptions(cps); } else { SendToProgram("xboard\n", cps); } @@ -9880,7 +10100,6 @@ TwoMachinesEventIfReady P((void)) return; } DisplayMessage("", ""); curMess = 0; - ThawUI(); TwoMachinesEvent(); } @@ -9940,7 +10159,15 @@ WriteTourneyFile (char *results, FILE *f) fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile); fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); + fprintf(f, "-usePolyglotBook %s\n", appData.usePolyglotBook ? "true" : "false"); + fprintf(f, "-polyglotBook \"%s\"\n", appData.polyglotBook); + fprintf(f, "-bookDepth %d\n", appData.bookDepth); + fprintf(f, "-bookVariation %d\n", appData.bookStrength); fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false"); + fprintf(f, "-defaultHashSize %d\n", appData.defaultHashSize); + fprintf(f, "-defaultCacheSizeEGTB %d\n", appData.defaultCacheSizeEGTB); + fprintf(f, "-ponderNextMove %s\n", appData.ponderNextMove ? "true" : "false"); + fprintf(f, "-smpCores %d\n", appData.smpCores); if(searchTime > 0) fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60); else { @@ -10014,6 +10241,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; @@ -10035,6 +10283,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; @@ -10113,6 +10362,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; @@ -10125,6 +10396,13 @@ SetPlayer (int player, char *p) ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE; appData.firstHasOwnBookUCI = !appData.defNoBook; appData.protocolVersion[0] = PROTOVER; ParseArgsFromString(buf); + } else { // no engine with this nickname is installed! + snprintf(buf, MSG_SIZ, _("No engine %s is installed"), engineName); + ReserveGame(nextGame, ' '); // unreserve game and drop out of match mode with error + matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0; + ModeHighlight(); + DisplayError(buf, 0); + return 0; } free(engineName); return i; @@ -10190,7 +10468,7 @@ Pairing (int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInte *blackPlayer = curRound + appData.tourneyType; } - // take care of white/black alternation per round. + // take care of white/black alternation per round. // For cycles and games this is already taken care of by default, derived from matchGame! return curRound & 1; } @@ -10199,7 +10477,7 @@ int NextTourneyGame (int nr, int *swapColors) { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game char *p, *q; - int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers; + int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers, OK = 1; FILE *tf; if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game tf = fopen(appData.tourneyFile, "r"); @@ -10239,7 +10517,7 @@ NextTourneyGame (int nr, int *swapColors) SendToProgram(buf, &pairing); return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again... } - pairingReceived = 0; // ... so we continue here + pairingReceived = 0; // ... so we continue here *swapColors = 0; appData.matchGames = appData.tourneyCycles * syncInterval - 1; whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1; @@ -10251,18 +10529,18 @@ NextTourneyGame (int nr, int *swapColors) // redefine engines, engine dir, etc. NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines if(first.pr == NoProc) { - SetPlayer(whitePlayer, appData.participants); // find white player amongst it, and parse its engine line + if(!SetPlayer(whitePlayer, appData.participants)) OK = 0; // find white player amongst it, and parse its engine line InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. } if(second.pr == NoProc) { SwapEngines(1); - SetPlayer(blackPlayer, appData.participants); // find black player amongst it, and parse its engine line + if(!SetPlayer(blackPlayer, appData.participants)) OK = 0; // find black player amongst it, and parse its engine line SwapEngines(1); // and make that valid for second engine by swapping InitEngine(&second, 1); } CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes UpdateLogos(FALSE); // leave display to ModeHiglight() - return 1; + return OK; } void @@ -10363,6 +10641,8 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) fromX = fromY = -1; // [HGM] abort any move the user is entering. + if(pausing) PauseEvent(); // can happen when we abort a paused game (New Game or Quit) + if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) { /* If we are playing on ICS, the server decides when the game is over, but the engine can offer to draw, claim @@ -10455,6 +10735,10 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) resultDetails = buf; } /* (Claiming a loss is accepted no questions asked!) */ + } else if(matchMode && result == GameIsDrawn && !strcmp(resultDetails, "Engine Abort Request")) { + forwardMostMove = backwardMostMove; // [HGM] delete game to surpress saving + result = GameUnfinished; + if(!*appData.tourneyFile) matchGame--; // replay even in plain match } /* [HGM] bare: don't allow bare King to win */ if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper @@ -10499,13 +10783,17 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates ) { if (*appData.saveGameFile != NULLCHAR) { + if(result == GameUnfinished && matchMode && *appData.tourneyFile) + AutoSaveGame(); // [HGM] protect tourney PGN from aborted games, and prompt for name instead + else SaveGameToFile(appData.saveGameFile, TRUE); } else if (appData.autoSaveGames) { - AutoSaveGame(); + if(gameMode != IcsObserving || !appData.onlyOwn) AutoSaveGame(); } if (*appData.savePositionFile != NULLCHAR) { SavePositionToFile(appData.savePositionFile); } + AddGameToBook(FALSE); // Only does something during Monte-Carlo book building } } @@ -10622,6 +10910,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) SendToProgram("quit\n", &first); DoSleep( appData.delayAfterQuit ); DestroyChildProcess(first.pr, first.useSigterm); + first.reload = TRUE; } first.pr = NoProc; } @@ -10647,11 +10936,12 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) SendToProgram("quit\n", &second); DoSleep( appData.delayAfterQuit ); DestroyChildProcess(second.pr, second.useSigterm); + second.reload = TRUE; } second.pr = NoProc; } - if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) { + if (matchMode && (gameMode == TwoMachinesPlay || (waitingForGame || startingEngine) && exiting)) { char resChar = '='; switch (result) { case WhiteWins: @@ -10676,7 +10966,7 @@ GameEnds (ChessMove result, char *resultDetails, int whosays) break; } - if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game + if(exiting) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result if(appData.afterGame && appData.afterGame[0]) RunCommand(appData.afterGame); ReserveGame(nextGame, resChar); // sets nextGame @@ -10775,8 +11065,8 @@ ResurrectChessProgram () if (appData.noChessProgram) return 1; - if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?) - if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit + if(matchMode /*&& appData.tourneyFile[0]*/) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?) + if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit, because we started engine if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again } else { @@ -10898,11 +11188,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 () @@ -10916,15 +11211,28 @@ AutoPlayOneMove () if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile) return FALSE; - if (gameMode == AnalyzeFile && currentMove > backwardMostMove) { + if (gameMode == AnalyzeFile && currentMove > backwardMostMove && programStats.depth) { pvInfoList[currentMove].depth = programStats.depth; pvInfoList[currentMove].score = programStats.score; pvInfoList[currentMove].time = 0; if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2); + else { // append analysis of final position as comment + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "{final score %+4.2f/%d}", programStats.score/100., programStats.depth); + AppendComment(currentMove, buf, 3); // the 3 prevents stripping of the score/depth! + } + programStats.depth = 0; } if (currentMove >= forwardMostMove) { - if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); } + if(gameMode == AnalyzeFile) { + if(appData.loadGameIndex == -1) { + GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE); + ScheduleDelayedEvent(AnalyzeNextGame, 10); + } else { + ExitAnalyzeMode(); SendToProgram("force\n", &first); + } + } // gameMode = EndOfGame; // ModeHighlight(); @@ -11533,7 +11841,7 @@ QuickCompare (Board board, int *minCounts, int *maxCounts) int QuickScan (Board board, Move *move) { // reconstruct game,and compare all positions in it - int cnt=0, stretch=0, total = MakePieceList(board, counts), delayedKing = -1; + int cnt=0, stretch=0, total = MakePieceList(board, counts); do { int piece = move->piece; int to = move->to, from = pieceList[piece]; @@ -11551,7 +11859,6 @@ QuickScan (Board board, Move *move) move++; continue; } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to) - int rook; piece = pieceList[piece]; // first two elements of pieceList contain King numbers from = pieceList[piece]; // so this must be King quickBoard[from] = 0; @@ -11581,7 +11888,7 @@ QuickScan (Board board, Move *move) if(stretch++ == 0) for(i=0; i= appData.stretch)) return cnt + 1 - stretch; - move++; delayedKing = -1; + move++; } while(1); } @@ -11600,12 +11907,12 @@ InitSearch () if(piece < BlackPawn) piece += BlackPawn; else if(piece < EmptySquare) piece -= BlackPawn; // color-flip reverseBoard[r][f] = piece; } - reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; + reverseBoard[EP_STATUS-1] = soughtBoard[EP_STATUS-1] ^ 3; for(r=0; r<6; r++) reverseBoard[CASTLING][r] = boards[currentMove][CASTLING][(r+3)%6]; if(appData.findMirror && appData.searchMode <= 3 && (!nrCastlingRights - || (boards[currentMove][CASTLING][2] == NoRights || + || (boards[currentMove][CASTLING][2] == NoRights || boards[currentMove][CASTLING][0] == NoRights && boards[currentMove][CASTLING][1] == NoRights ) - && (boards[currentMove][CASTLING][5] == NoRights || + && (boards[currentMove][CASTLING][5] == NoRights || boards[currentMove][CASTLING][3] == NoRights && boards[currentMove][CASTLING][4] == NoRights ) ) ) { flipSearch = TRUE; @@ -11627,6 +11934,7 @@ InitSearch () } GameInfo dummyInfo; +static int creatingBook; int GameContainsPosition (FILE *f, ListGame *lg) @@ -11769,6 +12077,9 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) gn = 1; } else { + if(oldGameMode == 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; } @@ -12087,6 +12398,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) cm = (ChessMove) Myylex(); } + if(!creatingBook) { if (first.pr == NoProc) { StartChessProgram(&first); } @@ -12099,6 +12411,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) } DisplayBothClocks(); } + } /* [HGM] server: flag to write setup moves in broadcast file as one */ loadFlag = appData.suppressLoadMoves; @@ -12160,11 +12473,15 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); - if (oldGameMode == AnalyzeFile || - oldGameMode == AnalyzeMode) { + if (oldGameMode == AnalyzeFile) { + appData.loadGameIndex = -1; // [HGM] order auto-stepping through games + AnalyzeFileEvent(); + } else + if (oldGameMode == AnalyzeMode) { AnalyzeFileEvent(); } + if(creatingBook) return TRUE; if (!matchMode && pos > 0) { ToNrEvent(pos); // [HGM] no autoplay if selected on position } else @@ -12516,7 +12833,6 @@ int SaveGamePGN (FILE *f) { int i, offset, linelen, newblock; - time_t tm; // char *movetext; char numtext[32]; int movelen, numlen, blank; @@ -12524,14 +12840,12 @@ 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); + char *fen = PositionToFEN(backwardMostMove, NULL, 1); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); fprintf(f, "\n{--------------\n"); PrintPosition(f, backwardMostMove); @@ -12800,7 +13114,7 @@ SavePosition (FILE *f, int dummy, char *dummy2) PrintPosition(f, currentMove); fprintf(f, "--------------]\n"); } else { - fen = PositionToFEN(currentMove, NULL); + fen = PositionToFEN(currentMove, NULL, 1); fprintf(f, "%s\n", fen); free(fen); } @@ -13202,6 +13516,20 @@ ExitEvent (int status) } void +PauseEngine (ChessProgramState *cps) +{ + SendToProgram("pause\n", cps); + cps->pause = 2; +} + +void +UnPauseEngine (ChessProgramState *cps) +{ + SendToProgram("resume\n", cps); + cps->pause = 1; +} + +void PauseEvent () { if (appData.debugMode) @@ -13209,8 +13537,24 @@ PauseEvent () if (pausing) { pausing = FALSE; ModeHighlight(); + if(stalledEngine) { // [HGM] pause: resume game by releasing withheld move + StartClocks(); + if(gameMode == TwoMachinesPlay) { // we might have to make the opponent resume pondering + if(stalledEngine->other->pause == 2) UnPauseEngine(stalledEngine->other); + else if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine->other); + } + if(appData.ponderNextMove) SendToProgram("hard\n", stalledEngine); + HandleMachineMove(stashedInputMove, stalledEngine); + stalledEngine = NULL; + return; + } if (gameMode == MachinePlaysWhite || - gameMode == MachinePlaysBlack) { + gameMode == TwoMachinesPlay || + gameMode == MachinePlaysBlack) { // the thinking engine must have used pause mode, or it would have been stalledEngine + if(first.pause) UnPauseEngine(&first); + else if(appData.ponderNextMove) SendToProgram("hard\n", &first); + if(second.pause) UnPauseEngine(&second); + else if(gameMode == TwoMachinesPlay && appData.ponderNextMove) SendToProgram("hard\n", &second); StartClocks(); } else { DisplayBothClocks(); @@ -13222,7 +13566,7 @@ PauseEvent () Reset(FALSE, TRUE); SendToICS(ics_prefix); SendToICS("refresh\n"); - } else if (currentMove < forwardMostMove) { + } else if (currentMove < forwardMostMove && gameMode != AnalyzeMode) { ForwardInner(forwardMostMove); } pauseExamInvalid = FALSE; @@ -13253,12 +13597,30 @@ PauseEvent () case TwoMachinesPlay: if (forwardMostMove == 0) return; /* don't pause if no one has moved */ - if ((gameMode == MachinePlaysWhite && - !WhiteOnMove(forwardMostMove)) || - (gameMode == MachinePlaysBlack && - WhiteOnMove(forwardMostMove))) { + if(gameMode == TwoMachinesPlay) { // [HGM] pause: stop clocks if engine can be paused immediately + ChessProgramState *onMove = (WhiteOnMove(forwardMostMove) == (first.twoMachinesColor[0] == 'w') ? &first : &second); + if(onMove->pause) { // thinking engine can be paused + PauseEngine(onMove); // do it + if(onMove->other->pause) // pondering opponent can always be paused immediately + PauseEngine(onMove->other); + else + SendToProgram("easy\n", onMove->other); + StopClocks(); + } else if(appData.ponderNextMove) SendToProgram("easy\n", onMove); // pre-emptively bring out of ponder + } else if(gameMode == (WhiteOnMove(forwardMostMove) ? MachinePlaysWhite : MachinePlaysBlack)) { // engine on move + if(first.pause) { + PauseEngine(&first); + StopClocks(); + } else if(appData.ponderNextMove) SendToProgram("easy\n", &first); // pre-emptively bring out of ponder + } else { // human on move, pause pondering by either method + if(first.pause) + PauseEngine(&first); + else if(appData.ponderNextMove) + SendToProgram("easy\n", &first); StopClocks(); } + // if no immediate pausing is possible, wait for engine to move, and stop clocks then + case AnalyzeMode: pausing = TRUE; ModeHighlight(); break; @@ -13293,16 +13655,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; @@ -13318,6 +13738,7 @@ AnalyzeModeEvent () StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + return 1; } void @@ -13326,9 +13747,19 @@ AnalyzeFileEvent () if (appData.noChessProgram || gameMode == AnalyzeFile) return; + if (!first.analysisSupport) { + char buf[MSG_SIZ]; + snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy); + DisplayError(buf, 0); + return; + } + if (gameMode != AnalyzeMode) { + keepInfo = 1; // mere annotating should not alter PGN tags EditGameEvent(); + keepInfo = 0; if (gameMode != EditGame) return; + if (!appData.showThinking) ToggleShowThinking(); ResurrectChessProgram(); SendToProgram("analyze\n", &first); first.analyzing = TRUE; @@ -13339,12 +13770,12 @@ AnalyzeFileEvent () gameMode = AnalyzeFile; pausing = FALSE; ModeHighlight(); - SetGameInfo(); 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 @@ -13514,7 +13945,7 @@ DisplayTwoMachinesTitle () gameInfo.white, _("vs."), gameInfo.black, nextGame+1, appData.matchGames+1, appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr"); - } else + } else if (first.twoMachinesColor[0] == 'w') { snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", gameInfo.white, _("vs."), gameInfo.black, @@ -13553,9 +13984,10 @@ WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry) StartChessProgram(cps); if (cps->protocolVersion == 1) { retry(); + ScheduleDelayedEvent(retry, 1); // Do this also through timeout to avoid recursive calling of 'retry' } else { /* kludge: allow timeout for initial "feature" command */ - FreezeUI(); + if(retry != TwoMachinesEventIfReady) FreezeUI(); snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which)); DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); @@ -13578,11 +14010,6 @@ TwoMachinesEvent P((void)) if (appData.noChessProgram) return; - if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) { - DisplayError("second engine does not play this", 0); - return; - } - switch (gameMode) { case TwoMachinesPlay: return; @@ -13613,14 +14040,23 @@ TwoMachinesEvent P((void)) // forwardMostMove = currentMove; TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... + startingEngine = TRUE; if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */ - if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features + if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features + + if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) { + startingEngine = FALSE; + DisplayError("second engine does not play this", 0); + return; + } + if(!stalling) { InitChessProgram(&second, FALSE); // unbalances ping of second engine SendToProgram("force\n", &second); @@ -13650,7 +14086,7 @@ TwoMachinesEvent P((void)) } gameMode = TwoMachinesPlay; - pausing = FALSE; + pausing = startingEngine = FALSE; ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); @@ -13891,8 +14327,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; } @@ -13907,14 +14343,22 @@ EditPositionDone (Boolean fakeRights) if(fakeRights) { // [HGM] suppress this if we just pasted a FEN. boards[0][EP_STATUS] = EP_NONE; boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1; - if(boards[0][0][BOARD_WIDTH>>1] == king) { - boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights; + if(boards[0][0][BOARD_WIDTH>>1] == king) { + boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? BOARD_LEFT : NoRights; boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights; } else boards[0][CASTLING][2] = NoRights; - if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { - boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights; + if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { + boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? BOARD_LEFT : NoRights; boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights; } else boards[0][CASTLING][5] = NoRights; + if(gameInfo.variant == VariantSChess) { + int i; + for(i=BOARD_LEFT; ibackwardMostMove; i--) { // seek back to start or previous null move if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break; } - SendBoard(&first, i); - for(currentMove=i; currentMovenumber <= 0 ) { + DisplayError(_("Game list not loaded or empty"), 0); + return; + } + + if(!secondTime && (f = fopen(appData.polyglotBook, "r"))) { + fclose(f); + secondTime++; + DisplayNote(_("Book file exists! Try again for overwrite.")); + return; + } + + creatingBook = TRUE; + secondTime = FALSE; + + /* Get list size */ + for (nItem = 1; nItem <= ((ListGame *) gameList.tailPred)->number; nItem++){ + LoadGame(f, nItem, "", TRUE); + AddGameToBook(TRUE); + lg = (ListGame *) lg->node.succ; + } + + creatingBook = FALSE; + FlushBook(); +} + +void BookEvent () { if (appData.noChessProgram) return; @@ -14921,6 +15406,8 @@ SetGameInfo () ChessMove r = GameUnfinished; char *p = NULL; + if(keepInfo) return; + if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame r = gameInfo.result; p = gameInfo.resultDetails; @@ -15018,7 +15505,7 @@ ReplaceComment (int index, char *text) char *p; float score; - if(index && sscanf(text, "%f/%d", &score, &len) == 2 && + if(index && sscanf(text, "%f/%d", &score, &len) == 2 && pvInfoList[index-1].depth == len && fabs(pvInfoList[index-1].score - score*100.) < 0.5 && (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any @@ -15073,7 +15560,8 @@ AppendComment (int index, char *text, Boolean addBraces) int oldlen, len; char *old; -if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP); +if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); + if(addBraces == 3) addBraces = 0; else // force appending literally text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */ CrushCRs(text); @@ -15140,8 +15628,11 @@ GetInfoFromComment (int index, char * text) int time = -1, sec = 0, deci; char * s_eval = FindStr( text, "[%eval " ); char * s_emt = FindStr( text, "[%emt " ); - +#if 0 if( s_eval != NULL || s_emt != NULL ) { +#else + if(0) { // [HGM] this code is not finished, and could actually be detrimental +#endif /* New style */ char delim; @@ -15171,6 +15662,7 @@ GetInfoFromComment (int index, char * text) } p = text; + if(!strncmp(p+1, "final score ", 12)) p += 12, index++; else if(p[1] == '(') { // comment starts with PV p = strchr(p, ')'); // locate end of PV if(p == NULL || sep < p+5) return text; @@ -15194,7 +15686,7 @@ GetInfoFromComment (int index, char * text) if(sec >= 0) time = 600*time + 10*sec; else if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec - score = score >= 0 ? score*100 + score_lo : score*100 - score_lo; + score = score > 0 || !score & p[1] != '-' ? score*100 + score_lo : score*100 - score_lo; /* [HGM] PV time: now locate end of PV info */ while( *++sep >= '0' && *sep <= '9'); // strip depth @@ -15331,7 +15823,7 @@ ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int cou sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 && - sscanf(message, "hint: %c", &c)!=1 && + sscanf(message, "hint: %c", &c)!=1 && sscanf(message, "pong %c", &c)!=1 && start != '#') { quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### "; print = (appData.engineComments >= 2); @@ -15505,14 +15997,15 @@ IntFeature (char **p, char *name, int *loc, ChessProgramState *cps) } int -StringFeature (char **p, char *name, char loc[], ChessProgramState *cps) +StringFeature (char **p, char *name, char **loc, ChessProgramState *cps) { char buf[MSG_SIZ]; int len = strlen(name); if (strncmp((*p), name, len) == 0 && (*p)[len] == '=' && (*p)[len+1] == '\"') { (*p) += len + 2; - sscanf(*p, "%[^\"]", loc); + ASSIGN(*loc, *p); // kludge alert: assign rest of line just to be sure allocation is large enough so that sscanf below always fits + sscanf(*p, "%[^\"]", *loc); while (**p && **p != '\"') (*p)++; if (**p == '\"') (*p)++; snprintf(buf, MSG_SIZ, "accepted %s\n", name); @@ -15625,6 +16118,7 @@ FeatureDone (ChessProgramState *cps, int val) ScheduleDelayedEvent(cb, val ? 1 : 3600000); } cps->initDone = val; + if(val) cps->reload = FALSE; } /* Parse feature command from engine */ @@ -15632,7 +16126,7 @@ void ParseFeatures (char *args, ChessProgramState *cps) { char *p = args; - char *q; + char *q = NULL; int val; char buf[MSG_SIZ]; @@ -15652,7 +16146,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 { @@ -15660,7 +16154,7 @@ 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; @@ -15669,7 +16163,7 @@ ParseFeatures (char *args, ChessProgramState *cps) if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue; if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue; if (BoolFeature(&p, "name", &cps->sendName, cps)) continue; - if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */ + if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause if (IntFeature(&p, "done", &val, cps)) { FeatureDone(cps, val); continue; @@ -15685,11 +16179,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", buf, cps)) { + if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue; + if (StringFeature(&p, "option", &q, cps)) { // read to freshly allocated temp buffer first + if(cps->reload) { FREE(q); q = NULL; continue; } // we are reloading because of xreuse FREE(cps->option[cps->nrOptions].name); - cps->option[cps->nrOptions].name = malloc(MSG_SIZ); - safeStrCpy(cps->option[cps->nrOptions].name, buf, MSG_SIZ); + cps->option[cps->nrOptions].name = q; q = NULL; 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); @@ -15813,9 +16307,9 @@ AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which) void TypeInEvent (char firstChar) { - if ((gameMode == BeginningOfGame && !appData.icsActive) || + if ((gameMode == BeginningOfGame && !appData.icsActive) || gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || - gameMode == AnalyzeMode || gameMode == EditGame || + gameMode == AnalyzeMode || gameMode == EditGame || gameMode == EditPosition || gameMode == IcsExamining || gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes @@ -15851,16 +16345,16 @@ TypeInDoneEvent (char *move) return; } - if (gameMode != EditGame && currentMove != forwardMostMove && + if (gameMode != EditGame && currentMove != forwardMostMove && gameMode != Training) { DisplayMoveError(_("Displayed move is not current")); } else { - int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar); if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized - if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, + if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { - UserMoveEvent(fromX, fromY, toX, toY, promoChar); + UserMoveEvent(fromX, fromY, toX, toY, promoChar); } else { DisplayMoveError(_("Could not parse move")); } @@ -16535,7 +17029,7 @@ PGNDate () char * -PositionToFEN (int move, char *overrideCastling) +PositionToFEN (int move, char *overrideCastling, int moveCounts) { int i, j, fromX, fromY, toX, toY; int whiteToPlay; @@ -16634,14 +17128,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 */ @@ -16649,7 +17159,8 @@ PositionToFEN (int move, char *overrideCastling) } if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && - gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) { + gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && + gameInfo.variant != VariantMakruk && gameInfo.variant != VariantASEAN ) { /* En passant target square */ if (move > backwardMostMove) { fromX = moveList[move - 1][0] - AAA; @@ -16681,9 +17192,10 @@ PositionToFEN (int move, char *overrideCastling) } } - /* [HGM] find reversible plies */ + if(moveCounts) { int i = 0, j=move; + /* [HGM] find reversible plies */ if (appData.debugMode) { int k; fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove); for(k=backwardMostMove; k<=forwardMostMove; k++) @@ -16695,9 +17207,10 @@ PositionToFEN (int move, char *overrideCastling) if( j == backwardMostMove ) i += initialRulePlies; sprintf(p, "%d ", i); p += i>=100 ? 4 : i >= 10 ? 3 : 2; - } - /* Fullmove number */ - sprintf(p, "%d", (move / 2) + 1); + + /* Fullmove number */ + sprintf(p, "%d", (move / 2) + 1); + } else *--p = NULLCHAR; return StrSave(buf); } @@ -16707,7 +17220,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; @@ -16836,17 +17349,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