X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=8837d9750198522425ef72a0c80f647c42a25d03;hb=da8802ac4d06115296e0f8ba955ecf5570741d08;hp=29250886a8e0dc50be0a601eab49d6c667f74ed4;hpb=0300f988935df94f3faeac3e3dbddbd6e8122fe3;p=xboard.git diff --git a/backend.c b/backend.c index 2925088..fac9abd 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 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)); @@ -222,6 +222,10 @@ int NextTourneyGame P((int nr, int *swap)); int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync)); FILE *WriteTourneyFile P((char *results, FILE *f)); void DisplayTwoMachinesTitle P(()); +static void ExcludeClick P((int index)); +void ToggleSecond P((void)); +void PauseEngine P((ChessProgramState *cps)); +static int NonStandardBoardSize P((void)); #ifdef WIN32 extern void ConsoleCreate(); @@ -265,10 +269,12 @@ char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ extern int chatCount; int chattingPartner; char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */ +char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */ 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 @@ -312,7 +318,7 @@ int promoDefaultAltered; #define TN_PORT 23 char* -safeStrCpy( char *dst, const char *src, size_t count ) +safeStrCpy (char *dst, const char *src, size_t count) { // [HGM] made safe int i; assert( dst != NULL ); @@ -324,7 +330,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; @@ -340,7 +346,7 @@ safeStrCpy( char *dst, const char *src, size_t count ) * We used this for all compiler */ double -u64ToDouble(u64 value) +u64ToDouble (u64 value) { double r; u64 tmp = value & u64Const(0x7fffffffffffffff); @@ -358,7 +364,7 @@ u64ToDouble(u64 value) by this function. */ int -PosFlags(index) +PosFlags (index) { int flags = F_ALL_CASTLE_OK; if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE; @@ -383,6 +389,7 @@ PosFlags(index) case VariantShatranj: case VariantCourier: case VariantMakruk: + case VariantASEAN: case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; @@ -392,7 +399,8 @@ PosFlags(index) return flags; } -FILE *gameFileFP, *debugFP; +FILE *gameFileFP, *debugFP, *serverFP; +char *currentDebugFile; // [HGM] debug split: to remember name /* [AS] Note: sometimes, the sscanf() function is used to parse the input @@ -420,7 +428,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; @@ -448,18 +456,18 @@ int adjudicateLossPlies = 6; char white_holding[64], black_holding[64]; TimeMark lastNodeCountTime; long lastNodeCount=0; -int shiftKey; // [HGM] set by mouse handler +int shiftKey, controlKey; // [HGM] set by mouse handler int have_sent_ICS_logon = 0; int movesPerSession; int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */ -long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack; +long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime; Boolean adjustedClock; long timeControl_2; /* [AS] Allow separate time controls */ -char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */ +char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; int matchGame = 0, nextGame = 0, roundNr = 0; -Boolean waitingForGame = FALSE; +Boolean waitingForGame = FALSE, startingEngine = FALSE; TimeMark programStartTime, pauseStart; char ics_handle[MSG_SIZ]; int have_set_title = 0; @@ -551,6 +559,20 @@ 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 } +}; + +ChessSquare lionArray[2][BOARD_FILES] = { + { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen, + WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackRook, BlackLion, BlackBishop, BlackQueen, + BlackKing, BlackBishop, BlackKnight, BlackRook } +}; + #if (BOARD_FILES>=10) ChessSquare ShogiArray[2][BOARD_FILES] = { @@ -631,8 +653,23 @@ ChessSquare CourierArray[2][BOARD_FILES] = { { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing, BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook } }; +ChessSquare ChuArray[6][BOARD_FILES] = { + { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing, + WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance }, + { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil, + BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance }, + { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall, + WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon }, + { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel, + BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon }, + { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion, + WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon }, + { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen, + BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon } +}; #else // !(BOARD_FILES>=12) #define CourierArray CapablancaArray +#define ChuArray CapablancaArray #endif // !(BOARD_FILES>=12) @@ -643,8 +680,7 @@ Board initialPosition; "++++", etc. Also strips ()'s */ int -string_to_rating(str) - char *str; +string_to_rating (char *str) { while(*str && !isdigit(*str)) ++str; if (!*str) @@ -654,7 +690,7 @@ string_to_rating(str) } void -ClearProgramStats() +ClearProgramStats () { /* Init programStats */ programStats.movelist[0] = 0; @@ -670,7 +706,7 @@ ClearProgramStats() } void -CommonEngineInit() +CommonEngineInit () { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings if (appData.firstPlaysBlack) { first.twoMachinesColor = "black\n"; @@ -704,7 +740,7 @@ CommonEngineInit() } void -UnloadEngine(ChessProgramState *cps) +UnloadEngine (ChessProgramState *cps) { /* Kill off first chess program */ if (cps->isr != NULL) @@ -723,7 +759,7 @@ UnloadEngine(ChessProgramState *cps) } void -ClearOptions(ChessProgramState *cps) +ClearOptions (ChessProgramState *cps) { int i; cps->nrOptions = cps->comboCnt = 0; @@ -734,12 +770,16 @@ ClearOptions(ChessProgramState *cps) } char *engineNames[] = { -"first", -"second" + /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings + such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */ +N_("first"), + /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings + such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */ +N_("second") }; void -InitEngine(ChessProgramState *cps, int n) +InitEngine (ChessProgramState *cps, int n) { // [HGM] all engine initialiation put in a function that does one engine ClearOptions(cps); @@ -772,12 +812,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; @@ -798,7 +840,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]; @@ -806,6 +848,7 @@ InitEngine(ChessProgramState *cps, int n) cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */ cps->isUCI = appData.isUCI[n]; /* [AS] */ cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */ + cps->highlight = 0; if (appData.protocolVersion[n] > PROTOVER || appData.protocolVersion[n] < 1) @@ -831,8 +874,10 @@ InitEngine(ChessProgramState *cps, int n) ChessProgramState *savCps; +GameMode oldMode; + void -LoadEngine() +LoadEngine () { int i; if(WaitForEngine(savCps, LoadEngine)) return; @@ -840,21 +885,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 < forwardMostMove; i++) SendMoveToProgram(i, savCps); + for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps); ThawUI(); SetGNUMode(); + if(oldMode == AnalyzeMode) AnalyzeModeEvent(); } void -ReplaceEngine(ChessProgramState *cps, int n) +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; @@ -868,40 +917,68 @@ 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 "\" " - "-firstOptions \"\" -firstNPS -1 -fn \"\""; + "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 " + "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false"; + +void +FloatToFront(char **list, char *engineLine) +{ + char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf; + int i=0; + if(appData.recentEngines <= 0) return; + TidyProgramName(engineLine, "localhost", tidy+1); + tidy[0] = buf[0] = '\n'; strcat(tidy, "\n"); + strncpy(buf+1, *list, MSG_SIZ-50); + if(p = strstr(buf, tidy)) { // tidy name appears in list + q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch + while(*p++ = *++q); // squeeze out + } + strcat(tidy, buf+1); // put list behind tidy name + p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list + if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last + ASSIGN(*list, tidy+1); +} + +char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine void -Load(ChessProgramState *cps, int i) +Load (ChessProgramState *cps, int i) { - char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ]; + char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar; if(engineLine && engineLine[0]) { // an engine was selected from the combo box snprintf(buf, MSG_SIZ, "-fcp %s", engineLine); SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second* - ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE; + ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE; + FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL; + appData.firstProtocolVersion = PROTOVER; ParseArgsFromString(buf); SwapEngines(i); ReplaceEngine(cps, i); + FloatToFront(&appData.recentEngineList, engineLine); return; } p = engineName; while(q = strchr(p, SLASH)) p = q+1; if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; } - if(engineDir[0] != NULLCHAR) - appData.directory[i] = engineDir; - else if(p != engineName) { // derive directory from engine path, when not given + if(engineDir[0] != NULLCHAR) { + ASSIGN(appData.directory[i], engineDir); p = engineName; + } else if(p != engineName) { // derive directory from engine path, when not given p[-1] = 0; - appData.directory[i] = strdup(engineName); + ASSIGN(appData.directory[i], engineName); p[-1] = SLASH; - } else appData.directory[i] = "."; + if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split! + } else { ASSIGN(appData.directory[i], "."); } + jar = (strstr(p, ".jar") == p + strlen(p) - 4); if(params[0]) { if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces snprintf(command, MSG_SIZ, "%s %s", p, params); p = command; } - appData.chessProgram[i] = strdup(p); + if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; } + ASSIGN(appData.chessProgram[i], p); appData.isUCI[i] = isUCI; appData.protocolVersion[i] = v1 ? 1 : PROTOVER; appData.hasOwnBookUCI[i] = hasBook; @@ -914,7 +991,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 ? "\"" : "", @@ -923,15 +1000,18 @@ Load(ChessProgramState *cps, int i) isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "", storeVariant ? " -variant " : "", storeVariant ? VariantName(gameInfo.variant) : ""); + if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions); firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1); - snprintf(firstChessProgramNames, len, "%s%s", q, buf); + if(insert != q) insert[-1] = NULLCHAR; + snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert); if(q) free(q); + FloatToFront(&appData.recentEngineList, buf); } ReplaceEngine(cps, i); } void -InitTimeControls() +InitTimeControls () { int matched, min, sec; /* @@ -962,7 +1042,7 @@ InitTimeControls() } void -InitBackEnd1() +InitBackEnd1 () { ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options @@ -1092,6 +1172,7 @@ InitBackEnd1() case VariantCapablanca: /* [HGM] should work */ case VariantCourier: /* [HGM] initial forced moves not implemented */ case VariantShogi: /* [HGM] could still mate with pawn drop */ + case VariantChu: /* [HGM] experimental */ case VariantKnightmate: /* [HGM] should work */ case VariantCylinder: /* [HGM] untested */ case VariantFalcon: /* [HGM] untested */ @@ -1112,6 +1193,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 */ @@ -1120,13 +1202,15 @@ InitBackEnd1() case VariantSChess: /* S-Chess, should work */ case VariantGrand: /* should work */ case VariantSpartan: /* should work */ + case VariantLion: /* should work */ break; } } } -int NextIntegerFromString( char ** str, long * value ) +int +NextIntegerFromString (char ** str, long * value) { int result = -1; char * s = *str; @@ -1151,7 +1235,8 @@ int NextIntegerFromString( char ** str, long * value ) return result; } -int NextTimeControlFromString( char ** str, long * value ) +int +NextTimeControlFromString (char ** str, long * value) { long temp; int result = NextIntegerFromString( str, &temp ); @@ -1168,7 +1253,8 @@ int NextTimeControlFromString( char ** str, long * value ) return result; } -int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType) +int +NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType) { /* [HGM] routine added to read '+moves/time' for secondary time control. */ int result = -1, type = 0; long temp, temp2; @@ -1209,18 +1295,17 @@ int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *i return result; } -int GetTimeQuota(int movenr, int lastUsed, char *tcString) +int +GetTimeQuota (int movenr, int lastUsed, char *tcString) { /* [HGM] get time to add from the multi-session time-control string */ int incType, moves=1; /* kludge to force reading of first session */ long time, increment; char *s = tcString; - if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version - if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString); + 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; - if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment); if(movenr == -1) return time; /* last move before new session */ if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking if(incType == '!' && lastUsed < increment) increment = lastUsed; @@ -1232,10 +1317,7 @@ int GetTimeQuota(int movenr, int lastUsed, char *tcString) } int -ParseTimeControl(tc, ti, mps) - char *tc; - float ti; - int mps; +ParseTimeControl (char *tc, float ti, int mps) { long tc1; long tc2; @@ -1249,16 +1331,16 @@ ParseTimeControl(tc, ti, 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; } @@ -1298,11 +1380,16 @@ ParseTimeControl(tc, ti, mps) } void -InitBackEnd2() +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 set_cont_sequence(appData.wrapContSeq); if (appData.matchGames > 0) { @@ -1328,7 +1415,7 @@ InitBackEnd2() } int -CalculateIndex(int index, int gameNr) +CalculateIndex (int index, int gameNr) { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account) int res; if(index > 0) return index; // fixed nmber @@ -1339,7 +1426,7 @@ CalculateIndex(int index, int gameNr) } int -LoadGameOrPosition(int gameNr) +LoadGameOrPosition (int gameNr) { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it) if (*appData.loadGameFile != NULLCHAR) { if (!LoadGameFromFile(appData.loadGameFile, @@ -1360,7 +1447,7 @@ LoadGameOrPosition(int gameNr) } void -ReserveGame(int gameNr, char resChar) +ReserveGame (int gameNr, char resChar) { FILE *tf = fopen(appData.tourneyFile, "r+"); char *p, *q, c, buf[MSG_SIZ]; @@ -1396,13 +1483,17 @@ ReserveGame(int gameNr, char resChar) free(p); appData.results = q; if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch && (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) { + int round = appData.defaultMatchGames * appData.tourneyType; + if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine + appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round UnloadEngine(&first); // next game belongs to other pairing; UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones. } + if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr); } void -MatchEvent(int mode) +MatchEvent (int mode) { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu int dummy; if(matchMode) { // already in match mode: switch it off @@ -1431,7 +1522,7 @@ MatchEvent(int mode) NextTourneyGame(-1, &dummy); ReserveGame(-1, 0); if(nextGame <= appData.matchGames) { - DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec")); + DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec.")); matchMode = mode; ScheduleDelayedEvent(NextMatchGame, 10000); return; @@ -1455,6 +1546,8 @@ MatchEvent(int mode) NextMatchGame(); } +char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line + void InitBackEnd3 P((void)) { @@ -1468,6 +1561,7 @@ InitBackEnd3 P((void)) free(programVersion); programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy)); sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); + FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram); } if (appData.icsActive) { @@ -1655,7 +1749,7 @@ InitBackEnd3 P((void)) } void -HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current ) +HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current) { DisplayBook(current+1); @@ -1673,7 +1767,7 @@ HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current ) * Returns 0 if okay, error code if not. */ int -establish() +establish () { char buf[MSG_SIZ]; @@ -1714,7 +1808,8 @@ establish() } } -void EscapeExpand(char *p, char *q) +void +EscapeExpand (char *p, char *q) { // [HGM] initstring: routine to shape up string arguments while(*p++ = *q++) if(p[-1] == '\\') switch(*q++) { @@ -1728,10 +1823,7 @@ void EscapeExpand(char *p, char *q) } void -show_bytes(fp, buf, count) - FILE *fp; - char *buf; - int count; +show_bytes (FILE *fp, char *buf, int count) { while (count--) { if (*buf < 040 || *(unsigned char *) buf > 0177) { @@ -1746,11 +1838,7 @@ show_bytes(fp, buf, count) /* Returns an errno value */ int -OutputMaybeTelnet(pr, message, count, outError) - ProcRef pr; - char *message; - int count; - int *outError; +OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError) { char buf[8192], *p, *q, *buflim; int left, newcount, outcount; @@ -1804,15 +1892,11 @@ OutputMaybeTelnet(pr, message, count, outError) } void -read_from_player(isr, closure, message, count, error) - InputSourceRef isr; - VOIDSTAR closure; - char *message; - int count; - int error; +read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error) { int outError, outCount; static int gotEof = 0; + static FILE *ini; /* Pass data read from player on to ICS */ if (count > 0) { @@ -1821,6 +1905,17 @@ read_from_player(isr, closure, message, count, error) 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); @@ -1831,7 +1926,7 @@ read_from_player(isr, closure, message, count, error) } void -KeepAlive() +KeepAlive () { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1); connectionAlive = FALSE; // only sticks if no response to 'date' command. @@ -1840,7 +1935,8 @@ KeepAlive() } /* added routine for printf style output to ics */ -void ics_printf(char *format, ...) +void +ics_printf (char *format, ...) { char buffer[MSG_SIZ]; va_list args; @@ -1853,8 +1949,7 @@ void ics_printf(char *format, ...) } void -SendToICS(s) - char *s; +SendToICS (char *s) { int count, outCount, outError; @@ -1871,9 +1966,7 @@ SendToICS(s) without a delay causes problems when using timestamp on ICC (at least on my machine). */ void -SendToICSDelayed(s,msdelay) - char *s; - long msdelay; +SendToICSDelayed (char *s, long msdelay) { int count, outCount, outError; @@ -1897,8 +1990,7 @@ SendToICSDelayed(s,msdelay) Also deletes any suffix starting with '(' */ char * -StripHighlightAndTitle(s) - char *s; +StripHighlightAndTitle (char *s) { static char retbuf[MSG_SIZ]; char *p = retbuf; @@ -1922,8 +2014,7 @@ StripHighlightAndTitle(s) /* Remove all highlighting escape sequences in s */ char * -StripHighlight(s) - char *s; +StripHighlight (char *s) { static char retbuf[MSG_SIZ]; char *p = retbuf; @@ -1941,11 +2032,12 @@ StripHighlight(s) return retbuf; } +char engineVariant[MSG_SIZ]; char *variantNames[] = VARIANT_NAMES; char * -VariantName(v) - VariantClass v; +VariantName (VariantClass v) { + if(v == VariantUnknown || *engineVariant) return engineVariant; return variantNames[v]; } @@ -1953,8 +2045,7 @@ VariantName(v) /* Identify a variant from the strings the chess servers use or the PGN Variant tag names we use. */ VariantClass -StringToVariant(e) - char *e; +StringToVariant (char *e) { char *p; int wnum = -1; @@ -2148,7 +2239,7 @@ StringToVariant(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; @@ -2165,10 +2256,7 @@ char star_match[STAR_MATCH_N][MSG_SIZ]; copied into star_match. */ int -looking_at(buf, index, pattern) - char *buf; - int *index; - char *pattern; +looking_at ( char *buf, int *index, char *pattern) { char *bufp = &buf[*index], *patternp = pattern; int star_count = 0; @@ -2206,9 +2294,7 @@ looking_at(buf, index, pattern) } void -SendToPlayer(data, length) - char *data; - int length; +SendToPlayer (char *data, int length) { int error, outCount; outCount = OutputToProcess(NoProc, data, length, &error); @@ -2218,9 +2304,7 @@ SendToPlayer(data, length) } void -PackHolding(packed, holding) - char packed[]; - char *holding; +PackHolding (char packed[], char *holding) { char *p = holding; char *q = packed; @@ -2255,8 +2339,7 @@ PackHolding(packed, holding) /* Telnet protocol requests from the front end */ void -TelnetRequest(ddww, option) - unsigned char ddww, option; +TelnetRequest (unsigned char ddww, unsigned char option) { unsigned char msg[3]; int outCount, outError; @@ -2304,21 +2387,21 @@ TelnetRequest(ddww, option) } void -DoEcho() +DoEcho () { if (!appData.icsActive) return; TelnetRequest(TN_DO, TN_ECHO); } void -DontEcho() +DontEcho () { if (!appData.icsActive) return; TelnetRequest(TN_DONT, TN_ECHO); } void -CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece) +CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece) { /* put the holdings sent to us by the server on the board holdings area */ int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn; @@ -2360,7 +2443,7 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece) void -VariantSwitch(Board board, VariantClass newVariant) +VariantSwitch (Board board, VariantClass newVariant) { int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j; static Board oldBoard; @@ -2430,6 +2513,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; @@ -2471,14 +2555,14 @@ int hMargin = 10, vMargin = 20, h, w; extern int squareSize, lineGap; void -PlotSeekAd(int i) +PlotSeekAd (int i) { int x, y, color = 0, r = ratingList[i]; float tc = tcList[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; @@ -2493,7 +2577,13 @@ PlotSeekAd(int i) } void -AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot) +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 = ""; VariantClass v = StringToVariant(type); @@ -2512,12 +2602,12 @@ AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, in seekNrList[nrOfSeekAds] = nr; zList[nrOfSeekAds] = 0; seekAdList[nrOfSeekAds++] = StrSave(buf); - if(plot) PlotSeekAd(nrOfSeekAds-1); + if(plot) PlotSingleSeekAd(nrOfSeekAds-1); } } void -EraseSeekDot(int i) +EraseSeekDot (int i) { int x = xList[i], y = yList[i], d=squareSize/4, k; DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1); @@ -2531,7 +2621,7 @@ EraseSeekDot(int i) } void -RemoveSeekAd(int nr) +RemoveSeekAd (int nr) { int i; for(i=0; i=minRating && i *\n")) continue; + // [HGM] chat: intercept tells by users for which we have an open chat window channel = -1; if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || @@ -3403,9 +3505,15 @@ read_from_ics(isr, closure, data, count, error) continue; } - if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) { - ICSInitScript(); - have_sent_ICS_logon = 1; + if (looking_at(buf, &i, "login:")) { + if (!have_sent_ICS_logon) { + if(ICSInitScript()) + have_sent_ICS_logon = 1; + else // no init script was found + have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password + } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it + have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script + } continue; } @@ -3492,7 +3600,7 @@ read_from_ics(isr, closure, data, count, error) gameInfo.whiteRating = string_to_rating(star_match[1]); gameInfo.blackRating = string_to_rating(star_match[3]); if (appData.debugMode) - fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), + fprintf(debugFP, "Ratings from header: W %d, B %d\n", gameInfo.whiteRating, gameInfo.blackRating); } continue; @@ -3551,7 +3659,7 @@ read_from_ics(isr, closure, data, count, error) if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book - if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line + if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line soughtPending = FALSE; seekGraphUp = TRUE; DrawSeekGraph(); @@ -3628,8 +3736,8 @@ read_from_ics(isr, closure, data, count, error) flipView = appData.flipView; DrawPosition(TRUE, boards[currentMove]); DisplayBothClocks(); - snprintf(str, MSG_SIZ, _("%s vs. %s"), - gameInfo.white, gameInfo.black); + snprintf(str, MSG_SIZ, "%s %s %s", + gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(str); gameMode = IcsIdle; } else { @@ -4046,8 +4154,8 @@ read_from_ics(isr, closure, data, count, error) snprintf(str, MSG_SIZ, "[%s-%s] %s-%s", wh, bh, gameInfo.white, gameInfo.black); } else { - snprintf(str, MSG_SIZ, _("%s [%s] vs. %s [%s]"), - gameInfo.white, white_holding, + snprintf(str, MSG_SIZ, "%s [%s] %s %s [%s]", + gameInfo.white, white_holding, _("vs."), gameInfo.black, black_holding); } if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured... @@ -4123,12 +4231,15 @@ read_from_ics(isr, closure, data, count, error) #define RELATION_STARTING_POSITION -4 /* FICS only */ void -ParseBoard12(string) - char *string; +ParseBoard12 (char *string) { +#if ZIPPY + int i, takeback; + char *bookHit = NULL; // [HGM] book +#endif GameMode newGameMode; - int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i; - int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback; + int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0; + int j, k, n, moveNum, white_stren, black_stren, white_time, black_time; int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count; char to_play, board_chars[200]; char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ]; @@ -4140,7 +4251,6 @@ ParseBoard12(string) int fromX, fromY, toX, toY; char promoChar; int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */ - char *bookHit = NULL; // [HGM] book Boolean weird = FALSE, reqFlag = FALSE; fromX = fromY = toX = toY = -1; @@ -4148,7 +4258,7 @@ ParseBoard12(string) newGame = FALSE; if (appData.debugMode) - fprintf(debugFP, _("Parsing board: %s\n"), string); + fprintf(debugFP, "Parsing board: %s\n", string); move_str[0] = NULLCHAR; elapsed_time[0] = NULLCHAR; @@ -4200,6 +4310,7 @@ ParseBoard12(string) newGameMode = ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ? IcsPlayingWhite : IcsPlayingBlack; + soughtPending =FALSE; // [HGM] seekgraph: solve race condition break; case RELATION_EXAMINING: newGameMode = IcsExamining; @@ -4215,9 +4326,12 @@ ParseBoard12(string) break; } - if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) + if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || + gameMode == IcsObserving && appData.dualBoard) // also allow use of second board for observing two games && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) { // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */ + int fac = strchr(elapsed_time, '.') ? 1 : 1000; + static int lastBgGame = -1; char *toSqr; for (k = 0; k < ranks; k++) { for (j = 0; j < files; j++) @@ -4239,14 +4353,35 @@ ParseBoard12(string) if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); } if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual if(partnerUp) DrawPosition(FALSE, partnerBoard); - if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual - snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000, - (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play); - DisplayMessage(partnerStatus, ""); + if(twoBoards) { + DisplayWhiteClock(white_time*fac, to_play == 'W'); + DisplayBlackClock(black_time*fac, to_play != 'W'); + activePartner = to_play; + if(gamenum != lastBgGame) { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "%s %s %s", white, _("vs."), black); + DisplayTitle(buf); + } + lastBgGame = gamenum; + activePartnerTime = to_play == 'W' ? white_time*fac : black_time*fac; + partnerUp = 0; flipView = !flipView; } // [HGM] dual + snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time*fac/60000, (white_time*fac%60000)/1000, + (black_time*fac/60000), (black_time*fac%60000)/1000, white_stren, black_stren, to_play); + if(!twoBoards) DisplayMessage(partnerStatus, ""); partnerBoardValid = TRUE; return; } + if(appData.dualBoard && appData.bgObserve) { + if((newGameMode == IcsPlayingWhite || newGameMode == IcsPlayingBlack) && moveNum == 1) + SendToICS(ics_prefix), SendToICS("pobserve\n"); + else if(newGameMode == IcsObserving && (gameMode == BeginningOfGame || gameMode == IcsIdle)) { + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "%spobserve %s\n", ics_prefix, white); + SendToICS(buf); + } + } + /* Modify behavior for initial board display on move listing of wild games. */ @@ -4281,6 +4416,7 @@ ParseBoard12(string) } if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files || + move_str[1] == '@' && !gameInfo.holdingsWidth || weird && (int)gameInfo.variant < (int)VariantShogi) { /* [HGM] We seem to have switched variant unexpectedly * Try to guess new variant from board size @@ -4290,7 +4426,8 @@ ParseBoard12(string) if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else if(ranks == 8 && files == 12) newVariant = VariantCourier; else if(ranks == 9 && files == 9) newVariant = VariantShogi; else - if(!weird) newVariant = VariantNormal; + if(ranks == 10 && files == 10) newVariant = VariantGrand; else + if(!weird) newVariant = move_str[1] == '@' ? VariantCrazyhouse : VariantNormal; VariantSwitch(boards[currentMove], newVariant); /* temp guess */ /* Get a move list just to see the header, which will tell us whether this is really bug or zh */ @@ -4499,7 +4636,10 @@ ParseBoard12(string) r = boards[moveNum][CASTLING][5] = initialRights[5]; } /* [HGM] e.p. rights. Assume that ICS sends file number here? */ - boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT; + boards[moveNum][EP_STATUS] = EP_NONE; + if(str[0] == 'P') boards[moveNum][EP_STATUS] = EP_PAWN_MOVE; + if(strchr(move_str, 'x')) boards[moveNum][EP_STATUS] = EP_CAPTURE; + if(double_push != -1) boards[moveNum][EP_STATUS] = double_push + BOARD_LEFT; if (ics_getting_history == H_GOT_REQ_HEADER || @@ -4587,11 +4727,10 @@ ParseBoard12(string) to canonical algebraic form. */ if (moveNum > 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); @@ -4663,7 +4802,7 @@ ParseBoard12(string) strcat(moveList[moveNum - 1], "\n"); if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat - && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board + && gameInfo.variant != VariantGrand&& gameInfo.variant != VariantSChess) // inherit info that ICS does not give from previous board for(k=0; k 0) { @@ -4842,15 +4981,21 @@ GetMoveListEvent() } void -AnalysisPeriodicEvent(force) - int force; +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) && !force) || !appData.periodicUpdates) 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 @@ -4859,16 +5004,14 @@ AnalysisPeriodicEvent(force) programStats.ok_to_send = 0; } -void ics_update_width(new_width) - int new_width; +void +ics_update_width (int new_width) { ics_printf("set width %d\n", new_width); } void -SendMoveToProgram(moveNum, cps) - int moveNum; - ChessProgramState *cps; +SendMoveToProgram (int moveNum, ChessProgramState *cps) { char buf[MSG_SIZ]; @@ -4920,6 +5063,11 @@ SendMoveToProgram(moveNum, cps) if(moveList[moveNum][0]== '@') snprintf(buf, MSG_SIZ, "@@@@\n"); else snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0], moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); + } else if(moveList[moveNum][4] == ';') { // [HGM] lion: move is double-step over intermediate square + snprintf(buf, MSG_SIZ, "%c%d%c%d,%c%d%c%d\n", moveList[moveNum][0], moveList[moveNum][1] - '0', // convert to two moves + moveList[moveNum][5], moveList[moveNum][6] - '0', + moveList[moveNum][5], moveList[moveNum][6] - '0', + moveList[moveNum][2], moveList[moveNum][3] - '0'); } else snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0', moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); @@ -4946,10 +5094,7 @@ SendMoveToProgram(moveNum, cps) } void -SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar) - ChessMove moveType; - int fromX, fromY, toX, toY; - char promoChar; +SendMoveToICS (ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar) { char user_move[MSG_SIZ]; char suffix[4]; @@ -4991,7 +5136,8 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar) 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)); @@ -5026,7 +5172,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar) } void -UploadGameEvent() +UploadGameEvent () { // [HGM] upload: send entire stored game to ICS as long-algebraic moves. int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm static char *castlingStrings[4] = { "none", "kside", "qside", "both" }; @@ -5055,7 +5201,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); @@ -5086,17 +5232,21 @@ UploadGameEvent() for(i = backwardMostMove; i= 0 && killY >= 0) sprintf(move+4, ";%c%c\n", AAA + killX, ONE + killY); } else { sprintf(move, "%c%c%c%c%c\n", AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); @@ -5114,8 +5265,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) } void -ProcessICSInitScript(f) - FILE *f; +ProcessICSInitScript (FILE *f) { char buf[MSG_SIZ]; @@ -5127,10 +5277,11 @@ ProcessICSInitScript(f) } -static int lastX, lastY, selectFlag, dragging; +static int lastX, lastY, lastLeftX, lastLeftY, selectFlag, dragging; +static ClickType lastClickType; void -Sweep(int step) +Sweep (int step) { ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep; if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn; @@ -5146,12 +5297,19 @@ Sweep(int step) else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing; if(!step) step = -1; } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn || - appData.testLegality && (promoSweep == king || - gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep)); + appData.testLegality && (promoSweep == king || promoSweep == WhiteLion || promoSweep == BlackLion) || + IS_SHOGI(gameInfo.variant) && promoSweep != CHUPROMOTED last && last != CHUPROMOTED promoSweep && last != promoSweep); + if(toX >= 0) { + int victim = boards[currentMove][toY][toX]; + boards[currentMove][toY][toX] = promoSweep; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][toY][toX] = victim; + } else ChangeDragPiece(promoSweep); } -int PromoScroll(int x, int y) +int +PromoScroll (int x, int y) { int step = 0; @@ -5167,7 +5325,7 @@ int PromoScroll(int x, int y) } void -NextPiece(int step) +NextPiece (int step) { ChessSquare piece = boards[currentMove][toY][toX]; do { @@ -5182,7 +5340,7 @@ NextPiece(int step) } /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */ void -AlphaRank(char *move, int n) +AlphaRank (char *move, int n) { // char *p = move, c; int x, y; @@ -5234,12 +5392,7 @@ char yy_textstr[8000]; /* Parser for moves from gnuchess, ICS, or user typein box */ Boolean -ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) - char *move; - int moveNum; - ChessMove *moveType; - int *fromX, *fromY, *toX, *toY; - char *promoChar; +ParseOneMove (char *move, int moveNum, ChessMove *moveType, int *fromX, int *fromY, int *toX, int *toY, char *promoChar) { *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr); @@ -5249,6 +5402,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case WhiteKingSideCastle: @@ -5322,14 +5476,15 @@ Boolean pushed = FALSE; char *lastParseAttempt; void -ParsePV(char *pv, Boolean storeComments, Boolean atEnd) +ParsePV (char *pv, Boolean storeComments, Boolean atEnd) { // Parse a string of PV moves, and append to current game, behind forwardMostMove int fromX, fromY, toX, toY; char promoChar; ChessMove moveType; 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; } @@ -5339,9 +5494,6 @@ ParsePV(char *pv, Boolean storeComments, Boolean atEnd) if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses lastParseAttempt = pv; valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar); -if(appData.debugMode){ -fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv); -} if(!valid && nr == 0 && ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){ nr++; moveType = Comment; // First move has been played; kludge to make sure we continue @@ -5388,7 +5540,7 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f } int -MultiPV(ChessProgramState *cps) +MultiPV (ChessProgramState *cps) { // check if engine supports MultiPV, and if so, return the number of the option that sets it int i; for(i=0; inrOptions; i++) @@ -5397,11 +5549,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; @@ -5413,27 +5568,31 @@ 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 + ExcludeClick(origIndex - lineStart); + return FALSE; } ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; + extendGame = (gameMode == AnalyzeMode && appData.autoExtend); return TRUE; } char * -PvToSAN(char *pv) +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 @@ -5482,7 +5644,7 @@ UnLoadPV() } void -MovePV(int x, int y, int h) +MovePV (int x, int y, int h) { // step through PV based on mouse coordinates (called on mouse move) int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15); @@ -5517,7 +5679,8 @@ int squaresLeft[4]; int piecesLeft[(int)BlackPawn]; int seed, nrOfShuffles; -void GetPositionNumber() +void +GetPositionNumber () { // sets global variable seed int i; @@ -5529,7 +5692,8 @@ void GetPositionNumber() } } -int put(Board board, int pieceType, int rank, int n, int shade) +int +put (Board board, int pieceType, int rank, int n, int shade) // put the piece on the (n-1)-th empty squares of the given shade { int i; @@ -5547,7 +5711,8 @@ int put(Board board, int pieceType, int rank, int n, int shade) } -void AddOnePiece(Board board, int pieceType, int rank, int shade) +void +AddOnePiece (Board board, int pieceType, int rank, int shade) // calculate where the next piece goes, (any empty square), and put it there { int i; @@ -5558,7 +5723,8 @@ void AddOnePiece(Board board, int pieceType, int rank, int shade) put(board, pieceType, rank, i, shade); } -void AddTwoPieces(Board board, int pieceType, int rank) +void +AddTwoPieces (Board board, int pieceType, int rank) // calculate where the next 2 identical pieces go, (any empty square), and put it there { int i, n=squaresLeft[ANY], j=n-1, k; @@ -5573,7 +5739,8 @@ void AddTwoPieces(Board board, int pieceType, int rank) put(board, pieceType, rank, i, ANY); } -void SetUpShuffle(Board board, int number) +void +SetUpShuffle (Board board, int number) { int i, p, first=1; @@ -5661,7 +5828,8 @@ void SetUpShuffle(Board board, int number) if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize } -int SetCharTable( char *table, const char * map ) +int +SetCharTable (char *table, const char * map) /* [HGM] moved here from winboard.c because of its general usefulness */ /* Basically a safe strcpy that uses the last character as King */ { @@ -5685,7 +5853,8 @@ int SetCharTable( char *table, const char * map ) return result; } -void Prelude(Board board) +void +Prelude (Board board) { // [HGM] superchess: random selection of exo-pieces int i, j, k; ChessSquare p; static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance }; @@ -5722,11 +5891,10 @@ void Prelude(Board board) } void -InitPosition(redraw) - int redraw; +InitPosition (int redraw) { ChessSquare (* pieces)[BOARD_FILES]; - int i, j, pawnRow, overrule, + int i, j, pawnRow=1, pieceRows=1, overrule, oldx = gameInfo.boardWidth, oldy = gameInfo.boardHeight, oldh = gameInfo.holdingsWidth; @@ -5783,9 +5951,13 @@ InitPosition(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; @@ -5812,6 +5984,7 @@ InitPosition(redraw) case VariantSChess: SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek"); gameInfo.holdingsSize = 7; + for(i=0; i=BOARD_RGHT-1) { initialPosition[0][j] = WhiteRook; @@ -5984,7 +6179,7 @@ InitPosition(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) { @@ -6017,14 +6212,12 @@ InitPosition(redraw) } void -SendBoard(cps, moveNum) - ChessProgramState *cps; - int moveNum; +SendBoard (ChessProgramState *cps, int moveNum) { char message[MSG_SIZ]; if (cps->useSetboard) { - 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); @@ -6094,11 +6287,121 @@ SendBoard(cps, moveNum) setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } +char exclusionHeader[MSG_SIZ]; +int exCnt, excludePtr; +typedef struct { int ff, fr, tf, tr, pc, mark; } Exclusion; +static Exclusion excluTab[200]; +static char excludeMap[(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8]; // [HGM] exclude: bitmap for excluced moves + +static void +WriteMap (int s) +{ + int j; + for(j=0; j<(BOARD_RANKS*BOARD_FILES*BOARD_RANKS*BOARD_FILES+7)/8; j++) excludeMap[j] = s; + exclusionHeader[19] = s ? '-' : '+'; // update tail state +} + +static void +ClearMap () +{ + safeStrCpy(exclusionHeader, "exclude: none best +tail \n", MSG_SIZ); + excludePtr = 24; exCnt = 0; + WriteMap(0); +} + +static void +UpdateExcludeHeader (int fromY, int fromX, int toY, int toX, char promoChar, char state) +{ // search given move in table of header moves, to know where it is listed (and add if not there), and update state + char buf[2*MOVE_LEN], *p; + Exclusion *e = excluTab; + int i; + for(i=0; i>= 3; + if(state == '*') state = (excludeMap[k] & 1< 18) { // tail + if(exclusionHeader[19] == '-') { // tail was excluded + SendToBoth("include all\n"); + WriteMap(0); // clear map completely + // now re-exclude selected moves + for(i=0; i index) { + char *p=exclusionHeader + excluTab[i].mark; // do trust header more than map (promotions!) + ExcludeOneMove(e[i].fr, e[i].ff, e[i].tr, e[i].tf, e[i].pc, *p == '+' ? '-' : '+'); + break; + } + } +} + ChessSquare -DefaultPromoChoice(int white) +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 @@ -6112,7 +6415,7 @@ DefaultPromoChoice(int white) static int autoQueen; // [HGM] oneclick int -HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect) +HasPromotionChoice (int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect) { /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */ /* [HGM] add Shogi promotions */ @@ -6129,9 +6432,13 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, in return FALSE; piece = boards[currentMove][fromY][fromX]; - if(gameInfo.variant == VariantShogi) { + if(gameInfo.variant == VariantChu) { + int p = piece >= BlackPawn ? BLACK_TO_WHITE piece : piece; promotionZoneSize = BOARD_HEIGHT/3; - highestPromotingPiece = (int)WhiteFerz; + highestPromotingPiece = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion; + } else if(gameInfo.variant == VariantShogi) { + promotionZoneSize = BOARD_HEIGHT/3; + highestPromotingPiece = (int)WhiteAlfil; } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) { promotionZoneSize = 3; } @@ -6183,7 +6490,8 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, in } // 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; } @@ -6194,7 +6502,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, in } // give caller the default choice even if we will not make it *promoChoice = ToLower(PieceToChar(defaultPromoChoice)); - if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+'); + if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece ? '=' : '+'); if( sweepSelect && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand && gameInfo.variant != VariantSuper) return FALSE; @@ -6205,7 +6513,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, in gameMode == IcsPlayingBlack && WhiteOnMove(currentMove); if(appData.testLegality && !premove) { moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), - fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR); + fromY, fromX, toY, toX, IS_SHOGI(gameInfo.variant) ? '+' : NULLCHAR); if(moveType != WhitePromotion && moveType != BlackPromotion) return FALSE; } @@ -6214,8 +6522,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, in } int -InPalace(row, column) - int row, column; +InPalace (int row, int column) { /* [HGM] for Xiangqi */ if( (row < 3 || row > BOARD_HEIGHT-4) && column < (BOARD_WIDTH + 4)/2 && @@ -6224,9 +6531,7 @@ InPalace(row, column) } int -PieceForSquare (x, y) - int x; - int y; +PieceForSquare (int x, int y) { if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1; @@ -6235,8 +6540,7 @@ PieceForSquare (x, y) } int -OKToStartUserMove(x, y) - int x, y; +OKToStartUserMove (int x, int y) { ChessSquare from_piece; int white_piece; @@ -6336,7 +6640,8 @@ OKToStartUserMove(x, 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; switch(gameMode) { @@ -6398,14 +6703,14 @@ int lastLoadGameNumber = 0, lastLoadPositionNumber = 0; int lastLoadGameUseList = FALSE; char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = EndOfFile; +int doubleClick; void -UserMoveEvent(fromX, fromY, toX, toY, promoChar) - int fromX, fromY, toX, toY; - int promoChar; +UserMoveEvent(int fromX, int fromY, int toX, int toY, int promoChar) { ChessMove moveType; - ChessSquare pdown, pup; + ChessSquare pup; + int ff=fromX, rf=fromY, ft=toX, rt=toY; /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he @@ -6534,7 +6839,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare; } } else - boards[0][fromY][fromX] = EmptySquare; + boards[0][fromY][fromX] = gatingPiece; DrawPosition(FALSE, boards[currentMove]); return; } @@ -6542,19 +6847,18 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) } if(toX < 0 || toY < 0) return; - pdown = boards[currentMove][fromY][fromX]; pup = boards[currentMove][toY][toX]; /* [HGM] If move started in holdings, it means a drop. Convert to standard form */ 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; } @@ -6572,15 +6876,19 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) } } + 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 + return; + } + FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } /* Common tail of UserMoveEvent and DropMenuEvent */ int -FinishMove(moveType, fromX, fromY, toX, toY, promoChar) - ChessMove moveType; - int fromX, fromY, toX, toY; - /*char*/int promoChar; +FinishMove (ChessMove moveType, int fromX, int fromY, int toX, int toY, int promoChar) { char *bookHit = 0; @@ -6646,6 +6954,8 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) else forwardMostMove = currentMove; } + ClearMap(); + /* If we need the chess program but it's dead, restart it */ ResurrectChessProgram(); @@ -6671,7 +6981,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) gameMode = MachinePlaysBlack; StartClocks(); SetGameInfo(); - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); if (first.sendName) { snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white); @@ -6701,14 +7011,21 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) gameMode == MachinePlaysBlack)) { SendTimeRemaining(&first, gameMode != MachinePlaysBlack); } - if (gameMode != EditGame && gameMode != PlayFromGameFile) { + if (gameMode != EditGame && gameMode != PlayFromGameFile && gameMode != AnalyzeMode) { // [HGM] book: if program might be playing, let it use book bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); first.maybeThinking = TRUE; } else if(fromY == DROP_RANK && fromX == EmptySquare) { if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard SendBoard(&first, currentMove+1); - } else SendMoveToProgram(forwardMostMove-1, &first); + if(second.analyzing) { + if(!second.useSetboard) SendToProgram("undo\n", &second); + SendBoard(&second, currentMove+1); + } + } else { + SendMoveToProgram(forwardMostMove-1, &first); + if(second.analyzing) SendMoveToProgram(forwardMostMove-1, &second); + } if (currentMove == cmailOldMove + 1) { cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; } @@ -6765,32 +7082,61 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } void -Mark(board, flags, kind, rf, ff, rt, ft, closure) - Board board; - int flags; - ChessMove kind; - int rf, ff, rt, ft; - VOIDSTAR closure; +MarkByFEN(char *fen) +{ + int r, f; + if(!appData.markers || !appData.highlightDragging) return; + for(r=0; r= 'A' && *fen <= 'Z') legal[r][f] = 1; else + if(*fen >= 'a' && *fen <= 'z') *fen += 'A' - 'a'; + if(*fen == '/' && f > BOARD_LEFT) f = BOARD_LEFT, r--; else + if(*fen == 'T') marker[r][f++] = 0; else + if(*fen == 'Y') marker[r][f++] = 1; else + if(*fen == 'G') marker[r][f++] = 3; else + if(*fen == 'B') marker[r][f++] = 4; else + if(*fen == 'C') marker[r][f++] = 5; else + if(*fen == 'M') marker[r][f++] = 6; else + if(*fen == 'W') marker[r][f++] = 7; else + if(*fen == 'D') marker[r][f++] = 8; else + if(*fen == 'R') marker[r][f++] = 2; else { + while(*fen <= '9' && *fen >= '0') s = 10*s + *fen++ - '0'; + f += s; fen -= s>0; + } + while(f >= BOARD_RGHT) f -= BOARD_RGHT - BOARD_LEFT, r--; + if(r < 0) break; + fen++; + } + DrawPosition(TRUE, NULL); +} + +void +Mark (Board board, int flags, ChessMove kind, int rf, int ff, int rt, int ft, VOIDSTAR closure) { typedef char Markers[BOARD_RANKS][BOARD_FILES]; Markers *m = (Markers *) closure; - if(rf == fromY && ff == fromX) + if(rf == fromY && ff == fromX && (killX < 0 && !(rt == rf && ft == ff) || abs(ft-killX) < 2 && abs(rt-killY) < 2)) (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare || kind == WhiteCapturesEnPassant - || kind == BlackCapturesEnPassant); + || kind == BlackCapturesEnPassant) + 3*(kind == FirstLeg && killX < 0); else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3; } void -MarkTargetSquares(int clear) +MarkTargetSquares (int clear) { - int x, y; - if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi || - !appData.testLegality || gameMode == EditPosition) return; - if(clear) { - for(x=0; x1) capt++; @@ -6798,11 +7144,11 @@ MarkTargetSquares(int clear) for(x=0; x= gameInfo.holdingsSize) ) return; - if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered) + if(gotPremove && x == premoveFromX && y == premoveFromY && clickType == Release) { + // could be static click on premove from-square: abort premove + gotPremove = 0; + ClearPremoveHighlights(); + } + + if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered && SubtractTimeMarks(&lastClickTime, &prevClickTime) >= 200) fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for @@ -6913,14 +7306,20 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } return; } - fromX = x; fromY = y; toX = toY = -1; + doubleClick = FALSE; + 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 = killX = killY = -1; if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) { /* First square */ if (OKToStartUserMove(fromX, fromY)) { second = 0; + ReportClick("lift", x, y); MarkTargetSquares(0); + if(gameMode == EditPosition && controlKey) gatingPiece = boards[currentMove][fromY][fromX]; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { promoSweep = defaultPromoChoice; @@ -6930,6 +7329,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } if (appData.highlightDragging) { SetHighlights(fromX, fromY, -1, -1); + } else { + ClearHighlights(); } } else fromX = fromY = -1; return; @@ -6949,19 +7350,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix) fromP = boards[currentMove][fromY][fromX]; toP = boards[currentMove][y][x]; frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess; - if ((WhitePawn <= fromP && fromP <= WhiteKing && + if( (killX < 0 || x != fromX || y != fromY) && // [HGM] lion: do not interpret igui as deselect! + ((WhitePawn <= fromP && fromP <= WhiteKing && WhitePawn <= toP && toP <= WhiteKing && !(fromP == WhiteKing && toP == WhiteRook && frc) && !(fromP == WhiteRook && toP == WhiteKing && frc)) || (BlackPawn <= fromP && fromP <= BlackKing && BlackPawn <= toP && toP <= BlackKing && !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling - !(fromP == BlackKing && toP == BlackRook && frc))) { + !(fromP == BlackKing && toP == BlackRook && frc)))) { /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); + killX = killY = -1; + if(second && gameMode == AnalyzeMode && SubtractTimeMarks(&lastClickTime, &prevClickTime) < 200) { + second = FALSE; // first double-click rather than scond click + doubleClick = first.excludeMoves; // used by UserMoveEvent to recognize exclude moves + } promoDefaultAltered = FALSE; MarkTargetSquares(1); - if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) { + if(!(second && appData.oneClick && OnlyMove(&x, &y, TRUE))) { if (appData.highlightDragging) { SetHighlights(x, y, -1, -1); } else { @@ -6972,9 +7379,10 @@ void LeftClick(ClickType clickType, int xPix, int yPix) (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1)) gatingPiece = boards[currentMove][fromY][fromX]; - else gatingPiece = EmptySquare; + else gatingPiece = doubleClick ? fromP : EmptySquare; fromX = x; fromY = y; dragging = 1; + ReportClick("lift", x, y); MarkTargetSquares(0); DragPieceBegin(xPix, yPix, FALSE); if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { @@ -6985,13 +7393,13 @@ void 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; } - if (clickType == Release && x == fromX && y == fromY) { + if (clickType == Release && x == fromX && y == fromY && killX < 0) { DragPieceEnd(xPix, yPix); dragging = 0; if(clearFlag) { // a deferred attempt to click-click move an empty square on top of a piece @@ -7005,11 +7413,13 @@ void 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; + MarkTargetSquares(1); ClearHighlights(); gotPremove = 0; ClearPremoveHighlights(); @@ -7022,12 +7432,21 @@ void LeftClick(ClickType clickType, int xPix, int yPix) clearFlag = 0; + if(gameMode != EditPosition && !appData.testLegality && !legal[y][x] && (x != killX || y != killY) && !sweepSelecting) { + if(dragging) DragPieceEnd(xPix, yPix), dragging = 0; + DisplayMessage(_("only marked squares are legal"),""); + DrawPosition(TRUE, NULL); + return; // ignore to-click + } + /* we now have a different from- and (possibly off-board) to-square */ /* Completed move */ - toX = x; - toY = y; + if(!sweepSelecting) { + toX = x; + toY = y; + } + saveAnimate = appData.animate; - MarkTargetSquares(1); if (clickType == Press) { if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { // must be Edit Position mode with empty-square selected @@ -7035,16 +7454,26 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click return; } - if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { + if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job + dragging = 1; + return; + } + if(x == killX && y == killY) { // second click on this square, which was selected as first-leg target + killX = killY = -1; // this informs us no second leg is coming, so treat as to-click without intermediate + } else + if(marker[y][x] == 5) return; // [HGM] lion: to-click on cyan square; defer action to release + if(legal[y][x] == 2 || HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) { + if(appData.sweepSelect) { ChessSquare piece = boards[currentMove][fromY][fromX]; - DragPieceBegin(xPix, yPix, TRUE); dragging = 1; promoSweep = defaultPromoChoice; - if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece; + if(PieceToChar(CHUPROMOTED piece) == '+') promoSweep = CHUPROMOTED piece; selectFlag = 0; lastX = xPix; lastY = yPix; Sweep(0); // Pawn that is going to promote: preview promotion piece + sweepSelecting = 1; DisplayMessage("", _("Pull pawn backwards to under-promote")); - DrawPosition(FALSE, boards[currentMove]); - return; + MarkTargetSquares(1); + } + return; // promo popup appears on up-click } /* Finish clickclick move */ if (appData.animate || appData.highlightLastMove) { @@ -7052,13 +7481,34 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } else { ClearHighlights(); } + } else if(sweepSelecting) { // this must be the up-click corresponding to the down-click that started the sweep + sweepSelecting = 0; + 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 + if(marker[y][x] == 5) { // [HGM] lion: this was the release of a to-click or drag on a cyan square + dragging *= 2; // flag button-less dragging if we are dragging + MarkTargetSquares(1); + if(x == killX && y == killY) killX = killY = -1; else { + killX = x; killY = y; //remeber this square as intermediate + MarkTargetSquares(0); + ReportClick("put", x, y); // and inform engine + ReportClick("lift", x, y); + return; + } + } DragPieceEnd(xPix, yPix); dragging = 0; /* Don't animate move and drag both */ appData.animate = FALSE; @@ -7087,17 +7537,20 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } ClearHighlights(); fromX = fromY = -1; + MarkTargetSquares(1); DrawPosition(TRUE, boards[currentMove]); return; } // off-board moves should not be highlighted if(x < 0 || y < 0) ClearHighlights(); + else ReportClick("put", x, y); - if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece)); + if(gatingPiece != EmptySquare && gameInfo.variant == VariantSChess) promoChoice = ToLower(PieceToChar(gatingPiece)); if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { SetHighlights(fromX, fromY, toX, toY); + 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]; @@ -7120,6 +7573,7 @@ void 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; @@ -7129,7 +7583,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } } -int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) +int +RightClick (ClickType action, int x, int y, int *fromX, int *fromY) { // front-end-free part taken out of PieceMenuPopup int whichMenu; int xSqr, ySqr; @@ -7219,7 +7674,8 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) return whichMenu; } -void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats ) +void +SendProgramStatsToFrontend (ChessProgramState * cps, ChessProgramStats * cpstats) { // char * hint = lastHint; FrontEndProgramStats stats; @@ -7246,7 +7702,7 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp } void -ClearEngineOutputPane(int which) +ClearEngineOutputPane (int which) { static FrontEndProgramStats dummyStats; dummyStats.which = which; @@ -7257,7 +7713,7 @@ ClearEngineOutputPane(int which) #define MAXPLAYERS 500 char * -TourneyStandings(int display) +TourneyStandings (int display) { int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0; int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS]; @@ -7301,7 +7757,7 @@ TourneyStandings(int display) } void -Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor) +Count (Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor) { // count all piece types int p, f, r; *nB = *nW = *wStale = *bStale = *bishopColor = 0; @@ -7319,7 +7775,7 @@ Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int * } int -SufficientDefence(int pCnt[], int side, int nMine, int nHis) +SufficientDefence (int pCnt[], int side, int nMine, int nHis) { int myPawns = pCnt[WhitePawn+side]; // my total Pawn count; int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side]; @@ -7348,7 +7804,7 @@ SufficientDefence(int pCnt[], int side, int nMine, int nHis) } int -MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor) +MatingPotential (int pCnt[], int side, int nMine, int nHis, int stale, int bisColor) { VariantClass v = gameInfo.variant; @@ -7374,6 +7830,9 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol || 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]; @@ -7398,7 +7857,7 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol } int -CompareWithRights(Board b1, Board b2) +CompareWithRights (Board b1, Board b2) { int rights = 0; if(!CompareBoards(b1, b2)) return FALSE; @@ -7420,13 +7879,13 @@ CompareWithRights(Board b1, Board b2) } int -Adjudicate(ChessProgramState *cps) +Adjudicate (ChessProgramState *cps) { // [HGM] some adjudications useful with buggy engines // [HGM] adjudicate: made into separate routine, which now can be called after every move // 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; @@ -7510,13 +7969,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; } @@ -7576,23 +8043,17 @@ Adjudicate(ChessProgramState *cps) } } else moveCount = 6; } - if (appData.debugMode) { int i; - fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", - forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS], - appData.drawRepeats); - for( i=forwardMostMove; i>=backwardMostMove; i-- ) - fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]); - - } // Repetition draws and 50-move rule can be applied independently of legality testing /* 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])) { @@ -7618,7 +8079,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) { @@ -7636,6 +8097,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); @@ -7729,7 +8196,8 @@ Adjudicate(ChessProgramState *cps) return 0; } -char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) +char * +SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial) { // [HGM] book: this routine intercepts moves to simulate book replies char *bookHit = NULL; @@ -7742,6 +8210,7 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) SendToProgram("force\n", cps); cps->bookSuspend = TRUE; // flag indicating it has to be restarted } + if(bookHit) setboardSpoiledMachineBlack = FALSE; // suppress 'go' in SendMoveToProgram if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move // now arrange restart after book miss if(bookHit) { @@ -7777,9 +8246,29 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) return bookHit; // notify caller of hit, so it can take action to send move to opponent } +int +LoadError (char *errmess, ChessProgramState *cps) +{ // unloads engine and switches back to -ncp mode if it was first + if(cps->initDone) return FALSE; + cps->isr = NULL; // this should suppress further error popups from breaking pipes + DestroyChildProcess(cps->pr, 9 ); // just to be sure + cps->pr = NoProc; + if(cps == &first) { + appData.noChessProgram = TRUE; + gameMode = MachinePlaysBlack; ModeHighlight(); // kludge to unmark Machine Black menu + gameMode = BeginningOfGame; ModeHighlight(); + SetNCPMode(); + } + if(GetDelayedEvent()) CancelDelayedEvent(), ThawUI(); // [HGM] cancel remaining loading effort scheduled after feature timeout + DisplayMessage("", ""); // erase waiting message + if(errmess) DisplayError(errmess, 0); // announce reason, if given + return TRUE; +} + char *savedMessage; ChessProgramState *savedState; -void DeferredBookMove(void) +void +DeferredBookMove (void) { if(savedState->lastPing != savedState->lastPong) ScheduleDelayedEvent(DeferredBookMove, 10); @@ -7788,19 +8277,20 @@ void DeferredBookMove(void) } static int savedWhitePlayer, savedBlackPlayer, pairingReceived; +static ChessProgramState *stalledEngine; +static char stashedInputMove[MSG_SIZ]; void -HandleMachineMove(message, cps) - char *message; - ChessProgramState *cps; +HandleMachineMove (char *message, ChessProgramState *cps) { + static char firstLeg[20]; char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ]; char realname[MSG_SIZ]; int fromX, fromY, toX, toY; ChessMove moveType; - char promoChar; + char promoChar, roar; char *p, *pv=buf1; - int machineWhite; + int machineWhite, oldError; char *bookHit; if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) { @@ -7814,7 +8304,7 @@ HandleMachineMove(message, cps) return; // Skim the pairing messages here. } - cps->userError = 0; + oldError = cps->userError; cps->userError = 0; FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit /* @@ -7855,6 +8345,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) { @@ -7919,18 +8425,31 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h return; } - if (appData.debugMode) { int f = forwardMostMove; - fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, - boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2], - boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]); - } if(cps->alphaRank) AlphaRank(machineMove, 4); + + // [HGM] lion: (some very limited) support for Alien protocol + killX = killY = -1; + if(machineMove[strlen(machineMove)-1] == ',') { // move ends in coma: non-final leg of composite move + safeStrCpy(firstLeg, machineMove, 20); // just remember it for processing when second leg arrives + return; + } else if(firstLeg[0]) { // there was a previous leg; + // only support case where same piece makes two step (and don't even test that!) + char buf[20], *p = machineMove+1, *q = buf+1, f; + safeStrCpy(buf, machineMove, 20); + while(isdigit(*q)) q++; // find start of to-square + safeStrCpy(machineMove, firstLeg, 20); + while(isdigit(*p)) p++; + safeStrCpy(p, q, 20); // glue to-square of second leg to from-square of first, to process over-all move + sscanf(buf, "%c%d", &f, &killY); killX = f - AAA; killY -= ONE - '0'; // pass intermediate square to MakeMove in global + firstLeg[0] = NULLCHAR; + } + if (!ParseOneMove(machineMove, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { /* 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) { @@ -7949,12 +8468,6 @@ FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book h ChessMove moveType; moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), fromY, fromX, toY, toX, promoChar); - if (appData.debugMode) { - int i; - for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", - boards[forwardMostMove][CASTLING][i], castlingRank[i]); - fprintf(debugFP, "castling rights\n"); - } if(moveType == IllegalMove) { snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); @@ -7993,6 +8506,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; @@ -8080,10 +8604,12 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. cps->other->maybeThinking = TRUE; } + roar = (killX >= 0 && IS_LION(boards[forwardMostMove][toY][toX])); + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ if (!pausing && appData.ringBellAfterMoves) { - RingBell(); + if(!roar) RingBell(); } /* @@ -8133,13 +8659,26 @@ 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 - int dummy, s=6; char buf[MSG_SIZ]; + if (!strncmp(message, "setup ", 6) && + (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || NonStandardBoardSize()) + ) { // [HGM] allow first engine to define opening position + int dummy, w, h, hand, s=6; char buf[MSG_SIZ], varName[MSG_SIZ]; if(appData.icsActive || forwardMostMove != 0 || cps != &first) return; + *buf = NULLCHAR; if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); if(startedFromSetupPosition) return; - ParseFEN(boards[0], &dummy, message+s); + dummy = sscanf(message+s, "%dx%d+%d_%s", &w, &h, &hand, varName); + if(dummy >= 3) { + while(message[s] && message[s++] != ' '); + if(BOARD_HEIGHT != h || BOARD_WIDTH != w + 4*(hand != 0) || gameInfo.holdingsSize != hand || + dummy == 4 && gameInfo.variant != StringToVariant(varName) ) { // engine wants to change board format or variant + appData.NrFiles = w; appData.NrRanks = h; appData.holdingsSize = hand; + if(dummy == 4) gameInfo.variant = StringToVariant(varName); // parent variant + InitPosition(1); // calls InitDrawingSizes to let new parameters take effect + if(*buf) SetCharTable(pieceToChar, buf); // do again, for it was spoiled by InitPosition + } + } + ParseFEN(boards[0], &dummy, message+s, FALSE); DrawPosition(TRUE, boards[0]); startedFromSetupPosition = TRUE; return; @@ -8152,7 +8691,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD); - if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) { + if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9, FALSE)) { DisplayError(_("Bad FEN received from engine"), 0); return ; } else { @@ -8201,7 +8740,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)) { @@ -8254,6 +8793,28 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (sscanf(message, "pong %d", &cps->lastPong) == 1) { return; } + if(!strncmp(message, "highlight ", 10)) { + if(appData.testLegality && appData.markers) return; + MarkByFEN(message+10); // [HGM] alien: allow engine to mark board squares + return; + } + if(!strncmp(message, "click ", 6)) { + char f, c=0; int x, y; // [HGM] alien: allow engine to finish user moves (i.e. engine-driven one-click moving) + if(appData.testLegality || !appData.oneClick) return; + sscanf(message+6, "%c%d%c", &f, &y, &c); + x = f - 'a' + BOARD_LEFT, y -= ONE - '0'; + if(flipView) x = BOARD_WIDTH-1 - x; else y = BOARD_HEIGHT-1 - y; + x = x*squareSize + (x+1)*lineGap + squareSize/2; + y = y*squareSize + (y+1)*lineGap + squareSize/2; + f = first.highlight; first.highlight = 0; // kludge to suppress lift/put in response to own clicks + if(lastClickType == Press) // if button still down, fake release on same square, to be ready for next click + LeftClick(Release, lastLeftX, lastLeftY); + controlKey = (c == ','); + LeftClick(Press, x, y); + LeftClick(Release, x, y); + first.highlight = f; + return; + } /* * If the move is illegal, cancel it and redraw the board. * Also deal with other error cases. Matching is rather loose @@ -8380,7 +8941,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. /* * If chess program startup fails, exit with an error message. - * Attempts to recover here are futile. + * Attempts to recover here are futile. [HGM] Well, we try anyway */ if ((StrStr(message, "unknown host") != NULL) || (StrStr(message, "No remote directory") != NULL) @@ -8394,8 +8955,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. _(cps->which), cps->program, cps->host, message); RemoveInputSource(cps->isr); if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else { - if(cps == &first) appData.noChessProgram = TRUE; - DisplayError(buf1, 0); + if(LoadError(oldError ? NULL : buf1, cps)) return; // error has then been handled by LoadError + if(!oldError) DisplayError(buf1, 0); // if reason neatly announced, suppress general error popup } return; } @@ -8594,7 +9155,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. DisplayInformation(_("Machine accepts your draw offer")); GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD); } else { - DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree")); + DisplayInformation(_("Machine offers a draw.\nSelect Action / Draw to accept.")); } } } @@ -8673,7 +9234,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(f = fopen(buf, "w")) { // export PV to applicable PV file fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv); fclose(f); - } else DisplayError(_("failed writing PV"), 0); + } + else + /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */ + DisplayError(_("failed writing PV"), 0); } tempStats.depth = plylev; @@ -8890,8 +9454,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. The display is not updated in any way. */ void -ParseGameHistory(game) - char *game; +ParseGameHistory (char *game) { ChessMove moveType; int fromX, fromY, toX, toY, boardIndex; @@ -8937,6 +9500,7 @@ ParseGameHistory(game) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case WhiteKingSideCastle: @@ -9074,10 +9638,7 @@ ParseGameHistory(game) /* Apply a move to the given board */ void -ApplyMove(fromX, fromY, toX, toY, promoChar, board) - int fromX, fromY, toX, toY; - int promoChar; - Board board; +ApplyMove (int fromX, int fromY, int toX, int toY, int promoChar, Board board) { ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0; int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1; @@ -9097,11 +9658,23 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) } piece = board[toY][toX] = (ChessSquare) fromX; } else { + ChessSquare victim; int i; - if( board[toY][toX] != EmptySquare ) + if( killX >= 0 && killY >= 0 ) // [HGM] lion: Lion trampled over something + victim = board[killY][killX], + board[killY][killX] = EmptySquare, board[EP_STATUS] = EP_CAPTURE; + if( board[toY][toX] != EmptySquare ) { + board[EP_STATUS] = EP_CAPTURE; + if( (fromX != toX || fromY != toY) && // not igui! + (captured == WhiteLion && board[fromY][fromX] != BlackLion || + captured == BlackLion && board[fromY][fromX] != WhiteLion ) ) { // [HGM] lion: Chu Lion-capture rules + board[EP_STATUS] = EP_IRON_LION; // non-Lion x Lion: no counter-strike allowed + } + } + if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) { if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants @@ -9137,6 +9710,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, 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 */ @@ -9186,11 +9766,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, 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 @@ -9247,11 +9827,11 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, 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 @@ -9275,8 +9855,9 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][fromX+1] = EmptySquare; } } else { - board[toY][toX] = board[fromY][fromX]; + ChessSquare piece = board[fromY][fromX]; // [HGM] lion: allow for igui (where from == to) board[fromY][fromX] = EmptySquare; + board[toY][toX] = piece; } } @@ -9358,10 +9939,13 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating } else if(promoChar == '+') { - /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */ - board[toY][toX] = (ChessSquare) (PROMOTED piece); + /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite ordinary Pawn promotion) */ + board[toY][toX] = (ChessSquare) (CHUPROMOTED piece); } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar - board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); + ChessSquare newPiece = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); + if((newPiece <= WhiteMan || newPiece >= BlackPawn && newPiece <= BlackMan) // unpromoted piece specified + && pieceToChar[PROMOTED newPiece] == '~') newPiece = PROMOTED newPiece; // but promoted version available + board[toY][toX] = newPiece; } if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR && gameInfo.holdingsSize) { @@ -9375,21 +9959,24 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[BOARD_HEIGHT-1-k][0] = EmptySquare; } } - } /* Updates forwardMostMove */ void -MakeMove(fromX, fromY, toX, toY, promoChar) - int fromX, fromY, toX, toY; - int promoChar; +MakeMove (int fromX, int fromY, int toX, int toY, int promoChar) { + int x = toX, y = toY; + char *s = parseList[forwardMostMove]; + ChessSquare p = boards[forwardMostMove][toY][toX]; // forwardMostMove++; // [HGM] bare: moved downstream + if(killX >= 0 && killY >= 0) x = killX, y = killY; // [HGM] lion: make SAN move to intermediate square, if there is one (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), - fromY, fromX, toY, toX, promoChar, - parseList[forwardMostMove]); + fromY, fromX, y, x, promoChar, + s); + if(killX >= 0 && killY >= 0) + sprintf(s + strlen(s), "%c%c%d", p == EmptySquare || toX == fromX && toY == fromY ? '-' : 'x', toX + AAA, toY + ONE - '0'); if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */ int timeLeft; static int lastLoadFlag=0; int king, piece; @@ -9425,8 +10012,12 @@ MakeMove(fromX, fromY, toX, toY, 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", @@ -9435,6 +10026,7 @@ MakeMove(fromX, fromY, toX, toY, 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); @@ -9479,15 +10071,11 @@ MakeMove(fromX, fromY, toX, toY, promoChar) strcat(parseList[forwardMostMove - 1], "#"); break; } - if (appData.debugMode) { - fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]); - } - } /* Updates currentMove if not pausing */ void -ShowMove(fromX, fromY, toX, toY) +ShowMove (int fromX, int fromY, int toX, int toY) { int instant = (gameMode == PlayFromGameFile) ? (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing; @@ -9498,22 +10086,27 @@ ShowMove(fromX, fromY, toX, toY) AnimateMove(boards[forwardMostMove - 1], fromX, fromY, toX, toY); } - if (appData.highlightLastMove) { - SetHighlights(fromX, fromY, toX, toY); - } } currentMove = forwardMostMove; } + killX = killY = -1; // [HGM] lion: used up + 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); } -void SendEgtPath(ChessProgramState *cps) +void +SendEgtPath (ChessProgramState *cps) { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */ char buf[MSG_SIZ], name[MSG_SIZ], *p; @@ -9546,12 +10139,41 @@ void 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 == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix + 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( gameInfo.variant == VariantChu ) + overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 12 || gameInfo.holdingsSize != 0; + return overruled; +} + void -InitChessProgram(cps, setup) - ChessProgramState *cps; - int setup; /* [HGM] needed to setup FRC opening position */ +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; @@ -9583,29 +10205,7 @@ InitChessProgram(cps, 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 */ @@ -9670,8 +10270,34 @@ InitChessProgram(cps, setup) void -StartChessProgram(cps) - ChessProgramState *cps; +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]; int err; @@ -9710,9 +10336,12 @@ StartChessProgram(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); } @@ -9733,12 +10362,11 @@ TwoMachinesEventIfReady P((void)) return; } DisplayMessage("", ""); curMess = 0; - ThawUI(); TwoMachinesEvent(); } char * -MakeName(char *template) +MakeName (char *template) { time_t clock; struct tm *tm; @@ -9767,7 +10395,7 @@ MakeName(char *template) } int -CountPlayers(char *p) +CountPlayers (char *p) { int n = 0; while(p = strchr(p, '\n')) p++, n++; // count participants @@ -9775,7 +10403,7 @@ CountPlayers(char *p) } FILE * -WriteTourneyFile(char *results, FILE *f) +WriteTourneyFile (char *results, FILE *f) { // write tournament parameters on tourneyFile; on success return the stream pointer for closing if(f == NULL) f = fopen(appData.tourneyFile, "w"); if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else { @@ -9793,7 +10421,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 { @@ -9806,10 +10442,10 @@ WriteTourneyFile(char *results, FILE *f) return f; } -#define MAXENGINES 1000 char *command[MAXENGINES], *mnemonic[MAXENGINES]; -void Substitute(char *participants, int expunge) +void +Substitute (char *participants, int expunge) { int i, changed, changes=0, nPlayers=0; char *p, *q, *r, buf[MSG_SIZ]; @@ -9830,7 +10466,7 @@ void Substitute(char *participants, int expunge) q = r; while(*q) nPlayers += (*q++ == '\n'); p = buf; while(*r && (*p = *r++) != '\n') p++; *p = NULLCHAR; - NamesToList(firstChessProgramNames, command, mnemonic); + NamesToList(firstChessProgramNames, command, mnemonic, "all"); for(i=1; mnemonic[i]; i++) if(!strcmp(buf, mnemonic[i])) break; if(mnemonic[i]) { // The substitute is valid FILE *f; @@ -9867,7 +10503,28 @@ void Substitute(char *participants, int expunge) } int -CreateTourney(char *name) +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; if(matchMode && strcmp(name, appData.tourneyFile)) { @@ -9888,6 +10545,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; @@ -9899,18 +10557,29 @@ CreateTourney(char *name) return 1; } -void NamesToList(char *names, char **engineList, char **engineMnemonic) +int +NamesToList (char *names, char **engineList, char **engineMnemonic, char *group) { char buf[MSG_SIZ], *p, *q; - int i=1; - while(*names) { - p = names; q = buf; + int i=1, header, skip, all = !strcmp(group, "all"), depth = 0; + insert = names; // afterwards, this global will point just after last retrieved engine line or group end in the 'names' + skip = !all && group[0]; // if group requested, we start in skip mode + for(;*names && depth >= 0 && i < MAXENGINES-1; names = p) { + p = names; q = buf; header = 0; while(*p && *p != '\n') *q++ = *p++; *q = 0; + if(*p == '\n') p++; + if(buf[0] == '#') { + if(strstr(buf, "# end") == buf) { if(!--depth) insert = p; continue; } // leave group, and suppress printing label + depth++; // we must be entering a new group + if(all) continue; // suppress printing group headers when complete list requested + header = 1; + if(skip && !strcmp(group, buf)) { depth = 0; skip = FALSE; } // start when we reach requested group + } + if(depth != header && !all || skip) continue; // skip contents of group (but print first-level header) if(engineList[i]) free(engineList[i]); engineList[i] = strdup(buf); - if(*p == '\n') p++; - TidyProgramName(engineList[i], "localhost", buf); + if(buf[0] != '#') insert = p, TidyProgramName(engineList[i], "localhost", buf); // group headers not tidied if(engineMnemonic[i]) free(engineMnemonic[i]); if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) { strcat(buf, " ("); @@ -9918,16 +10587,17 @@ void NamesToList(char *names, char **engineList, char **engineMnemonic) strcat(buf, ")"); } engineMnemonic[i] = strdup(buf); - names = p; i++; - if(i > MAXENGINES - 2) break; + i++; } engineList[i] = engineMnemonic[i] = NULL; + return i; } // following implemented as macro to avoid type limitations #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp; -void SwapEngines(int n) +void +SwapEngines (int n) { // swap settings for first engine and other engine (so far only some selected options) int h; char *p; @@ -9944,27 +10614,80 @@ void SwapEngines(int n) SWAP(pgnName, p) SWAP(pvSAN, h) SWAP(engOptions, p) + SWAP(engInitString, p) + SWAP(computerString, p) + SWAP(features, p) + SWAP(fenOverride, p) + SWAP(NPS, h) + SWAP(accumulateTC, h) + SWAP(host, p) } -void -SetPlayer(int player) +int +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; - char buf[MSG_SIZ], *engineName, *p = appData.participants; + char buf[MSG_SIZ], *engineName; for(i=0; i= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1); } + } else if(appData.tourneyType > 1) { + *blackPlayer = curPairing; // in multi-gauntlet, assign gauntlet engines to second, so first an be kept loaded during round + *whitePlayer = curRound + appData.tourneyType; } else if(appData.tourneyType > 0) { *whitePlayer = curPairing; *blackPlayer = curRound + appData.tourneyType; } - // 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; } int -NextTourneyGame(int nr, int *swapColors) +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"); @@ -10053,33 +10779,55 @@ 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; matchGame = 1; roundNr = nr / syncInterval + 1; } - if(first.pr != NoProc || second.pr != NoProc) return 1; // engines already loaded + if(first.pr != NoProc && second.pr != NoProc || nr<0) return 1; // engines already loaded // redefine engines, engine dir, etc. - NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines - SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line - SwapEngines(1); - SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line - SwapEngines(1); // and make that valid for second engine by swapping - InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. - InitEngine(&second, 1); + NamesToList(firstChessProgramNames, command, mnemonic, "all"); // get mnemonics of installed engines + if(first.pr == NoProc) { + 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); + 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 -NextMatchGame() +NextMatchGame () { // performs game initialization that does not invoke engines, and then tries to start the game int res, firstWhite, swapColors = 0; if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed + if(matchMode && appData.debugMode) { // [HGM] debug split: game is part of a match; we might have to create a debug file just for this game + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, appData.nameOfDebugFile, nextGame+1); // expand name of debug file with %d in it + if(strcmp(buf, currentDebugFile)) { // name has changed + FILE *f = fopen(buf, "w"); + if(f) { // if opening the new file failed, just keep using the old one + ASSIGN(currentDebugFile, buf); + fclose(debugFP); + debugFP = f; + } + if(appData.serverFileName) { + if(serverFP) fclose(serverFP); + serverFP = fopen(appData.serverFileName, "w"); + if(serverFP && first.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", first.tidy); + if(serverFP && second.pr != NoProc) fprintf(serverFP, "StartChildProcess (dir=\".\") .\\%s\n", second.tidy); + } + } + } firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement @@ -10093,7 +10841,8 @@ NextMatchGame() TwoMachinesEvent(); } -void UserAdjudicationEvent( int result ) +void +UserAdjudicationEvent (int result) { ChessMove gameResult = GameIsDrawn; @@ -10111,7 +10860,8 @@ void UserAdjudicationEvent( int result ) // [HGM] save: calculate checksum of game to make games easily identifiable -int StringCheckSum(char *s) +int +StringCheckSum (char *s) { int i = 0; if(s==NULL) return 0; @@ -10119,7 +10869,8 @@ int StringCheckSum(char *s) return i; } -int GameCheckSum() +int +GameCheckSum () { int i, sum=0; for(i=backwardMostMove; i= GE_ENGINE1)) { /* If we are playing on ICS, the server decides when the @@ -10247,6 +10997,10 @@ GameEnds(result, resultDetails, 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 @@ -10291,13 +11045,17 @@ GameEnds(result, resultDetails, 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 } } @@ -10365,6 +11123,7 @@ GameEnds(result, resultDetails, whosays) PlayIcsUnfinishedSound(); } } + if(appData.quitNext) { ExitEvent(0); return; } } else if (gameMode == EditGame || gameMode == PlayFromGameFile || gameMode == AnalyzeMode || @@ -10414,6 +11173,7 @@ GameEnds(result, resultDetails, whosays) SendToProgram("quit\n", &first); DoSleep( appData.delayAfterQuit ); DestroyChildProcess(first.pr, first.useSigterm); + first.reload = TRUE; } first.pr = NoProc; } @@ -10439,11 +11199,12 @@ GameEnds(result, resultDetails, 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: @@ -10468,7 +11229,7 @@ GameEnds(result, resultDetails, 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 @@ -10490,7 +11251,7 @@ GameEnds(result, resultDetails, whosays) first.matchWins, second.matchWins, appData.matchGames - (first.matchWins + second.matchWins)); if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title - if(strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney); + if(ranking && strcmp(ranking, "busy") && appData.afterTourney && appData.afterTourney[0]) RunCommand(appData.afterTourney); popupRequested++; // [HGM] crash: postpone to after resetting endingGame if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match first.twoMachinesColor = "black\n"; @@ -10528,9 +11289,7 @@ GameEnds(result, resultDetails, whosays) /* Assumes program was just initialized (initString sent). Leaves program in force mode. */ void -FeedMovesToProgram(cps, upto) - ChessProgramState *cps; - int upto; +FeedMovesToProgram (ChessProgramState *cps, int upto) { int i; @@ -10561,7 +11320,7 @@ FeedMovesToProgram(cps, upto) int -ResurrectChessProgram() +ResurrectChessProgram () { /* The chess program may have exited. If so, restart it and feed it all the moves made so far. */ @@ -10569,8 +11328,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 { @@ -10600,8 +11359,7 @@ ResurrectChessProgram() * Button procedures */ void -Reset(redraw, init) - int redraw, init; +Reset (int redraw, int init) { int i; @@ -10625,6 +11383,7 @@ Reset(redraw, init) lastHint[0] = NULLCHAR; ClearGameInfo(&gameInfo); gameInfo.variant = StringToVariant(appData.variant); + if(gameInfo.variant == VariantNormal && strcmp(appData.variant, "normal")) gameInfo.variant = VariantUnknown; ics_user_moved = ics_clock_paused = FALSE; ics_getting_history = H_FALSE; ics_gamenum = -1; @@ -10638,6 +11397,7 @@ Reset(redraw, init) ClearPremoveHighlights(); gotPremove = FALSE; alarmSounded = FALSE; + killX = killY = -1; // [HGM] lion GameEnds(EndOfFile, NULL, GE_PLAYER); if(appData.serverMovesName != NULL) { @@ -10658,6 +11418,7 @@ Reset(redraw, init) ModeHighlight(); if(appData.icsActive) gameInfo.variant = VariantNormal; currentMove = forwardMostMove = backwardMostMove = 0; + MarkTargetSquares(1); InitPosition(redraw); for (i = 0; i < MAX_MOVES; i++) { if (commentList[i] != NULL) { @@ -10679,10 +11440,11 @@ Reset(redraw, init) DisplayMessage("", ""); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved + ClearMap(); // [HGM] exclude: invalidate map } void -AutoPlayGameLoop() +AutoPlayGameLoop () { for (;;) { if (!AutoPlayOneMove()) @@ -10691,14 +11453,19 @@ 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() +AutoPlayOneMove () { int fromX, fromY, toX, toY; @@ -10709,15 +11476,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(); @@ -10757,8 +11537,7 @@ AutoPlayOneMove() int -LoadGameOneMove(readAhead) - ChessMove readAhead; +LoadGameOneMove (ChessMove readAhead) { int fromX = 0, fromY = 0, toX = 0, toY = 0, done; char promoChar = NULLCHAR; @@ -10799,6 +11578,7 @@ LoadGameOneMove(readAhead) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteKingSideCastle: case WhiteQueenSideCastle: case BlackKingSideCastle: @@ -10820,6 +11600,7 @@ LoadGameOneMove(readAhead) toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; + if(promoChar == ';') promoChar = NULLCHAR; break; case WhiteDrop: @@ -10986,6 +11767,7 @@ LoadGameOneMove(readAhead) thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); + killX = killY = -1; // [HGM] lion: used up currentMove = forwardMostMove; return TRUE; } @@ -10993,11 +11775,7 @@ LoadGameOneMove(readAhead) /* Load the nth game from the given file */ int -LoadGameFromFile(filename, n, title, useList) - char *filename; - int n; - char *title; - /*Boolean*/ int useList; +LoadGameFromFile (char *filename, int n, char *title, int useList) { FILE *f; char buf[MSG_SIZ]; @@ -11035,7 +11813,7 @@ LoadGameFromFile(filename, n, title, useList) void -MakeRegisteredMove() +MakeRegisteredMove () { int fromX, fromY, toX, toY; char promoChar; @@ -11100,11 +11878,7 @@ MakeRegisteredMove() /* Wrapper around LoadGame for use when a Cmail message is loaded */ int -CmailLoadGame(f, gameNumber, title, useList) - FILE *f; - int gameNumber; - char *title; - int useList; +CmailLoadGame (FILE *f, int gameNumber, char *title, int useList) { int retVal; @@ -11145,8 +11919,7 @@ CmailLoadGame(f, gameNumber, title, useList) /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */ int -ReloadGame(offset) - int offset; +ReloadGame (int offset) { int gameNumber = lastLoadGameNumber + offset; if (lastLoadGameFP == NULL) { @@ -11169,7 +11942,7 @@ ReloadGame(offset) int keys[EmptySquare+1]; int -PositionMatches(Board b1, Board b2) +PositionMatches (Board b1, Board b2) { int r, f, sum=0; switch(appData.searchMode) { @@ -11216,7 +11989,8 @@ Move initialSpace[DSIZE+1000]; // gamble on that game will not be more than 500 Move *moveDatabase = initialSpace; unsigned int movePtr, dataSize = DSIZE; -int MakePieceList(Board board, int *counts) +int +MakePieceList (Board board, int *counts) { int r, f, n=Q_PROMO, total=0; for(r=0;r dataSize) { if(appData.debugMode) fprintf(debugFP, "move-cache overflow, enlarge to %d MB\n", dataSize/128); dataSize *= 8; // increase size by factor 8 (512KB -> 4MB -> 32MB -> 256MB -> 2GB) - if(dataSize) newSpace = (Move*) calloc(8*dataSize + 1000, sizeof(Move)); + if(dataSize) newSpace = (Move*) calloc(dataSize + 1000, sizeof(Move)); if(newSpace) { int i; Move *p = moveDatabase, *q = newSpace; @@ -11295,7 +12071,8 @@ int PackGame(Board board) return movePtr; } -int QuickCompare(Board board, int *minCounts, int *maxCounts) +int +QuickCompare (Board board, int *minCounts, int *maxCounts) { // compare according to search mode int r, f; switch(appData.searchMode) @@ -11329,7 +12106,8 @@ int QuickCompare(Board board, int *minCounts, int *maxCounts) return TRUE; } -int QuickScan(Board board, Move *move) +int +QuickScan (Board board, Move *move) { // reconstruct game,and compare all positions in it int cnt=0, stretch=0, total = MakePieceList(board, counts); do { @@ -11352,15 +12130,18 @@ int QuickScan(Board board, Move *move) piece = pieceList[piece]; // first two elements of pieceList contain King numbers from = pieceList[piece]; // so this must be King quickBoard[from] = 0; - quickBoard[to] = piece; pieceList[piece] = to; - move++; - continue; + from = pieceList[(++move)->piece]; // for FRC this has to be done here + quickBoard[from] = 0; // rook + quickBoard[to] = piece; + to = move->to; piece = move->piece; + goto aftercastle; } } if(appData.searchMode > 2) counts[pieceType[quickBoard[to]]]--; // account capture if((total -= (quickBoard[to] != 0)) < soughtTotal) return -1; // piece count dropped below what we search for quickBoard[from] = 0; + aftercastle: quickBoard[to] = piece; pieceList[piece] = to; cnt++; turn ^= 3; @@ -11379,7 +12160,8 @@ int QuickScan(Board board, Move *move) } while(1); } -void InitSearch() +void +InitSearch () { int r, f; flipSearch = FALSE; @@ -11393,12 +12175,12 @@ void 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; @@ -11420,8 +12202,10 @@ void InitSearch() } GameInfo dummyInfo; +static int creatingBook; -int GameContainsPosition(FILE *f, ListGame *lg) +int +GameContainsPosition (FILE *f, ListGame *lg) { int next, btm=0, plyNr=0, scratch=forwardMostMove+2&~1; int fromX, fromY, toX, toY; @@ -11437,7 +12221,7 @@ int GameContainsPosition(FILE *f, ListGame *lg) for(next = WhitePawn; next>8 ^ random()<<6 ^random()<<20; initDone = TRUE; } - if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen); + if(lg->gameInfo.fen) ParseFEN(boards[scratch], &btm, lg->gameInfo.fen, FALSE); else CopyBoard(boards[scratch], initialPosition); // default start position if(lg->moves) { turn = btm + 1; @@ -11481,6 +12265,7 @@ int GameContainsPosition(FILE *f, ListGame *lg) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteKingSideCastle: case WhiteQueenSideCastle: case BlackKingSideCastle: @@ -11524,11 +12309,7 @@ int GameContainsPosition(FILE *f, ListGame *lg) /* Load the nth game from open file f */ int -LoadGame(f, gameNumber, title, useList) - FILE *f; - int gameNumber; - char *title; - int useList; +LoadGame (FILE *f, int gameNumber, char *title, int useList) { ChessMove cm; char buf[MSG_SIZ]; @@ -11549,6 +12330,7 @@ LoadGame(f, gameNumber, title, useList) if (gameMode != BeginningOfGame) { Reset(FALSE, TRUE); } + killX = killY = -1; // [HGM] lion: in case we did not Reset gameFileFP = f; if (lastLoadGameFP != NULL && lastLoadGameFP != f) { @@ -11565,6 +12347,9 @@ LoadGame(f, gameNumber, title, 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; } @@ -11589,7 +12374,7 @@ LoadGame(f, gameNumber, title, useList) yynewfile(f); if (lg && lg->gameInfo.white && lg->gameInfo.black) { - snprintf(buf, sizeof(buf), _("%s vs. %s"), lg->gameInfo.white, + snprintf(buf, sizeof(buf), "%s %s %s", lg->gameInfo.white, _("vs."), lg->gameInfo.black); DisplayTitle(buf); } else if (*title != NULLCHAR) { @@ -11699,6 +12484,7 @@ LoadGame(f, gameNumber, title, useList) break; case NormalMove: + case FirstLeg: /* Only a NormalMove can be at the start of a game * without a position diagram. */ if (lastLoadGameStart == EndOfFile ) { @@ -11760,7 +12546,7 @@ LoadGame(f, gameNumber, title, useList) if (gameInfo.fen != NULL) { Board initial_position; startedFromSetupPosition = TRUE; - if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) { + if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen, TRUE)) { Reset(TRUE, TRUE); DisplayError(_("Bad FEN position in file"), 0); return FALSE; @@ -11883,6 +12669,7 @@ LoadGame(f, gameNumber, title, useList) cm = (ChessMove) Myylex(); } + if(!creatingBook) { if (first.pr == NoProc) { StartChessProgram(&first); } @@ -11895,6 +12682,7 @@ LoadGame(f, gameNumber, title, useList) } DisplayBothClocks(); } + } /* [HGM] server: flag to write setup moves in broadcast file as one */ loadFlag = appData.suppressLoadMoves; @@ -11956,12 +12744,25 @@ LoadGame(f, gameNumber, title, 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(gameInfo.result == GameUnfinished && gameInfo.resultDetails && appData.clockMode) { + long int w, b; // [HGM] adjourn: restore saved clock times + char *p = strstr(gameInfo.resultDetails, "(Clocks:"); + if(p && sscanf(p+8, "%ld,%ld", &w, &b) == 2) { + timeRemaining[0][forwardMostMove] = whiteTimeRemaining = 1000*w + 500; + timeRemaining[1][forwardMostMove] = blackTimeRemaining = 1000*b + 500; + } } - if (!matchMode && pos >= 0) { + if(creatingBook) return TRUE; + if (!matchMode && pos > 0) { ToNrEvent(pos); // [HGM] no autoplay if selected on position } else if (matchMode || appData.timeDelay == 0) { @@ -11979,8 +12780,7 @@ LoadGame(f, gameNumber, title, useList) /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */ int -ReloadPosition(offset) - int offset; +ReloadPosition (int offset) { int positionNumber = lastLoadPositionNumber + offset; if (lastLoadPositionFP == NULL) { @@ -11997,10 +12797,7 @@ ReloadPosition(offset) /* Load the nth position from the given file */ int -LoadPositionFromFile(filename, n, title) - char *filename; - int n; - char *title; +LoadPositionFromFile (char *filename, int n, char *title) { FILE *f; char buf[MSG_SIZ]; @@ -12021,10 +12818,7 @@ LoadPositionFromFile(filename, n, title) /* Load the nth position from the given open file, and close it */ int -LoadPosition(f, positionNumber, title) - FILE *f; - int positionNumber; - char *title; +LoadPosition (FILE *f, int positionNumber, char *title) { char *p, line[MSG_SIZ]; Board initial_position; @@ -12089,7 +12883,7 @@ LoadPosition(f, positionNumber, title) } if (fenMode) { - if (!ParseFEN(initial_position, &blackPlaysFirst, line)) { + if (!ParseFEN(initial_position, &blackPlaysFirst, line, TRUE)) { DisplayError(_("Bad FEN position in file"), 0); return FALSE; } @@ -12156,8 +12950,7 @@ int i, j; void -CopyPlayerNameIntoFileName(dest, src) - char **dest, *src; +CopyPlayerNameIntoFileName (char **dest, char *src) { while (*src != NULLCHAR && *src != ',') { if (*src == ' ') { @@ -12169,8 +12962,8 @@ CopyPlayerNameIntoFileName(dest, src) } } -char *DefaultFileName(ext) - char *ext; +char * +DefaultFileName (char *ext) { static char def[MSG_SIZ]; char *p; @@ -12190,9 +12983,7 @@ char *DefaultFileName(ext) /* Save the current game to the given file */ int -SaveGameToFile(filename, append) - char *filename; - int append; +SaveGameToFile (char *filename, int append) { FILE *f; char buf[MSG_SIZ]; @@ -12226,8 +13017,7 @@ SaveGameToFile(filename, append) } char * -SavePart(str) - char *str; +SavePart (char *str) { static char buf[MSG_SIZ]; char *p; @@ -12244,8 +13034,8 @@ SavePart(str) #define PGN_SIDE_WHITE 0 #define PGN_SIDE_BLACK 1 -/* [AS] */ -static int FindFirstMoveOutOfBook( int side ) +static int +FindFirstMoveOutOfBook (int side) { int result = -1; @@ -12288,8 +13078,8 @@ static int FindFirstMoveOutOfBook( int side ) return result; } -/* [AS] */ -void GetOutOfBookInfo( char * buf ) +void +GetOutOfBookInfo (char * buf) { int oob[2]; int i; @@ -12320,11 +13110,9 @@ void GetOutOfBookInfo( char * buf ) /* Save game in PGN style and close the file */ int -SaveGamePGN(f) - FILE *f; +SaveGamePGN (FILE *f) { int i, offset, linelen, newblock; - time_t tm; // char *movetext; char numtext[32]; int movelen, numlen, blank; @@ -12332,12 +13120,12 @@ SaveGamePGN(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); @@ -12474,8 +13262,11 @@ SaveGamePGN(f) /* Print result */ if (gameInfo.resultDetails != NULL && gameInfo.resultDetails[0] != NULLCHAR) { - fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails, - PGNResult(gameInfo.result)); + char buf[MSG_SIZ], *p = gameInfo.resultDetails; + if(gameInfo.result == GameUnfinished && appData.clockMode && + (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay)) // [HGM] adjourn: save clock settings + snprintf(buf, MSG_SIZ, "%s (Clocks: %ld, %ld)", p, whiteTimeRemaining/1000, blackTimeRemaining/1000), p = buf; + fprintf(f, "{%s} %s\n\n", p, PGNResult(gameInfo.result)); } else { fprintf(f, "%s\n\n", PGNResult(gameInfo.result)); } @@ -12487,8 +13278,7 @@ SaveGamePGN(f) /* Save game in old style and close the file */ int -SaveGameOldStyle(f) - FILE *f; +SaveGameOldStyle (FILE *f) { int i, offset; time_t tm; @@ -12552,10 +13342,7 @@ SaveGameOldStyle(f) /* Save the current game to open file f and close the file */ int -SaveGame(f, dummy, dummy2) - FILE *f; - int dummy; - char *dummy2; +SaveGame (FILE *f, int dummy, char *dummy2) { if (gameMode == EditPosition) EditPositionDone(TRUE); lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving @@ -12567,8 +13354,7 @@ SaveGame(f, dummy, dummy2) /* Save the current position to the given file */ int -SavePositionToFile(filename) - char *filename; +SavePositionToFile (char *filename) { FILE *f; char buf[MSG_SIZ]; @@ -12596,10 +13382,7 @@ SavePositionToFile(filename) /* Save the current position to the given open file and close the file */ int -SavePosition(f, dummy, dummy2) - FILE *f; - int dummy; - char *dummy2; +SavePosition (FILE *f, int dummy, char *dummy2) { time_t tm; char *fen; @@ -12614,7 +13397,7 @@ SavePosition(f, dummy, dummy2) PrintPosition(f, currentMove); fprintf(f, "--------------]\n"); } else { - fen = PositionToFEN(currentMove, NULL); + fen = PositionToFEN(currentMove, NULL, 1); fprintf(f, "%s\n", fen); free(fen); } @@ -12623,8 +13406,7 @@ SavePosition(f, dummy, dummy2) } void -ReloadCmailMsgEvent(unregister) - int unregister; +ReloadCmailMsgEvent (int unregister) { #if !WIN32 static char *inFilename = NULL; @@ -12690,7 +13472,7 @@ ReloadCmailMsgEvent(unregister) } int -RegisterMove() +RegisterMove () { FILE *f; char string[MSG_SIZ]; @@ -12780,7 +13562,7 @@ RegisterMove() } void -MailMoveEvent() +MailMoveEvent () { #if !WIN32 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1"; @@ -12868,7 +13650,7 @@ MailMoveEvent() } char * -CmailMsg() +CmailMsg () { #if WIN32 return NULL; @@ -12941,7 +13723,7 @@ CmailMsg() } void -ResetGameEvent() +ResetGameEvent () { if (gameMode == Training) SetTrainingModeOff(); @@ -12955,8 +13737,7 @@ ResetGameEvent() } void -ExitEvent(status) - int status; +ExitEvent (int status) { exiting++; if (exiting > 2) { @@ -13018,15 +13799,45 @@ ExitEvent(status) } void -PauseEvent() +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) fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing); 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(); @@ -13038,7 +13849,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; @@ -13069,12 +13880,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; @@ -13083,7 +13912,7 @@ PauseEvent() } void -EditCommentEvent() +EditCommentEvent () { char title[MSG_SIZ]; @@ -13100,7 +13929,7 @@ EditCommentEvent() void -EditTagsEvent() +EditTagsEvent () { char *tags = PGNTags(&gameInfo); bookUp = FALSE; @@ -13109,16 +13938,74 @@ EditTagsEvent() } void -AnalyzeModeEvent() +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; @@ -13134,17 +14021,28 @@ AnalyzeModeEvent() StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + return 1; } void -AnalyzeFileEvent() +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; @@ -13155,16 +14053,16 @@ 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 -MachineWhiteEvent() +MachineWhiteEvent () { char buf[MSG_SIZ]; char *bookHit = NULL; @@ -13204,7 +14102,7 @@ MachineWhiteEvent() pausing = FALSE; ModeHighlight(); SetGameInfo(); - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); if (first.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black); @@ -13245,7 +14143,7 @@ MachineWhiteEvent() } void -MachineBlackEvent() +MachineBlackEvent () { char buf[MSG_SIZ]; char *bookHit = NULL; @@ -13281,7 +14179,7 @@ MachineBlackEvent() pausing = FALSE; ModeHighlight(); SetGameInfo(); - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); DisplayTitle(buf); if (first.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white); @@ -13321,35 +14219,35 @@ MachineBlackEvent() void -DisplayTwoMachinesTitle() +DisplayTwoMachinesTitle () { char buf[MSG_SIZ]; if (appData.matchGames > 0) { if(appData.tourneyFile[0]) { - snprintf(buf, MSG_SIZ, _("%s vs. %s (%d/%d%s)"), - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)", + gameInfo.white, _("vs."), gameInfo.black, nextGame+1, appData.matchGames+1, appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr"); - } else + } else if (first.twoMachinesColor[0] == 'w') { - snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"), - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", + gameInfo.white, _("vs."), gameInfo.black, first.matchWins, second.matchWins, matchGame - 1 - (first.matchWins + second.matchWins)); } else { - snprintf(buf, MSG_SIZ, _("%s vs. %s (%d-%d-%d)"), - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", + gameInfo.white, _("vs."), gameInfo.black, second.matchWins, first.matchWins, matchGame - 1 - (first.matchWins + second.matchWins)); } } else { - snprintf(buf, MSG_SIZ, _("%s vs. %s"), gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); } DisplayTitle(buf); } void -SettingsMenuIfReady() +SettingsMenuIfReady () { if (second.lastPing != second.lastPong) { DisplayMessage("", _("Waiting for second chess program")); @@ -13362,17 +14260,18 @@ SettingsMenuIfReady() } int -WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry) +WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry) { char buf[MSG_SIZ]; if (cps->pr == NoProc) { 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(); - snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which); + if(retry != TwoMachinesEventIfReady) FreezeUI(); + snprintf(buf, MSG_SIZ, _("Starting %s chess program"), _(cps->which)); DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); } @@ -13400,7 +14299,7 @@ TwoMachinesEvent P((void)) case MachinePlaysWhite: case MachinePlaysBlack: if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) { - DisplayError(_("Wait until your turn,\nor select Move Now"), 0); + DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0); return; } /* fall through */ @@ -13424,14 +14323,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); @@ -13447,6 +14355,7 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait); return; } + // we are now committed to starting the game stalling = 0; DisplayMessage("", ""); if (startedFromSetupPosition) { @@ -13460,7 +14369,7 @@ TwoMachinesEvent P((void)) } gameMode = TwoMachinesPlay; - pausing = FALSE; + pausing = startingEngine = FALSE; ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); @@ -13519,7 +14428,7 @@ TwoMachinesEvent P((void)) } void -TrainingEvent() +TrainingEvent () { if (gameMode == Training) { SetTrainingModeOff(); @@ -13542,7 +14451,7 @@ TrainingEvent() } void -IcsClientEvent() +IcsClientEvent () { if (!appData.icsActive) return; switch (gameMode) { @@ -13576,9 +14485,8 @@ IcsClientEvent() return; } - void -EditGameEvent() +EditGameEvent () { int i; @@ -13668,7 +14576,7 @@ EditGameEvent() void -EditPositionEvent() +EditPositionEvent () { if (gameMode == EditPosition) { EditGameEvent(); @@ -13689,10 +14597,11 @@ EditPositionEvent() currentMove = forwardMostMove = backwardMostMove = 0; HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); DisplayMove(-1); + if(!appData.pieceMenu) DisplayMessage(_("Click clock to clear board"), ""); } void -ExitAnalyzeMode() +ExitAnalyzeMode () { /* [DM] icsEngineAnalyze - possible call from other functions */ if (appData.icsEngineAnalyze) { @@ -13701,14 +14610,14 @@ 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; } void -EditPositionDone(Boolean fakeRights) +EditPositionDone (Boolean fakeRights) { int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing; @@ -13717,14 +14626,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; currentMove= forwardMostMove) to = forwardMostMove; @@ -14468,7 +15419,7 @@ ToNrEvent(int to) } void -RevertEvent(Boolean annotate) +RevertEvent (Boolean annotate) { if(PopTail(annotate)) { // [HGM] vari: restore old game tail return; @@ -14486,13 +15437,13 @@ RevertEvent(Boolean annotate) } void -RetractMoveEvent() +RetractMoveEvent () { switch (gameMode) { case MachinePlaysWhite: case MachinePlaysBlack: if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) { - DisplayError(_("Wait until your turn,\nor select Move Now"), 0); + DisplayError(_("Wait until your turn,\nor select 'Move Now'."), 0); return; } if (forwardMostMove < 2) return; @@ -14525,7 +15476,7 @@ RetractMoveEvent() } void -MoveNowEvent() +MoveNowEvent () { ChessProgramState *cps; @@ -14560,7 +15511,7 @@ MoveNowEvent() } void -TruncateGameEvent() +TruncateGameEvent () { EditGameEvent(); if (gameMode != EditGame) return; @@ -14568,7 +15519,7 @@ TruncateGameEvent() } void -TruncateGame() +TruncateGame () { CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate if (forwardMostMove > currentMove) { @@ -14584,20 +15535,20 @@ TruncateGame() } void -HintEvent() +HintEvent () { if (appData.noChessProgram) return; switch (gameMode) { case MachinePlaysWhite: if (WhiteOnMove(forwardMostMove)) { - DisplayError(_("Wait until your turn"), 0); + DisplayError(_("Wait until your turn."), 0); return; } break; case BeginningOfGame: case MachinePlaysBlack: if (!WhiteOnMove(forwardMostMove)) { - DisplayError(_("Wait until your turn"), 0); + DisplayError(_("Wait until your turn."), 0); return; } break; @@ -14610,20 +15561,54 @@ HintEvent() } void -BookEvent() +CreateBookEvent () +{ + ListGame * lg = (ListGame *) gameList.head; + FILE *f, *g; + int nItem; + static int secondTime = FALSE; + + if( !(f = GameFile()) || ((ListGame *) gameList.tailPred)->number <= 0 ) { + DisplayError(_("Game list not loaded or empty"), 0); + return; + } + + if(!secondTime && (g = fopen(appData.polyglotBook, "r"))) { + fclose(g); + 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; switch (gameMode) { case MachinePlaysWhite: if (WhiteOnMove(forwardMostMove)) { - DisplayError(_("Wait until your turn"), 0); + DisplayError(_("Wait until your turn."), 0); return; } break; case BeginningOfGame: case MachinePlaysBlack: if (!WhiteOnMove(forwardMostMove)) { - DisplayError(_("Wait until your turn"), 0); + DisplayError(_("Wait until your turn."), 0); return; } break; @@ -14641,7 +15626,7 @@ BookEvent() } void -AboutGameEvent() +AboutGameEvent () { char *tags = PGNTags(&gameInfo); TagsPopUp(tags, CmailMsg()); @@ -14651,9 +15636,7 @@ AboutGameEvent() /* end button procedures */ void -PrintPosition(fp, move) - FILE *fp; - int move; +PrintPosition (FILE *fp, int move) { int i, j; @@ -14671,8 +15654,7 @@ PrintPosition(fp, move) } void -PrintOpponents(fp) - FILE *fp; +PrintOpponents (FILE *fp) { if (gameInfo.white != NULL) { fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black); @@ -14683,10 +15665,9 @@ PrintOpponents(fp) /* Find last component of program's own name, using some heuristics */ void -TidyProgramName(prog, host, buf) - char *prog, *host, buf[MSG_SIZ]; +TidyProgramName (char *prog, char *host, char buf[MSG_SIZ]) { - char *p, *q; + char *p, *q, c; int local = (strcmp(host, "localhost") == 0); while (!local && (p = strchr(prog, ';')) != NULL) { p++; @@ -14703,7 +15684,8 @@ TidyProgramName(prog, host, buf) while (p >= prog && *p != '/' && *p != '\\') p--; p++; if(p == prog && *p == '"') p++; - if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4; + c = *q; *q = 0; + if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) *q = c, q -= 4; else *q = c; memcpy(buf, p, q - p); buf[q - p] = NULLCHAR; if (!local) { @@ -14713,7 +15695,7 @@ TidyProgramName(prog, host, buf) } char * -TimeControlTagValue() +TimeControlTagValue () { char buf[MSG_SIZ]; if (!appData.clockMode) { @@ -14729,13 +15711,15 @@ TimeControlTagValue() } void -SetGameInfo() +SetGameInfo () { /* This routine is used only for certain modes */ VariantClass v = gameInfo.variant; 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; @@ -14827,15 +15811,13 @@ SetGameInfo() } void -ReplaceComment(index, text) - int index; - char *text; +ReplaceComment (int index, char *text) { int len; 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 @@ -14870,8 +15852,7 @@ ReplaceComment(index, text) } void -CrushCRs(text) - char *text; +CrushCRs (char *text) { char *p = text; char *q = text; @@ -14885,21 +15866,21 @@ CrushCRs(text) } void -AppendComment(index, text, addBraces) - int index; - char *text; - Boolean addBraces; // [HGM] braces: tells if we should add {} +AppendComment (int index, char *text, Boolean addBraces) +/* addBraces tells if we should add {} */ { 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); while (*text == '\n') text++; len = strlen(text); while (len > 0 && text[len - 1] == '\n') len--; + text[len] = NULLCHAR; if (len == 0) return; @@ -14934,7 +15915,8 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); } } -static char * FindStr( char * text, char * sub_text ) +static char * +FindStr (char * text, char * sub_text) { char * result = strstr( text, sub_text ); @@ -14947,7 +15929,8 @@ static char * FindStr( char * text, char * sub_text ) /* [AS] Try to extract PV info from PGN comment */ /* [HGM] PV time: and then remove it, to prevent it appearing twice */ -char *GetInfoFromComment( int index, char * text ) +char * +GetInfoFromComment (int index, char * text) { char * sep = text, *p; @@ -14957,8 +15940,11 @@ char *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; @@ -14988,6 +15974,7 @@ char *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; @@ -15011,7 +15998,7 @@ char *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 @@ -15042,9 +16029,7 @@ char *GetInfoFromComment( int index, char * text ) } void -SendToProgram(message, cps) - char *message; - ChessProgramState *cps; +SendToProgram (char *message, ChessProgramState *cps) { int count, outCount, error; char buf[MSG_SIZ]; @@ -15058,6 +16043,10 @@ SendToProgram(message, cps) fprintf(debugFP, "%ld >%-6s: %s", SubtractTimeMarks(&now, &programStartTime), cps->which, message); + if(serverFP) + fprintf(serverFP, "%ld >%-6s: %s", + SubtractTimeMarks(&now, &programStartTime), + cps->which, message), fflush(serverFP); } count = strlen(message); @@ -15084,12 +16073,7 @@ SendToProgram(message, cps) } void -ReceiveFromProgram(isr, closure, message, count, error) - InputSourceRef isr; - VOIDSTAR closure; - char *message; - int count; - int error; +ReceiveFromProgram (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error) { char *end_str; char buf[MSG_SIZ]; @@ -15099,10 +16083,10 @@ ReceiveFromProgram(isr, closure, message, count, error) if (count <= 0) { if (count == 0) { RemoveInputSource(cps->isr); - if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"), _(cps->which), cps->program); - if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ + if(LoadError(cps->userError ? NULL : buf, cps)) return; // [HGM] should not generate fatal error during engine load + if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program); if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; } @@ -15151,7 +16135,7 @@ ReceiveFromProgram(isr, closure, message, count, error) 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); @@ -15164,6 +16148,11 @@ ReceiveFromProgram(isr, closure, message, count, error) SubtractTimeMarks(&now, &programStartTime), cps->which, quote, message); + if(serverFP) + fprintf(serverFP, "%ld <%-6s: %s%s\n", + SubtractTimeMarks(&now, &programStartTime), cps->which, + quote, + message), fflush(serverFP); } } @@ -15179,10 +16168,7 @@ ReceiveFromProgram(isr, closure, message, count, error) void -SendTimeControl(cps, mps, tc, inc, sd, st) - ChessProgramState *cps; - int mps, inc, sd, st; - long tc; +SendTimeControl (ChessProgramState *cps, int mps, long tc, int inc, int sd, int st) { char buf[MSG_SIZ]; int seconds; @@ -15246,7 +16232,8 @@ SendTimeControl(cps, mps, tc, inc, sd, st) } } -ChessProgramState *WhitePlayer() +ChessProgramState * +WhitePlayer () /* [HGM] return pointer to 'first' or 'second', depending on who plays white */ { if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || @@ -15256,9 +16243,7 @@ ChessProgramState *WhitePlayer() } void -SendTimeRemaining(cps, machineWhite) - ChessProgramState *cps; - int /*boolean*/ machineWhite; +SendTimeRemaining (ChessProgramState *cps, int machineWhite) { char message[MSG_SIZ]; long time, otime; @@ -15276,9 +16261,6 @@ SendTimeRemaining(cps, machineWhite) } /* [HGM] translate opponent's time by time-odds factor */ otime = (otime * cps->other->timeOdds) / cps->timeOdds; - if (appData.debugMode) { - fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds); - } if (time <= 0) time = 1; if (otime <= 0) otime = 1; @@ -15290,12 +16272,29 @@ SendTimeRemaining(cps, machineWhite) SendToProgram(message, cps); } +char * +EngineDefinedVariant (ChessProgramState *cps, int n) +{ // return name of n-th unknown variant that engine supports + static char buf[MSG_SIZ]; + char *p, *s = cps->variants; + if(!s) return NULL; + do { // parse string from variants feature + VariantClass v; + p = strchr(s, ','); + if(p) *p = NULLCHAR; + v = StringToVariant(s); + if(v == VariantNormal && strcmp(s, "normal") && !strstr(s, "_normal")) v = VariantUnknown; // garbage is recognized as normal + if(v == VariantUnknown) { // non-standard variant in list of engine-supported variants + if(--n < 0) safeStrCpy(buf, s, MSG_SIZ); + } + if(p) *p++ = ','; + if(n < 0) return buf; + } while(s = p); + return NULL; +} + int -BoolFeature(p, name, loc, cps) - char **p; - char *name; - int *loc; - ChessProgramState *cps; +BoolFeature (char **p, char *name, int *loc, ChessProgramState *cps) { char buf[MSG_SIZ]; int len = strlen(name); @@ -15315,11 +16314,7 @@ BoolFeature(p, name, loc, cps) } int -IntFeature(p, name, loc, cps) - char **p; - char *name; - int *loc; - ChessProgramState *cps; +IntFeature (char **p, char *name, int *loc, ChessProgramState *cps) { char buf[MSG_SIZ]; int len = strlen(name); @@ -15335,18 +16330,15 @@ IntFeature(p, name, loc, cps) } int -StringFeature(p, name, loc, cps) - 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); @@ -15357,7 +16349,7 @@ StringFeature(p, name, loc, cps) } int -ParseOption(Option *opt, ChessProgramState *cps) +ParseOption (Option *opt, ChessProgramState *cps) // [HGM] options: process the string that defines an engine option, and determine // name, type, default value, and allowed value range { @@ -15398,8 +16390,8 @@ ParseOption(Option *opt, ChessProgramState *cps) if(sscanf(p, " -check %d", &def) < 1) return FALSE; opt->value = (def != 0); opt->type = CheckBox; - } else if(p = strstr(opt->name, " -combo ")) { - opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type + } else if(p = strstr(opt->name, " -combo ")) { + opt->textValue = (char*) (opt->choice = &cps->comboList[cps->comboCnt]); // cheat with pointer type cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices if(*q == '*') cps->comboList[cps->comboCnt-1]++; opt->value = n = 0; @@ -15448,9 +16440,7 @@ ParseOption(Option *opt, ChessProgramState *cps) } void -FeatureDone(cps, val) - ChessProgramState* cps; - int val; +FeatureDone (ChessProgramState *cps, int val) { DelayedEventCallback cb = GetDelayedEvent(); if ((cb == InitBackEnd3 && cps == &first) || @@ -15461,16 +16451,15 @@ FeatureDone(cps, val) ScheduleDelayedEvent(cb, val ? 1 : 3600000); } cps->initDone = val; + if(val) cps->reload = FALSE; } /* Parse feature command from engine */ void -ParseFeatures(args, cps) - char* args; - ChessProgramState *cps; +ParseFeatures (char *args, ChessProgramState *cps) { char *p = args; - char *q; + char *q = NULL; int val; char buf[MSG_SIZ]; @@ -15504,9 +16493,10 @@ ParseFeatures(args, cps) if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue; if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue; if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue; + if (BoolFeature(&p, "exclude", &cps->excludeMoves, cps)) continue; if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue; if (BoolFeature(&p, "name", &cps->sendName, cps)) continue; - if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */ + if (BoolFeature(&p, "pause", &cps->pause, cps)) continue; // [HGM] pause if (IntFeature(&p, "done", &val, cps)) { FeatureDone(cps, val); continue; @@ -15517,13 +16507,17 @@ ParseFeatures(args, cps) /* End of additions by Tord */ /* [HGM] added features: */ + if (BoolFeature(&p, "highlight", &cps->highlight, cps)) continue; if (BoolFeature(&p, "debug", &cps->debug, cps)) continue; if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue; if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue; if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue; if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue; - if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) { + if (StringFeature(&p, "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 = 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); @@ -15559,8 +16553,7 @@ ParseFeatures(args, cps) } void -PeriodicUpdatesEvent(newState) - int newState; +PeriodicUpdatesEvent (int newState) { if (newState == appData.periodicUpdates) return; @@ -15578,8 +16571,7 @@ PeriodicUpdatesEvent(newState) } void -PonderNextMoveEvent(newState) - int newState; +PonderNextMoveEvent (int newState) { if (newState == appData.ponderNextMove) return; if (gameMode == EditPosition) EditPositionDone(TRUE); @@ -15599,9 +16591,7 @@ PonderNextMoveEvent(newState) } void -NewSettingEvent(option, feature, command, value) - char *command; - int option, value, *feature; +NewSettingEvent (int option, int *feature, char *command, int value) { char buf[MSG_SIZ]; @@ -15614,7 +16604,7 @@ NewSettingEvent(option, feature, command, value) } void -ShowThinkingEvent() +ShowThinkingEvent () // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup { static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated @@ -15641,8 +16631,7 @@ ShowThinkingEvent() } void -AskQuestionEvent(title, question, replyPrefix, which) - char *title; char *question; char *replyPrefix; char *which; +AskQuestionEvent (char *title, char *question, char *replyPrefix, char *which) { ProcRef pr = (which[0] == '1') ? first.pr : second.pr; if (pr == NoProc) return; @@ -15650,11 +16639,11 @@ AskQuestionEvent(title, question, replyPrefix, which) } void -TypeInEvent(char firstChar) +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 @@ -15664,7 +16653,7 @@ TypeInEvent(char firstChar) } void -TypeInDoneEvent(char *move) +TypeInDoneEvent (char *move) { Board board; int n, fromX, fromY, toX, toY; @@ -15672,7 +16661,7 @@ TypeInDoneEvent(char *move) ChessMove moveType; // [HGM] FENedit - if(gameMode == EditPosition && ParseFEN(board, &n, move) ) { + if(gameMode == EditPosition && ParseFEN(board, &n, move, TRUE) ) { EditPositionPasteFEN(move); return; } @@ -15681,17 +16670,25 @@ TypeInDoneEvent(char *move) ToNrEvent(2*n-1); return; } + // undocumented kludge: allow command-line option to be typed in! + // (potentially fatal, and does not implement the effect of the option.) + // should only be used for options that are values on which future decisions will be made, + // and definitely not on options that would be used during initialization. + if(strstr(move, "!!! -") == move) { + ParseArgsFromString(move+4); + return; + } - if (gameMode != EditGame && currentMove != forwardMostMove && + 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")); } @@ -15699,8 +16696,7 @@ TypeInDoneEvent(char *move) } void -DisplayMove(moveNumber) - int moveNumber; +DisplayMove (int moveNumber) { char message[MSG_SIZ]; char res[MSG_SIZ]; @@ -15757,9 +16753,7 @@ DisplayMove(moveNumber) } void -DisplayComment(moveNumber, text) - int moveNumber; - char *text; +DisplayComment (int moveNumber, char *text) { char title[MSG_SIZ]; @@ -15781,8 +16775,7 @@ DisplayComment(moveNumber, text) * ioctl, which does not work properly on some flavors of Unix. */ void -Attention(cps) - ChessProgramState *cps; +Attention (ChessProgramState *cps) { #if ATTENTION if (!cps->useSigint) return; @@ -15809,7 +16802,7 @@ Attention(cps) } int -CheckFlags() +CheckFlags () { if (whiteTimeRemaining <= 0) { if (!whiteFlag) { @@ -15859,7 +16852,7 @@ CheckFlags() } void -CheckTimeControl() +CheckTimeControl () { if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode gameMode == PlayFromGameFile || forwardMostMove == 0) return; @@ -15884,7 +16877,7 @@ CheckTimeControl() } void -DisplayBothClocks() +DisplayBothClocks () { int wom = gameMode == EditPosition ? !blackPlaysFirst : WhiteOnMove(currentMove); @@ -15906,8 +16899,7 @@ DisplayBothClocks() /* Get the current time as a TimeMark */ void -GetTimeMark(tm) - TimeMark *tm; +GetTimeMark (TimeMark *tm) { #if HAVE_GETTIMEOFDAY @@ -15939,8 +16931,7 @@ GetTimeMark(tm) time marks. We assume the difference will fit in a long! */ long -SubtractTimeMarks(tm2, tm1) - TimeMark *tm2, *tm1; +SubtractTimeMarks (TimeMark *tm2, TimeMark *tm1) { return 1000L*(tm2->sec - tm1->sec) + (long) (tm2->ms - tm1->ms); @@ -15961,8 +16952,7 @@ static TimeMark tickStartTM; static long intendedTickLength; long -NextTickLength(timeRemaining) - long timeRemaining; +NextTickLength (long timeRemaining) { long nominalTickLength, nextTickLength; @@ -15978,7 +16968,7 @@ NextTickLength(timeRemaining) /* Adjust clock one minute up or down */ void -AdjustClock(Boolean which, int dir) +AdjustClock (Boolean which, int dir) { if(appData.autoCallFlag) { DisplayError(_("Clock adjustment not allowed in auto-flag mode"), 0); return; } if(which) blackTimeRemaining += 60000*dir; @@ -15989,7 +16979,7 @@ AdjustClock(Boolean which, int dir) /* Stop clocks and reset to a fresh time control */ void -ResetClocks() +ResetClocks () { (void) StopClockTimer(); if (appData.icsActive) { @@ -16015,7 +17005,7 @@ ResetClocks() /* Decrement running clock by amount of time that has passed */ void -DecrementClocks() +DecrementClocks () { long timeRemaining; long lastTickLength, fudge; @@ -16059,6 +17049,16 @@ DecrementClocks() } if (CheckFlags()) return; + if(twoBoards) { // count down secondary board's clocks as well + activePartnerTime -= lastTickLength; + partnerUp = 1; + if(activePartner == 'W') + DisplayWhiteClock(activePartnerTime, TRUE); // the counting clock is always the highlighted one! + else + DisplayBlackClock(activePartnerTime, TRUE); + partnerUp = 0; + } + tickStartTM = now; intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge; StartClockTimer(intendedTickLength); @@ -16095,7 +17095,7 @@ DecrementClocks() from the color that is *not* on move now. */ void -SwitchClocks(int newMoveNr) +SwitchClocks (int newMoveNr) { long lastTickLength; TimeMark now; @@ -16158,7 +17158,7 @@ SwitchClocks(int newMoveNr) /* Stop both clocks */ void -StopClocks() +StopClocks () { long lastTickLength; TimeMark now; @@ -16184,7 +17184,7 @@ StopClocks() /* Start clock of player on move. Time may have been reset, so if clock is already running, stop and restart it. */ void -StartClocks() +StartClocks () { (void) StopClockTimer(); /* in case it was running already */ DisplayBothClocks(); @@ -16215,8 +17215,7 @@ StartClocks() } char * -TimeString(ms) - long ms; +TimeString (long ms) { long second, minute, hour, day; char *sign = ""; @@ -16263,8 +17262,7 @@ TimeString(ms) * This is necessary because some C libraries aren't ANSI C compliant yet. */ char * -StrStr(string, match) - char *string, *match; +StrStr (char *string, char *match) { int i, length; @@ -16278,8 +17276,7 @@ StrStr(string, match) } char * -StrCaseStr(string, match) - char *string, *match; +StrCaseStr (char *string, char *match) { int i, j, length; @@ -16298,8 +17295,7 @@ StrCaseStr(string, match) #ifndef _amigados int -StrCaseCmp(s1, s2) - char *s1, *s2; +StrCaseCmp (char *s1, char *s2) { char c1, c2; @@ -16314,24 +17310,21 @@ StrCaseCmp(s1, s2) int -ToLower(c) - int c; +ToLower (int c) { return isupper(c) ? tolower(c) : c; } int -ToUpper(c) - int c; +ToUpper (int c) { return islower(c) ? toupper(c) : c; } #endif /* !_amigados */ char * -StrSave(s) - char *s; +StrSave (char *s) { char *ret; @@ -16343,8 +17336,7 @@ StrSave(s) } char * -StrSavePtr(s, savePtr) - char *s, **savePtr; +StrSavePtr (char *s, char **savePtr) { if (*savePtr) { free(*savePtr); @@ -16356,7 +17348,7 @@ StrSavePtr(s, savePtr) } char * -PGNDate() +PGNDate () { time_t clock; struct tm *tm; @@ -16371,9 +17363,7 @@ PGNDate() char * -PositionToFEN(move, overrideCastling) - int move; - char *overrideCastling; +PositionToFEN (int move, char *overrideCastling, int moveCounts) { int i, j, fromX, fromY, toX, toY; int whiteToPlay; @@ -16405,7 +17395,7 @@ PositionToFEN(move, overrideCastling) *p++ = '+'; piece = (ChessSquare)(DEMOTED piece); } - *p++ = PieceToChar(piece); + *p++ = (piece == DarkSquare ? '*' : PieceToChar(piece)); if(p[-1] == '~') { /* [HGM] flag promoted pieces as '~' (Crazyhouse) */ p[-1] = PieceToChar((ChessSquare)(DEMOTED piece)); @@ -16472,14 +17462,30 @@ PositionToFEN(move, 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 */ @@ -16487,7 +17493,8 @@ PositionToFEN(move, 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; @@ -16519,9 +17526,10 @@ PositionToFEN(move, 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++) @@ -16533,45 +17541,40 @@ PositionToFEN(move, 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); } Boolean -ParseFEN(board, blackPlaysFirst, fen) - Board board; - int *blackPlaysFirst; - char *fen; +ParseFEN (Board board, int *blackPlaysFirst, char *fen, Boolean autoSize) { - int i, j; + int i, j, k, w=0; char *p, c; - int emptycount; + int emptycount, virgin[BOARD_FILES]; ChessSquare piece; p = fen; - /* [HGM] by default clear Crazyhouse holdings, if present */ - if(gameInfo.holdingsWidth) { - for(i=0; i= 0; i--) { j = 0; for (;;) { - if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) { - if (*p == '/') p++; + if (*p == '/' || *p == ' ' || *p == '[' ) { + if(j > w) w = j; emptycount = gameInfo.boardWidth - j; while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; + if (*p == '/') p++; + else if(autoSize) { // we stumbled unexpectedly into end of board + for(k=i; k= 10) } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */ @@ -16580,6 +17583,8 @@ ParseFEN(board, blackPlaysFirst, fen) while (emptycount--) board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; #endif + } else if (*p == '*') { + board[i][(j++)+gameInfo.holdingsWidth] = DarkSquare; p++; } else if (isdigit(*p)) { emptycount = *p++ - '0'; while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */ @@ -16591,7 +17596,7 @@ ParseFEN(board, blackPlaysFirst, fen) if(*p=='+') { piece = CharToPiece(*++p); if(piece == EmptySquare) return FALSE; /* unknown piece */ - piece = (ChessSquare) (PROMOTED piece ); p++; + piece = (ChessSquare) (CHUPROMOTED piece ); p++; if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */ } else piece = CharToPiece(*p++); @@ -16609,6 +17614,18 @@ ParseFEN(board, blackPlaysFirst, fen) } while (*p == '/' || *p == ' ') p++; + if(autoSize) appData.NrFiles = w, InitPosition(TRUE); + + /* [HGM] by default clear Crazyhouse holdings, if present */ + if(gameInfo.holdingsWidth) { + 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