X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=8554b5696b0b6362bd14f8a23257c79adaa0fa5a;hb=30ded7c1180da9dc3dc703d92f4c0617ea092647;hp=7e8ed598b3db54170608a586a766028379be6810;hpb=40eab476400a6232e304c760c8d8559fbe697c46;p=xboard.git diff --git a/backend.c b/backend.c index 7e8ed59..8554b56 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 @@ -223,6 +224,8 @@ 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(); @@ -326,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; @@ -385,6 +388,7 @@ PosFlags (index) case VariantShatranj: case VariantCourier: case VariantMakruk: + case VariantASEAN: case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; @@ -462,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; @@ -554,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] = { @@ -778,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; @@ -804,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]; @@ -837,6 +850,8 @@ InitEngine (ChessProgramState *cps, int n) ChessProgramState *savCps; +GameMode oldMode; + void LoadEngine () { @@ -846,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; @@ -874,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 " @@ -946,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 ? "\"" : "", @@ -1147,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 */ @@ -1283,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; } @@ -1335,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 @@ -1844,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) { @@ -1852,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); @@ -2173,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; @@ -2513,9 +2549,7 @@ PlotSeekAd (int i) void PlotSingleSeekAd (int i) { - DrawSeekOpen(); PlotSeekAd(i); - DrawSeekClose(); } void @@ -2602,14 +2636,13 @@ DrawSeekGraph () h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap; w = BOARD_WIDTH * (squareSize + lineGap) + lineGap; - DrawSeekOpen(); DrawSeekBackground(0, 0, w, h); DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin); DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5); 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); @@ -5059,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)); @@ -5400,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; } @@ -5465,6 +5507,8 @@ 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, int pane) { @@ -5496,6 +5540,7 @@ LoadMultiPV (int x, int y, char *buf, int index, int *start, int *end, int pane) } ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; + extendGame = (gameMode == AnalyzeMode && appData.autoExtend); return TRUE; } @@ -5505,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 @@ -5865,6 +5912,12 @@ InitPosition (int redraw) startedFromSetupPosition = TRUE; SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk"); break; + case VariantASEAN: + pieces = aseanArray; + nrCastlingRights = 0; + startedFromSetupPosition = TRUE; + SetCharTable(pieceToChar, "PN.R.Q....BKpn.r.q....bk"); + break; case VariantTwoKings: pieces = twoKingsArray; break; @@ -5993,7 +6046,7 @@ InitPosition (int redraw) pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */ if(pawnRow < 1) pawnRow = 1; - if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2; + if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN || gameInfo.variant == VariantGrand) pawnRow = 2; /* User pieceToChar list overrules defaults */ if(appData.pieceToCharTable != NULL) @@ -6064,7 +6117,7 @@ InitPosition (int redraw) initialPosition[2][0] = BlackAngel; initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall; initialPosition[5][BOARD_WIDTH-1] = WhiteAngel; - initialPosition[1][1] = initialPosition[2][1] = + initialPosition[1][1] = initialPosition[2][1] = initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1; } if (appData.debugMode) { @@ -6285,7 +6338,8 @@ ChessSquare DefaultPromoChoice (int white) { ChessSquare result; - if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) result = WhiteFerz; // no choice else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) result= WhiteKing; // in Suicide Q is the last thing we want @@ -6370,7 +6424,8 @@ HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, i } // we either have a choice what to promote to, or (in Shogi) whether to promote - if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) { + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) { *promoChoice = PieceToChar(BlackFerz); // no choice return FALSE; } @@ -6519,7 +6574,7 @@ OKToStartUserMove (int x, int y) } Boolean -OnlyMove (int *x, int *y, Boolean captures) +OnlyMove (int *x, int *y, Boolean captures) { DisambiguateClosure cl; if (appData.zippyPlay || !appData.testLegality) return FALSE; @@ -6732,12 +6787,12 @@ UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) { if( pup != EmptySquare ) return; moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop; - if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", + if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]); // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings - while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; + while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; fromY = DROP_RANK; } @@ -6755,7 +6810,7 @@ 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(doubleClick && gameMode == AnalyzeMode) { // [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 @@ -7016,7 +7071,7 @@ CanPromote (ChessSquare piece, int y) if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || - gameInfo.variant == VariantMakruk) return FALSE; + gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) return FALSE; return (piece == BlackPawn && y == 1 || piece == WhitePawn && y == BOARD_HEIGHT-2 || piece == BlackLance && y == 1 || @@ -7112,7 +7167,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) return; } doubleClick = FALSE; - if(gameMode == AnalyzeMode && pausing && first.excludeMoves) { // use pause state to exclude moves + if(gameMode == AnalyzeMode && (pausing || controlKey) && first.excludeMoves) { // use pause state to exclude moves doubleClick = TRUE; gatingPiece = boards[currentMove][y][x]; } fromX = x; fromY = y; toX = toY = -1; @@ -7194,7 +7249,7 @@ LeftClick (ClickType clickType, int xPix, int yPix) } } if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on - second = FALSE; + second = FALSE; } // ignore clicks on holdings if(x < BOARD_LEFT || x >= BOARD_RGHT) return; @@ -7596,6 +7651,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]; @@ -7648,7 +7706,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; @@ -7732,13 +7790,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; } @@ -7803,10 +7869,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])) { @@ -7832,7 +7900,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) { @@ -7850,6 +7918,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); @@ -7998,7 +8072,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 @@ -8023,6 +8097,8 @@ DeferredBookMove (void) } static int savedWhitePlayer, savedBlackPlayer, pairingReceived; +static ChessProgramState *stalledEngine; +static char stashedInputMove[MSG_SIZ]; void HandleMachineMove (char *message, ChessProgramState *cps) @@ -8088,6 +8164,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) { @@ -8158,7 +8250,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) { @@ -8355,12 +8447,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; @@ -8423,7 +8517,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)) { @@ -9411,11 +9505,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 @@ -9472,11 +9566,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 @@ -9733,12 +9827,12 @@ ShowMove (int fromX, int fromY, int toX, int toY) if (instant) return; DisplayMove(currentMove - 1); - DrawPosition(FALSE, boards[currentMove]); if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { if (appData.highlightLastMove) { // [HGM] moved to after DrawPosition, as with arrow it could redraw old board SetHighlights(fromX, fromY, toX, toY); } } + DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); } @@ -9777,11 +9871,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; @@ -9813,29 +9934,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 */ @@ -9900,6 +9999,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]; @@ -9939,9 +10065,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); } @@ -9962,7 +10091,6 @@ TwoMachinesEventIfReady P((void)) return; } DisplayMessage("", ""); curMess = 0; - ThawUI(); TwoMachinesEvent(); } @@ -10022,7 +10150,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 { @@ -10251,6 +10387,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; @@ -10316,7 +10459,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; } @@ -10325,7 +10468,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"); @@ -10365,7 +10508,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; @@ -10377,18 +10520,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 @@ -10489,6 +10632,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 @@ -10581,6 +10726,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 @@ -10625,13 +10774,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 } } @@ -10748,6 +10901,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; } @@ -10773,11 +10927,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: @@ -10802,7 +10957,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 @@ -10901,8 +11056,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 { @@ -11047,17 +11202,23 @@ 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) { if(appData.loadGameIndex == -1) { - GameEnds(EndOfFile, NULL, GE_FILE); + GameEnds(gameInfo.result, gameInfo.resultDetails ? gameInfo.resultDetails : "", GE_FILE); ScheduleDelayedEvent(AnalyzeNextGame, 10); } else { ExitAnalyzeMode(); SendToProgram("force\n", &first); @@ -11737,12 +11898,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; @@ -11764,6 +11925,7 @@ InitSearch () } GameInfo dummyInfo; +static int creatingBook; int GameContainsPosition (FILE *f, ListGame *lg) @@ -11906,7 +12068,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) gn = 1; } else { - if(gameMode == AnalyzeFile && appData.loadGameIndex == -1) + 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); @@ -12227,6 +12389,7 @@ LoadGame (FILE *f, int gameNumber, char *title, int useList) cm = (ChessMove) Myylex(); } + if(!creatingBook) { if (first.pr == NoProc) { StartChessProgram(&first); } @@ -12239,6 +12402,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; @@ -12300,14 +12464,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 - keepInfo = 1; AnalyzeFileEvent(); - keepInfo = 0; + } else + if (oldGameMode == AnalyzeMode) { + AnalyzeFileEvent(); } + if(creatingBook) return TRUE; if (!matchMode && pos > 0) { ToNrEvent(pos); // [HGM] no autoplay if selected on position } else @@ -13342,6 +13507,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) @@ -13349,8 +13528,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(); @@ -13362,7 +13557,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; @@ -13393,12 +13588,29 @@ 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(); @@ -13449,18 +13661,59 @@ ToggleSecond () } } +/* Toggle ShowThinking */ void +ToggleShowThinking() +{ + appData.showThinking = !appData.showThinking; + ShowThinkingEvent(); +} + +int AnalyzeModeEvent () { - if (gameMode == AnalyzeMode) { ToggleSecond(); return; } + 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; @@ -13476,6 +13729,7 @@ AnalyzeModeEvent () StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + return 1; } void @@ -13484,9 +13738,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; @@ -13497,12 +13761,12 @@ AnalyzeFileEvent () gameMode = AnalyzeFile; pausing = FALSE; ModeHighlight(); - SetGameInfo(); StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay)); + AnalysisPeriodicEvent(1); } void @@ -13672,7 +13936,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, @@ -13711,9 +13975,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); @@ -13766,16 +14031,19 @@ TwoMachinesEvent P((void)) // forwardMostMove = currentMove; TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... + startingEngine = TRUE; if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */ - if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features + if(!first.initDone && GetDelayedEvent() == TwoMachinesEventIfReady) return; // [HGM] engine #1 still waiting for feature timeout if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features if(second.protocolVersion >= 2 && !strstr(second.variants, VariantName(gameInfo.variant))) { + startingEngine = FALSE; DisplayError("second engine does not play this", 0); return; } @@ -13809,7 +14077,7 @@ TwoMachinesEvent P((void)) } gameMode = TwoMachinesPlay; - pausing = FALSE; + pausing = startingEngine = FALSE; ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); @@ -14258,6 +14526,7 @@ EditPositionMenuEvent (ChessSquare selection, int x, int y) if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantASEAN || gameInfo.variant == VariantMakruk ) selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz); goto defaultlabel; @@ -14747,7 +15016,7 @@ BackwardInner (int target) for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break; } - SendBoard(&first, i); + SendBoard(&first, i); if(second.analyzing) SendBoard(&second, 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; @@ -15193,7 +15496,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 @@ -15248,7 +15551,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); @@ -15315,8 +15619,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; @@ -15346,6 +15653,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; @@ -15369,7 +15677,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 @@ -15506,7 +15814,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); @@ -15680,14 +15988,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); @@ -15800,6 +16109,7 @@ FeatureDone (ChessProgramState *cps, int val) ScheduleDelayedEvent(cb, val ? 1 : 3600000); } cps->initDone = val; + if(val) cps->reload = FALSE; } /* Parse feature command from engine */ @@ -15807,7 +16117,7 @@ void ParseFeatures (char *args, ChessProgramState *cps) { char *p = args; - char *q; + char *q = NULL; int val; char buf[MSG_SIZ]; @@ -15827,7 +16137,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 { @@ -15835,7 +16145,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; @@ -15844,7 +16154,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; @@ -15860,11 +16170,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); @@ -15988,9 +16298,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 @@ -16026,16 +16336,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")); } @@ -16840,7 +17150,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; @@ -17124,7 +17435,8 @@ ParseFEN (Board board, int *blackPlaysFirst, char *fen) /* read e.p. field in games that know e.p. capture */ 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 ) { if(*p=='-') { p++; board[EP_STATUS] = EP_NONE; } else { @@ -17435,3 +17747,59 @@ LoadVariation (int index, char *text) ToNrEvent(currentMove+1); } +void +LoadTheme () +{ + char *p, *q, buf[MSG_SIZ]; + if(engineLine && engineLine[0]) { // a theme was selected from the listbox + snprintf(buf, MSG_SIZ, "-theme %s", engineLine); + ParseArgsFromString(buf); + ActivateTheme(TRUE); // also redo colors + return; + } + p = nickName; + if(*p && !strchr(p, '"')) // theme name specified and well-formed; add settings to theme list + { + int len; + q = appData.themeNames; + snprintf(buf, MSG_SIZ, "\"%s\"", nickName); + if(appData.useBitmaps) { + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt true -lbtf \"%s\" -dbtf \"%s\" -lbtm %d -dbtm %d", + appData.liteBackTextureFile, appData.darkBackTextureFile, + appData.liteBackTextureMode, + appData.darkBackTextureMode ); + } else { + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ubt false -lsc %s -dsc %s", + Col2Text(2), // lightSquareColor + Col2Text(3) ); // darkSquareColor + } + if(appData.useBorder) { + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub true -border \"%s\"", + appData.border); + } else { + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -ub false"); + } + if(appData.useFont) { + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf true -pf \"%s\" -fptc \"%s\" -fpfcw %s -fpbcb %s", + appData.renderPiecesWithFont, + appData.fontToPieceTable, + Col2Text(9), // appData.fontBackColorWhite + Col2Text(10) ); // appData.fontForeColorBlack + } else { + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -upf false -pid \"%s\"", + appData.pieceDirectory); + if(!appData.pieceDirectory[0]) + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -wpc %s -bpc %s", + Col2Text(0), // whitePieceColor + Col2Text(1) ); // blackPieceColor + } + snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), " -hsc %s -phc %s\n", + Col2Text(4), // highlightSquareColor + Col2Text(5) ); // premoveHighlightColor + appData.themeNames = malloc(len = strlen(q) + strlen(buf) + 1); + if(insert != q) insert[-1] = NULLCHAR; + snprintf(appData.themeNames, len, "%s\n%s%s", q, buf, insert); + if(q) free(q); + } + ActivateTheme(FALSE); +}