X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=6238eeb727ad4611eb6b0da8b48369639556ed0a;hb=62b9ce815b3e4ad57092f321c293895aeea4bf45;hp=fcc611a1412ae90f3ba446b7fa71243c64da38a4;hpb=2ee0112d8e774b268d91c618cddab6a0cfeab4fc;p=xboard.git diff --git a/backend.c b/backend.c index fcc611a..e6f48a4 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 Free Software Foundation, Inc. * * Enhancements Copyright 2005 Alessandro Scotti * @@ -55,11 +55,14 @@ #ifdef WIN32 #include -#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); +int flock(int f, int code); +#define LOCK_EX 2 +#define SLASH '\\' #else -#define DoSleep( n ) if( (n) >= 0) sleep(n) +#include +#define SLASH '/' #endif @@ -144,12 +147,6 @@ 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)); @@ -165,8 +162,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, @@ -188,7 +183,7 @@ void SendTimeControl P((ChessProgramState *cps, char *TimeControlTagValue P((void)); void Attention P((ChessProgramState *cps)); void FeedMovesToProgram P((ChessProgramState *cps, int upto)); -void ResurrectChessProgram P((void)); +int ResurrectChessProgram P((void)); void DisplayComment P((int moveNumber, char *text)); void DisplayMove P((int moveNumber)); @@ -201,7 +196,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)); @@ -210,8 +204,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)); @@ -225,6 +217,11 @@ void OutputKibitz(int window, char *text); int PerpetualChase(int first, int last); int EngineOutputIsUp(); 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 *f)); +void DisplayTwoMachinesTitle P(()); #ifdef WIN32 extern void ConsoleCreate(); @@ -241,6 +238,7 @@ char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [H void ics_update_width P((int new_width)); extern char installDir[MSG_SIZ]; VariantClass startVariant; /* [HGM] nicks: initial variant */ +Boolean abortMatch; extern int tinyLayout, smallLayout; ChessProgramStats programStats; @@ -267,6 +265,7 @@ 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 lastMsg[MSG_SIZ]; ChessSquare pieceSweep = EmptySquare; ChessSquare promoSweep = EmptySquare, defaultPromoChoice; int promoDefaultAltered; @@ -313,7 +312,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 ); @@ -325,7 +324,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,count); + fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count); } return dst; @@ -341,7 +340,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); @@ -359,7 +358,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; @@ -384,6 +383,7 @@ PosFlags(index) case VariantShatranj: case VariantCourier: case VariantMakruk: + case VariantGrand: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -407,7 +407,7 @@ char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ]; char thinkOutput1[MSG_SIZ*10]; -ChessProgramState first, second; +ChessProgramState first, second, pairing; /* premove variables */ int premoveToX = 0; @@ -454,11 +454,13 @@ 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; +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 */ long timeRemaining[2][MAX_MOVES]; -int matchGame = 0; -TimeMark programStartTime; +int matchGame = 0, nextGame = 0, roundNr = 0; +Boolean waitingForGame = FALSE; +TimeMark programStartTime, pauseStart; char ics_handle[MSG_SIZ]; int have_set_title = 0; @@ -481,7 +483,7 @@ int nrCastlingRights; // For TwoKings, or to implement castling-unknown status int initialRulePlies, FENrulePlies; FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) int loadFlag = 0; -int shuffleOpenings; +Boolean shuffleOpenings; int mute; // mute all sounds // [HGM] vari: next 12 to save and restore variations @@ -496,6 +498,8 @@ ChessMove savedResult[MAX_VARIATIONS]; void PushTail P((int firstMove, int lastMove)); Boolean PopTail P((Boolean annotate)); +void PushInner P((int firstMove, int lastMove)); +void PopInner P((Boolean annotate)); void CleanupTail P((void)); ChessSquare FIDEArray[2][BOARD_FILES] = { @@ -584,6 +588,13 @@ ChessSquare JanusArray[2][BOARD_FILES] = { BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook } }; +ChessSquare GrandArray[2][BOARD_FILES] = { + { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, + WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare }, + { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing, + BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare } +}; + #ifdef GOTHIC ChessSquare GothicArray[2][BOARD_FILES] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, @@ -597,10 +608,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 @@ -632,8 +643,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) @@ -643,7 +653,7 @@ string_to_rating(str) } void -ClearProgramStats() +ClearProgramStats () { /* Init programStats */ programStats.movelist[0] = 0; @@ -659,7 +669,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"; @@ -693,7 +703,26 @@ CommonEngineInit() } void -ClearOptions(ChessProgramState *cps) +UnloadEngine (ChessProgramState *cps) +{ + /* Kill off first chess program */ + if (cps->isr != NULL) + RemoveInputSource(cps->isr); + cps->isr = NULL; + + if (cps->pr != NoProc) { + ExitAnalyzeMode(); + DoSleep( appData.delayBeforeQuit ); + SendToProgram("quit\n", cps); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(cps->pr, cps->useSigterm); + } + cps->pr = NoProc; + if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which); +} + +void +ClearOptions (ChessProgramState *cps) { int i; cps->nrOptions = cps->comboCnt = 0; @@ -708,7 +737,8 @@ char *engineNames[] = { "second" }; -InitEngine(ChessProgramState *cps, int n) +void +InitEngine (ChessProgramState *cps, int n) { // [HGM] all engine initialiation put in a function that does one engine ClearOptions(cps); @@ -763,7 +793,11 @@ InitEngine(ChessProgramState *cps, int n) /* [HGM] debug */ cps->debug = FALSE; + cps->supportsNPS = UNKNOWN; + cps->memSize = FALSE; + cps->maxCores = FALSE; + cps->egtFormats[0] = NULLCHAR; /* [HGM] options */ cps->optionSettings = appData.engOptions[n]; @@ -780,7 +814,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); @@ -791,18 +825,153 @@ 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; + +void +LoadEngine () +{ + int i; + if(WaitForEngine(savCps, LoadEngine)) return; + CommonEngineInit(); // recalculate time odds + 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 + } + InitChessProgram(savCps, FALSE); + SendToProgram("force\n", savCps); + DisplayMessage("", ""); + if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove); + for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps); + ThawUI(); + SetGNUMode(); +} + +void +ReplaceEngine (ChessProgramState *cps, int n) +{ + EditGameEvent(); + UnloadEngine(cps); + appData.noChessProgram = FALSE; + appData.clockMode = TRUE; + InitEngine(cps, n); + UpdateLogos(TRUE); + if(n) return; // only startup first engine immediately; second can wait + savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-( + LoadEngine(); +} + +extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params; +extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick; + +static char resetOptions[] = + "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 " + "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" " + "-firstOptions \"\" -firstNPS -1 -fn \"\""; + +void +Load (ChessProgramState *cps, int i) +{ + char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ]; + if(engineLine && engineLine[0]) { // an engine was selected from the combo box + snprintf(buf, MSG_SIZ, "-fcp %s", engineLine); + SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second* + ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE; + appData.firstProtocolVersion = PROTOVER; + ParseArgsFromString(buf); + SwapEngines(i); + ReplaceEngine(cps, i); + 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 + p[-1] = 0; + appData.directory[i] = strdup(engineName); + p[-1] = SLASH; + } else appData.directory[i] = "."; + if(params[0]) { + if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces + snprintf(command, MSG_SIZ, "%s %s", p, params); + p = command; + } + appData.chessProgram[i] = strdup(p); + appData.isUCI[i] = isUCI; + appData.protocolVersion[i] = v1 ? 1 : PROTOVER; + appData.hasOwnBookUCI[i] = hasBook; + if(!nickName[0]) useNick = FALSE; + if(useNick) ASSIGN(appData.pgnName[i], nickName); + if(addToList) { + int len; + char quote; + q = firstChessProgramNames; + 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], + useNick ? " -fn \"" : "", + useNick ? nickName : "", + useNick ? "\"" : "", + v1 ? " -firstProtocolVersion 1" : "", + hasBook ? "" : " -fNoOwnBookUCI", + isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "", + storeVariant ? " -variant " : "", + storeVariant ? VariantName(gameInfo.variant) : ""); + firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1); + snprintf(firstChessProgramNames, len, "%s%s", q, buf); + if(q) free(q); + } + ReplaceEngine(cps, i); } void -InitBackEnd1() +InitTimeControls () { int matched, min, sec; + /* + * Parse timeControl resource + */ + if (!ParseTimeControl(appData.timeControl, appData.timeIncrement, + appData.movesPerSession)) { + char buf[MSG_SIZ]; + snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl); + DisplayFatalError(buf, 0, 2); + } + + /* + * Parse searchTime resource + */ + if (*appData.searchTime != NULLCHAR) { + matched = sscanf(appData.searchTime, "%d:%d", &min, &sec); + if (matched == 1) { + searchTime = min * 60; + } else if (matched == 2) { + searchTime = min * 60 + sec; + } else { + char buf[MSG_SIZ]; + snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime); + DisplayFatalError(buf, 0, 2); + } + } +} + +void +InitBackEnd1 () +{ ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant 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(); programStats.ok_to_send = 1; @@ -846,31 +1015,7 @@ InitBackEnd1() } } - /* - * Parse timeControl resource - */ - if (!ParseTimeControl(appData.timeControl, appData.timeIncrement, - appData.movesPerSession)) { - char buf[MSG_SIZ]; - snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl); - DisplayFatalError(buf, 0, 2); - } - - /* - * Parse searchTime resource - */ - if (*appData.searchTime != NULLCHAR) { - matched = sscanf(appData.searchTime, "%d:%d", &min, &sec); - if (matched == 1) { - searchTime = min * 60; - } else if (matched == 2) { - searchTime = min * 60 + sec; - } else { - char buf[MSG_SIZ]; - snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime); - DisplayFatalError(buf, 0, 2); - } - } + InitTimeControls(); /* [AS] Adjudication threshold */ adjudicateLossThreshold = appData.adjudicateLossThreshold; @@ -879,6 +1024,13 @@ InitBackEnd1() InitEngine(&second, 1); CommonEngineInit(); + pairing.which = "pairing"; // pairing engine + pairing.pr = NoProc; + pairing.isr = NULL; + pairing.program = appData.pairingEngine; + pairing.host = "localhost"; + pairing.dir = "."; + if (appData.icsActive) { appData.clockMode = TRUE; /* changes dynamically in ICS mode */ } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode @@ -910,7 +1062,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); @@ -928,7 +1080,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); @@ -959,13 +1111,14 @@ InitBackEnd1() case VariantAtomic: /* should work except for win condition */ case Variant3Check: /* should work except for win condition */ case VariantShatranj: /* should work except for all win conditions */ - case VariantMakruk: /* should work except for daw countdown */ + case VariantMakruk: /* should work except for draw countdown */ case VariantBerolina: /* might work if TestLegality is off */ case VariantCapaRandom: /* should work */ case VariantJanus: /* should work */ case VariantSuper: /* experimental */ case VariantGreat: /* experimental, requires legality testing to be off */ case VariantSChess: /* S-Chess, should work */ + case VariantGrand: /* should work */ case VariantSpartan: /* should work */ break; } @@ -973,7 +1126,8 @@ InitBackEnd1() } -int NextIntegerFromString( char ** str, long * value ) +int +NextIntegerFromString (char ** str, long * value) { int result = -1; char * s = *str; @@ -998,7 +1152,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 ); @@ -1015,7 +1170,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; @@ -1056,7 +1212,8 @@ 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; @@ -1079,10 +1236,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; @@ -1145,7 +1299,7 @@ ParseTimeControl(tc, ti, mps) } void -InitBackEnd2() +InitBackEnd2 () { if (appData.debugMode) { fprintf(debugFP, "%s\n", programVersion); @@ -1174,38 +1328,136 @@ InitBackEnd2() } } +int +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 + if(index == 0) return 1; + res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc + if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind + return res; +} + +int +LoadGameOrPosition (int gameNr) +{ // [HGM] taken out of MatchEvent and NextMatchGame (to combine it) + if (*appData.loadGameFile != NULLCHAR) { + if (!LoadGameFromFile(appData.loadGameFile, + CalculateIndex(appData.loadGameIndex, gameNr), + appData.loadGameFile, FALSE)) { + DisplayFatalError(_("Bad game file"), 0, 1); + return 0; + } + } else if (*appData.loadPositionFile != NULLCHAR) { + if (!LoadPositionFromFile(appData.loadPositionFile, + CalculateIndex(appData.loadPositionIndex, gameNr), + appData.loadPositionFile)) { + DisplayFatalError(_("Bad position file"), 0, 1); + return 0; + } + } + return 1; +} + +void +ReserveGame (int gameNr, char resChar) +{ + FILE *tf = fopen(appData.tourneyFile, "r+"); + char *p, *q, c, buf[MSG_SIZ]; + if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match + safeStrCpy(buf, lastMsg, MSG_SIZ); + DisplayMessage(_("Pick new game"), ""); + flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it + ParseArgsFromFile(tf); + p = q = appData.results; + if(appData.debugMode) { + char *r = appData.participants; + fprintf(debugFP, "results = '%s'\n", p); + while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++; + fprintf(debugFP, "\n"); + } + while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!) + nextGame = q - p; + q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one! + safeStrCpy(q, p, strlen(p) + 2); + if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result + if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame); + if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done + if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char + q[nextGame] = '*'; + } + fseek(tf, -(strlen(p)+4), SEEK_END); + c = fgetc(tf); + if(c != '"') // depending on DOS or Unix line endings we can be one off + fseek(tf, -(strlen(p)+2), SEEK_END); + else fseek(tf, -(strlen(p)+3), SEEK_END); + fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing + DisplayMessage(buf, ""); + 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, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr); +} + 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 + abortMatch = TRUE; + if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game. + return; + } +// if(gameMode != BeginningOfGame) { +// DisplayError(_("You can only start a match from the initial position."), 0); +// return; +// } + abortMatch = FALSE; + if(mode == 2) appData.matchGames = appData.defaultMatchGames; /* Set up machine vs. machine match */ - if (appData.noChessProgram) { + nextGame = 0; + NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it + if(appData.tourneyFile[0]) { + ReserveGame(-1, 0); + if(nextGame > appData.matchGames) { + char buf[MSG_SIZ]; + if(strchr(appData.results, '*') == NULL) { + FILE *f; + appData.tourneyCycles++; + 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")); + matchMode = mode; + ScheduleDelayedEvent(NextMatchGame, 10000); + return; + } + } + } + snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile); + DisplayError(buf, 0); + appData.tourneyFile[0] = 0; + return; + } + } else + if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically DisplayFatalError(_("Can't have a match with no chess programs"), 0, 2); return; } matchMode = mode; - matchGame = 1; - if (*appData.loadGameFile != NULLCHAR) { - int index = appData.loadGameIndex; // [HGM] autoinc - if(index<0) lastIndex = index = 1; - if (!LoadGameFromFile(appData.loadGameFile, - index, - appData.loadGameFile, FALSE)) { - DisplayFatalError(_("Bad game file"), 0, 1); - return; - } - } else if (*appData.loadPositionFile != NULLCHAR) { - int index = appData.loadPositionIndex; // [HGM] autoinc - if(index<0) lastIndex = index = 1; - if (!LoadPositionFromFile(appData.loadPositionFile, - index, - appData.loadPositionFile)) { - DisplayFatalError(_("Bad position file"), 0, 1); - return; - } - } - first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches - TwoMachinesEvent(); + matchGame = roundNr = 1; + first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches + NextMatchGame(); } void @@ -1238,7 +1490,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); @@ -1268,6 +1520,12 @@ InitBackEnd3 P((void)) DisplayMessage("", ""); if (StrCaseCmp(appData.initialMode, "") == 0) { initialMode = BeginningOfGame; + if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back + gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted + ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it + gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position" + ModeHighlight(); + } } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) { initialMode = TwoMachinesPlay; } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) { @@ -1286,7 +1544,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); @@ -1294,6 +1552,15 @@ InitBackEnd3 P((void)) } if (appData.matchMode) { + if(appData.tourneyFile[0]) { // start tourney from command line + FILE *f; + if(f = fopen(appData.tourneyFile, "r")) { + ParseArgsFromFile(f); // make sure tourney parmeters re known + fclose(f); + appData.clockMode = TRUE; + SetGNUMode(); + } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file + } MatchEvent(TRUE); } else if (*appData.cmailGameName != NULLCHAR) { /* Set up cmail mode */ @@ -1392,6 +1659,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) @@ -1399,7 +1678,7 @@ InitBackEnd3 P((void)) * Returns 0 if okay, error code if not. */ int -establish() +establish () { char buf[MSG_SIZ]; @@ -1440,7 +1719,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++) { @@ -1454,10 +1734,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) { @@ -1472,11 +1749,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; @@ -1530,12 +1803,7 @@ 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; @@ -1557,7 +1825,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. @@ -1566,7 +1834,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; @@ -1579,12 +1848,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); @@ -1597,13 +1865,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) { @@ -1623,8 +1889,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; @@ -1648,8 +1913,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; @@ -1669,8 +1933,7 @@ StripHighlight(s) char *variantNames[] = VARIANT_NAMES; char * -VariantName(v) - VariantClass v; +VariantName (VariantClass v) { return variantNames[v]; } @@ -1679,8 +1942,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; @@ -1864,7 +2126,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); @@ -1891,10 +2153,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; @@ -1932,9 +2191,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); @@ -1944,9 +2201,7 @@ SendToPlayer(data, length) } void -PackHolding(packed, holding) - char packed[]; - char *holding; +PackHolding (char packed[], char *holding) { char *p = holding; char *q = packed; @@ -1981,8 +2236,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; @@ -2030,21 +2284,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; @@ -2086,7 +2340,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; @@ -2197,7 +2451,7 @@ 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 @@ -2219,7 +2473,7 @@ PlotSeekAd(int i) } void -AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot) +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); @@ -2243,7 +2497,7 @@ AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, in } 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); @@ -2257,7 +2511,7 @@ EraseSeekDot(int i) } void -RemoveSeekAd(int nr) +RemoveSeekAd (int nr) { int i; for(i=0; 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:") || @@ -2925,9 +3182,9 @@ read_from_ics(isr, closure, data, count, error) } } // [HGM] chat: end of patch + backup = i; if (appData.zippyTalk || appData.zippyPlay) { /* [DM] Backup address for color zippy lines */ - backup = i; #if ZIPPY if (loggedOn == TRUE) if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) || @@ -3092,6 +3349,8 @@ read_from_ics(isr, closure, data, count, error) continue; } + if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't + if (looking_at(buf, &i, "\\ ")) { if (prevColor != ColorNormal) { if (oldi > next_out) { @@ -3275,7 +3534,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) { // [HGM] seekgraph: on ICC sought-list has no termination line soughtPending = FALSE; seekGraphUp = TRUE; DrawSeekGraph(); @@ -3352,8 +3611,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 { @@ -3579,6 +3838,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) { @@ -3614,7 +3874,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); @@ -3766,11 +4026,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... @@ -3846,8 +4106,7 @@ read_from_ics(isr, closure, data, count, error) #define RELATION_STARTING_POSITION -4 /* FICS only */ void -ParseBoard12(string) - char *string; +ParseBoard12 (char *string) { GameMode newGameMode; int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i; @@ -4154,6 +4413,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) { @@ -4189,6 +4455,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) { @@ -4557,8 +4824,7 @@ GetMoveListEvent() } void -AnalysisPeriodicEvent(force) - int force; +AnalysisPeriodicEvent (int force) { if (((programStats.ok_to_send == 0 || programStats.line_is_book) && !force) || !appData.periodicUpdates) @@ -4574,19 +4840,22 @@ 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] == '@') { + // 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); } @@ -4624,6 +4893,16 @@ SendMoveToProgram(moveNum, cps) else SendToProgram("O-O-O\n", cps); } else SendToProgram(moveList[moveNum], 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 + snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0', + moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4); + SendToProgram(buf, cps); } else SendToProgram(moveList[moveNum], cps); /* End of additions by Tord */ @@ -4646,12 +4925,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: @@ -4667,7 +4949,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: @@ -4677,7 +4959,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: @@ -4720,13 +5002,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; @@ -4787,12 +5069,10 @@ UploadGameEvent() } void -CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) - int rf, ff, rt, ft; - char promoChar; - char move[7]; +CoordsToComputerAlgebraic (int rf, int ff, int rt, int ft, char promoChar, char move[7]) { if (rf == DROP_RANK) { + if(ff == EmptySquare) sprintf(move, "@@@@\n"); else // [HGM] pass sprintf(move, "%c@%c%c\n", ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt); } else { @@ -4807,8 +5087,7 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) } void -ProcessICSInitScript(f) - FILE *f; +ProcessICSInitScript (FILE *f) { char buf[MSG_SIZ]; @@ -4823,7 +5102,7 @@ ProcessICSInitScript(f) static int lastX, lastY, selectFlag, dragging; void -Sweep(int step) +Sweep (int step) { ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep; if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn; @@ -4837,19 +5116,20 @@ Sweep(int step) 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)); 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; @@ -4860,7 +5140,7 @@ int PromoScroll(int x, int y) } void -NextPiece(int step) +NextPiece (int step) { ChessSquare piece = boards[currentMove][toY][toX]; do { @@ -4875,7 +5155,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; @@ -4927,12 +5207,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); @@ -5011,19 +5286,26 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } +Boolean pushed = FALSE; +char *lastParseAttempt; void -ParsePV(char *pv, Boolean storeComments) +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) { + PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position! + pushed = TRUE; + } endPV = forwardMostMove; do { while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace 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); @@ -5058,76 +5340,123 @@ fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, f endPV++; CopyBoard(boards[endPV], boards[endPV-1]); ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]); - moveList[endPV-1][0] = fromX + AAA; - moveList[endPV-1][1] = fromY + ONE; - moveList[endPV-1][2] = toX + AAA; - moveList[endPV-1][3] = toY + ONE; - moveList[endPV-1][4] = promoChar; - moveList[endPV-1][5] = NULLCHAR; + CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]); strncat(moveList[endPV-1], "\n", MOVE_LEN); - if(storeComments) - CoordsToAlgebraic(boards[endPV - 1], + CoordsToAlgebraic(boards[endPV - 1], PosFlags(endPV - 1), fromY, fromX, toY, toX, promoChar, parseList[endPV - 1]); - else - parseList[endPV-1][0] = NULLCHAR; } while(valid); - currentMove = endPV; + if(atEnd == 2) return; // used hidden, for PV conversion + currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1; if(currentMove == forwardMostMove) ClearPremoveHighlights(); else SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE, moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE); DrawPosition(TRUE, boards[currentMove]); } +int +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++) + if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin) + return i; + return -1; +} + 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 startPV; - char *p; + int startPV, multi, lineStart, origIndex = index; + char *p, buf2[MSG_SIZ]; if(index < 0 || index >= strlen(buf)) return FALSE; // sanity lastX = x; lastY = y; while(index > 0 && buf[index-1] != '\n') index--; // beginning of line - startPV = index; + lineStart = startPV = index; while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index; if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3; index = startPV; 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; - ParsePV(buf+startPV, FALSE); + if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) { + int n = first.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; + *start = *end = 0; + return FALSE; + } + ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode); *start = startPV; *end = index-1; return TRUE; } +char * +PvToSAN (char *pv) +{ + static char buf[10*MSG_SIZ]; + int i, k=0, savedEnd=endPV, saveFMM = forwardMostMove; + *buf = NULLCHAR; + if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV); + ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it + for(i = forwardMostMove; i forwardMostMove) { + Boolean saveAnimate = appData.animate; + if(pushed) { + if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space + if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official + } else storedGames--; // abandon shelved tail of original game + } + pushed = FALSE; + forwardMostMove = currentMove; + currentMove = oldFMM; + appData.animate = FALSE; + ToNrEvent(forwardMostMove); + appData.animate = saveAnimate; + } currentMove = forwardMostMove; + if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation ClearPremoveHighlights(); DrawPosition(TRUE, boards[currentMove]); } 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; @@ -5156,7 +5485,8 @@ int squaresLeft[4]; int piecesLeft[(int)BlackPawn]; int seed, nrOfShuffles; -void GetPositionNumber() +void +GetPositionNumber () { // sets global variable seed int i; @@ -5168,7 +5498,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; @@ -5186,7 +5517,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; @@ -5197,7 +5529,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; @@ -5212,7 +5545,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; @@ -5300,7 +5634,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 */ { @@ -5324,7 +5659,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 }; @@ -5361,8 +5697,7 @@ void Prelude(Board board) } void -InitPosition(redraw) - int redraw; +InitPosition (int redraw) { ChessSquare (* pieces)[BOARD_FILES]; int i, j, pawnRow, overrule, @@ -5428,6 +5763,14 @@ InitPosition(redraw) case VariantTwoKings: pieces = twoKingsArray; break; + case VariantGrand: + pieces = GrandArray; + nrCastlingRights = 0; + SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); + gameInfo.boardWidth = 10; + gameInfo.boardHeight = 10; + gameInfo.holdingsSize = 7; + break; case VariantCapaRandom: shuffleOpenings = TRUE; case VariantCapablanca: @@ -5544,7 +5887,7 @@ InitPosition(redraw) pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */ if(pawnRow < 1) pawnRow = 1; - if(gameInfo.variant == VariantMakruk) pawnRow = 2; + if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2; /* User pieceToChar list overrules defaults */ if(appData.pieceToCharTable != NULL) @@ -5558,7 +5901,7 @@ InitPosition(redraw) initialPosition[i][j] = s; if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue; - initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth]; + initialPosition[gameInfo.variant == VariantGrand][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) { @@ -5571,7 +5914,13 @@ InitPosition(redraw) } } } - initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth]; + if(gameInfo.variant == VariantGrand) { + if(j==BOARD_LEFT || j>=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]; } if( (gameInfo.variant == VariantShogi) && !overrule ) { @@ -5642,9 +5991,7 @@ InitPosition(redraw) } void -SendBoard(cps, moveNum) - ChessProgramState *cps; - int moveNum; +SendBoard (ChessProgramState *cps, int moveNum) { char message[MSG_SIZ]; @@ -5656,21 +6003,25 @@ SendBoard(cps, moveNum) } 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)), @@ -5687,11 +6038,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", @@ -5713,7 +6067,7 @@ SendBoard(cps, moveNum) } ChessSquare -DefaultPromoChoice(int white) +DefaultPromoChoice (int white) { ChessSquare result; if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) @@ -5730,7 +6084,7 @@ DefaultPromoChoice(int white) static int autoQueen; // [HGM] oneclick int -HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) +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 */ @@ -5750,7 +6104,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) if(gameInfo.variant == VariantShogi) { promotionZoneSize = BOARD_HEIGHT/3; highestPromotingPiece = (int)WhiteFerz; - } else if(gameInfo.variant == VariantMakruk) { + } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) { promotionZoneSize = 3; } @@ -5812,9 +6166,9 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) } // give caller the default choice even if we will not make it *promoChoice = ToLower(PieceToChar(defaultPromoChoice)); - if(gameInfo.variant == VariantShogi) *promoChoice = '+'; - if(appData.sweepSelect && gameInfo.variant != VariantGreat - && gameInfo.variant != VariantShogi + if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+'); + if( sweepSelect && gameInfo.variant != VariantGreat + && gameInfo.variant != VariantGrand && gameInfo.variant != VariantSuper) return FALSE; if(autoQueen) return FALSE; // predetermined @@ -5832,8 +6186,7 @@ HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) } 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 && @@ -5842,9 +6195,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; @@ -5853,8 +6204,7 @@ PieceForSquare (x, y) } int -OKToStartUserMove(x, y) - int x, y; +OKToStartUserMove (int x, int y) { ChessSquare from_piece; int white_piece; @@ -5873,7 +6223,6 @@ OKToStartUserMove(x, y) (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */ switch (gameMode) { - case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: @@ -5901,6 +6250,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")); @@ -5944,6 +6295,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; @@ -5952,9 +6304,10 @@ OKToStartUserMove(x, y) } Boolean -OnlyMove(int *x, int *y, Boolean captures) { +OnlyMove (int *x, int *y, Boolean captures) +{ DisambiguateClosure cl; - if (appData.zippyPlay) return FALSE; + if (appData.zippyPlay || !appData.testLegality) return FALSE; switch(gameMode) { case MachinePlaysBlack: case IcsPlayingWhite: @@ -6016,9 +6369,7 @@ char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = EndOfFile; 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; @@ -6035,7 +6386,6 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) */ switch (gameMode) { - case PlayFromGameFile: case AnalyzeFile: case TwoMachinesPlay: case EndOfGame: @@ -6061,6 +6411,8 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) } break; + case PlayFromGameFile: + if(!shiftKey ||!appData.variations) return; // [HGM] only variations case EditGame: case IcsExamining: case BeginningOfGame: @@ -6176,6 +6528,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) /* [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) { @@ -6189,15 +6544,12 @@ UserMoveEvent(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; - if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { - // [HGM] superchess: suppress promotions to non-available piece + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) { + // [HGM] superchess: suppress promotions to non-available piece (but P always allowed) int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); if(WhiteOnMove(currentMove)) { if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0; @@ -6252,7 +6604,7 @@ 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; @@ -6283,7 +6635,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); @@ -6317,6 +6669,9 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) // [HGM] book: if program might be playing, let it use book bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); first.maybeThinking = TRUE; + } else if(fromY == DROP_RANK && fromX == EmptySquare) { + if(!first.useSetboard) SendToProgram("undo\n", &first); // kludge to change stm in engines that do not support setboard + SendBoard(&first, currentMove+1); } else SendMoveToProgram(forwardMostMove-1, &first); if (currentMove == cmailOldMove + 1) { cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; @@ -6374,12 +6729,7 @@ 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; +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; @@ -6391,16 +6741,16 @@ Mark(board, flags, kind, rf, ff, rt, ft, closure) } void -MarkTargetSquares(int clear) +MarkTargetSquares (int clear) { int x, y; - if(!appData.markers || !appData.highlightDragging || + if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi || !appData.testLegality || gameMode == EditPosition) return; if(clear) { for(x=0; x1) capt++; if(capt) @@ -6411,7 +6761,7 @@ MarkTargetSquares(int clear) } int -Explode(Board board, int fromX, int fromY, int toX, int toY) +Explode (Board board, int fromX, int fromY, int toX, int toY) { if(gameInfo.variant == VariantAtomic && (board[toY][toX] != EmptySquare || // capture? @@ -6426,7 +6776,8 @@ Explode(Board board, int fromX, int fromY, int toX, int toY) ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging -int CanPromote(ChessSquare piece, int y) +int +CanPromote (ChessSquare piece, int y) { if(gameMode == EditPosition) return FALSE; // no promotions when editing position // some variants have fixed promotion piece, no promotion at all, or another selection mechanism @@ -6440,7 +6791,8 @@ int CanPromote(ChessSquare piece, int y) piece == WhiteLance && y == BOARD_HEIGHT-2 ); } -void LeftClick(ClickType clickType, int xPix, int yPix) +void +LeftClick (ClickType clickType, int xPix, int yPix) { int x, y; Boolean saveAnimate; @@ -6455,7 +6807,6 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } if (clickType == Press) ErrorPopDown(); - MarkTargetSquares(1); x = EventToSquare(xPix, BOARD_WIDTH); y = EventToSquare(yPix, BOARD_HEIGHT); @@ -6470,7 +6821,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) defaultPromoChoice = promoSweep; promoSweep = EmptySquare; // terminate sweep promoDefaultAltered = TRUE; - if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting + if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting } if(promotionChoice) { // we are waiting for a click to indicate promotion piece @@ -6479,13 +6830,14 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y); if(gameInfo.holdingsWidth && (WhiteOnMove(currentMove) - ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0 - : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) { + ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0 + : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) { // click in right holdings, for determining promotion piece ChessSquare p = boards[currentMove][y][x]; if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p); - if(p != EmptySquare) { - FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p))); + if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral + if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer + FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p))); fromX = fromY = -1; return; } @@ -6522,7 +6874,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) } return; } - fromX = x; fromY = y; + fromX = x; fromY = y; toX = toY = -1; if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) || // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) { @@ -6530,7 +6882,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if (OKToStartUserMove(fromX, fromY)) { second = 0; MarkTargetSquares(0); - DragPieceBegin(xPix, yPix); dragging = 1; + DragPieceBegin(xPix, yPix, FALSE); dragging = 1; if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) { promoSweep = defaultPromoChoice; selectFlag = 0; lastX = xPix; lastY = yPix; @@ -6569,6 +6921,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) /* Clicked again on same color piece -- changed his mind */ second = (x == fromX && y == fromY); promoDefaultAltered = FALSE; + MarkTargetSquares(1); if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) { if (appData.highlightDragging) { SetHighlights(x, y, -1, -1); @@ -6584,7 +6937,7 @@ void LeftClick(ClickType clickType, int xPix, int yPix) fromX = x; fromY = y; dragging = 1; MarkTargetSquares(0); - DragPieceBegin(xPix, yPix); + DragPieceBegin(xPix, yPix, FALSE); if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) { promoSweep = defaultPromoChoice; selectFlag = 0; lastX = xPix; lastY = yPix; @@ -6635,13 +6988,25 @@ void LeftClick(ClickType clickType, int xPix, int yPix) toX = x; toY = y; saveAnimate = appData.animate; + MarkTargetSquares(1); if (clickType == Press) { if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) { // must be Edit Position mode with empty-square selected - fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag + 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; + promoSweep = defaultPromoChoice; + if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece; + selectFlag = 0; lastX = xPix; lastY = yPix; + Sweep(0); // Pawn that is going to promote: preview promotion piece + DisplayMessage("", _("Pull pawn backwards to under-promote")); + DrawPosition(FALSE, boards[currentMove]); + return; + } /* Finish clickclick move */ if (appData.animate || appData.highlightLastMove) { SetHighlights(fromX, fromY, toX, toY); @@ -6692,9 +7057,9 @@ void LeftClick(ClickType clickType, int xPix, int yPix) if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece)); - if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { + if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) { SetHighlights(fromX, fromY, toX, toY); - if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + 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]; promotionChoice = TRUE; @@ -6725,7 +7090,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; @@ -6764,16 +7130,16 @@ int RightClick(ClickType action, int x, int y, int *fromX, int *fromY) if (action != Press) return -2; // return code to be ignored switch (gameMode) { case IcsExamining: - if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; + if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1; case EditPosition: - if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; + if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1; if (xSqr < 0 || ySqr < 0) return -1; if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep 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: @@ -6815,7 +7181,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; @@ -6842,7 +7209,62 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp } void -Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor) +ClearEngineOutputPane (int which) +{ + static FrontEndProgramStats dummyStats; + dummyStats.which = which; + dummyStats.pv = "#"; + SetProgramStats( &dummyStats ); +} + +#define MAXPLAYERS 500 + +char * +TourneyStandings (int display) +{ + int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0; + int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS]; + char result, *p, *names[MAXPLAYERS]; + + if(appData.tourneyType < 0 && !strchr(appData.results, '*')) + return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO + names[0] = p = strdup(appData.participants); + while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants + + for(i=0; i 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s) + for(w=0; w bScore) bScore = score[i], b = i; + ranking[w] = b; points[w] = bScore; score[b] = -2; + } + p = malloc(nPlayers*34+1); + for(w=0; w>8, ourPerpetual&255); } else if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet break; // Abort repetition-checking loop. @@ -7246,7 +7692,8 @@ Adjudicate(ChessProgramState *cps) return 0; } -char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) +char * +SendMoveToBookUser (int moveNr, ChessProgramState *cps, int initial) { // [HGM] book: this routine intercepts moves to simulate book replies char *bookHit = NULL; @@ -7264,8 +7711,23 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) if(bookHit) { // after a book hit we never send 'go', and the code after the call to this routine // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove'). - char buf[MSG_SIZ]; - snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it + char buf[MSG_SIZ], *move = bookHit; + if(cps->useSAN) { + int fromX, fromY, toX, toY; + char promoChar; + ChessMove moveType; + move = buf + 30; + if (ParseOneMove(bookHit, forwardMostMove, &moveType, + &fromX, &fromY, &toX, &toY, &promoChar)) { + (void) CoordsToAlgebraic(boards[forwardMostMove], + PosFlags(forwardMostMove), + fromY, fromX, toY, toX, promoChar, move); + } else { + if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n"); + bookHit = NULL; + } + } + snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it SendToProgram(buf, cps); if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go' } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine @@ -7281,7 +7743,8 @@ char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) char *savedMessage; ChessProgramState *savedState; -void DeferredBookMove(void) +void +DeferredBookMove (void) { if(savedState->lastPing != savedState->lastPong) ScheduleDelayedEvent(DeferredBookMove, 10); @@ -7289,20 +7752,31 @@ void DeferredBookMove(void) HandleMachineMove(savedMessage, savedState); } +static int savedWhitePlayer, savedBlackPlayer, pairingReceived; + void -HandleMachineMove(message, cps) - char *message; - ChessProgramState *cps; +HandleMachineMove (char *message, ChessProgramState *cps) { 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 *p; + char *p, *pv=buf1; int machineWhite; char *bookHit; + if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) { + // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands). + if(savedWhitePlayer == 0 || savedBlackPlayer == 0) { + DisplayError(_("Invalid pairing from pairing engine"), 0); + return; + } + pairingReceived = 1; + NextMatchGame(); + return; // Skim the pairing messages here. + } + cps->userError = 0; FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit @@ -7622,10 +8096,12 @@ 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 + if ((!appData.testLegality || gameInfo.variant == VariantFairy) && + !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position int dummy, s=6; char buf[MSG_SIZ]; - if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return; + if(appData.icsActive || forwardMostMove != 0 || cps != &first) return; if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf); + if(startedFromSetupPosition) return; ParseFEN(boards[0], &dummy, message+s); DrawPosition(TRUE, boards[0]); startedFromSetupPosition = TRUE; @@ -7659,6 +8135,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if (!strncmp(message, "telluser ", 9)) { if(message[9] == '\\' && message[10] == '\\') EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box + PlayTellSound(); DisplayNote(message + 9); return; } @@ -7666,6 +8143,7 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. cps->userError = 1; if(message[14] == '\\' && message[15] == '\\') EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box + PlayTellSound(); DisplayError(message + 14, 0); return; } @@ -7780,7 +8258,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; @@ -7851,6 +8330,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")) { @@ -7875,7 +8356,10 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"), _(cps->which), cps->program, cps->host, message); RemoveInputSource(cps->isr); - DisplayFatalError(buf1, 0, 1); + if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else { + if(cps == &first) appData.noChessProgram = TRUE; + DisplayError(buf1, 0); + } return; } @@ -8140,6 +8624,20 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. curscore = -curscore; } + 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 DisplayError(_("failed writing PV"), 0); + } tempStats.depth = plylev; tempStats.nodes = nodes; @@ -8161,15 +8659,15 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. } /* Buffer overflow protection */ - if (buf1[0] != NULLCHAR) { - if (strlen(buf1) >= sizeof(tempStats.movelist) + if (pv[0] != NULLCHAR) { + if (strlen(pv) >= sizeof(tempStats.movelist) && appData.debugMode) { fprintf(debugFP, "PV is too long; using the first %u bytes.\n", (unsigned) sizeof(tempStats.movelist) - 1); } - safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) ); + safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) ); } else { sprintf(tempStats.movelist, " no PV\n"); } @@ -8206,14 +8704,14 @@ if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats. if( buf1[0] != NULLCHAR ) { unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1; - if( strlen(buf1) > max_len ) { + if( strlen(pv) > max_len ) { if( appData.debugMode) { fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n"); } - buf1[max_len+1] = '\0'; + pv[max_len+1] = '\0'; } - strcat( thinkOutput, buf1 ); + strcat( thinkOutput, pv); } if (currentMove == forwardMostMove || gameMode == AnalyzeMode @@ -8355,8 +8853,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; @@ -8426,6 +8923,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])); @@ -8538,13 +9036,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 ? 3 : 1; + int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 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 */ @@ -8553,15 +9048,19 @@ 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 { int i; + if( board[toY][toX] != EmptySquare ) + board[EP_STATUS] = EP_CAPTURE; + 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 @@ -8642,18 +9141,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][BOARD_LEFT] = EmptySquare; } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi || board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) - && toY >= BOARD_HEIGHT-promoRank + && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere ) { /* white pawn promotion */ board[toY][toX] = CharToPiece(ToUpper(promoChar)); - if (board[toY][toX] == EmptySquare) { - board[toY][toX] = WhiteQueen; - } if(gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; - } else if ((fromY == BOARD_HEIGHT-4) + } else if ((fromY >= BOARD_HEIGHT>>1) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -8706,18 +9202,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][2] = BlackRook; } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi || board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi) - && toY < promoRank + && toY < promoRank && promoChar ) { /* black pawn promotion */ board[toY][toX] = CharToPiece(ToLower(promoChar)); - if (board[toY][toX] == EmptySquare) { - board[toY][toX] = BlackQueen; - } if(gameInfo.variant==VariantBughouse || gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; - } else if ((fromY == 3) + } else if ((fromY < BOARD_HEIGHT>>1) && (toX != fromX) && gameInfo.variant != VariantXiangqi && gameInfo.variant != VariantBerolina @@ -8778,7 +9271,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) if (captured != EmptySquare && gameInfo.holdingsSize > 0 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) { /* [HGM] holdings: Add to holdings, if holdings exist */ - if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) { // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured; } @@ -8829,8 +9322,8 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); } - if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) - && promoChar != NULLCHAR && gameInfo.holdingsSize) { + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) + && promoChar != NULLCHAR && gameInfo.holdingsSize) { // [HGM] superchess: take promotion piece out of holdings int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); if((int)piece < (int)BlackPawn) { // determine stm from piece color @@ -8846,12 +9339,15 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) /* 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) { // forwardMostMove++; // [HGM] bare: moved downstream + (void) CoordsToAlgebraic(boards[forwardMostMove], + PosFlags(forwardMostMove), + fromY, fromX, toY, toX, promoChar, + parseList[forwardMostMove]); + 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]; @@ -8859,10 +9355,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; @@ -8883,20 +9383,24 @@ MakeMove(fromX, fromY, toX, toY, promoChar) 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); + 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; + 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 @@ -8910,6 +9414,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); @@ -8917,10 +9422,6 @@ 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: @@ -8943,7 +9444,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar) /* 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; @@ -8969,7 +9470,8 @@ ShowMove(fromX, fromY, toX, toY) HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); } -void SendEgtPath(ChessProgramState *cps) +void +SendEgtPath (ChessProgramState *cps) { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */ char buf[MSG_SIZ], name[MSG_SIZ], *p; @@ -9003,15 +9505,15 @@ void SendEgtPath(ChessProgramState *cps) } 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; 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 */ @@ -9057,6 +9559,8 @@ InitChessProgram(cps, setup) 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, @@ -9118,12 +9622,12 @@ InitChessProgram(cps, setup) SendToProgram(buf, cps); } cps->initDone = TRUE; + ClearEngineOutputPane(cps == &second); } void -StartChessProgram(cps) - ChessProgramState *cps; +StartChessProgram (ChessProgramState *cps) { char buf[MSG_SIZ]; int err; @@ -9148,9 +9652,14 @@ StartChessProgram(cps) if (err != 0) { snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program); - DisplayFatalError(buf, err, 1); - cps->pr = NoProc; - cps->isr = NULL; + DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it + if(cps != &first) return; + appData.noChessProgram = TRUE; + ThawUI(); + SetNCPMode(); +// DisplayFatalError(buf, err, 1); +// cps->pr = NoProc; +// cps->isr = NULL; return; } @@ -9165,52 +9674,392 @@ StartChessProgram(cps) } } - void TwoMachinesEventIfReady P((void)) { + static int curMess = 0; if (first.lastPing != first.lastPong) { - DisplayMessage("", _("Waiting for first chess program")); + if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1; ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000 return; } if (second.lastPing != second.lastPong) { - DisplayMessage("", _("Waiting for second chess program")); + if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2; ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000 return; } + DisplayMessage("", ""); curMess = 0; ThawUI(); TwoMachinesEvent(); } -void -NextMatchGame P((void)) +char * +MakeName (char *template) { - int index; /* [HGM] autoinc: step load index during match */ - Reset(FALSE, TRUE); - if (*appData.loadGameFile != NULLCHAR) { - index = appData.loadGameIndex; - if(index < 0) { // [HGM] autoinc - lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1; - if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1; - } - LoadGameFromFile(appData.loadGameFile, - index, - appData.loadGameFile, FALSE); - } else if (*appData.loadPositionFile != NULLCHAR) { - index = appData.loadPositionIndex; - if(index < 0) { // [HGM] autoinc - lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1; - if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1; + time_t clock; + struct tm *tm; + static char buf[MSG_SIZ]; + char *p = buf; + int i; + + clock = time((time_t *)NULL); + tm = localtime(&clock); + + while(*p++ = *template++) if(p[-1] == '%') { + switch(*template++) { + case 0: *p = 0; return buf; + case 'Y': i = tm->tm_year+1900; break; + case 'y': i = tm->tm_year-100; break; + case 'M': i = tm->tm_mon+1; break; + case 'd': i = tm->tm_mday; break; + case 'h': i = tm->tm_hour; break; + case 'm': i = tm->tm_min; break; + case 's': i = tm->tm_sec; break; + default: i = 0; + } + snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p); + } + return buf; +} + +int +CountPlayers (char *p) +{ + int n = 0; + while(p = strchr(p, '\n')) p++, n++; // count participants + return n; +} + +FILE * +WriteTourneyFile (char *results, FILE *f) +{ // write tournament parameters on tourneyFile; on success return the stream pointer for closing + if(f == NULL) f = fopen(appData.tourneyFile, "w"); + if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else { + // 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); + fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false"); + fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false"); + fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile); + fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile); + fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex); + fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile); + fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex); + fprintf(f, "-rewindIndex %d\n", appData.rewindIndex); + fprintf(f, "-discourageOwnBooks %s\n", appData.defNoBook ? "true" : "false"); + if(searchTime > 0) + fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60); + else { + fprintf(f, "-mps %d\n", appData.movesPerSession); + fprintf(f, "-tc %s\n", appData.timeControl); + fprintf(f, "-inc %.2f\n", appData.timeIncrement); } - LoadPositionFromFile(appData.loadPositionFile, - index, - appData.loadPositionFile); + fprintf(f, "-results \"%s\"\n", results); } - TwoMachinesEventIfReady(); + 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); + 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 +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); + return 0; + } + f = fopen(name, "r"); + if(f) { // file exists + ASSIGN(appData.tourneyFile, name); + ParseArgsFromFile(f); // parse it + } else { + if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants + if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) { + DisplayError(_("Not enough participants"), 0); + return 0; + } + ASSIGN(appData.tourneyFile, name); + if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1 + if((f = WriteTourneyFile("", NULL)) == NULL) return 0; + } + fclose(f); + appData.noChessProgram = FALSE; + appData.clockMode = TRUE; + SetGNUMode(); + return 1; +} + +void +NamesToList (char *names, char **engineList, char **engineMnemonic) +{ + char buf[MSG_SIZ], *p, *q; + int i=1; + while(*names) { + p = names; q = buf; + while(*p && *p != '\n') *q++ = *p++; + *q = 0; + if(engineList[i]) free(engineList[i]); + engineList[i] = strdup(buf); + if(*p == '\n') p++; + TidyProgramName(engineList[i], "localhost", buf); + 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, " ("); + sscanf(q + 8, "%s", buf + strlen(buf)); + strcat(buf, ")"); + } + engineMnemonic[i] = strdup(buf); + names = p; i++; + if(i > MAXENGINES - 2) break; + } + engineList[i] = engineMnemonic[i] = NULL; +} + +// 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) +{ // swap settings for first engine and other engine (so far only some selected options) + int h; + char *p; + if(n == 0) return; + SWAP(directory, p) + SWAP(chessProgram, p) + SWAP(isUCI, h) + SWAP(hasOwnBookUCI, h) + SWAP(protocolVersion, h) + SWAP(reuse, h) + SWAP(scoreIsAbsolute, h) + SWAP(timeOdds, h) + SWAP(logo, p) + SWAP(pgnName, p) + SWAP(pvSAN, h) + SWAP(engOptions, p) +} + +void +SetPlayer (int player) +{ // [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; + for(i=0; i 0) { + roundsPerCycle = nPlayers - appData.tourneyType; + pairingsPerRound = appData.tourneyType; + } + gamesPerRound = pairingsPerRound * appData.defaultMatchGames; + gamesPerCycle = gamesPerRound * roundsPerCycle; + appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match + curCycle = nr / gamesPerCycle; nr %= gamesPerCycle; + curRound = nr / gamesPerRound; nr %= gamesPerRound; + curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames; + matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file + roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1; + + if(appData.cycleSync) *syncInterval = gamesPerCycle; + if(appData.roundSync) *syncInterval = gamesPerRound; + + if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame); + + if(appData.tourneyType == 0) { + if(curPairing == (nPlayers-1)/2 ) { + *whitePlayer = curRound; + *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd + } else { + *whitePlayer = curRound - (nPlayers-1)/2 + curPairing; + if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1); + *blackPlayer = curRound + (nPlayers-1)/2 - curPairing; + if(*blackPlayer >= 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. + // For cycles and games this is already taken care of by default, derived from matchGame! + return curRound & 1; +} + +int +NextTourneyGame (int nr, int *swapColors) +{ // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game + char *p, *q; + int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers; + FILE *tf; + if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game + tf = fopen(appData.tourneyFile, "r"); + if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; } + ParseArgsFromFile(tf); fclose(tf); + InitTimeControls(); // TC might be altered from tourney file + + nPlayers = CountPlayers(appData.participants); // count participants + if(appData.tourneyType < 0) syncInterval = nPlayers/2; else + *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval); + + if(syncInterval) { + p = q = appData.results; + while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; } + if(firstBusy/syncInterval < (nextGame/syncInterval)) { + DisplayMessage(_("Waiting for other game(s)"),""); + waitingForGame = TRUE; + ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish + return 0; + } + waitingForGame = FALSE; + } + + if(appData.tourneyType < 0) { + if(nr>=0 && !pairingReceived) { + char buf[1<<16]; + if(pairing.pr == NoProc) { + if(!appData.pairingEngine[0]) { + DisplayFatalError(_("No pairing engine specified"), 0, 1); + return 0; + } + StartChessProgram(&pairing); // starts the pairing engine + } + snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results); + SendToProgram(buf, &pairing); + snprintf(buf, 1<<16, "pairing %d\n", nr+1); + SendToProgram(buf, &pairing); + return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again... + } + pairingReceived = 0; // ... so we continue here + *swapColors = 0; + appData.matchGames = appData.tourneyCycles * syncInterval - 1; + whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1; + matchGame = 1; roundNr = nr / syncInterval + 1; + } + + if(first.pr != NoProc && second.pr != NoProc) return 1; // engines already loaded + + // redefine engines, engine dir, etc. + NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines + if(first.pr == NoProc || nr < 0) { + SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line + InitEngine(&first, 0); // initialize ChessProgramStates based on new settings. + } + if(second.pr == NoProc) { + SwapEngines(1); + SetPlayer(blackPlayer); // 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; } -void UserAdjudicationEvent( int result ) +void +NextMatchGame () +{ // performs game initialization that does not invoke engines, and then tries to start the game + int res, firstWhite, swapColors = 0; + if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed + 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); + 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) { ChessMove gameResult = GameIsDrawn; @@ -9228,7 +10077,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; @@ -9236,7 +10086,8 @@ int StringCheckSum(char *s) return i; } -int GameCheckSum() +int +GameCheckSum () { int i, sum=0; for(i=backwardMostMove; i appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done + else ranking = strdup("busy"); //suppress popup when aborted but not finished + } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame + + if (nextGame <= appData.matchGames && !abortMatch) { gameMode = nextGameMode; - matchGame++; - if(appData.matchPause>10000 || appData.matchPause<10) - appData.matchPause = 10000; /* [HGM] make pause adjustable */ - ScheduleDelayedEvent(NextMatchGame, appData.matchPause); + matchGame = nextGame; // this will be overruled in tourney mode! + GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause + ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself) endingGame = 0; /* [HGM] crash */ return; } else { @@ -9598,6 +10454,8 @@ GameEnds(result, resultDetails, whosays) first.tidy, second.tidy, 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"; @@ -9615,19 +10473,27 @@ GameEnds(result, resultDetails, whosays) ModeHighlight(); endingGame = 0; /* [HGM] crash */ if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion. - if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else { - matchMode = FALSE; appData.matchGames = matchGame = 0; - DisplayNote(buf); + if(matchMode == TRUE) { // match through command line: exit with or without popup + if(ranking) { + ToNrEvent(forwardMostMove); + if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0); + else ExitEvent(0); + } else DisplayFatalError(buf, 0, 0); + } else { // match through menu; just stop, with or without popup + matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0; + ModeHighlight(); + if(ranking){ + if(strcmp(ranking, "busy")) DisplayNote(ranking); + } else DisplayNote(buf); } + if(ranking) free(ranking); } } /* 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; @@ -9657,15 +10523,23 @@ FeedMovesToProgram(cps, upto) } -void -ResurrectChessProgram() +int +ResurrectChessProgram () { /* The chess program may have exited. If so, restart it and feed it all the moves made so far. */ + static int doInit = 0; - if (appData.noChessProgram || first.pr != NoProc) return; + if (appData.noChessProgram) return 1; - StartChessProgram(&first); + 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(!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 { + if (first.pr != NoProc) return 1; + StartChessProgram(&first); + } InitChessProgram(&first, FALSE); FeedMovesToProgram(&first, currentMove); @@ -9682,14 +10556,14 @@ ResurrectChessProgram() SendToProgram("analyze\n", &first); first.analyzing = TRUE; } + return 1; } /* * Button procedures */ void -Reset(redraw, init) - int redraw, init; +Reset (int redraw, int init) { int i; @@ -9698,6 +10572,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; @@ -9745,6 +10620,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) { @@ -9755,7 +10631,8 @@ Reset(redraw, init) ResetClocks(); timeRemaining[0][0] = whiteTimeRemaining; timeRemaining[1][0] = blackTimeRemaining; - if (first.pr == NULL) { + + if (first.pr == NoProc) { StartChessProgram(&first); } if (init) { @@ -9768,7 +10645,7 @@ Reset(redraw, init) } void -AutoPlayGameLoop() +AutoPlayGameLoop () { for (;;) { if (!AutoPlayOneMove()) @@ -9784,7 +10661,7 @@ AutoPlayGameLoop() int -AutoPlayOneMove() +AutoPlayOneMove () { int fromX, fromY, toX, toY; @@ -9804,8 +10681,8 @@ AutoPlayOneMove() if (currentMove >= forwardMostMove) { if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); } - gameMode = EditGame; - ModeHighlight(); +// gameMode = EndOfGame; +// ModeHighlight(); /* [AS] Clear current move marker at the end of a game */ /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */ @@ -9843,8 +10720,7 @@ AutoPlayOneMove() int -LoadGameOneMove(readAhead) - ChessMove readAhead; +LoadGameOneMove (ChessMove readAhead) { int fromX = 0, fromY = 0, toX = 0, toY = 0, done; char promoChar = NULLCHAR; @@ -10079,11 +10955,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]; @@ -10121,7 +10993,7 @@ LoadGameFromFile(filename, n, title, useList) void -MakeRegisteredMove() +MakeRegisteredMove () { int fromX, fromY, toX, toY; char promoChar; @@ -10186,11 +11058,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; @@ -10231,8 +11099,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) { @@ -10252,22 +11119,379 @@ 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(piece == pieceList[2] && fromY == toY && (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(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; + quickBoard[to] = piece; + pieceList[piece] = to; + move++; + continue; + } + } + 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; + 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); + 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 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 */ @@ -10293,6 +11517,7 @@ LoadGame(f, gameNumber, title, useList) if (lg) { fseek(f, lg->offset, 0); GameListHighlight(gameNumber); + pos = lg->position; gn = 1; } else { @@ -10320,7 +11545,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) { @@ -10692,10 +11917,11 @@ LoadGame(f, gameNumber, title, useList) AnalyzeFileEvent(); } + 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(); } @@ -10709,8 +11935,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) { @@ -10727,10 +11952,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]; @@ -10751,10 +11973,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; @@ -10773,7 +11992,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); } @@ -10845,7 +12064,6 @@ LoadPosition(f, positionNumber, title) } startedFromSetupPosition = TRUE; - SendToProgram("force\n", &first); CopyBoard(boards[0], initial_position); if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; @@ -10858,7 +12076,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");} @@ -10884,8 +12105,7 @@ int i, j; void -CopyPlayerNameIntoFileName(dest, src) - char **dest, *src; +CopyPlayerNameIntoFileName (char **dest, char *src) { while (*src != NULLCHAR && *src != ',') { if (*src == ' ') { @@ -10897,8 +12117,8 @@ CopyPlayerNameIntoFileName(dest, src) } } -char *DefaultFileName(ext) - char *ext; +char * +DefaultFileName (char *ext) { static char def[MSG_SIZ]; char *p; @@ -10918,30 +12138,41 @@ 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, 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); return FALSE; } else { - return SaveGame(f, 0, NULL); + safeStrCpy(buf, lastMsg, MSG_SIZ); + 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... + result = SaveGame(f, 0, NULL); + DisplayMessage(buf, ""); + return result; } } } char * -SavePart(str) - char *str; +SavePart (char *str) { static char buf[MSG_SIZ]; char *p; @@ -10958,8 +12189,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; @@ -11002,8 +12233,8 @@ static int FindFirstMoveOutOfBook( int side ) return result; } -/* [AS] */ -void GetOutOfBookInfo( char * buf ) +void +GetOutOfBookInfo (char * buf) { int oob[2]; int i; @@ -11034,8 +12265,7 @@ 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; @@ -11201,8 +12431,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; @@ -11266,10 +12495,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 @@ -11281,8 +12507,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]; @@ -11296,7 +12521,13 @@ SavePositionToFile(filename) DisplayError(buf, errno); return FALSE; } else { + safeStrCpy(buf, lastMsg, MSG_SIZ); + DisplayMessage(_("Waiting for access to save file"), ""); + flock(fileno(f), LOCK_EX); // [HGM] lock + DisplayMessage(_("Saving position"), ""); + lseek(fileno(f), 0, SEEK_END); // better safe than sorry... SavePosition(f, 0, NULL); + DisplayMessage(buf, ""); return TRUE; } } @@ -11304,10 +12535,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; @@ -11331,8 +12559,7 @@ SavePosition(f, dummy, dummy2) } void -ReloadCmailMsgEvent(unregister) - int unregister; +ReloadCmailMsgEvent (int unregister) { #if !WIN32 static char *inFilename = NULL; @@ -11398,7 +12625,7 @@ ReloadCmailMsgEvent(unregister) } int -RegisterMove() +RegisterMove () { FILE *f; char string[MSG_SIZ]; @@ -11488,7 +12715,7 @@ RegisterMove() } void -MailMoveEvent() +MailMoveEvent () { #if !WIN32 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1"; @@ -11576,7 +12803,7 @@ MailMoveEvent() } char * -CmailMsg() +CmailMsg () { #if WIN32 return NULL; @@ -11649,7 +12876,7 @@ CmailMsg() } void -ResetGameEvent() +ResetGameEvent () { if (gameMode == Training) SetTrainingModeOff(); @@ -11663,8 +12890,7 @@ ResetGameEvent() } void -ExitEvent(status) - int status; +ExitEvent (int status) { exiting++; if (exiting > 2) { @@ -11718,12 +12944,15 @@ ExitEvent(status) RemoveInputSource(second.isr); } + if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing); + if (pairing.isr != NULL) RemoveInputSource(pairing.isr); + ShutDownFrontEnd(); exit(status); } void -PauseEvent() +PauseEvent () { if (appData.debugMode) fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing); @@ -11788,7 +13017,7 @@ PauseEvent() } void -EditCommentEvent() +EditCommentEvent () { char title[MSG_SIZ]; @@ -11805,15 +13034,16 @@ EditCommentEvent() void -EditTagsEvent() +EditTagsEvent () { char *tags = PGNTags(&gameInfo); + bookUp = FALSE; EditTagsPopUp(tags, NULL); free(tags); } void -AnalyzeModeEvent() +AnalyzeModeEvent () { if (appData.noChessProgram || gameMode == AnalyzeMode) return; @@ -11841,7 +13071,7 @@ AnalyzeModeEvent() } void -AnalyzeFileEvent() +AnalyzeFileEvent () { if (appData.noChessProgram || gameMode == AnalyzeFile) return; @@ -11864,10 +13094,11 @@ AnalyzeFileEvent() StartAnalysisClock(); GetTimeMark(&lastNodeCountTime); lastNodeCount = 0; + if(appData.timeDelay > 0) StartLoadGameTimer((long)(1000.0 * appData.timeDelay)); } void -MachineWhiteEvent() +MachineWhiteEvent () { char buf[MSG_SIZ]; char *bookHit = NULL; @@ -11907,7 +13138,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); @@ -11948,7 +13179,7 @@ MachineWhiteEvent() } void -MachineBlackEvent() +MachineBlackEvent () { char buf[MSG_SIZ]; char *bookHit = NULL; @@ -11984,7 +13215,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); @@ -12024,29 +13255,35 @@ MachineBlackEvent() void -DisplayTwoMachinesTitle() +DisplayTwoMachinesTitle () { char buf[MSG_SIZ]; if (appData.matchGames > 0) { + if(appData.tourneyFile[0]) { + snprintf(buf, MSG_SIZ, "%s %s %s (%d/%d%s)", + gameInfo.white, _("vs."), gameInfo.black, + nextGame+1, appData.matchGames+1, + appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr"); + } else if (first.twoMachinesColor[0] == 'w') { - snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)", - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", + gameInfo.white, _("vs."), gameInfo.black, first.matchWins, second.matchWins, matchGame - 1 - (first.matchWins + second.matchWins)); } else { - snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)", - gameInfo.white, gameInfo.black, + snprintf(buf, MSG_SIZ, "%s %s %s (%d-%d-%d)", + gameInfo.white, _("vs."), gameInfo.black, second.matchWins, first.matchWins, matchGame - 1 - (first.matchWins + second.matchWins)); } } else { - snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black); + snprintf(buf, MSG_SIZ, "%s %s %s", gameInfo.white, _("vs."), gameInfo.black); } DisplayTitle(buf); } void -SettingsMenuIfReady() +SettingsMenuIfReady () { if (second.lastPing != second.lastPong) { DisplayMessage("", _("Waiting for second chess program")); @@ -12059,16 +13296,18 @@ SettingsMenuIfReady() } int -WaitForSecond(DelayedEventCallback retry) +WaitForEngine (ChessProgramState *cps, DelayedEventCallback retry) { - if (second.pr == NULL) { - StartChessProgram(&second); - if (second.protocolVersion == 1) { + char buf[MSG_SIZ]; + if (cps->pr == NoProc) { + StartChessProgram(cps); + if (cps->protocolVersion == 1) { retry(); } else { /* kludge: allow timeout for initial "feature" command */ FreezeUI(); - DisplayMessage("", _("Starting second chess program")); + snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which); + DisplayMessage("", buf); ScheduleDelayedEvent(retry, FEATURE_TIMEOUT); } return 1; @@ -12084,6 +13323,8 @@ TwoMachinesEvent P((void)) ChessProgramState *onmove; char *bookHit = NULL; static int stalling = 0; + TimeMark now; + long wait; if (appData.noChessProgram) return; @@ -12117,24 +13358,29 @@ TwoMachinesEvent P((void)) // forwardMostMove = currentMove; TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this... - ResurrectChessProgram(); /* in case first program isn't running */ - if(WaitForSecond(TwoMachinesEventIfReady)) return; + 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.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position - DisplayMessage("", _("Waiting for first chess program")); - ScheduleDelayedEvent(TwoMachinesEvent, 10); + ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } if(!stalling) { - InitChessProgram(&second, FALSE); + InitChessProgram(&second, FALSE); // unbalances ping of second engine SendToProgram("force\n", &second); - } - if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash - if(!stalling) DisplayMessage("", _("Waiting for second chess program")); stalling = 1; - ScheduleDelayedEvent(TwoMachinesEvent, 10); + ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); return; } + GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load + if(appData.matchPause>10000 || appData.matchPause<10) + appData.matchPause = 10000; /* [HGM] make pause adjustable */ + wait = SubtractTimeMarks(&now, &pauseStart); + if(wait < appData.matchPause) { + ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait); + return; + } stalling = 0; DisplayMessage("", ""); if (startedFromSetupPosition) { @@ -12149,7 +13395,7 @@ TwoMachinesEvent P((void)) gameMode = TwoMachinesPlay; pausing = FALSE; - ModeHighlight(); + ModeHighlight(); // [HGM] logo: this triggers display update of logos SetGameInfo(); DisplayTwoMachinesTitle(); firstMove = TRUE; @@ -12158,7 +13404,7 @@ TwoMachinesEvent P((void)) } else { onmove = &second; } - + if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]); SendToProgram(first.computerString, &first); if (first.sendName) { snprintf(buf, MSG_SIZ, "name %s\n", second.tidy); @@ -12207,7 +13453,7 @@ TwoMachinesEvent P((void)) } void -TrainingEvent() +TrainingEvent () { if (gameMode == Training) { SetTrainingModeOff(); @@ -12230,7 +13476,7 @@ TrainingEvent() } void -IcsClientEvent() +IcsClientEvent () { if (!appData.icsActive) return; switch (gameMode) { @@ -12264,9 +13510,8 @@ IcsClientEvent() return; } - void -EditGameEvent() +EditGameEvent () { int i; @@ -12338,9 +13583,11 @@ EditGameEvent() SendToProgram("undo\n", &first); i--; } + if(!adjustedClock) { whiteTimeRemaining = timeRemaining[0][currentMove]; blackTimeRemaining = timeRemaining[1][currentMove]; DisplayBothClocks(); + } if (whiteFlag || blackFlag) { whiteFlag = blackFlag = 0; } @@ -12354,7 +13601,7 @@ EditGameEvent() void -EditPositionEvent() +EditPositionEvent () { if (gameMode == EditPosition) { EditGameEvent(); @@ -12378,7 +13625,7 @@ EditPositionEvent() } void -ExitAnalyzeMode() +ExitAnalyzeMode () { /* [DM] icsEngineAnalyze - possible call from other functions */ if (appData.icsEngineAnalyze) { @@ -12394,7 +13641,7 @@ ExitAnalyzeMode() } void -EditPositionDone(Boolean fakeRights) +EditPositionDone (Boolean fakeRights) { int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing; @@ -12437,8 +13684,7 @@ EditPositionDone(Boolean fakeRights) /* Pause for `ms' milliseconds */ /* !! Ugh, this is a kludge. Fix it sometime. --tpm */ void -TimeDelay(ms) - long ms; +TimeDelay (long ms) { TimeMark m1, m2; @@ -12450,8 +13696,7 @@ TimeDelay(ms) /* !! Ugh, this is a kludge. Fix it sometime. --tpm */ void -SendMultiLineToICS(buf) - char *buf; +SendMultiLineToICS (char *buf) { char temp[MSG_SIZ+1], *p; int len; @@ -12476,7 +13721,7 @@ SendMultiLineToICS(buf) } void -SetWhiteToPlayEvent() +SetWhiteToPlayEvent () { if (gameMode == EditPosition) { blackPlaysFirst = FALSE; @@ -12488,7 +13733,7 @@ SetWhiteToPlayEvent() } void -SetBlackToPlayEvent() +SetBlackToPlayEvent () { if (gameMode == EditPosition) { blackPlaysFirst = TRUE; @@ -12502,9 +13747,7 @@ SetBlackToPlayEvent() } void -EditPositionMenuEvent(selection, x, y) - ChessSquare selection; - int x, y; +EditPositionMenuEvent (ChessSquare selection, int x, int y) { char buf[MSG_SIZ]; ChessSquare piece = boards[0][y][x]; @@ -12632,9 +13875,7 @@ EditPositionMenuEvent(selection, x, y) void -DropMenuEvent(selection, x, y) - ChessSquare selection; - int x, y; +DropMenuEvent (ChessSquare selection, int x, int y) { ChessMove moveType; @@ -12675,7 +13916,7 @@ DropMenuEvent(selection, x, y) } void -AcceptEvent() +AcceptEvent () { /* Accept a pending offer of any kind from opponent */ @@ -12700,7 +13941,7 @@ AcceptEvent() } void -DeclineEvent() +DeclineEvent () { /* Decline a pending offer of any kind from opponent */ @@ -12725,7 +13966,7 @@ DeclineEvent() } void -RematchEvent() +RematchEvent () { /* Issue ICS rematch command */ if (appData.icsActive) { @@ -12735,7 +13976,7 @@ RematchEvent() } void -CallFlagEvent() +CallFlagEvent () { /* Call your opponent's flag (claim a win on time) */ if (appData.icsActive) { @@ -12772,13 +14013,15 @@ CallFlagEvent() } void -ClockClick(int which) +ClockClick (int which) { // [HGM] code moved to back-end from winboard.c if(which) { // black clock if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetBlackToPlayEvent(); - } else if (gameMode == EditGame || shiftKey) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !blackFlag && WhiteOnMove(currentMove)) { + UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move: if not out of time, enters null move + } else if (shiftKey) { AdjustClock(which, -1); } else if (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack) { @@ -12788,7 +14031,9 @@ ClockClick(int which) if (gameMode == EditPosition || gameMode == IcsExamining) { if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0); SetWhiteToPlayEvent(); - } else if (gameMode == EditGame || shiftKey) { + } else if ((gameMode == AnalyzeMode || gameMode == EditGame) && !whiteFlag && !WhiteOnMove(currentMove)) { + UserMoveEvent((int)EmptySquare, DROP_RANK, 0, 0, 0); // [HGM] multi-move + } else if (shiftKey) { AdjustClock(which, -1); } else if (gameMode == IcsPlayingBlack || gameMode == MachinePlaysWhite) { @@ -12798,7 +14043,7 @@ ClockClick(int which) } void -DrawEvent() +DrawEvent () { /* Offer draw or accept pending draw offer from opponent */ @@ -12840,7 +14085,7 @@ DrawEvent() } void -AdjournEvent() +AdjournEvent () { /* Offer Adjourn or accept pending Adjourn offer from opponent */ @@ -12854,7 +14099,7 @@ AdjournEvent() void -AbortEvent() +AbortEvent () { /* Offer Abort or accept pending Abort offer from opponent */ @@ -12867,7 +14112,7 @@ AbortEvent() } void -ResignEvent() +ResignEvent () { /* Resign. You can do this even if it's not your turn. */ @@ -12901,7 +14146,7 @@ ResignEvent() void -StopObservingEvent() +StopObservingEvent () { /* Stop observing current games */ SendToICS(ics_prefix); @@ -12909,7 +14154,7 @@ StopObservingEvent() } void -StopExaminingEvent() +StopExaminingEvent () { /* Stop observing current game */ SendToICS(ics_prefix); @@ -12917,8 +14162,7 @@ StopExaminingEvent() } void -ForwardInner(target) - int target; +ForwardInner (int target) { int limit; @@ -12929,6 +14173,9 @@ ForwardInner(target) if (gameMode == EditPosition) return; + seekGraphUp = FALSE; + MarkTargetSquares(1); + if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); @@ -12983,7 +14230,7 @@ ForwardInner(target) void -ForwardEvent() +ForwardEvent () { if (gameMode == IcsExamining && !pausing) { SendToICS(ics_prefix); @@ -12994,7 +14241,7 @@ ForwardEvent() } void -ToEndEvent() +ToEndEvent () { if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) { /* to optimze, we temporarily turn off analysis mode while we feed @@ -13024,8 +14271,7 @@ ToEndEvent() } void -BackwardInner(target) - int target; +BackwardInner (int target) { int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */ @@ -13034,6 +14280,8 @@ BackwardInner(target) target, currentMove, forwardMostMove); if (gameMode == EditPosition) return; + seekGraphUp = FALSE; + MarkTargetSquares(1); if (currentMove <= backwardMostMove) { ClearHighlights(); DrawPosition(full_redraw, boards[currentMove]); @@ -13064,6 +14312,16 @@ BackwardInner(target) if (gameMode == EditGame || gameMode==AnalyzeMode || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { while (currentMove > target) { + 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); + for(currentMove=i; currentMove= forwardMostMove) to = forwardMostMove; @@ -13137,7 +14395,7 @@ ToNrEvent(int to) } void -RevertEvent(Boolean annotate) +RevertEvent (Boolean annotate) { if(PopTail(annotate)) { // [HGM] vari: restore old game tail return; @@ -13155,7 +14413,7 @@ RevertEvent(Boolean annotate) } void -RetractMoveEvent() +RetractMoveEvent () { switch (gameMode) { case MachinePlaysWhite: @@ -13194,7 +14452,7 @@ RetractMoveEvent() } void -MoveNowEvent() +MoveNowEvent () { ChessProgramState *cps; @@ -13229,7 +14487,7 @@ MoveNowEvent() } void -TruncateGameEvent() +TruncateGameEvent () { EditGameEvent(); if (gameMode != EditGame) return; @@ -13237,7 +14495,7 @@ TruncateGameEvent() } void -TruncateGame() +TruncateGame () { CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate if (forwardMostMove > currentMove) { @@ -13253,7 +14511,7 @@ TruncateGame() } void -HintEvent() +HintEvent () { if (appData.noChessProgram) return; switch (gameMode) { @@ -13279,7 +14537,7 @@ HintEvent() } void -BookEvent() +BookEvent () { if (appData.noChessProgram) return; switch (gameMode) { @@ -13310,7 +14568,7 @@ BookEvent() } void -AboutGameEvent() +AboutGameEvent () { char *tags = PGNTags(&gameInfo); TagsPopUp(tags, CmailMsg()); @@ -13320,9 +14578,7 @@ AboutGameEvent() /* end button procedures */ void -PrintPosition(fp, move) - FILE *fp; - int move; +PrintPosition (FILE *fp, int move) { int i, j; @@ -13340,8 +14596,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); @@ -13352,8 +14607,7 @@ 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; int local = (strcmp(host, "localhost") == 0); @@ -13382,7 +14636,7 @@ TidyProgramName(prog, host, buf) } char * -TimeControlTagValue() +TimeControlTagValue () { char buf[MSG_SIZ]; if (!appData.clockMode) { @@ -13398,7 +14652,7 @@ TimeControlTagValue() } void -SetGameInfo() +SetGameInfo () { /* This routine is used only for certain modes */ VariantClass v = gameInfo.variant; @@ -13438,19 +14692,19 @@ SetGameInfo() gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); - if (matchGame > 0) { + if (roundNr > 0) { char buf[MSG_SIZ]; - snprintf(buf, MSG_SIZ, "%d", matchGame); + snprintf(buf, MSG_SIZ, "%d", roundNr); gameInfo.round = StrSave(buf); } else { gameInfo.round = StrSave("-"); } if (first.twoMachinesColor[0] == 'w') { - gameInfo.white = StrSave(first.tidy); - gameInfo.black = StrSave(second.tidy); + gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy); + gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy); } else { - gameInfo.white = StrSave(second.tidy); - gameInfo.black = StrSave(first.tidy); + gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy); + gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy); } gameInfo.timeControl = TimeControlTagValue(); break; @@ -13496,9 +14750,7 @@ SetGameInfo() } void -ReplaceComment(index, text) - int index; - char *text; +ReplaceComment (int index, char *text) { int len; char *p; @@ -13539,8 +14791,7 @@ ReplaceComment(index, text) } void -CrushCRs(text) - char *text; +CrushCRs (char *text) { char *p = text; char *q = text; @@ -13554,10 +14805,8 @@ 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; @@ -13569,10 +14818,12 @@ if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); 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') @@ -13589,7 +14840,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... @@ -13602,7 +14853,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 ); @@ -13615,7 +14867,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; @@ -13689,7 +14942,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 ) { @@ -13710,14 +14963,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) { @@ -13732,27 +14983,27 @@ SendToProgram(message, cps) outCount = OutputToProcess(cps->pr, message, count, &error); if (outCount < count && !exiting && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */ + if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which)); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { - gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ 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; } + gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ } else { - gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; } + gameInfo.result = res; } gameInfo.resultDetails = StrSave(buf); } + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; } if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1; } } 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]; @@ -13761,18 +15012,23 @@ ReceiveFromProgram(isr, closure, message, count, error) if (isr != cps->isr) return; /* Killed intentionally */ 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((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) { - gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ 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; } + gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ } else { - gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; } + gameInfo.result = res; } gameInfo.resultDetails = StrSave(buf); } - RemoveInputSource(cps->isr); + if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; } if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1; } else { snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"), @@ -13837,10 +15093,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; @@ -13904,7 +15157,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' || @@ -13914,9 +15168,7 @@ ChessProgramState *WhitePlayer() } void -SendTimeRemaining(cps, machineWhite) - ChessProgramState *cps; - int /*boolean*/ machineWhite; +SendTimeRemaining (ChessProgramState *cps, int machineWhite) { char message[MSG_SIZ]; long time, otime; @@ -13949,11 +15201,7 @@ SendTimeRemaining(cps, machineWhite) } 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); @@ -13973,11 +15221,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); @@ -13993,11 +15237,7 @@ 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); @@ -14015,7 +15255,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 { @@ -14106,14 +15346,13 @@ 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) || (cb == SettingsMenuIfReady && cps == &second) || - (cb == TwoMachinesEventIfReady && cps == &second)) { + (cb == LoadEngine) || + (cb == TwoMachinesEventIfReady)) { CancelDelayedEvent(); ScheduleDelayedEvent(cb, val ? 1 : 3600000); } @@ -14122,9 +15361,7 @@ FeatureDone(cps, val) /* Parse feature command from engine */ void -ParseFeatures(args, cps) - char* args; - ChessProgramState *cps; +ParseFeatures (char *args, ChessProgramState *cps) { char *p = args; char *q; @@ -14136,6 +15373,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; @@ -14146,7 +15384,7 @@ ParseFeatures(args, cps) continue; } if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue; - if (StringFeature(&p, "myname", &cps->tidy, cps)) { + if (StringFeature(&p, "myname", cps->tidy, cps)) { if (gameMode == TwoMachinesPlay) { DisplayTwoMachinesTitle(); } else { @@ -14154,7 +15392,7 @@ ParseFeatures(args, cps) } continue; } - if (StringFeature(&p, "variants", &cps->variants, cps)) continue; + if (StringFeature(&p, "variants", cps->variants, cps)) continue; if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue; if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue; if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue; @@ -14178,8 +15416,8 @@ ParseFeatures(args, cps) if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue; if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue; if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; - if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue; - if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) { + if (StringFeature(&p, "egt", cps->egtFormats, cps)) continue; + if (StringFeature(&p, "option", cps->option[cps->nrOptions].name, cps)) { 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); @@ -14215,8 +15453,7 @@ ParseFeatures(args, cps) } void -PeriodicUpdatesEvent(newState) - int newState; +PeriodicUpdatesEvent (int newState) { if (newState == appData.periodicUpdates) return; @@ -14234,8 +15471,7 @@ PeriodicUpdatesEvent(newState) } void -PonderNextMoveEvent(newState) - int newState; +PonderNextMoveEvent (int newState) { if (newState == appData.ponderNextMove) return; if (gameMode == EditPosition) EditPositionDone(TRUE); @@ -14255,9 +15491,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]; @@ -14270,7 +15504,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 @@ -14297,8 +15531,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; @@ -14306,57 +15539,56 @@ AskQuestionEvent(title, question, replyPrefix, which) } void -TypeInEvent(char firstChar) +TypeInEvent (char firstChar) { - if ((gameMode == BeginningOfGame && !appData.icsActive) || - gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || - 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 - ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile || - gameMode == IcsObserving || gameMode == TwoMachinesPlay ) || + if ((gameMode == BeginningOfGame && !appData.icsActive) || + gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || + 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 + ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile || + gameMode == IcsObserving || gameMode == TwoMachinesPlay ) || gameMode == Training) PopUpMoveDialog(firstChar); } void -TypeInDoneEvent(char *move) +TypeInDoneEvent (char *move) { Board board; int n, fromX, fromY, toX, toY; char promoChar; - ChessMove moveType; - - // [HGM] FENedit - if(gameMode == EditPosition && ParseFEN(board, &n, move) ) { - EditPositionPasteFEN(move); - return; - } - // [HGM] movenum: allow move number to be typed in any mode - if(sscanf(move, "%d", &n) == 1 && n != 0 ) { - ToNrEvent(2*n-1); - return; - } - - if (gameMode != EditGame && currentMove != forwardMostMove && - gameMode != Training) { - DisplayMoveError(_("Displayed move is not current")); - } else { - 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, - &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { - UserMoveEvent(fromX, fromY, toX, toY, promoChar); - } else { - DisplayMoveError(_("Could not parse move")); - } - } -} - -void -DisplayMove(moveNumber) - int moveNumber; + ChessMove moveType; + + // [HGM] FENedit + if(gameMode == EditPosition && ParseFEN(board, &n, move) ) { + EditPositionPasteFEN(move); + return; + } + // [HGM] movenum: allow move number to be typed in any mode + if(sscanf(move, "%d", &n) == 1 && n != 0 ) { + ToNrEvent(2*n-1); + return; + } + + if (gameMode != EditGame && currentMove != forwardMostMove && + gameMode != Training) { + DisplayMoveError(_("Displayed move is not current")); + } else { + 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, + &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { + UserMoveEvent(fromX, fromY, toX, toY, promoChar); + } else { + DisplayMoveError(_("Could not parse move")); + } + } +} + +void +DisplayMove (int moveNumber) { char message[MSG_SIZ]; char res[MSG_SIZ]; @@ -14413,13 +15645,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])); @@ -14428,14 +15656,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); } @@ -14447,8 +15667,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; @@ -14475,7 +15694,7 @@ Attention(cps) } int -CheckFlags() +CheckFlags () { if (whiteTimeRemaining <= 0) { if (!whiteFlag) { @@ -14525,7 +15744,7 @@ CheckFlags() } void -CheckTimeControl() +CheckTimeControl () { if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode gameMode == PlayFromGameFile || forwardMostMove == 0) return; @@ -14550,7 +15769,7 @@ CheckTimeControl() } void -DisplayBothClocks() +DisplayBothClocks () { int wom = gameMode == EditPosition ? !blackPlaysFirst : WhiteOnMove(currentMove); @@ -14572,8 +15791,7 @@ DisplayBothClocks() /* Get the current time as a TimeMark */ void -GetTimeMark(tm) - TimeMark *tm; +GetTimeMark (TimeMark *tm) { #if HAVE_GETTIMEOFDAY @@ -14605,8 +15823,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); @@ -14627,8 +15844,7 @@ static TimeMark tickStartTM; static long intendedTickLength; long -NextTickLength(timeRemaining) - long timeRemaining; +NextTickLength (long timeRemaining) { long nominalTickLength, nextTickLength; @@ -14644,16 +15860,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) { @@ -14672,13 +15890,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; @@ -14758,7 +15977,7 @@ DecrementClocks() from the color that is *not* on move now. */ void -SwitchClocks(int newMoveNr) +SwitchClocks (int newMoveNr) { long lastTickLength; TimeMark now; @@ -14821,7 +16040,7 @@ SwitchClocks(int newMoveNr) /* Stop both clocks */ void -StopClocks() +StopClocks () { long lastTickLength; TimeMark now; @@ -14847,7 +16066,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(); @@ -14878,8 +16097,7 @@ StartClocks() } char * -TimeString(ms) - long ms; +TimeString (long ms) { long second, minute, hour, day; char *sign = ""; @@ -14926,8 +16144,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; @@ -14941,8 +16158,7 @@ StrStr(string, match) } char * -StrCaseStr(string, match) - char *string, *match; +StrCaseStr (char *string, char *match) { int i, j, length; @@ -14961,8 +16177,7 @@ StrCaseStr(string, match) #ifndef _amigados int -StrCaseCmp(s1, s2) - char *s1, *s2; +StrCaseCmp (char *s1, char *s2) { char c1, c2; @@ -14977,24 +16192,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; @@ -15006,8 +16218,7 @@ StrSave(s) } char * -StrSavePtr(s, savePtr) - char *s, **savePtr; +StrSavePtr (char *s, char **savePtr) { if (*savePtr) { free(*savePtr); @@ -15019,7 +16230,7 @@ StrSavePtr(s, savePtr) } char * -PGNDate() +PGNDate () { time_t clock; struct tm *tm; @@ -15034,13 +16245,11 @@ PGNDate() char * -PositionToFEN(move, overrideCastling) - int move; - char *overrideCastling; +PositionToFEN (int move, char *overrideCastling) { int i, j, fromX, fromY, toX, toY; int whiteToPlay; - char buf[128]; + char buf[MSG_SIZ]; char *p, *q; int emptycount; ChessSquare piece; @@ -15051,6 +16260,7 @@ PositionToFEN(move, overrideCastling) /* Piece placement data */ for (i = BOARD_HEIGHT - 1; i >= 0; i--) { + if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); } emptycount = 0; for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { if (boards[move][i][j] == EmptySquare) { @@ -15203,10 +16413,7 @@ PositionToFEN(move, overrideCastling) } Boolean -ParseFEN(board, blackPlaysFirst, fen) - Board board; - int *blackPlaysFirst; - char *fen; +ParseFEN (Board board, int *blackPlaysFirst, char *fen) { int i, j; char *p, c; @@ -15446,7 +16653,7 @@ ParseFEN(board, blackPlaysFirst, fen) } void -EditPositionPasteFEN(char *fen) +EditPositionPasteFEN (char *fen) { if (fen != NULL) { Board initial_position; @@ -15469,7 +16676,8 @@ EditPositionPasteFEN(char *fen) static char cseq[12] = "\\ "; -Boolean set_cont_sequence(char *new_seq) +Boolean +set_cont_sequence (char *new_seq) { int len; Boolean ret; @@ -15494,7 +16702,8 @@ Boolean set_cont_sequence(char *new_seq) for the dest buffer. lp argument indicats line position upon entry, and it's passed back upon exit. */ -int wrap(char *dest, char *src, int count, int width, int *lp) +int +wrap (char *dest, char *src, int count, int width, int *lp) { int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen; @@ -15575,18 +16784,13 @@ int wrap(char *dest, char *src, int count, int width, int *lp) } // [HGM] vari: routines for shelving variations +Boolean modeRestore = FALSE; void -PushTail(int firstMove, int lastMove) +PushInner (int firstMove, int lastMove) { int i, j, nrMoves = lastMove - firstMove; - if(appData.icsActive) { // only in local mode - forwardMostMove = currentMove; // mimic old ICS behavior - return; - } - if(storedGames >= MAX_VARIATIONS-1) return; - // push current tail of game on stack savedResult[storedGames] = gameInfo.result; savedDetails[storedGames] = gameInfo.resultDetails; @@ -15611,21 +16815,30 @@ PushTail(int firstMove, int lastMove) storedGames++; forwardMostMove = firstMove; // truncate game so we can start variation +} + +void +PushTail (int firstMove, int lastMove) +{ + if(appData.icsActive) { // only in local mode + forwardMostMove = currentMove; // mimic old ICS behavior + return; + } + if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk + + PushInner(firstMove, lastMove); if(storedGames == 1) GreyRevert(FALSE); + if(gameMode == PlayFromGameFile) gameMode = EditGame, modeRestore = TRUE; } -Boolean -PopTail(Boolean annotate) +void +PopInner (Boolean annotate) { int i, j, nrMoves; char buf[8000], moveBuf[20]; - if(appData.icsActive) return FALSE; // only in local mode - if(!storedGames) return FALSE; // sanity - CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open - - storedGames--; - ToNrEvent(savedFirst[storedGames]); // sets currentMove + ToNrEvent(savedFirst[storedGames-1]); // sets currentMove + storedGames--; // do this after ToNrEvent, to make sure HistorySet will refresh entire game after PopInner returns nrMoves = savedLast[storedGames] - currentMove; if(annotate) { int cnt = 10; @@ -15663,12 +16876,25 @@ PopTail(Boolean annotate) } gameInfo.resultDetails = savedDetails[storedGames]; forwardMostMove = currentMove + nrMoves; - if(storedGames == 0) GreyRevert(TRUE); +} + +Boolean +PopTail (Boolean annotate) +{ + if(appData.icsActive) return FALSE; // only in local mode + if(!storedGames) return FALSE; // sanity + CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open + + PopInner(annotate); + if(currentMove < forwardMostMove) ForwardEvent(); else + HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); + + if(storedGames == 0) { GreyRevert(TRUE); if(modeRestore) modeRestore = FALSE, gameMode = PlayFromGameFile; } return TRUE; } void -CleanupTail() +CleanupTail () { // remove all shelved variations int i; for(i=0; i