X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=12a82a53a89bcb3160c8cc1362a4e961b3b9738c;hb=36a3483dd6f8361b0ccb7c5648e83922883bb9a6;hp=eaaa87aea8e095f335dd2eb3918864feab8ad582;hpb=fd1213c4557770ea82b25e443230345f50dfb97c;p=xboard.git diff --git a/backend.c b/backend.c index eaaa87a..0af55fa 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, 2014 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -55,16 +55,32 @@ #ifdef WIN32 #include -#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); + int flock(int f, int code); +# define LOCK_EX 2 +# define SLASH '\\' -int flock(int f, int code); -#define LOCK_EX 2 -#define SLASH '\\' +# ifdef ARC_64BIT +# define EGBB_NAME "egbbdll64.dll" +# else +# define EGBB_NAME "egbbdll.dll" +# endif #else -#define DoSleep( n ) if( (n) >= 0) sleep(n) -#define SLASH '/' +# include +# define SLASH '/' + +# include +# ifdef ARC_64BIT +# define EGBB_NAME "egbbso64.so" +# else +# define EGBB_NAME "egbbso.so" +# endif + // kludge to allow Windows code in back-end by converting it to corresponding Linux code +# define CDECL +# define HMODULE void * +# define LoadLibrary(x) dlopen(x, RTLD_LAZY) +# define GetProcAddress dlsym #endif @@ -131,6 +147,8 @@ extern int gettimeofday(struct timeval *, struct timezone *); # include "zippy.h" #endif #include "backendz.h" +#include "evalgraph.h" +#include "engineoutput.h" #include "gettext.h" #ifdef ENABLE_NLS @@ -149,18 +167,11 @@ extern int gettimeofday(struct timeval *, struct timezone *); #endif -/* A point in time */ -typedef struct { - long sec; /* Assuming this is >= 32 bits */ - int ms; /* Assuming this is >= 16 bits */ -} TimeMark; - int establish P((void)); 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)); @@ -170,8 +181,6 @@ int LoadGameOneMove P((ChessMove readAhead)); int LoadGameFromFile P((char *filename, int n, char *title, int useList)); int LoadPositionFromFile P((char *filename, int n, char *title)); int SavePositionToFile P((char *filename)); -void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar, - Board board)); void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar)); void ShowMove P((int fromX, int fromY, int toX, int toY)); int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, @@ -206,7 +215,6 @@ void StopClocks P((void)); void ResetClocks P((void)); char *PGNDate P((void)); void SetGameInfo P((void)); -Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen)); int RegisterMove P((void)); void MakeRegisteredMove P((void)); void TruncateGame P((void)); @@ -215,8 +223,6 @@ void CopyPlayerNameIntoFileName P((char **, char *)); char *SavePart P((char *)); int SaveGameOldStyle P((FILE *)); int SaveGamePGN P((FILE *)); -void GetTimeMark P((TimeMark *)); -long SubtractTimeMarks P((TimeMark *, TimeMark *)); int CheckFlags P((void)); long NextTickLength P((long)); void CheckTimeControl P((void)); @@ -233,15 +239,18 @@ void InitDrawingSizes(int x, int y); void NextMatchGame P((void)); 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 *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((VariantClass v, int w, int h, int s)); #ifdef WIN32 extern void ConsoleCreate(); #endif ChessProgramState *WhitePlayer(); -void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c int VerifyDisplayMode P(()); char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment @@ -278,10 +287,13 @@ 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 */ +static int initPing = -1; /* States for ics_getting_history */ #define H_FALSE 0 @@ -325,7 +337,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 ); @@ -337,7 +349,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; @@ -353,7 +365,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); @@ -371,7 +383,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; @@ -396,16 +408,19 @@ PosFlags(index) case VariantShatranj: case VariantCourier: case VariantMakruk: + case VariantASEAN: case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; default: break; } + if(appData.fischerCastling) flags |= F_FRC_TYPE_CASTLING, flags &= ~F_ALL_CASTLE_OK; // [HGM] fischer 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 @@ -433,7 +448,7 @@ Boolean alarmSounded; /* end premove variables */ char *ics_prefix = "$"; -int ics_type = ICS_GENERIC; +enum ICS_TYPE ics_type = ICS_GENERIC; int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0; int pauseExamForwardMostMove = 0; @@ -461,17 +476,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; @@ -563,6 +579,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] = { @@ -607,6 +637,13 @@ ChessSquare GrandArray[2][BOARD_FILES] = { BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare } }; +ChessSquare ChuChessArray[2][BOARD_FILES] = { + { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion, + WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan }, + { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen, + BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan } +}; + #ifdef GOTHIC ChessSquare GothicArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, @@ -620,10 +657,10 @@ ChessSquare GothicArray[2][BOARD_FILES] = { #ifdef FALCON ChessSquare FalconArray[2][BOARD_FILES] = { - { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, - WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook }, - { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, - BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook } + { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen, + WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen, + BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook } }; #else // !FALCON #define FalconArray CapablancaArray @@ -643,8 +680,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) @@ -655,8 +707,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) @@ -666,7 +717,7 @@ string_to_rating(str) } void -ClearProgramStats() +ClearProgramStats () { /* Init programStats */ programStats.movelist[0] = 0; @@ -682,7 +733,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"; @@ -716,7 +767,7 @@ CommonEngineInit() } void -UnloadEngine(ChessProgramState *cps) +UnloadEngine (ChessProgramState *cps) { /* Kill off first chess program */ if (cps->isr != NULL) @@ -727,15 +778,14 @@ UnloadEngine(ChessProgramState *cps) ExitAnalyzeMode(); DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", cps); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(cps->pr, cps->useSigterm); + DestroyChildProcess(cps->pr, 4 + cps->useSigterm); } cps->pr = NoProc; if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which); } void -ClearOptions(ChessProgramState *cps) +ClearOptions (ChessProgramState *cps) { int i; cps->nrOptions = cps->comboCnt = 0; @@ -746,12 +796,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); @@ -784,12 +838,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; @@ -807,10 +863,11 @@ InitEngine(ChessProgramState *cps, int n) /* [HGM] debug */ cps->debug = FALSE; + cps->drawDepth = appData.drawDepth[n]; cps->supportsNPS = UNKNOWN; cps->memSize = FALSE; cps->maxCores = FALSE; - cps->egtFormats[0] = NULLCHAR; + ASSIGN(cps->egtFormats, ""); /* [HGM] options */ cps->optionSettings = appData.engOptions[n]; @@ -818,6 +875,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) @@ -827,7 +885,7 @@ InitEngine(ChessProgramState *cps, int n) len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"), appData.protocolVersion[n]); - if( (len > MSG_SIZ) && appData.debugMode ) + if( (len >= MSG_SIZ) && appData.debugMode ) fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); DisplayFatalError(buf, 0, 2); @@ -838,12 +896,15 @@ InitEngine(ChessProgramState *cps, int n) } InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard + ParseFeatures(appData.featureDefaults, cps); } ChessProgramState *savCps; +GameMode oldMode; + void -LoadEngine() +LoadEngine () { int i; if(WaitForEngine(savCps, LoadEngine)) return; @@ -851,21 +912,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; @@ -879,39 +944,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 " - "-firstOptions \"\" -firstNPS -1 -fn \"\""; + "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" " + "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 " + "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false"; void -Load(ChessProgramState *cps, int i) +FloatToFront(char **list, char *engineLine) { - char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ]; - if(engineLine[0]) { // an engine was selected from the combo box + 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) +{ + 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; + 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(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces + 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; @@ -924,7 +1018,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 ? "\"" : "", @@ -933,15 +1027,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; /* @@ -972,7 +1069,7 @@ InitTimeControls() } void -InitBackEnd1() +InitBackEnd1 () { ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options @@ -980,6 +1077,7 @@ InitBackEnd1() GetTimeMark(&programStartTime); srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level + appData.seedBase = random() + (random()<<15); pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended ClearProgramStats(); @@ -1071,7 +1169,7 @@ InitBackEnd1() case VariantKriegspiel: /* need to hide pieces and move details */ /* case VariantFischeRandom: (Fabien: moved below) */ len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant); - if( (len > MSG_SIZ) && appData.debugMode ) + if( (len >= MSG_SIZ) && appData.debugMode ) fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); DisplayFatalError(buf, 0, 2); @@ -1089,7 +1187,7 @@ InitBackEnd1() case Variant36: default: len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant); - if( (len > MSG_SIZ) && appData.debugMode ) + if( (len >= MSG_SIZ) && appData.debugMode ) fprintf(debugFP, "InitBackEnd1: buffer truncated.\n"); DisplayFatalError(buf, 0, 2); @@ -1101,6 +1199,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 */ @@ -1121,6 +1220,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 */ @@ -1129,13 +1229,16 @@ InitBackEnd1() case VariantSChess: /* S-Chess, should work */ case VariantGrand: /* should work */ case VariantSpartan: /* should work */ + case VariantLion: /* should work */ + case VariantChuChess: /* should work */ break; } } } -int NextIntegerFromString( char ** str, long * value ) +int +NextIntegerFromString (char ** str, long * value) { int result = -1; char * s = *str; @@ -1160,7 +1263,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 ); @@ -1177,7 +1281,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; @@ -1218,18 +1323,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; @@ -1241,10 +1345,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; @@ -1258,16 +1359,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; } @@ -1307,11 +1408,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) { @@ -1337,7 +1443,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 @@ -1348,7 +1454,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, @@ -1369,7 +1475,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]; @@ -1405,13 +1511,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 @@ -1435,12 +1545,12 @@ MatchEvent(int mode) if(strchr(appData.results, '*') == NULL) { FILE *f; appData.tourneyCycles++; - if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles + if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles fclose(f); 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; @@ -1464,6 +1574,8 @@ MatchEvent(int mode) NextMatchGame(); } +char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line + void InitBackEnd3 P((void)) { @@ -1471,12 +1583,30 @@ InitBackEnd3 P((void)) char buf[MSG_SIZ]; int err, len; + if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode && // mode involves only first engine + !strcmp(appData.variant, "normal") && // no explicit variant request + appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 && // no size overrides requested + !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") && // but 'normal' won't work with engine + !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960 + char c, *q = first.variants, *p = strchr(q, ','); + if(p) *p = NULLCHAR; + if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however + int w, h, s; + if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any) + appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1; + ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine + Reset(TRUE, FALSE); // and re-initialize + } + if(p) *p = ','; + } + InitChessProgram(&first, startedFromSetupPosition); if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */ 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) { @@ -1494,7 +1624,7 @@ InitBackEnd3 P((void)) len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"), appData.icsHost, appData.icsPort); - if( (len > MSG_SIZ) && appData.debugMode ) + if( (len >= MSG_SIZ) && appData.debugMode ) fprintf(debugFP, "InitBackEnd3: buffer truncated.\n"); DisplayFatalError(buf, err, 1); @@ -1548,7 +1678,7 @@ InitBackEnd3 P((void)) initialMode = Training; } else { len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode); - if( (len > MSG_SIZ) && appData.debugMode ) + if( (len >= MSG_SIZ) && appData.debugMode ) fprintf(debugFP, "InitBackEnd3: buffer truncated.\n"); DisplayFatalError(buf, 0, 2); @@ -1663,6 +1793,18 @@ InitBackEnd3 P((void)) } } +void +HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current) +{ + DisplayBook(current+1); + + MoveHistorySet( movelist, first, last, current, pvInfoList ); + + EvalGraphSet( first, last, current, pvInfoList ); + + MakeEngineOutputTitle(); +} + /* * Establish will establish a contact to a remote host.port. * Sets icsPR to a ProcRef for a process (or pseudo-process) @@ -1670,7 +1812,7 @@ InitBackEnd3 P((void)) * Returns 0 if okay, error code if not. */ int -establish() +establish () { char buf[MSG_SIZ]; @@ -1711,7 +1853,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++) { @@ -1725,10 +1868,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) { @@ -1743,11 +1883,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; @@ -1801,15 +1937,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) { @@ -1818,6 +1950,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); @@ -1828,7 +1971,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. @@ -1837,7 +1980,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; @@ -1850,12 +1994,11 @@ void ics_printf(char *format, ...) } void -SendToICS(s) - char *s; +SendToICS (char *s) { int count, outCount, outError; - if (icsPR == NULL) return; + if (icsPR == NoProc) return; count = strlen(s); outCount = OutputMaybeTelnet(icsPR, s, count, &outError); @@ -1868,13 +2011,11 @@ 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; - if (icsPR == NULL) return; + if (icsPR == NoProc) return; count = strlen(s); if (appData.debugMode) { @@ -1894,8 +2035,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; @@ -1919,8 +2059,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; @@ -1938,11 +2077,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]; } @@ -1950,8 +2090,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; @@ -1973,7 +2112,8 @@ StringToVariant(e) found = TRUE; } else for (i=0; i= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue; v = (VariantClass) i; found = TRUE; break; @@ -2135,7 +2275,7 @@ StringToVariant(e) break; default: len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum); - if( (len > MSG_SIZ) && appData.debugMode ) + if( (len >= MSG_SIZ) && appData.debugMode ) fprintf(debugFP, "StringToVariant: buffer truncated.\n"); DisplayError(buf, 0); @@ -2145,7 +2285,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; @@ -2162,10 +2302,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; @@ -2203,9 +2340,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); @@ -2215,9 +2350,7 @@ SendToPlayer(data, length) } void -PackHolding(packed, holding) - char packed[]; - char *holding; +PackHolding (char packed[], char *holding) { char *p = holding; char *q = packed; @@ -2252,8 +2385,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; @@ -2301,21 +2433,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; @@ -2357,7 +2489,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; @@ -2427,6 +2559,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; @@ -2468,14 +2601,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; @@ -2490,7 +2623,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); @@ -2509,12 +2648,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); @@ -2528,7 +2667,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:") || @@ -3400,9 +3551,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; } @@ -3489,7 +3646,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; @@ -3548,7 +3705,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(); @@ -3625,8 +3782,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 { @@ -3852,6 +4009,7 @@ read_from_ics(isr, closure, data, count, error) strncmp(why, "Continuing ", 11) == 0) { gs_gamenum = gamenum; safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0])); + if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages) VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board #if ZIPPY if (appData.zippyPlay) { @@ -3887,7 +4045,7 @@ read_from_ics(isr, closure, data, count, error) #if ZIPPY if (appData.zippyPlay && first.initDone) { ZippyGameEnd(endtype, why); - if (first.pr == NULL) { + if (first.pr == NoProc) { /* Start the next process early so that we'll be ready for the next challenge */ StartChessProgram(&first); @@ -4039,11 +4197,11 @@ read_from_ics(isr, closure, data, count, error) char wh[16], bh[16]; PackHolding(wh, white_holding); PackHolding(bh, black_holding); - snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh, + 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... @@ -4119,12 +4277,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]; @@ -4136,7 +4297,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; @@ -4144,7 +4304,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; @@ -4196,6 +4356,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; @@ -4211,9 +4372,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++) @@ -4235,14 +4399,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. */ @@ -4277,6 +4462,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 @@ -4286,7 +4472,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 */ @@ -4427,6 +4614,13 @@ ParseBoard12(string) board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;; } } + if(moveNum==0 && gameInfo.variant == VariantSChess) { + board[5][BOARD_RGHT+1] = WhiteAngel; + board[6][BOARD_RGHT+1] = WhiteMarshall; + board[1][0] = BlackMarshall; + board[2][0] = BlackAngel; + board[1][1] = board[2][1] = board[5][BOARD_RGHT] = board[6][BOARD_RGHT] = 1; + } CopyBoard(boards[moveNum], board); boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set if (moveNum == 0) { @@ -4462,6 +4656,7 @@ ParseBoard12(string) if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i; initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j); + boards[moveNum][CASTLING][2] = boards[moveNum][CASTLING][5] = NoRights; if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; } for(k=BOARD_LEFT; k 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); @@ -4635,7 +4832,7 @@ ParseBoard12(string) default: break; case MT_CHECK: - if(gameInfo.variant != VariantShogi) + if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[moveNum - 1], "+"); break; case MT_CHECKMATE: @@ -4651,7 +4848,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) { @@ -4830,15 +5027,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 @@ -4847,19 +5050,27 @@ 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]; + if(moveList[moveNum][1] == '@' && moveList[moveNum][0] == '@') { + if(gameInfo.variant == VariantLion || gameInfo.variant == VariantChuChess || gameInfo.variant == VariantChu) { + sprintf(buf, "%s@@@@\n", cps->useUsermove ? "usermove " : ""); + SendToProgram(buf, cps); + return; + } + // null move in variant where engine does not understand it (for analysis purposes) + SendBoard(cps, moveNum + 1); // send position after move in stead. + return; + } if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -4883,8 +5094,7 @@ SendMoveToProgram(moveNum, cps) /* Added by Tord: Send castle moves in "O-O" in FRC games if required by * the engine. It would be nice to have a better way to identify castle * moves here. */ - if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) - && cps->useOOCastle) { + if(appData.fischerCastling && cps->useOOCastle) { int fromX = moveList[moveNum][0] - AAA; int fromY = moveList[moveNum][1] - ONE; int toX = moveList[moveNum][2] - AAA; @@ -4898,8 +5108,16 @@ SendMoveToProgram(moveNum, cps) } else SendToProgram(moveList[moveNum], cps); } 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'); + SendToProgram(buf, cps); + } else if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move + 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 @@ -4928,12 +5146,15 @@ 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]; + + if(gameInfo.variant == VariantSChess && promoChar) { + snprintf(suffix, 4, "=%c", toX == BOARD_WIDTH<<1 ? ToUpper(promoChar) : ToLower(promoChar)); + if(moveType == NormalMove) moveType = WhitePromotion; // kludge to do gating + } else suffix[0] = NULLCHAR; switch (moveType) { default: @@ -4949,7 +5170,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar) case WhiteHSideCastleFR: case BlackHSideCastleFR: /* POP Fabien */ - snprintf(user_move, MSG_SIZ, "o-o\n"); + snprintf(user_move, MSG_SIZ, "o-o%s\n", suffix); break; case WhiteQueenSideCastle: case BlackQueenSideCastle: @@ -4959,7 +5180,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar) case WhiteASideCastleFR: case BlackASideCastleFR: /* POP Fabien */ - snprintf(user_move, MSG_SIZ, "o-o-o\n"); + snprintf(user_move, MSG_SIZ, "o-o-o%s\n",suffix); break; case WhiteNonPromotion: case BlackNonPromotion: @@ -4967,7 +5188,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)); @@ -5002,13 +5224,13 @@ 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" }; if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) { - DisplayError("You cannot do this while you are playing or observing", 0); - return; + DisplayError(_("You cannot do this while you are playing or observing"), 0); + return; } if(gameMode != IcsExamining) { // is this ever not the case? char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0; @@ -5031,7 +5253,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); @@ -5062,25 +5284,31 @@ 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); @@ -5089,8 +5317,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) } void -ProcessICSInitScript(f) - FILE *f; +ProcessICSInitScript (FILE *f) { char buf[MSG_SIZ]; @@ -5102,36 +5329,59 @@ ProcessICSInitScript(f) } -static int lastX, lastY, selectFlag, dragging; +static int lastX, lastY, lastLeftX, lastLeftY, selectFlag; +int dragging; +static ClickType lastClickType; + +int +Partner (ChessSquare *p) +{ // change piece into promotion partner if one shogi-promotes to the other + int stride = gameInfo.variant == VariantChu ? 22 : 11; + ChessSquare partner; + partner = (*p/stride & 1 ? *p - stride : *p + stride); + if(PieceToChar(*p) != '+' && PieceToChar(partner) != '+') return 0; + *p = partner; + return 1; +} void -Sweep(int step) +Sweep (int step) { ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep; + static int toggleFlag; if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn; if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare; if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn; if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare; - if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare; + if(fromY != BOARD_HEIGHT-2 && fromY != 1 && gameInfo.variant != VariantChuChess) pawn = EmptySquare; + if(!step) toggleFlag = Partner(&last); // piece has shogi-promotion do { - promoSweep -= step; + if(step && !(toggleFlag && Partner(&promoSweep))) promoSweep -= step; if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap else if((int)promoSweep == -1) promoSweep = WhiteKing; else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn; else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing; - if(!step) step = 1; + 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)); + !toggleFlag && PieceToChar(promoSweep) == '+' || // skip promoted versions of other + appData.testLegality && (promoSweep == king || gameInfo.variant != VariantChuChess && + (promoSweep == WhiteLion || promoSweep == BlackLion))); + 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; if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE; - if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE; + if(abs(x - lastX) < 25 && abs(y - lastY) < 25) return FALSE; if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; if(!step) return FALSE; lastX = x; lastY = y; @@ -5142,7 +5392,7 @@ int PromoScroll(int x, int y) } void -NextPiece(int step) +NextPiece (int step) { ChessSquare piece = boards[currentMove][toY][toX]; do { @@ -5157,7 +5407,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; @@ -5209,12 +5459,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); @@ -5224,6 +5469,7 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case WhiteKingSideCastle: @@ -5257,7 +5503,8 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) if (appData.testLegality) { return (*moveType != IllegalMove); } else { - return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare && + return !(*fromX == *toX && *fromY == *toY && killX < 0) && boards[moveNum][*fromY][*fromX] != EmptySquare && + // [HGM] lion: if this is a double move we are less critical WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn); } @@ -5297,14 +5544,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; } @@ -5314,9 +5562,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 @@ -5363,7 +5608,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++) @@ -5372,11 +5617,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; @@ -5388,27 +5636,34 @@ 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; + } else if(!strncmp(buf+lineStart, "dep\t", 4)) { // column headers clicked + Collapse(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; + 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 @@ -5457,13 +5715,13 @@ 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; + int margin = h>>3, step = 0, threshold = (pieceSweep == EmptySquare ? 10 : 15); // we must somehow check if right button is still down (might be released off board!) if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-( - if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return; + if(abs(x - lastX) < threshold && abs(y - lastY) < threshold) return; if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1; if(!step) return; lastX = x; lastY = y; @@ -5492,7 +5750,8 @@ int squaresLeft[4]; int piecesLeft[(int)BlackPawn]; int seed, nrOfShuffles; -void GetPositionNumber() +void +GetPositionNumber () { // sets global variable seed int i; @@ -5504,7 +5763,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; @@ -5522,7 +5782,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; @@ -5533,7 +5794,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; @@ -5548,7 +5810,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; @@ -5636,7 +5899,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 */ { @@ -5660,7 +5924,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 }; @@ -5697,17 +5962,16 @@ 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; static int oldv; - if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request + if(appData.icsActive) shuffleOpenings = appData.fischerCastling = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request /* [AS] Initialize pv info list [HGM] and game status */ { @@ -5748,6 +6012,7 @@ InitPosition(redraw) switch (gameInfo.variant) { case VariantFischeRandom: shuffleOpenings = TRUE; + appData.fischerCastling = TRUE; default: break; case VariantShatranj: @@ -5758,9 +6023,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; @@ -5774,6 +6043,7 @@ InitPosition(redraw) break; case VariantCapaRandom: shuffleOpenings = TRUE; + appData.fischerCastling = TRUE; case VariantCapablanca: pieces = CapablancaArray; gameInfo.boardWidth = 10; @@ -5787,6 +6057,7 @@ InitPosition(redraw) case VariantSChess: SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek"); gameInfo.holdingsSize = 7; + for(i=0; i= BOARD_RGHT || overrule) continue; - initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth]; + initialPosition[gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess][j] = pieces[0][j-gameInfo.holdingsWidth]; initialPosition[pawnRow][j] = WhitePawn; initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn; if(gameInfo.variant == VariantXiangqi) { @@ -5915,14 +6206,24 @@ InitPosition(redraw) } } } - if(gameInfo.variant == VariantGrand) { + if(gameInfo.variant == VariantChu) { + if(j == (BOARD_WIDTH-2)/3 || j == BOARD_WIDTH - (BOARD_WIDTH+1)/3) + initialPosition[pawnRow+1][j] = WhiteCobra, + initialPosition[BOARD_HEIGHT-pawnRow-2][j] = BlackCobra; + for(i=1; i=BOARD_RGHT-1) { initialPosition[0][j] = WhiteRook; initialPosition[BOARD_HEIGHT-1][j] = BlackRook; } } - initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth]; + initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess)][j] = pieces[1][j-gameInfo.holdingsWidth]; } + if(gameInfo.variant == VariantChuChess) initialPosition[0][BOARD_WIDTH/2] = WhiteKing, initialPosition[BOARD_HEIGHT-1][BOARD_WIDTH/2-1] = BlackKing; if( (gameInfo.variant == VariantShogi) && !overrule ) { j=BOARD_LEFT+1; @@ -5959,7 +6260,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) { @@ -5992,35 +6293,37 @@ 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); } else { ChessSquare *bp; - int i, j; + int i, j, left=0, right=BOARD_WIDTH; /* Kludge to set black to move, avoiding the troublesome and now * deprecated "black" command. */ if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move... SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps); + if(!cps->extendedEdit) left = BOARD_LEFT, right = BOARD_RGHT; // only board proper + SendToProgram("edit\n", cps); SendToProgram("#\n", cps); for (i = BOARD_HEIGHT - 1; i >= 0; i--) { - bp = &boards[moveNum][i][BOARD_LEFT]; - for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) { + bp = &boards[moveNum][i][left]; + for (j = left; j < right; j++, bp++) { + if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue; if ((int) *bp < (int) BlackPawn) { - snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), - AAA + j, ONE + i); + if(j == BOARD_RGHT+1) + snprintf(message, MSG_SIZ, "%c@%d\n", PieceToChar(*bp), bp[-1]); + else snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp), AAA + j, ONE + i); if(message[0] == '+' || message[0] == '~') { snprintf(message, MSG_SIZ,"%c%c%c+\n", PieceToChar((ChessSquare)(DEMOTED *bp)), @@ -6037,11 +6340,14 @@ SendBoard(cps, moveNum) SendToProgram("c\n", cps); for (i = BOARD_HEIGHT - 1; i >= 0; i--) { - bp = &boards[moveNum][i][BOARD_LEFT]; - for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) { + bp = &boards[moveNum][i][left]; + for (j = left; j < right; j++, bp++) { + if(j == BOARD_LEFT-1 || j == BOARD_RGHT) continue; if (((int) *bp != (int) EmptySquare) && ((int) *bp >= (int) BlackPawn)) { - snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)), + if(j == BOARD_LEFT-2) + snprintf(message, MSG_SIZ, "%c@%d\n", ToUpper(PieceToChar(*bp)), bp[1]); + else snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)), AAA + j, ONE + i); if(message[0] == '+' || message[0] == '~') { snprintf(message, MSG_SIZ,"%c%c%c+\n", @@ -6062,11 +6368,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 @@ -6080,12 +6496,12 @@ 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 */ int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn; - ChessSquare piece; + ChessSquare piece, partner; ChessMove moveType; Boolean premove; @@ -6097,15 +6513,19 @@ 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 = (p >= WhiteLion || PieceToChar(piece + 22) == '.') ? WhitePawn : WhiteLion; + } else if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantChuChess) { promotionZoneSize = BOARD_HEIGHT/3; - highestPromotingPiece = (int)WhiteFerz; - } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) { + highestPromotingPiece = (int)WhiteAlfil; + } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess) { promotionZoneSize = 3; } - // Treat Lance as Pawn when it is not representing Amazon - if(gameInfo.variant != VariantSuper) { + // Treat Lance as Pawn when it is not representing Amazon or Lance + if(gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) { if(piece == WhiteLance) piece = WhitePawn; else if(piece == BlackLance) piece = BlackPawn; } @@ -6114,10 +6534,13 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, in if((int)piece >= BlackPawn) { if(toY >= promotionZoneSize && fromY >= promotionZoneSize) return FALSE; + if(fromY < promotionZoneSize && gameInfo.variant == VariantChuChess) return FALSE; highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece; } else { if( toY < BOARD_HEIGHT - promotionZoneSize && fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE; + if(fromY >= BOARD_HEIGHT - promotionZoneSize && gameInfo.variant == VariantChuChess) + return FALSE; } if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece @@ -6151,8 +6574,13 @@ 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) { - *promoChoice = PieceToChar(BlackFerz); // no choice + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || + gameInfo.variant == VariantMakruk || gameInfo.variant == VariantASEAN) { + ChessSquare p=BlackFerz; // no choice + while(p < EmptySquare) { //but make sure we use piece that exists + *promoChoice = PieceToChar(p++); + if(*promoChoice != '.') break; + } return FALSE; } // no sense asking what we must promote to if it is going to explode... @@ -6162,7 +6590,9 @@ 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 ? '=' : '+'); + partner = piece; // pieces can promote if the pieceToCharTable says so + if(IS_SHOGI(gameInfo.variant)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? '=' : '+'); // obsolete? + else if(Partner(&partner)) *promoChoice = (defaultPromoChoice == piece && sweepSelect ? NULLCHAR : '+'); if( sweepSelect && gameInfo.variant != VariantGreat && gameInfo.variant != VariantGrand && gameInfo.variant != VariantSuper) return FALSE; @@ -6173,7 +6603,8 @@ 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) || gameInfo.variant == VariantChuChess ? '+' : NULLCHAR); + if(moveType == IllegalMove) *promoChoice = NULLCHAR; // could be the fact we promoted was illegal if(moveType != WhitePromotion && moveType != BlackPromotion) return FALSE; } @@ -6182,8 +6613,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 && @@ -6192,9 +6622,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; @@ -6203,8 +6631,7 @@ PieceForSquare (x, y) } int -OKToStartUserMove(x, y) - int x, y; +OKToStartUserMove (int x, int y) { ChessSquare from_piece; int white_piece; @@ -6223,7 +6650,6 @@ OKToStartUserMove(x, y) (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */ switch (gameMode) { - case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: @@ -6251,6 +6677,8 @@ OKToStartUserMove(x, y) } break; + case PlayFromGameFile: + if(!shiftKey || !appData.variations) return FALSE; // [HGM] allow starting variation in this mode case EditGame: if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); @@ -6294,6 +6722,7 @@ OKToStartUserMove(x, y) } if (currentMove != forwardMostMove && gameMode != AnalyzeMode && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode + && gameMode != PlayFromGameFile // [HGM] as EditGame, with protected main line && gameMode != AnalyzeFile && gameMode != Training) { DisplayMoveError(_("Displayed position is not current")); return FALSE; @@ -6302,7 +6731,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) { @@ -6364,14 +6794,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 @@ -6385,7 +6815,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) */ switch (gameMode) { - case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: @@ -6411,6 +6840,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) } break; + case PlayFromGameFile: + if(!shiftKey ||!appData.variations) return; // [HGM] only variations case EditGame: case IcsExamining: case BeginningOfGame: @@ -6499,7 +6930,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; } @@ -6507,25 +6938,27 @@ 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; } /* [HGM] always test for legality, to get promotion info */ moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), fromY, fromX, toY, toX, promoChar); + + if(fromY == DROP_RANK && fromX == EmptySquare && (gameMode == AnalyzeMode || gameMode == EditGame)) moveType = NormalMove; + /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { if (moveType == IllegalMove || moveType == ImpossibleMove) { @@ -6534,15 +6967,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; @@ -6602,12 +7039,14 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) /* Ok, now we know that the move is good, so we can kill the previous line in Analysis Mode */ - if ((gameMode == AnalyzeMode || gameMode == EditGame) + if ((gameMode == AnalyzeMode || gameMode == EditGame || gameMode == PlayFromGameFile && appData.variations && shiftKey) && currentMove < forwardMostMove) { if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game else forwardMostMove = currentMove; } + ClearMap(); + /* If we need the chess program but it's dead, restart it */ ResurrectChessProgram(); @@ -6633,7 +7072,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); @@ -6663,11 +7102,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 SendMoveToProgram(forwardMostMove-1, &first); + } 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); + 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; } @@ -6724,32 +7173,66 @@ 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); +} + +static char baseMarker[BOARD_RANKS][BOARD_FILES], baseLegal[BOARD_RANKS][BOARD_FILES]; + +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; } +static int hoverSavedValid; + void -MarkTargetSquares(int clear) +MarkTargetSquares (int clear) { - int x, y; - if(!appData.markers || !appData.highlightDragging || - !appData.testLegality || gameMode == EditPosition) return; - if(clear) { - for(x=0; x1) capt++; @@ -6757,11 +7240,11 @@ MarkTargetSquares(int clear) for(x=0; x= BOARD_HEIGHT-1-zone || piece == BlackLance && y == 1 || piece == WhiteLance && y == BOARD_HEIGHT-2 ); } -void LeftClick(ClickType clickType, int xPix, int yPix) +void +HoverEvent (int xPix, int yPix, int x, int y) +{ + static int oldX = -1, oldY = -1, oldFromX = -1, oldFromY = -1; + int r, f; + if(!first.highlight) return; + if(fromX != oldFromX || fromY != oldFromY) oldX = oldY = -1; // kludge to fake entry on from-click + if(x == oldX && y == oldY) return; // only do something if we enter new square + oldFromX = fromX; oldFromY = fromY; + if(oldX == -1 && oldY == -1 && x == fromX && y == fromY) { // record markings after from-change + for(r=0; r= 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 @@ -6872,14 +7403,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; @@ -6889,6 +7426,8 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } if (appData.highlightDragging) { SetHighlights(fromX, fromY, -1, -1); + } else { + ClearHighlights(); } } else fromX = fromY = -1; return; @@ -6907,20 +7446,26 @@ void LeftClick(ClickType clickType, int xPix, int yPix) /* Check if clicking again on the same color piece */ 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 && + frc = appData.fischerCastling || gameInfo.variant == VariantSChess; + 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 { @@ -6931,9 +7476,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)) { @@ -6944,13 +7490,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 @@ -6964,11 +7510,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(); @@ -6981,29 +7529,49 @@ 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; + } + + piece = boards[currentMove][fromY][fromX]; + saveAnimate = appData.animate; - MarkTargetSquares(1); if (clickType == Press) { + if(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece; if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { // must be Edit Position mode with empty-square selected fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag 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)) { - ChessSquare piece = boards[currentMove][fromY][fromX]; - DragPieceBegin(xPix, yPix, TRUE); dragging = 1; + if(dragging == 2) { // [HGM] lion: just turn buttonless drag into normal drag, and let release to the job + 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) { promoSweep = defaultPromoChoice; - if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece; + if(gameInfo.variant != VariantChuChess && 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) { @@ -7011,13 +7579,35 @@ 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; appData.animate = FALSE; // do not animate, a selected piece already on to-square + 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(gameInfo.variant == VariantChuChess && piece != WhitePawn && piece != BlackPawn) defaultPromoChoice = piece; + 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 + ReportClick("put", x, y); // and inform engine + ReportClick("lift", x, y); + MarkTargetSquares(0); + return; + } + } DragPieceEnd(xPix, yPix); dragging = 0; /* Don't animate move and drag both */ appData.animate = FALSE; @@ -7046,17 +7636,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]; @@ -7070,7 +7663,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) DisplayMessage("Click in holdings to choose piece", ""); return; } - PromotionPopUp(); + PromotionPopUp(promoChoice); } else { int oldMove = currentMove; UserMoveEvent(fromX, fromY, toX, toY, promoChoice); @@ -7079,6 +7672,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; @@ -7088,7 +7682,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; @@ -7136,7 +7731,7 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) toX = xSqr; toY = ySqr; lastX = x, lastY = y; if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY; NextPiece(0); - return -2; + return 2; // grab case IcsObserving: if(!appData.icsEngineAnalyze) return -1; case IcsPlayingWhite: @@ -7178,7 +7773,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; @@ -7205,7 +7801,7 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp } void -ClearEngineOutputPane(int which) +ClearEngineOutputPane (int which) { static FrontEndProgramStats dummyStats; dummyStats.which = which; @@ -7216,7 +7812,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]; @@ -7260,7 +7856,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; @@ -7278,7 +7874,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]; @@ -7307,7 +7903,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; @@ -7333,6 +7929,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]; @@ -7357,13 +7956,35 @@ MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisCol } int -Adjudicate(ChessProgramState *cps) +CompareWithRights (Board b1, Board b2) +{ + int rights = 0; + if(!CompareBoards(b1, b2)) return FALSE; + if(b1[EP_STATUS] != b2[EP_STATUS]) return FALSE; + /* compare castling rights */ + if( b1[CASTLING][2] != b2[CASTLING][2] && (b2[CASTLING][0] != NoRights || b2[CASTLING][1] != NoRights) ) + rights++; /* King lost rights, while rook still had them */ + if( b1[CASTLING][2] != NoRights ) { /* king has rights */ + if( b1[CASTLING][0] != b2[CASTLING][0] || b1[CASTLING][1] != b2[CASTLING][1] ) + rights++; /* but at least one rook lost them */ + } + if( b1[CASTLING][5] != b1[CASTLING][5] && (b2[CASTLING][3] != NoRights || b2[CASTLING][4] != NoRights) ) + rights++; + if( b1[CASTLING][5] != NoRights ) { + if( b1[CASTLING][3] != b2[CASTLING][3] || b1[CASTLING][4] != b2[CASTLING][4] ) + rights++; + } + return rights == 0; +} + +int +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; @@ -7436,6 +8057,7 @@ Adjudicate(ChessProgramState *cps) case MT_NONE: default: break; + case MT_STEALMATE: case MT_STALEMATE: case MT_STAINMATE: reason = "Xboard adjudication: Stalemate"; @@ -7447,13 +8069,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; } @@ -7513,23 +8143,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])) { @@ -7555,7 +8179,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) { @@ -7573,13 +8197,21 @@ 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); ourPerpetual = PerpetualChase(k+1, forwardMostMove); if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit + static char resdet[MSG_SIZ]; result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; - details = "Xboard adjudication: perpetual chasing"; + details = resdet; + snprintf(resdet, MSG_SIZ, "Xboard adjudication: perpetual chasing of %c%c", ourPerpetual>>8, ourPerpetual&255); } else if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet break; // Abort repetition-checking loop. @@ -7664,10 +8296,66 @@ Adjudicate(ChessProgramState *cps) return 0; } -char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) +typedef int (CDECL *PPROBE_EGBB) (int player, int *piece, int *square); +typedef int (CDECL *PLOAD_EGBB) (char *path, int cache_size, int load_options); +static int egbbCode[] = { 6, 5, 4, 3, 2, 1 }; + +static int +BitbaseProbe () +{ + int pieces[10], squares[10], cnt=0, r, f, res; + static int loaded; + static PPROBE_EGBB probeBB; + if(!appData.testLegality) return 10; + if(BOARD_HEIGHT != 8 || BOARD_RGHT-BOARD_LEFT != 8) return 12; + if(gameInfo.holdingsSize && gameInfo.variant != VariantSuper && gameInfo.variant != VariantSChess) return 12; + if(loaded == 2 && forwardMostMove < 2) loaded = 0; // retry on new game + for(r=0; r= BlackPawn); + int type = piece - black*BlackPawn; + if(piece == EmptySquare) continue; + if(type != WhiteKing && type > WhiteQueen) return 12; // unorthodox piece + if(type == WhiteKing) type = WhiteQueen + 1; + type = egbbCode[type]; + squares[cnt] = r*(BOARD_RGHT - BOARD_LEFT) + f - BOARD_LEFT; + pieces[cnt] = type + black*6; + if(++cnt > 5) return 11; + } + pieces[cnt] = squares[cnt] = 0; + // probe EGBB + if(loaded == 2) return 13; // loading failed before + if(loaded == 0) { + loaded = 2; // prepare for failure + char *p, *path = strstr(appData.egtFormats, "scorpio:"), buf[MSG_SIZ]; + HMODULE lib; + PLOAD_EGBB loadBB; + if(!path) return 13; // no egbb installed + strncpy(buf, path + 8, MSG_SIZ); + if(p = strchr(buf, ',')) *p = NULLCHAR; else p = buf + strlen(buf); + snprintf(p, MSG_SIZ - strlen(buf), "%c%s", SLASH, EGBB_NAME); + lib = LoadLibrary(buf); + if(!lib) { DisplayError(_("could not load EGBB library"), 0); return 13; } + loadBB = (PLOAD_EGBB) GetProcAddress(lib, "load_egbb_xmen"); + probeBB = (PPROBE_EGBB) GetProcAddress(lib, "probe_egbb_xmen"); + if(!loadBB || !probeBB) { DisplayError(_("wrong EGBB version"), 0); return 13; } + p[1] = NULLCHAR; loadBB(buf, 64*1028, 2); // 2 = SMART_LOAD + loaded = 1; // success! + } + res = probeBB(forwardMostMove & 1, pieces, squares); + return res > 0 ? 1 : res < 0 ? -1 : 0; +} + +char * +SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial) { // [HGM] book: this routine intercepts moves to simulate book replies char *bookHit = NULL; + if(cps->drawDepth && BitbaseProbe() == 0) { // [HG} egbb: reduce depth in drawn position + char buf[MSG_SIZ]; + snprintf(buf, MSG_SIZ, "sd %d\n", cps->drawDepth); + SendToProgram(buf, cps); + } //first determine if the incoming move brings opponent into his book if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI)) bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move @@ -7677,6 +8365,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) { @@ -7712,9 +8401,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); @@ -7723,19 +8432,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) { @@ -7749,7 +8459,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 /* @@ -7790,6 +8500,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) { @@ -7854,20 +8580,33 @@ 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); - 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); + DisplayMoveError(buf1); + snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c via %c%c) res=%d", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, killX+AAA, killY+ONE, moveType); if (gameMode == TwoMachinesPlay) { GameEnds(machineWhite ? BlackWins : WhiteWins, buf1, GE_XBOARD); @@ -7884,19 +8623,13 @@ 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); GameEnds(machineWhite ? BlackWins : WhiteWins, buf1, GE_XBOARD); return; - } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom) + } else if(!appData.fischerCastling) /* [HGM] Kludge to handle engines that send FRC-style castling when they shouldn't (like TSCP-Gothic) */ switch(moveType) { @@ -7928,6 +8661,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; @@ -8015,10 +8759,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(); } /* @@ -8068,11 +8814,27 @@ 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 && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position - int dummy, s=6; char buf[MSG_SIZ]; - if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return; + if (!strncmp(message, "setup ", 6) && + (!appData.testLegality || gameInfo.variant == VariantFairy || gameInfo.variant == VariantUnknown || + NonStandardBoardSize(gameInfo.variant, gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize)) + ) { // [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); - ParseFEN(boards[0], &dummy, message+s); + if(startedFromSetupPosition) return; + 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; @@ -8085,7 +8847,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 { @@ -8134,7 +8896,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)) { @@ -8185,6 +8947,36 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } } if (sscanf(message, "pong %d", &cps->lastPong) == 1) { + if(initPing == cps->lastPong) { + if(gameInfo.variant == VariantUnknown) { + DisplayError(_("Engine did not send setup for non-standard variant"), 0); + *engineVariant = NULLCHAR; appData.variant = VariantNormal; // back to normal as error recovery? + GameEnds(GameUnfinished, NULL, GE_XBOARD); + } + initPing = -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; } /* @@ -8228,7 +9020,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (StrStr(message, "analyze")) { cps->analysisSupport = FALSE; cps->analyzing = FALSE; - Reset(FALSE, TRUE); +// Reset(FALSE, TRUE); // [HGM] this caused discrepancy between display and internal state! + EditGameEvent(); // [HGM] try to preserve loaded game snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy); DisplayError(buf2, 0); return; @@ -8299,6 +9092,8 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. parseList[currentMove], _(cps->which)); DisplayMoveError(buf1); DrawPosition(FALSE, boards[currentMove]); + + SetUserThinkingEnables(); return; } if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) { @@ -8310,7 +9105,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) @@ -8324,8 +9119,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; } @@ -8524,7 +9319,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.")); } } } @@ -8593,6 +9388,22 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1); + if(serverMoves && (time > 100 || time == 0 && plylev > 7)) { + char buf[MSG_SIZ]; + FILE *f; + snprintf(buf, MSG_SIZ, "%s", appData.serverMovesName); + buf[strlen(buf)-1] = gameMode == MachinePlaysWhite ? 'w' : + gameMode == MachinePlaysBlack ? 'b' : cps->twoMachinesColor[0]; + if(appData.debugMode) fprintf(debugFP, "write PV on file '%s'\n", buf); + if(f = fopen(buf, "w")) { // export PV to applicable PV file + fprintf(f, "%5.2f/%-2d %s", curscore/100., plylev, pv); + fclose(f); + } + else + /* TRANSLATORS: PV = principal variation, the variation the chess engine thinks is the best for everyone */ + DisplayError(_("failed writing PV"), 0); + } + tempStats.depth = plylev; tempStats.nodes = nodes; tempStats.time = time; @@ -8807,8 +9618,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; @@ -8854,6 +9664,7 @@ ParseGameHistory(game) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case WhiteKingSideCastle: @@ -8878,6 +9689,7 @@ ParseGameHistory(game) break; case WhiteDrop: case BlackDrop: + if(currentMoveString[0] == '@') continue; // no null moves in ICS mode! fromX = moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); @@ -8976,7 +9788,7 @@ ParseGameHistory(game) default: break; case MT_CHECK: - if(gameInfo.variant != VariantShogi) + if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[boardIndex - 1], "+"); break; case MT_CHECKMATE: @@ -8990,13 +9802,10 @@ 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; + int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand || gameInfo.variant == VariantChuChess ? 3 : 1; /* [HGM] compute & store e.p. status and castling rights for new position */ /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */ @@ -9005,15 +9814,31 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) oldEP = (signed char)board[EP_STATUS]; board[EP_STATUS] = EP_NONE; - if( board[toY][toX] != EmptySquare ) - board[EP_STATUS] = EP_CAPTURE; - if (fromY == DROP_RANK) { /* must be first */ + if(fromX == EmptySquare) { // [HGM] pass: empty drop encodes null move; nothing to change. + board[EP_STATUS] = EP_CAPTURE; // null move considered irreversible + return; + } piece = board[toY][toX] = (ChessSquare) fromX; } else { + ChessSquare victim; int i; + 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 @@ -9049,6 +9874,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 */ @@ -9093,16 +9925,16 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX+1] = board[fromY][BOARD_LEFT]; board[fromY][BOARD_LEFT] = EmptySquare; } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi || - board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) + board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere ) { /* 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 @@ -9154,16 +9986,16 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi || - board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) + board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantChu) && toY < promoRank && promoChar ) { /* 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 @@ -9187,8 +10019,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; } } @@ -9266,14 +10099,20 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = EmptySquare; } } + if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) { 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); + if(gameInfo.variant == VariantChuChess && (piece == WhiteKnight || piece == BlackKnight)) + board[toY][toX] = piece + WhiteLion - WhiteKnight; // adjust Knight promotions to Lion } 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) { @@ -9287,17 +10126,25 @@ 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, 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; piece = boards[forwardMostMove][fromY][fromX]; @@ -9305,10 +10152,14 @@ MakeMove(fromX, fromY, toX, toY, promoChar) if(gameInfo.variant == VariantKnightmate) king += (int) WhiteUnicorn - (int) WhiteKing; if(forwardMostMove == 0) { - if(blackPlaysFirst) + if(gameMode == MachinePlaysBlack || gameMode == BeginningOfGame) + fprintf(serverMoves, "%s;", UserName()); + else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') fprintf(serverMoves, "%s;", second.tidy); fprintf(serverMoves, "%s;", first.tidy); - if(!blackPlaysFirst) + if(gameMode == MachinePlaysWhite) + fprintf(serverMoves, "%s;", UserName()); + else if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w') fprintf(serverMoves, "%s;", second.tidy); } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";"); lastLoadFlag = loadFlag; @@ -9328,21 +10179,30 @@ 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", 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", pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score); if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000; 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); } fflush(serverMoves); } - if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations - DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), - 0, 1); + if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations.. + GameEnds(GameUnfinished, _("Game too long; increase MAX_MOVES and recompile"), GE_XBOARD); return; } UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this @@ -9356,6 +10216,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar) SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; + adjustedClock = FALSE; gameInfo.result = GameUnfinished; if (gameInfo.resultDetails != NULL) { free(gameInfo.resultDetails); @@ -9363,17 +10224,13 @@ MakeMove(fromX, fromY, toX, toY, promoChar) } CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[forwardMostMove - 1]); - (void) CoordsToAlgebraic(boards[forwardMostMove - 1], - PosFlags(forwardMostMove - 1), - fromY, fromX, toY, toX, promoChar, - parseList[forwardMostMove - 1]); switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) { case MT_NONE: case MT_STALEMATE: default: break; case MT_CHECK: - if(gameInfo.variant != VariantShogi) + if(!IS_SHOGI(gameInfo.variant)) strcat(parseList[forwardMostMove - 1], "+"); break; case MT_CHECKMATE: @@ -9381,15 +10238,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; @@ -9400,23 +10253,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); - DisplayBook(currentMove); } -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; @@ -9449,16 +10306,81 @@ void SendEgtPath(ChessProgramState *cps) } } +static int +NonStandardBoardSize (VariantClass v, int boardWidth, int boardHeight, int holdingsSize) +{ + int width = 8, height = 8, holdings = 0; // most common sizes + if( v == VariantUnknown || *engineVariant) return 0; // engine-defined name never needs prefix + // correct the deviations default for each variant + if( v == VariantXiangqi ) width = 9, height = 10; + if( v == VariantShogi ) width = 9, height = 9, holdings = 7; + if( v == VariantBughouse || v == VariantCrazyhouse) holdings = 5; + if( v == VariantCapablanca || v == VariantCapaRandom || + v == VariantGothic || v == VariantFalcon || v == VariantJanus ) + width = 10; + if( v == VariantCourier ) width = 12; + if( v == VariantSuper ) holdings = 8; + if( v == VariantGreat ) width = 10, holdings = 8; + if( v == VariantSChess ) holdings = 7; + if( v == VariantGrand ) width = 10, height = 10, holdings = 7; + if( v == VariantChuChess) width = 10, height = 10; + if( v == VariantChu ) width = 12, height = 12; + return boardWidth >= 0 && boardWidth != width || // -1 is default, + boardHeight >= 0 && boardHeight != height || // and thus by definition OK + holdingsSize >= 0 && holdingsSize != holdings; +} + +char variantError[MSG_SIZ]; + +char * +SupportedVariant (char *list, VariantClass v, int boardWidth, int boardHeight, int holdingsSize, int proto, char *engine) +{ // returns error message (recognizable by upper-case) if engine does not support the variant + char *p, *variant = VariantName(v); + static char b[MSG_SIZ]; + if(NonStandardBoardSize(v, boardWidth, boardHeight, holdingsSize)) { /* [HGM] make prefix for non-standard board size. */ + snprintf(b, MSG_SIZ, "%dx%d+%d_%s", boardWidth, boardHeight, + holdingsSize, variant); // cook up sized variant name + /* [HGM] varsize: try first if this deviant size variant is specifically known */ + if(StrStr(list, b) == NULL) { + // specific sized variant not known, check if general sizing allowed + if(proto != 1 && StrStr(list, "boardsize") == NULL) { + snprintf(variantError, MSG_SIZ, "Board size %dx%d+%d not supported by %s", + boardWidth, boardHeight, holdingsSize, engine); + return NULL; + } + /* [HGM] here we really should compare with the maximum supported board size */ + } + } else snprintf(b, MSG_SIZ,"%s", variant); + if(proto == 1) return b; // for protocol 1 we cannot check and hope for the best + p = StrStr(list, b); + while(p && (p != list && p[-1] != ',' || p[strlen(b)] && p[strlen(b)] != ',') ) p = StrStr(p+1, b); + if(p == NULL) { + // occurs not at all in list, or only as sub-string + snprintf(variantError, MSG_SIZ, _("Variant %s not supported by %s"), b, engine); + if(p = StrStr(list, b)) { // handle requesting parent variant when only size-overridden is supported + int l = strlen(variantError); + char *q; + while(p != list && p[-1] != ',') p--; + q = strchr(p, ','); + if(q) *q = NULLCHAR; + snprintf(variantError + l, MSG_SIZ - l, _(", but %s is"), p); + if(q) *q= ','; + } + return NULL; + } + return b; +} + 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; if (appData.noChessProgram) return; hintRequested = FALSE; bookRequested = FALSE; + ParseFeatures(appData.features[cps == &second], cps); // [HGM] allow user to overrule features /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */ /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */ if(cps->memSize) { /* [HGM] memory */ @@ -9475,55 +10397,15 @@ InitChessProgram(cps, setup) if (gameInfo.variant != VariantNormal && gameInfo.variant != VariantLoadable /* [HGM] also send variant if board size non-standard */ - || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0 - ) { - char *v = VariantName(gameInfo.variant); - if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) { - /* [HGM] in protocol 1 we have to assume all variants valid */ - snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy); - DisplayFatalError(buf, 0, 1); + || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0) { + + b = SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth, + gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, cps->tidy); + if (b == NULL) { + DisplayFatalError(variantError, 0, 1); 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) { - 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 */ - if(StrStr(cps->variants, b) == NULL) { - // specific sized variant not known, check if general sizing allowed - if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best - if(StrStr(cps->variants, "boardsize") == NULL) { - snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s", - gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy); - DisplayFatalError(buf, 0, 1); - return; - } - /* [HGM] here we really should compare with the maximum supported board size */ - } - } - } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant)); snprintf(buf, MSG_SIZ, "variant %s\n", b); SendToProgram(buf, cps); } @@ -9563,7 +10445,7 @@ InitChessProgram(cps, setup) SendToProgram("easy\n", cps); } if (cps->usePing) { - snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing); + snprintf(buf, MSG_SIZ, "ping %d\n", initPing = ++cps->lastPing); SendToProgram(buf, cps); } cps->initDone = TRUE; @@ -9572,8 +10454,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; @@ -9612,9 +10520,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); } @@ -9635,12 +10546,11 @@ TwoMachinesEventIfReady P((void)) return; } DisplayMessage("", ""); curMess = 0; - ThawUI(); TwoMachinesEvent(); } char * -MakeName(char *template) +MakeName (char *template) { time_t clock; struct tm *tm; @@ -9669,7 +10579,7 @@ MakeName(char *template) } int -CountPlayers(char *p) +CountPlayers (char *p) { int n = 0; while(p = strchr(p, '\n')) p++, n++; // count participants @@ -9677,12 +10587,13 @@ CountPlayers(char *p) } FILE * -WriteTourneyFile(char *results) +WriteTourneyFile (char *results, FILE *f) { // write tournament parameters on tourneyFile; on success return the stream pointer for closing - FILE *f = fopen(appData.tourneyFile, "w"); + if(f == NULL) f = fopen(appData.tourneyFile, "w"); if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else { // create a file with tournament description fprintf(f, "-participants {%s}\n", appData.participants); + fprintf(f, "-seedBase %d\n", appData.seedBase); fprintf(f, "-tourneyType %d\n", appData.tourneyType); fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles); fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames); @@ -9694,6 +10605,15 @@ WriteTourneyFile(char *results) 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 { @@ -9706,10 +10626,94 @@ WriteTourneyFile(char *results) return f; } +char *command[MAXENGINES], *mnemonic[MAXENGINES]; + +void +Substitute (char *participants, int expunge) +{ + int i, changed, changes=0, nPlayers=0; + char *p, *q, *r, buf[MSG_SIZ]; + if(participants == NULL) return; + if(appData.tourneyFile[0] == NULLCHAR) { free(participants); return; } + r = p = participants; q = appData.participants; + while(*p && *p == *q) { + if(*p == '\n') r = p+1, nPlayers++; + p++; q++; + } + if(*p) { // difference + while(*p && *p++ != '\n'); + while(*q && *q++ != '\n'); + changed = nPlayers; + changes = 1 + (strcmp(p, q) != 0); + } + if(changes == 1) { // a single engine mnemonic was changed + q = r; while(*q) nPlayers += (*q++ == '\n'); + p = buf; while(*r && (*p = *r++) != '\n') p++; + *p = NULLCHAR; + 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; + if(appData.tourneyFile[0] && (f = fopen(appData.tourneyFile, "r+")) ) { + flock(fileno(f), LOCK_EX); + ParseArgsFromFile(f); + fseek(f, 0, SEEK_SET); + FREE(appData.participants); appData.participants = participants; + if(expunge) { // erase results of replaced engine + int len = strlen(appData.results), w, b, dummy; + for(i=0; i 1) DisplayError(_("You can only change one engine at the time"), 0); + free(participants); + return; +} + +int +CheckPlayers (char *participants) +{ + int i; + char buf[MSG_SIZ], *p; + NamesToList(firstChessProgramNames, command, mnemonic, "all"); + while(p = strchr(participants, '\n')) { + *p = NULLCHAR; + for(i=1; mnemonic[i]; i++) if(!strcmp(participants, mnemonic[i])) break; + if(!mnemonic[i]) { + snprintf(buf, MSG_SIZ, _("No engine %s is installed"), participants); + *p = '\n'; + DisplayError(buf, 0); + return 1; + } + *p = '\n'; + participants = p + 1; + } + return 0; +} + int -CreateTourney(char *name) +CreateTourney (char *name) { FILE *f; + if(matchMode && strcmp(name, appData.tourneyFile)) { + ASSIGN(name, appData.tourneyFile); //do not allow change of tourneyfile while playing + } if(name[0] == NULLCHAR) { if(appData.participants[0]) DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0); @@ -9725,9 +10729,10 @@ 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) return 0; + if((f = WriteTourneyFile("", NULL)) == NULL) return 0; } fclose(f); appData.noChessProgram = FALSE; @@ -9736,21 +10741,29 @@ CreateTourney(char *name) return 1; } -#define MAXENGINES 1000 -char *command[MAXENGINES], *mnemonic[MAXENGINES]; - -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, " ("); @@ -9758,16 +10771,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] = NULL; + 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; @@ -9783,26 +10797,82 @@ void SwapEngines(int n) SWAP(logo, p) 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(drawDepth, 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"); @@ -9891,45 +10964,70 @@ 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) 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 firstWhite, swapColors = 0; + 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 second.twoMachinesColor = firstWhite ? "black\n" : "white\n"; appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program + if(appData.loadGameIndex == -2) srandom(appData.seedBase + 68163*(nextGame & ~1)); // deterministic seed to force same opening Reset(FALSE, first.pr != NoProc); - appData.noChessProgram = FALSE; - if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file + res = LoadGameOrPosition(matchGame); // setup game + appData.noChessProgram = FALSE; // LoadGameOrPosition might call Reset too! + if(!res) return; // abort when bad game/pos file TwoMachinesEvent(); } -void UserAdjudicationEvent( int result ) +void +UserAdjudicationEvent (int result) { ChessMove gameResult = GameIsDrawn; @@ -9947,7 +11045,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; @@ -9955,7 +11054,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 @@ -10083,6 +11182,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 @@ -10112,7 +11215,7 @@ GameEnds(result, resultDetails, whosays) if(result==WhiteWins) c = '+'; if(result==BlackWins) c = '-'; if(resultDetails != NULL) - fprintf(serverMoves, ";%c;%s\n", c, resultDetails); + fprintf(serverMoves, ";%c;%s\n", c, resultDetails), fflush(serverMoves); } if (resultDetails != NULL) { gameInfo.result = result; @@ -10127,13 +11230,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 } } @@ -10201,6 +11308,7 @@ GameEnds(result, resultDetails, whosays) PlayIcsUnfinishedSound(); } } + if(appData.quitNext) { ExitEvent(0); return; } } else if (gameMode == EditGame || gameMode == PlayFromGameFile || gameMode == AnalyzeMode || @@ -10248,8 +11356,8 @@ GameEnds(result, resultDetails, whosays) ExitAnalyzeMode(); DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(first.pr, first.useSigterm); + DestroyChildProcess(first.pr, 4 + first.useSigterm); + first.reload = TRUE; } first.pr = NoProc; } @@ -10273,13 +11381,13 @@ GameEnds(result, resultDetails, whosays) if (second.pr != NoProc) { DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(second.pr, second.useSigterm); + DestroyChildProcess(second.pr, 4 + 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: @@ -10304,8 +11412,9 @@ 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 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done else ranking = strdup("busy"); //suppress popup when aborted but not finished @@ -10325,6 +11434,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(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"; @@ -10362,9 +11472,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; @@ -10375,7 +11483,8 @@ FeedMovesToProgram(cps, upto) if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ]; // [HGM] variantswitch: make engine aware of new variant - if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL) + if(!SupportedVariant(cps->variants, gameInfo.variant, gameInfo.boardWidth, + gameInfo.boardHeight, gameInfo.holdingsSize, cps->protocolVersion, "")) return; // [HGM] refrain from feeding moves altogether if variant is unsupported! snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant)); SendToProgram(buf, cps); @@ -10395,7 +11504,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. */ @@ -10403,8 +11512,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 { @@ -10434,8 +11543,7 @@ ResurrectChessProgram() * Button procedures */ void -Reset(redraw, init) - int redraw, init; +Reset (int redraw, int init) { int i; @@ -10444,6 +11552,7 @@ Reset(redraw, init) redraw, init, gameMode); } CleanupTail(); // [HGM] vari: delete any stored variations + CommentPopDown(); // [HGM] make sure no comments to the previous game keep hanging on pausing = pauseExamInvalid = FALSE; startedFromSetupPosition = blackPlaysFirst = FALSE; firstMove = TRUE; @@ -10458,6 +11567,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; @@ -10471,6 +11581,7 @@ Reset(redraw, init) ClearPremoveHighlights(); gotPremove = FALSE; alarmSounded = FALSE; + killX = killY = -1; // [HGM] lion GameEnds(EndOfFile, NULL, GE_PLAYER); if(appData.serverMovesName != NULL) { @@ -10491,6 +11602,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) { @@ -10502,7 +11614,7 @@ Reset(redraw, init) timeRemaining[0][0] = whiteTimeRemaining; timeRemaining[1][0] = blackTimeRemaining; - if (first.pr == NULL) { + if (first.pr == NoProc) { StartChessProgram(&first); } if (init) { @@ -10512,10 +11624,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()) @@ -10524,14 +11637,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; @@ -10542,17 +11660,30 @@ 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); } - gameMode = EditGame; - ModeHighlight(); + 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(); /* [AS] Clear current move marker at the end of a game */ /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */ @@ -10590,8 +11721,7 @@ AutoPlayOneMove() int -LoadGameOneMove(readAhead) - ChessMove readAhead; +LoadGameOneMove (ChessMove readAhead) { int fromX = 0, fromY = 0, toX = 0, toY = 0, done; char promoChar = NULLCHAR; @@ -10632,6 +11762,7 @@ LoadGameOneMove(readAhead) case WhiteNonPromotion: case BlackNonPromotion: case NormalMove: + case FirstLeg: case WhiteKingSideCastle: case WhiteQueenSideCastle: case BlackKingSideCastle: @@ -10653,6 +11784,7 @@ LoadGameOneMove(readAhead) toX = currentMoveString[2] - AAA; toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; + if(promoChar == ';') promoChar = NULLCHAR; break; case WhiteDrop: @@ -10819,6 +11951,7 @@ LoadGameOneMove(readAhead) thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); + killX = killY = -1; // [HGM] lion: used up currentMove = forwardMostMove; return TRUE; } @@ -10826,11 +11959,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]; @@ -10868,7 +11997,7 @@ LoadGameFromFile(filename, n, title, useList) void -MakeRegisteredMove() +MakeRegisteredMove () { int fromX, fromY, toX, toY; char promoChar; @@ -10933,11 +12062,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; @@ -10978,8 +12103,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) { @@ -10999,22 +12123,402 @@ ReloadGame(offset) } } +int keys[EmptySquare+1]; + +int +PositionMatches (Board b1, Board b2) +{ + int r, f, sum=0; + switch(appData.searchMode) { + case 1: return CompareWithRights(b1, b2); + case 2: + for(r=0; r fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { + int from = toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT; + moveDatabase[movePtr++].piece = Q_WCASTL; + quickBoard[sq] = piece; + piece = quickBoard[from]; quickBoard[from] = 0; + moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1; + } else if((rook = quickBoard[sq]) && pieceType[rook] == WhiteRook) { // FRC castling + quickBoard[sq] = 0; // remove Rook + moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); // King to-square + moveDatabase[movePtr++].piece = Q_WCASTL; + quickBoard[sq] = pieceList[1]; // put King + piece = rook; + moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1; + } + } else + if(piece == pieceList[2] && fromY == toY) { + if((toX > fromX+1 || toX < fromX-1) && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1) { + int from = (toX>fromX ? BOARD_RGHT-1 : BOARD_LEFT) + (BOARD_HEIGHT-1 <<4); + moveDatabase[movePtr++].piece = Q_BCASTL; + quickBoard[sq] = piece; + piece = quickBoard[from]; quickBoard[from] = 0; + moveDatabase[movePtr].to = pieceList[piece] = sq = toX>fromX ? sq-1 : sq+1; + } else if((rook = quickBoard[sq]) && pieceType[rook] == BlackRook) { // FRC castling + quickBoard[sq] = 0; // remove Rook + moveDatabase[movePtr].to = sq = (toX>fromX ? BOARD_RGHT-2 : BOARD_LEFT+2); + moveDatabase[movePtr++].piece = Q_BCASTL; + quickBoard[sq] = pieceList[2]; // put King + piece = rook; + moveDatabase[movePtr].to = pieceList[rook] = sq = toX>fromX ? sq-1 : sq+1; + } + } else + if(epOK && (pieceType[piece] == WhitePawn || pieceType[piece] == BlackPawn) && fromX != toX && quickBoard[sq] == 0) { + quickBoard[(fromY<<4)+toX] = 0; + moveDatabase[movePtr].piece = Q_EP; + moveDatabase[movePtr++].to = (fromY<<4)+toX; + moveDatabase[movePtr].to = sq; + } else + if(promoPiece != pieceType[piece]) { + moveDatabase[movePtr++].piece = Q_PROMO; + moveDatabase[movePtr].to = pieceType[piece] = (int) promoPiece; + } + moveDatabase[movePtr].piece = piece; + quickBoard[sq] = piece; + movePtr++; +} + +int +PackGame (Board board) +{ + Move *newSpace = NULL; + moveDatabase[movePtr].piece = 0; // terminate previous game + if(movePtr > 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(dataSize + 1000, sizeof(Move)); + if(newSpace) { + int i; + Move *p = moveDatabase, *q = newSpace; + for(i=0; i 8*DSIZE) free(moveDatabase); // and free old space (if it was allocated) + moveDatabase = newSpace; + } else { // calloc failed, we must be out of memory. Too bad... + dataSize = 0; // prevent calloc events for all subsequent games + return 0; // and signal this one isn't cached + } + } + movePtr++; + MakePieceList(board, counts); + return movePtr; +} + +int +QuickCompare (Board board, int *minCounts, int *maxCounts) +{ // compare according to search mode + int r, f; + switch(appData.searchMode) + { + case 1: // exact position match + if(!(turn & board[EP_STATUS-1])) return FALSE; // wrong side to move + for(r=0; r maxCounts[r]) return FALSE; + } + return TRUE; +} + +int +QuickScan (Board board, Move *move) +{ // reconstruct game,and compare all positions in it + int cnt=0, stretch=0, total = MakePieceList(board, counts); + do { + int piece = move->piece; + int to = move->to, from = pieceList[piece]; + if(piece <= Q_PROMO) { // special moves encoded by otherwise invalid piece numbers 1-4 + if(!piece) return -1; + if(piece == Q_PROMO) { // promotion, encoded as (Q_PROMO, to) + (piece, promoType) + piece = (++move)->piece; + from = pieceList[piece]; + counts[pieceType[piece]]--; + pieceType[piece] = (ChessSquare) move->to; + counts[move->to]++; + } else if(piece == Q_EP) { // e.p. capture, encoded as (Q_EP, ep-sqr) + (piece, to) + counts[pieceType[quickBoard[to]]]--; + quickBoard[to] = 0; total--; + move++; + continue; + } else if(piece <= Q_BCASTL) { // castling, encoded as (Q_XCASTL, king-to) + (rook, rook-to) + piece = pieceList[piece]; // first two elements of pieceList contain King numbers + from = pieceList[piece]; // so this must be King + quickBoard[from] = 0; + pieceList[piece] = to; + 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; + if(QuickCompare(soughtBoard, minSought, maxSought) || + appData.ignoreColors && QuickCompare(reverseBoard, minReverse, maxReverse) || + flipSearch && (QuickCompare(flipBoard, minSought, maxSought) || + appData.ignoreColors && QuickCompare(rotateBoard, minReverse, maxReverse)) + ) { + static int lastCounts[EmptySquare+1]; + int i; + if(stretch) for(i=0; i= appData.stretch)) return cnt + 1 - stretch; + move++; + } while(1); +} + +void +InitSearch () +{ + int r, f; + flipSearch = FALSE; + CopyBoard(soughtBoard, boards[currentMove]); + soughtTotal = MakePieceList(soughtBoard, maxSought); + soughtBoard[EP_STATUS-1] = (currentMove & 1) + 1; + if(currentMove == 0 && gameMode == EditPosition) soughtBoard[EP_STATUS-1] = blackPlaysFirst + 1; // (!) + CopyBoard(reverseBoard, boards[currentMove]); + for(r=0; r= 5) { + for(r=BOARD_HEIGHT/2; rgameInfo.variant != gameInfo.variant) return -1; // wrong variant + if(appData.eloThreshold1 && (lg->gameInfo.whiteRating < appData.eloThreshold1 && lg->gameInfo.blackRating < appData.eloThreshold1)) return -1; + if(appData.eloThreshold2 && (lg->gameInfo.whiteRating < appData.eloThreshold2 || lg->gameInfo.blackRating < appData.eloThreshold2)) return -1; + if(appData.dateThreshold && (!lg->gameInfo.date || atoi(lg->gameInfo.date) < appData.dateThreshold)) return -1; + if(!initDone) { + for(next = WhitePawn; next>8 ^ random()<<6 ^random()<<20; + initDone = TRUE; + } + 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; + if((next = QuickScan( boards[scratch], &moveDatabase[lg->moves] )) < 0) return -1; // quick scan rules out it is there + if(appData.searchMode >= 4) return next; // for material searches, trust QuickScan. + } + if(btm) plyNr++; + if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr; + fseek(f, lg->offset, 0); + yynewfile(f); + while(1) { + yyboardindex = scratch; + quickFlag = plyNr+1; + next = Myylex(); + quickFlag = 0; + switch(next) { + case PGNTag: + if(plyNr) return -1; // after we have seen moves, any tags will be start of next game + default: + continue; + + case XBoardGame: + case GNUChessGame: + if(plyNr) return -1; // after we have seen moves, this is for new game + continue; + + case AmbiguousMove: // we cannot reconstruct the game beyond these two + case ImpossibleMove: + case WhiteWins: // game ends here with these four + case BlackWins: + case GameIsDrawn: + case GameUnfinished: + return -1; + + case IllegalMove: + if(appData.testLegality) return -1; + case WhiteCapturesEnPassant: + case BlackCapturesEnPassant: + case WhitePromotion: + case BlackPromotion: + case WhiteNonPromotion: + case BlackNonPromotion: + case NormalMove: + case FirstLeg: + case WhiteKingSideCastle: + case WhiteQueenSideCastle: + case BlackKingSideCastle: + case BlackQueenSideCastle: + case WhiteKingSideCastleWild: + case WhiteQueenSideCastleWild: + case BlackKingSideCastleWild: + case BlackQueenSideCastleWild: + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + fromX = currentMoveString[0] - AAA; + fromY = currentMoveString[1] - ONE; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; + promoChar = currentMoveString[4]; + break; + case WhiteDrop: + case BlackDrop: + fromX = next == WhiteDrop ? + (int) CharToPiece(ToUpper(currentMoveString[0])) : + (int) CharToPiece(ToLower(currentMoveString[0])); + fromY = DROP_RANK; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; + promoChar = 0; + break; + } + // Move encountered; peform it. We need to shuttle between two boards, as even/odd index determines side to move + plyNr++; + ApplyMove(fromX, fromY, toX, toY, promoChar, boards[scratch]); + if(PositionMatches(boards[scratch], boards[currentMove])) return plyNr; + if(appData.ignoreColors && PositionMatches(boards[scratch], reverseBoard)) return plyNr; + if(appData.findMirror) { + if(PositionMatches(boards[scratch], flipBoard)) return plyNr; + if(appData.ignoreColors && PositionMatches(boards[scratch], rotateBoard)) return plyNr; + } + } +} /* 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]; int gn = gameNumber; ListGame *lg = NULL; int numPGNTags = 0; - int err; + int err, pos = -1; GameMode oldGameMode; VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ @@ -11028,6 +12532,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) { @@ -11040,9 +12545,13 @@ LoadGame(f, gameNumber, title, useList) if (lg) { fseek(f, lg->offset, 0); GameListHighlight(gameNumber); + pos = lg->position; 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; } @@ -11067,7 +12576,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) { @@ -11177,6 +12686,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 ) { @@ -11238,7 +12748,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; @@ -11361,6 +12871,7 @@ LoadGame(f, gameNumber, title, useList) cm = (ChessMove) Myylex(); } + if(!creatingBook) { if (first.pr == NoProc) { StartChessProgram(&first); } @@ -11373,6 +12884,7 @@ LoadGame(f, gameNumber, title, useList) } DisplayBothClocks(); } + } /* [HGM] server: flag to write setup moves in broadcast file as one */ loadFlag = appData.suppressLoadMoves; @@ -11434,15 +12946,29 @@ 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(creatingBook) return TRUE; + if (!matchMode && pos > 0) { + ToNrEvent(pos); // [HGM] no autoplay if selected on position + } else if (matchMode || appData.timeDelay == 0) { ToEndEvent(); - gameMode = EditGame; - ModeHighlight(); } else if (appData.timeDelay > 0) { AutoPlayGameLoop(); } @@ -11456,8 +12982,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) { @@ -11474,10 +12999,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]; @@ -11498,10 +13020,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; @@ -11520,7 +13039,7 @@ LoadPosition(f, positionNumber, title) lastLoadPositionFP = f; lastLoadPositionNumber = positionNumber; safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0])); - if (first.pr == NoProc) { + if (first.pr == NoProc && !appData.noChessProgram) { StartChessProgram(&first); InitChessProgram(&first, FALSE); } @@ -11566,7 +13085,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; } @@ -11592,7 +13111,6 @@ LoadPosition(f, positionNumber, title) } startedFromSetupPosition = TRUE; - SendToProgram("force\n", &first); CopyBoard(boards[0], initial_position); if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; @@ -11605,7 +13123,10 @@ LoadPosition(f, positionNumber, title) DisplayMessage("", _("White to play")); } initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */ - SendBoard(&first, forwardMostMove); + if(first.pr != NoProc) { // [HGM] in tourney-mode a position can be loaded before the chess engine is installed + SendToProgram("force\n", &first); + SendBoard(&first, forwardMostMove); + } if (appData.debugMode) { int i, j; for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");} @@ -11631,8 +13152,7 @@ int i, j; void -CopyPlayerNameIntoFileName(dest, src) - char **dest, *src; +CopyPlayerNameIntoFileName (char **dest, char *src) { while (*src != NULLCHAR && *src != ',') { if (*src == ' ') { @@ -11644,8 +13164,8 @@ CopyPlayerNameIntoFileName(dest, src) } } -char *DefaultFileName(ext) - char *ext; +char * +DefaultFileName (char *ext) { static char def[MSG_SIZ]; char *p; @@ -11665,18 +13185,22 @@ 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]; - int result; + int result, i, t,tot=0; if (strcmp(filename, "-") == 0) { return SaveGame(stdout, 0, NULL); } else { - f = fopen(filename, append ? "a" : "w"); + for(i=0; i<10; i++) { // upto 10 tries + f = fopen(filename, append ? "a" : "w"); + if(f && i) fprintf(f, "[Delay \"%d retries, %d msec\"]\n",i,tot); + if(f || errno != 13) break; + DoSleep(t = 5 + random()%11); // wait 5-15 msec + tot += t; + } if (f == NULL) { snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename); DisplayError(buf, errno); @@ -11686,7 +13210,7 @@ SaveGameToFile(filename, append) DisplayMessage(_("Waiting for access to save file"), ""); flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing DisplayMessage(_("Saving game"), ""); - if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry... + if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError(_("Bad Seek"), errno); // better safe than sorry... result = SaveGame(f, 0, NULL); DisplayMessage(buf, ""); return result; @@ -11695,8 +13219,7 @@ SaveGameToFile(filename, append) } char * -SavePart(str) - char *str; +SavePart (char *str) { static char buf[MSG_SIZ]; char *p; @@ -11713,8 +13236,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; @@ -11757,8 +13280,8 @@ static int FindFirstMoveOutOfBook( int side ) return result; } -/* [AS] */ -void GetOutOfBookInfo( char * buf ) +void +GetOutOfBookInfo (char * buf) { int oob[2]; int i; @@ -11789,11 +13312,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; @@ -11801,12 +13322,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); @@ -11943,8 +13464,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)); } @@ -11956,8 +13480,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; @@ -12021,10 +13544,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 @@ -12036,8 +13556,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]; @@ -12065,10 +13584,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; @@ -12083,7 +13599,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); } @@ -12092,8 +13608,7 @@ SavePosition(f, dummy, dummy2) } void -ReloadCmailMsgEvent(unregister) - int unregister; +ReloadCmailMsgEvent (int unregister) { #if !WIN32 static char *inFilename = NULL; @@ -12159,7 +13674,7 @@ ReloadCmailMsgEvent(unregister) } int -RegisterMove() +RegisterMove () { FILE *f; char string[MSG_SIZ]; @@ -12249,7 +13764,7 @@ RegisterMove() } void -MailMoveEvent() +MailMoveEvent () { #if !WIN32 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1"; @@ -12337,7 +13852,7 @@ MailMoveEvent() } char * -CmailMsg() +CmailMsg () { #if WIN32 return NULL; @@ -12410,7 +13925,7 @@ CmailMsg() } void -ResetGameEvent() +ResetGameEvent () { if (gameMode == Training) SetTrainingModeOff(); @@ -12424,8 +13939,7 @@ ResetGameEvent() } void -ExitEvent(status) - int status; +ExitEvent (int status) { exiting++; if (exiting > 2) { @@ -12463,14 +13977,12 @@ ExitEvent(status) DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ ); + DestroyChildProcess(first.pr, 4 + first.useSigterm /* [AS] first.useSigterm */ ); } if (second.pr != NoProc) { DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DoSleep( appData.delayAfterQuit ); - DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ ); + DestroyChildProcess(second.pr, 4 + second.useSigterm /* [AS] second.useSigterm */ ); } if (first.isr != NULL) { RemoveInputSource(first.isr); @@ -12487,15 +13999,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(); @@ -12507,7 +14049,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; @@ -12538,12 +14080,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; @@ -12552,7 +14112,7 @@ PauseEvent() } void -EditCommentEvent() +EditCommentEvent () { char title[MSG_SIZ]; @@ -12569,7 +14129,7 @@ EditCommentEvent() void -EditTagsEvent() +EditTagsEvent () { char *tags = PGNTags(&gameInfo); bookUp = FALSE; @@ -12578,16 +14138,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; @@ -12595,7 +14213,10 @@ AnalyzeModeEvent() first.maybeThinking = FALSE; /* avoid killing GNU Chess */ EngineOutputPopUp(); } - if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode; + if (!appData.icsEngineAnalyze) { + gameMode = AnalyzeMode; + ClearEngineOutputPane(0); // [TK] exclude: to print exclusion/multipv header + } pausing = FALSE; ModeHighlight(); SetGameInfo(); @@ -12603,17 +14224,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; @@ -12624,15 +14256,16 @@ AnalyzeFileEvent() gameMode = AnalyzeFile; pausing = FALSE; ModeHighlight(); - SetGameInfo(); StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0f * appData.timeDelay)); + AnalysisPeriodicEvent(1); } void -MachineWhiteEvent() +MachineWhiteEvent () { char buf[MSG_SIZ]; char *bookHit = NULL; @@ -12672,7 +14305,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); @@ -12713,7 +14346,7 @@ MachineWhiteEvent() } void -MachineBlackEvent() +MachineBlackEvent () { char buf[MSG_SIZ]; char *bookHit = NULL; @@ -12749,7 +14382,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); @@ -12789,35 +14422,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")); @@ -12830,17 +14463,18 @@ SettingsMenuIfReady() } int -WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry) +WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry) { char buf[MSG_SIZ]; - if (cps->pr == NULL) { + 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); } @@ -12868,7 +14502,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 */ @@ -12892,14 +14526,24 @@ 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(!SupportedVariant(second.variants, gameInfo.variant, gameInfo.boardWidth, + gameInfo.boardHeight, gameInfo.holdingsSize, second.protocolVersion, second.tidy)) { + 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); @@ -12915,6 +14559,7 @@ TwoMachinesEvent P((void)) ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait); return; } + // we are now committed to starting the game stalling = 0; DisplayMessage("", ""); if (startedFromSetupPosition) { @@ -12928,7 +14573,7 @@ TwoMachinesEvent P((void)) } gameMode = TwoMachinesPlay; - pausing = FALSE; + pausing = startingEngine = FALSE; ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); @@ -12987,7 +14632,7 @@ TwoMachinesEvent P((void)) } void -TrainingEvent() +TrainingEvent () { if (gameMode == Training) { SetTrainingModeOff(); @@ -13010,7 +14655,7 @@ TrainingEvent() } void -IcsClientEvent() +IcsClientEvent () { if (!appData.icsActive) return; switch (gameMode) { @@ -13044,9 +14689,8 @@ IcsClientEvent() return; } - void -EditGameEvent() +EditGameEvent () { int i; @@ -13118,9 +14762,11 @@ EditGameEvent() SendToProgram("undo\n", &first); i--; } + if(!adjustedClock) { whiteTimeRemaining = timeRemaining[0][currentMove]; blackTimeRemaining = timeRemaining[1][currentMove]; DisplayBothClocks(); + } if (whiteFlag || blackFlag) { whiteFlag = blackFlag = 0; } @@ -13134,7 +14780,7 @@ EditGameEvent() void -EditPositionEvent() +EditPositionEvent () { if (gameMode == EditPosition) { EditGameEvent(); @@ -13155,10 +14801,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) { @@ -13167,14 +14814,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; @@ -13183,14 +14830,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; i target) { - SendToProgram("undo\n", &first); + if(moveList[currentMove-1][1] == '@' && moveList[currentMove-1][0] == '@') { + // null move cannot be undone. Reload program with move history before it. + int i; + for(i=target; i>backwardMostMove; i--) { // seek back to start or previous null move + if(moveList[i-1][1] == '@' && moveList[i-1][0] == '@') break; + } + SendBoard(&first, i); + if(second.analyzing) SendBoard(&second, i); + for(currentMove=i; currentMove= forwardMostMove) to = forwardMostMove; @@ -13919,7 +15623,7 @@ ToNrEvent(int to) } void -RevertEvent(Boolean annotate) +RevertEvent (Boolean annotate) { if(PopTail(annotate)) { // [HGM] vari: restore old game tail return; @@ -13937,13 +15641,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; @@ -13976,7 +15680,7 @@ RetractMoveEvent() } void -MoveNowEvent() +MoveNowEvent () { ChessProgramState *cps; @@ -14011,7 +15715,7 @@ MoveNowEvent() } void -TruncateGameEvent() +TruncateGameEvent () { EditGameEvent(); if (gameMode != EditGame) return; @@ -14019,7 +15723,7 @@ TruncateGameEvent() } void -TruncateGame() +TruncateGame () { CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate if (forwardMostMove > currentMove) { @@ -14035,20 +15739,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; @@ -14061,20 +15765,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; @@ -14092,7 +15830,7 @@ BookEvent() } void -AboutGameEvent() +AboutGameEvent () { char *tags = PGNTags(&gameInfo); TagsPopUp(tags, CmailMsg()); @@ -14102,9 +15840,7 @@ AboutGameEvent() /* end button procedures */ void -PrintPosition(fp, move) - FILE *fp; - int move; +PrintPosition (FILE *fp, int move) { int i, j; @@ -14122,8 +15858,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); @@ -14134,10 +15869,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++; @@ -14154,7 +15888,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) { @@ -14164,7 +15899,7 @@ TidyProgramName(prog, host, buf) } char * -TimeControlTagValue() +TimeControlTagValue () { char buf[MSG_SIZ]; if (!appData.clockMode) { @@ -14180,13 +15915,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; @@ -14278,15 +16015,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 @@ -14321,8 +16056,7 @@ ReplaceComment(index, text) } void -CrushCRs(text) - char *text; +CrushCRs (char *text) { char *p = text; char *q = text; @@ -14336,25 +16070,26 @@ 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; if (commentList[index] != NULL) { + Boolean addClosingBrace = addBraces; old = commentList[index]; oldlen = strlen(old); while(commentList[index][oldlen-1] == '\n') @@ -14371,7 +16106,7 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n"); else strcat(commentList[index], "\n"); strcat(commentList[index], text); - if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n"); + if(addClosingBrace) strcat(commentList[index], addClosingBrace == 2 ? ")\n" : "\n}\n"); else strcat(commentList[index], "\n"); } else { commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4... @@ -14384,7 +16119,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 ); @@ -14397,7 +16133,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; @@ -14407,8 +16144,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; @@ -14438,6 +16178,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; @@ -14461,7 +16202,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 @@ -14471,7 +16212,7 @@ char *GetInfoFromComment( int index, char * text ) while( *++sep >= '0' && *sep <= '9'); // strip seconds if(deci >= 0) while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds - while(*sep == ' ') sep++; + while(*sep == ' ' || *sep == '\n' || *sep == '\r') sep++; } if( depth <= 0 ) { @@ -14492,14 +16233,12 @@ 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]; - if (cps->pr == NULL) return; + if (cps->pr == NoProc) return; Attention(cps); if (appData.debugMode) { @@ -14508,6 +16247,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); @@ -14534,12 +16277,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]; @@ -14549,10 +16287,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; } @@ -14601,7 +16339,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); @@ -14614,6 +16352,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); } } @@ -14629,10 +16372,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; @@ -14696,7 +16436,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' || @@ -14706,9 +16447,7 @@ ChessProgramState *WhitePlayer() } void -SendTimeRemaining(cps, machineWhite) - ChessProgramState *cps; - int /*boolean*/ machineWhite; +SendTimeRemaining (ChessProgramState *cps, int machineWhite) { char message[MSG_SIZ]; long time, otime; @@ -14726,9 +16465,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; @@ -14740,12 +16476,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); @@ -14765,11 +16518,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); @@ -14785,18 +16534,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); @@ -14807,7 +16553,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 { @@ -14848,8 +16594,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; @@ -14898,9 +16644,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) || @@ -14911,16 +16655,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]; @@ -14929,6 +16672,7 @@ ParseFeatures(args, cps) if (*p == NULLCHAR) return; if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue; + if (BoolFeature(&p, "xedit", &cps->extendedEdit, cps)) continue; if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue; if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue; if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue; @@ -14953,9 +16697,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; @@ -14966,13 +16711,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); @@ -15008,8 +16757,7 @@ ParseFeatures(args, cps) } void -PeriodicUpdatesEvent(newState) - int newState; +PeriodicUpdatesEvent (int newState) { if (newState == appData.periodicUpdates) return; @@ -15027,8 +16775,7 @@ PeriodicUpdatesEvent(newState) } void -PonderNextMoveEvent(newState) - int newState; +PonderNextMoveEvent (int newState) { if (newState == appData.ponderNextMove) return; if (gameMode == EditPosition) EditPositionDone(TRUE); @@ -15048,9 +16795,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]; @@ -15063,7 +16808,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 @@ -15090,8 +16835,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; @@ -15099,11 +16843,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 @@ -15113,7 +16857,7 @@ TypeInEvent(char firstChar) } void -TypeInDoneEvent(char *move) +TypeInDoneEvent (char *move) { Board board; int n, fromX, fromY, toX, toY; @@ -15121,7 +16865,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; } @@ -15130,17 +16874,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")); } @@ -15148,8 +16900,7 @@ TypeInDoneEvent(char *move) } void -DisplayMove(moveNumber) - int moveNumber; +DisplayMove (int moveNumber) { char message[MSG_SIZ]; char res[MSG_SIZ]; @@ -15206,13 +16957,9 @@ DisplayMove(moveNumber) } void -DisplayComment(moveNumber, text) - int moveNumber; - char *text; +DisplayComment (int moveNumber, char *text) { char title[MSG_SIZ]; - char buf[8000]; // comment can be long! - int score, depth; if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0])); @@ -15221,14 +16968,6 @@ DisplayComment(moveNumber, text) WhiteOnMove(moveNumber) ? " " : ".. ", parseList[moveNumber]); } - // [HGM] PV info: display PV info together with (or as) comment - if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) { - if(text == NULL) text = ""; - score = pvInfoList[moveNumber].score; - snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., - depth, (pvInfoList[moveNumber].time+50)/100, text); - text = buf; - } if (text != NULL && (appData.autoDisplayComment || commentUp)) CommentPopUp(title, text); } @@ -15240,8 +16979,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; @@ -15268,7 +17006,7 @@ Attention(cps) } int -CheckFlags() +CheckFlags () { if (whiteTimeRemaining <= 0) { if (!whiteFlag) { @@ -15318,7 +17056,7 @@ CheckFlags() } void -CheckTimeControl() +CheckTimeControl () { if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode gameMode == PlayFromGameFile || forwardMostMove == 0) return; @@ -15343,7 +17081,7 @@ CheckTimeControl() } void -DisplayBothClocks() +DisplayBothClocks () { int wom = gameMode == EditPosition ? !blackPlaysFirst : WhiteOnMove(currentMove); @@ -15365,8 +17103,7 @@ DisplayBothClocks() /* Get the current time as a TimeMark */ void -GetTimeMark(tm) - TimeMark *tm; +GetTimeMark (TimeMark *tm) { #if HAVE_GETTIMEOFDAY @@ -15398,8 +17135,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); @@ -15420,8 +17156,7 @@ static TimeMark tickStartTM; static long intendedTickLength; long -NextTickLength(timeRemaining) - long timeRemaining; +NextTickLength (long timeRemaining) { long nominalTickLength, nextTickLength; @@ -15437,16 +17172,18 @@ 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; else whiteTimeRemaining += 60000*dir; DisplayBothClocks(); + adjustedClock = TRUE; } /* Stop clocks and reset to a fresh time control */ void -ResetClocks() +ResetClocks () { (void) StopClockTimer(); if (appData.icsActive) { @@ -15465,13 +17202,14 @@ ResetClocks() } lastWhite = lastBlack = whiteStartMove = blackStartMove = 0; DisplayBothClocks(); + adjustedClock = FALSE; } #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */ /* Decrement running clock by amount of time that has passed */ void -DecrementClocks() +DecrementClocks () { long timeRemaining; long lastTickLength, fudge; @@ -15515,6 +17253,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); @@ -15551,7 +17299,7 @@ DecrementClocks() from the color that is *not* on move now. */ void -SwitchClocks(int newMoveNr) +SwitchClocks (int newMoveNr) { long lastTickLength; TimeMark now; @@ -15614,7 +17362,7 @@ SwitchClocks(int newMoveNr) /* Stop both clocks */ void -StopClocks() +StopClocks () { long lastTickLength; TimeMark now; @@ -15640,7 +17388,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(); @@ -15671,8 +17419,7 @@ StartClocks() } char * -TimeString(ms) - long ms; +TimeString (long ms) { long second, minute, hour, day; char *sign = ""; @@ -15719,8 +17466,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; @@ -15734,8 +17480,7 @@ StrStr(string, match) } char * -StrCaseStr(string, match) - char *string, *match; +StrCaseStr (char *string, char *match) { int i, j, length; @@ -15754,8 +17499,7 @@ StrCaseStr(string, match) #ifndef _amigados int -StrCaseCmp(s1, s2) - char *s1, *s2; +StrCaseCmp (char *s1, char *s2) { char c1, c2; @@ -15770,24 +17514,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; @@ -15799,8 +17540,7 @@ StrSave(s) } char * -StrSavePtr(s, savePtr) - char *s, **savePtr; +StrSavePtr (char *s, char **savePtr) { if (*savePtr) { free(*savePtr); @@ -15812,7 +17552,7 @@ StrSavePtr(s, savePtr) } char * -PGNDate() +PGNDate () { time_t clock; struct tm *tm; @@ -15827,9 +17567,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; @@ -15861,7 +17599,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)); @@ -15910,7 +17648,7 @@ PositionToFEN(move, overrideCastling) } else { if(nrCastlingRights) { q = p; - if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) { + if(appData.fischerCastling) { /* [HGM] write directly from rights */ if(boards[move][CASTLING][2] != NoRights && boards[move][CASTLING][0] != NoRights ) @@ -15928,14 +17666,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 */ @@ -15943,7 +17697,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; @@ -15975,9 +17730,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++) @@ -15989,45 +17745,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 */ @@ -16036,6 +17787,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 */ @@ -16047,7 +17800,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++); @@ -16065,6 +17818,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