X-Git-Url: http://winboard.nl/cgi-bin?a=blobdiff_plain;f=backend.c;h=b8cfa92f2ceacc4fbf810305c9e9b49ecc4c22ef;hb=fbc553e01f81123465d606732caecdc0567ce06d;hp=859a47f22594cc42cc1e1b41c612a696d5086392;hpb=86517e539f33650be656c482020a878fec0723c9;p=xboard.git diff --git a/backend.c b/backend.c index 859a47f..b8cfa92 100644 --- a/backend.c +++ b/backend.c @@ -55,8 +55,7 @@ #else -#define DoSleep( n ) -typedef int BOOL; +#define DoSleep( n ) if( (n) >= 0) sleep(n) #endif @@ -121,6 +120,16 @@ extern int gettimeofday(struct timeval *, struct timezone *); # include "zippy.h" #endif #include "backendz.h" +#include "gettext.h" + +#ifdef ENABLE_NLS +# define _(s) gettext (s) +# define N_(s) gettext_noop (s) +#else +# define _(s) (s) +# define N_(s) s +#endif + /* A point in time */ typedef struct { @@ -128,23 +137,6 @@ typedef struct { int ms; /* Assuming this is >= 16 bits */ } TimeMark; -/* Search stats from chessprogram */ -typedef struct { - char movelist[2*MSG_SIZ]; /* Last PV we were sent */ - int depth; /* Current search depth */ - int nr_moves; /* Total nr of root moves */ - int moves_left; /* Moves remaining to be searched */ - char move_name[MOVE_LEN]; /* Current move being searched, if provided */ - unsigned long nodes; /* # of nodes searched */ - int time; /* Search time (centiseconds) */ - int score; /* Score (centipawns) */ - int got_only_move; /* If last msg was "(only move)" */ - int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */ - int ok_to_send; /* handshaking between send & recv */ - int line_is_book; /* 1 if movelist is book moves */ - int seen_stat; /* 1 if we've seen the stat01: line */ -} ChessProgramStats; - int establish P((void)); void read_from_player P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); @@ -165,7 +157,7 @@ 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)); -void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, +int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY, /*char*/int promoChar)); void BackwardInner P((int target)); void ForwardInner P((int target)); @@ -217,10 +209,22 @@ void InitBackEnd3 P((void)); void FeatureDone P((ChessProgramState* cps, int val)); void InitChessProgram P((ChessProgramState *cps, int setup)); +#ifdef WIN32 + extern void ConsoleCreate(); +#endif + +ChessProgramState *WhitePlayer(); +void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c +int VerifyDisplayMode P(()); + char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment +void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c +char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move +char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book +extern char installDir[MSG_SIZ]; extern int tinyLayout, smallLayout; -static ChessProgramStats programStats; +ChessProgramStats programStats; static int exiting = 0; /* [HGM] moved to top */ static int setboardSpoiledMachineBlack = 0, errorExitFlag = 0; extern int startedFromPositionFile; @@ -228,6 +232,8 @@ int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */ int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */ VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */ +int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */ +int opponentKibitzes; /* States for ics_getting_history */ #define H_FALSE 0 @@ -299,6 +305,26 @@ static char * safeStrCat( char * dst, const char * src, size_t count ) return dst; } +/* Some compiler can't cast u64 to double + * This function do the job for us: + + * We use the highest bit for cast, this only + * works if the highest bit is not + * in use (This should not happen) + * + * We used this for all compiler + */ +double +u64ToDouble(u64 value) +{ + double r; + u64 tmp = value & u64Const(0x7fffffffffffffff); + r = (double)(s64)tmp; + if (value & u64Const(0x8000000000000000)) + r += 9.2233720368547758080e18; /* 2^63 */ + return r; +} + /* Fake up flags for now, as we aren't keeping track of castling availability yet. [HGM] Change of logic: the flag now only indicates the type of castlings allowed by the rule of the game. @@ -313,9 +339,9 @@ PosFlags(index) if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE; switch (gameInfo.variant) { case VariantSuicide: - case VariantGiveaway: - flags |= F_IGNORE_CHECK; flags &= ~F_ALL_CASTLE_OK; + case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC! + flags |= F_IGNORE_CHECK; break; case VariantAtomic: flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE; @@ -364,12 +390,8 @@ int gotPremove = 0; Boolean alarmSounded; /* end premove variables */ -#define ICS_GENERIC 0 -#define ICS_ICC 1 -#define ICS_FICS 2 -#define ICS_CHESSNET 3 /* not really supported */ -int ics_type = ICS_GENERIC; char *ics_prefix = "$"; +int ics_type = ICS_GENERIC; int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0; int pauseExamForwardMostMove = 0; @@ -490,6 +512,13 @@ ChessSquare CapablancaArray[2][BOARD_SIZE] = { BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook } }; +ChessSquare GreatArray[2][BOARD_SIZE] = { + { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, + WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon }, + { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, + BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon }, +}; + ChessSquare JanusArray[2][BOARD_SIZE] = { { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook }, @@ -523,6 +552,7 @@ ChessSquare FalconArray[2][BOARD_SIZE] = { #define XiangqiPosition FIDEArray #define CapablancaArray FIDEArray #define GothicArray FIDEArray +#define GreatArray FIDEArray #endif // !(BOARD_SIZE>=10) #if (BOARD_SIZE>=12) @@ -575,6 +605,8 @@ InitBackEnd1() { int matched, min, sec; + ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options + GetTimeMark(&programStartTime); ClearProgramStats(); @@ -625,7 +657,7 @@ InitBackEnd1() if (!ParseTimeControl(appData.timeControl, appData.timeIncrement, appData.movesPerSession)) { char buf[MSG_SIZ]; - sprintf(buf, "bad timeControl option %s", appData.timeControl); + sprintf(buf, _("bad timeControl option %s"), appData.timeControl); DisplayFatalError(buf, 0, 2); } @@ -640,7 +672,7 @@ InitBackEnd1() searchTime = min * 60 + sec; } else { char buf[MSG_SIZ]; - sprintf(buf, "bad searchTime option %s", appData.searchTime); + sprintf(buf, _("bad searchTime option %s"), appData.searchTime); DisplayFatalError(buf, 0, 2); } } @@ -737,7 +769,7 @@ InitBackEnd1() if (appData.firstProtocolVersion > PROTOVER || appData.firstProtocolVersion < 1) { char buf[MSG_SIZ]; - sprintf(buf, "protocol version %d not supported", + sprintf(buf, _("protocol version %d not supported"), appData.firstProtocolVersion); DisplayFatalError(buf, 0, 2); } else { @@ -747,7 +779,7 @@ InitBackEnd1() if (appData.secondProtocolVersion > PROTOVER || appData.secondProtocolVersion < 1) { char buf[MSG_SIZ]; - sprintf(buf, "protocol version %d not supported", + sprintf(buf, _("protocol version %d not supported"), appData.secondProtocolVersion); DisplayFatalError(buf, 0, 2); } else { @@ -776,15 +808,22 @@ InitBackEnd1() + strlen(PATCHLEVEL)); sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL); } else { +#if 0 char *p, *q; q = first.program; while (*q != ' ' && *q != NULLCHAR) q++; p = q; - while (p > first.program && *(p-1) != '/') p--; + while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */ programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION) + strlen(PATCHLEVEL) + (q - p)); sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL); strncat(programVersion, p, q - p); +#else + /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */ + programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION) + + strlen(PATCHLEVEL) + strlen(first.tidy)); + sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy); +#endif } if (!appData.icsActive) { @@ -798,7 +837,7 @@ InitBackEnd1() case VariantBughouse: /* need four players and two boards */ case VariantKriegspiel: /* need to hide pieces and move details */ /* case VariantFischeRandom: (Fabien: moved below) */ - sprintf(buf, "Variant %s supported only in ICS mode", appData.variant); + sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant); DisplayFatalError(buf, 0, 2); return; @@ -813,7 +852,7 @@ InitBackEnd1() case Variant35: case Variant36: default: - sprintf(buf, "Unknown variant name %s", appData.variant); + sprintf(buf, _("Unknown variant name %s"), appData.variant); DisplayFatalError(buf, 0, 2); return; @@ -844,9 +883,15 @@ InitBackEnd1() case VariantShatranj: /* should work except for all win conditions */ 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 */ break; } } + + InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard + InitEngineUCI( installDir, &second ); } int NextIntegerFromString( char ** str, long * value ) @@ -1023,13 +1068,19 @@ InitBackEnd2() } else if (appData.matchMode) { appData.matchGames = 1; } + if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */ + appData.matchGames = appData.sameColorGames; + if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */ + if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1; + if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1; + } Reset(TRUE, FALSE); if (appData.noChessProgram || first.protocolVersion == 1) { InitBackEnd3(); } else { /* kludge: allow timeout for initial "feature" commands */ FreezeUI(); - DisplayMessage("", "Starting chess program"); + DisplayMessage("", _("Starting chess program")); ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT); } } @@ -1043,14 +1094,19 @@ InitBackEnd3 P((void)) InitChessProgram(&first, startedFromSetupPosition); + if (appData.icsActive) { +#ifdef WIN32 + /* [DM] Make a console window if needed [HGM] merged ifs */ + ConsoleCreate(); +#endif err = establish(); if (err != 0) { if (*appData.icsCommPort != NULLCHAR) { - sprintf(buf, "Could not open comm port %s", + sprintf(buf, _("Could not open comm port %s"), appData.icsCommPort); } else { - sprintf(buf, "Could not connect to host %s, port %s", + sprintf(buf, _("Could not connect to host %s, port %s"), appData.icsHost, appData.icsPort); } DisplayFatalError(buf, err, 1); @@ -1095,7 +1151,7 @@ InitBackEnd3 P((void)) } else if (StrCaseCmp(appData.initialMode, "Training") == 0) { initialMode = Training; } else { - sprintf(buf, "Unknown initialMode %s", appData.initialMode); + sprintf(buf, _("Unknown initialMode %s"), appData.initialMode); DisplayFatalError(buf, 0, 2); return; } @@ -1103,24 +1159,28 @@ InitBackEnd3 P((void)) if (appData.matchMode) { /* Set up machine vs. machine match */ if (appData.noChessProgram) { - DisplayFatalError("Can't have a match with no chess programs", + DisplayFatalError(_("Can't have a match with no chess programs"), 0, 2); return; } matchMode = TRUE; matchGame = 1; if (*appData.loadGameFile != NULLCHAR) { + int index = appData.loadGameIndex; // [HGM] autoinc + if(index<0) lastIndex = index = 1; if (!LoadGameFromFile(appData.loadGameFile, - appData.loadGameIndex, + index, appData.loadGameFile, FALSE)) { - DisplayFatalError("Bad game file", 0, 1); + 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, - appData.loadPositionIndex, + index, appData.loadPositionFile)) { - DisplayFatalError("Bad position file", 0, 1); + DisplayFatalError(_("Bad position file"), 0, 1); return; } } @@ -1132,7 +1192,7 @@ InitBackEnd3 P((void)) /* Set up other modes */ if (initialMode == AnalyzeFile) { if (*appData.loadGameFile == NULLCHAR) { - DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1); + DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1); return; } } @@ -1160,50 +1220,51 @@ InitBackEnd3 P((void)) } if (initialMode == AnalyzeMode) { if (appData.noChessProgram) { - DisplayFatalError("Analysis mode requires a chess engine", 0, 2); + DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2); return; } if (appData.icsActive) { - DisplayFatalError("Analysis mode does not work with ICS mode",0,2); + DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2); return; } AnalyzeModeEvent(); } else if (initialMode == AnalyzeFile) { - ShowThinkingEvent(TRUE); + appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent + ShowThinkingEvent(); AnalyzeFileEvent(); AnalysisPeriodicEvent(1); } else if (initialMode == MachinePlaysWhite) { if (appData.noChessProgram) { - DisplayFatalError("MachineWhite mode requires a chess engine", + DisplayFatalError(_("MachineWhite mode requires a chess engine"), 0, 2); return; } if (appData.icsActive) { - DisplayFatalError("MachineWhite mode does not work with ICS mode", + DisplayFatalError(_("MachineWhite mode does not work with ICS mode"), 0, 2); return; } MachineWhiteEvent(); } else if (initialMode == MachinePlaysBlack) { if (appData.noChessProgram) { - DisplayFatalError("MachineBlack mode requires a chess engine", + DisplayFatalError(_("MachineBlack mode requires a chess engine"), 0, 2); return; } if (appData.icsActive) { - DisplayFatalError("MachineBlack mode does not work with ICS mode", + DisplayFatalError(_("MachineBlack mode does not work with ICS mode"), 0, 2); return; } MachineBlackEvent(); } else if (initialMode == TwoMachinesPlay) { if (appData.noChessProgram) { - DisplayFatalError("TwoMachines mode requires a chess engine", + DisplayFatalError(_("TwoMachines mode requires a chess engine"), 0, 2); return; } if (appData.icsActive) { - DisplayFatalError("TwoMachines mode does not work with ICS mode", + DisplayFatalError(_("TwoMachines mode does not work with ICS mode"), 0, 2); return; } @@ -1214,7 +1275,7 @@ InitBackEnd3 P((void)) EditPositionEvent(); } else if (initialMode == Training) { if (*appData.loadGameFile == NULLCHAR) { - DisplayFatalError("Training mode requires a game file", 0, 2); + DisplayFatalError(_("Training mode requires a game file"), 0, 2); return; } TrainingEvent(); @@ -1362,14 +1423,14 @@ read_from_player(isr, closure, message, count, error) gotEof = 0; outCount = OutputMaybeTelnet(icsPR, message, count, &outError); if (outCount < count) { - DisplayFatalError("Error writing to ICS", outError, 1); + DisplayFatalError(_("Error writing to ICS"), outError, 1); } } else if (count < 0) { RemoveInputSource(isr); - DisplayFatalError("Error reading from keyboard", error, 1); + DisplayFatalError(_("Error reading from keyboard"), error, 1); } else if (gotEof++ > 0) { RemoveInputSource(isr); - DisplayFatalError("Got end of file from keyboard", 0, 0); + DisplayFatalError(_("Got end of file from keyboard"), 0, 0); } } @@ -1384,7 +1445,7 @@ SendToICS(s) count = strlen(s); outCount = OutputMaybeTelnet(icsPR, s, count, &outError); if (outCount < count) { - DisplayFatalError("Error writing to ICS", outError, 1); + DisplayFatalError(_("Error writing to ICS"), outError, 1); } } @@ -1409,7 +1470,7 @@ SendToICSDelayed(s,msdelay) outCount = OutputToProcessDelayed(icsPR, s, count, &outError, msdelay); if (outCount < count) { - DisplayFatalError("Error writing to ICS", outError, 1); + DisplayFatalError(_("Error writing to ICS"), outError, 1); } } @@ -1501,7 +1562,8 @@ StringToVariant(e) if (!found) { if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random")) - || StrCaseStr(e, "wild/fr")) { + || StrCaseStr(e, "wild/fr") + || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) { v = VariantFischeRandom; } else if ((i = 4, p = StrCaseStr(e, "wild")) || (i = 1, p = StrCaseStr(e, "w"))) { @@ -1640,13 +1702,19 @@ StringToVariant(e) case 48: v = VariantJanus; break; + case 49: + v = VariantSuper; + break; + case 50: + v = VariantGreat; + break; case -1: /* Found "wild" or "w" in the string but no number; must assume it's normal chess. */ v = VariantNormal; break; default: - sprintf(buf, "Unknown wild type %d", wnum); + sprintf(buf, _("Unknown wild type %d"), wnum); DisplayError(buf, 0); v = VariantUnknown; break; @@ -1654,7 +1722,7 @@ StringToVariant(e) } } if (appData.debugMode) { - fprintf(debugFP, "recognized '%s' (%d) as variant %s\n", + fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"), e, wnum, VariantName(v)); } return v; @@ -1719,7 +1787,7 @@ SendToPlayer(data, length) int error, outCount; outCount = OutputToProcess(NoProc, data, length, &error); if (outCount < length) { - DisplayFatalError("Error writing to display", error, 1); + DisplayFatalError(_("Error writing to display"), error, 1); } } @@ -1805,7 +1873,7 @@ TelnetRequest(ddww, option) msg[2] = option; outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError); if (outCount < 3) { - DisplayFatalError("Error writing to ICS", outError, 1); + DisplayFatalError(_("Error writing to ICS"), outError, 1); } } @@ -1856,7 +1924,7 @@ CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece) j = PieceToNumber(piece); if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */ if(j < 0) continue; /* should not happen */ - piece = (ChessSquare) ( j + (int)lowestPiece ); + piece = (ChessSquare) ( (int)piece + (int)lowestPiece ); board[holdingsStartRow+j*direction][holdingsColumn] = piece; board[holdingsStartRow+j*direction][countsColumn]++; } @@ -1963,6 +2031,7 @@ static int player2Rating = -1; /*----------------------------*/ ColorClass curColor = ColorNormal; +int suppressKibitz = 0; void read_from_ics(isr, closure, data, count, error) @@ -1994,9 +2063,9 @@ read_from_ics(isr, closure, data, count, error) int buf_len; int next_out; int tkind; + int backup; /* [DM] For zippy color lines */ char *p; -#ifdef WIN32 if (appData.debugMode) { if (!error) { fprintf(debugFP, "= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && + buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') + buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous } buf[buf_len] = NULLCHAR; @@ -2172,7 +2243,28 @@ read_from_ics(isr, closure, data, count, error) parse[parse_pos++] = buf[i]; if (buf[i] == '\n') { parse[parse_pos] = NULLCHAR; - AppendComment(forwardMostMove, StripHighlight(parse)); + if(!suppressKibitz) // [HGM] kibitz + AppendComment(forwardMostMove, StripHighlight(parse)); + else { // [HGM kibitz: divert memorized engine kibitz to engine-output window + int nrDigit = 0, nrAlph = 0, i; + if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input + { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; } + parse[parse_pos] = NULLCHAR; + // try to be smart: if it does not look like search info, it should go to + // ICS interaction window after all, not to engine-output window. + for(i=0; i= '0' && parse[i] <= '9'); + nrAlph += (parse[i] >= 'a' && parse[i] <= 'z'); + nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z'); + } + if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info + OutputKibitz(suppressKibitz, parse); + } else { + char tmp[MSG_SIZ]; + sprintf(tmp, _("your opponent kibitzes: %s"), parse); + SendToPlayer(tmp, strlen(tmp)); + } + } started = STARTED_NONE; } else { /* Don't match patterns against characters in chatter */ @@ -2253,17 +2345,53 @@ read_from_ics(isr, closure, data, count, error) } oldi = i; + // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window + if (appData.autoKibitz && started == STARTED_NONE && + !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze + (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) { + if(looking_at(buf, &i, "* kibitzes: ") && + (StrStr(star_match[0], gameInfo.white) == star_match[0] || + StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent + suppressKibitz = TRUE; + if((StrStr(star_match[0], gameInfo.white) == star_match[0]) + && (gameMode == IcsPlayingWhite) || + (StrStr(star_match[0], gameInfo.black) == star_match[0]) + && (gameMode == IcsPlayingBlack) ) // opponent kibitz + started = STARTED_CHATTER; // own kibitz we simply discard + else { + started = STARTED_COMMENT; // make sure it will be collected in parse[] + parse_pos = 0; parse[0] = NULLCHAR; + savingComment = TRUE; + suppressKibitz = gameMode != IcsObserving ? 2 : + (StrStr(star_match[0], gameInfo.white) == NULL) + 1; + } + continue; + } else + if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz + started = STARTED_CHATTER; + suppressKibitz = TRUE; + } + } // [HGM] kibitz: end of patch + if (appData.zippyTalk || appData.zippyPlay) { + /* [DM] Backup address for color zippy lines */ + backup = i; #if ZIPPY - if (ZippyControl(buf, &i) || - ZippyConverse(buf, &i) || - (appData.zippyPlay && ZippyMatch(buf, &i))) { - loggedOn = TRUE; - continue; + #ifdef WIN32 + if (loggedOn == TRUE) + if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) || + (appData.zippyPlay && ZippyMatch(buf, &backup))); + #else + if (ZippyControl(buf, &i) || + ZippyConverse(buf, &i) || + (appData.zippyPlay && ZippyMatch(buf, &i))) { + loggedOn = TRUE; + if (!appData.colorize) continue; } + #endif #endif - } else { - if (/* Don't color "message" or "messages" output */ + } // [DM] 'else { ' deleted + if (/* Don't color "message" or "messages" output */ (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) || looking_at(buf, &i, "*. * at *:*: ") || looking_at(buf, &i, "--* (*:*): ") || @@ -2418,7 +2546,6 @@ read_from_ics(isr, closure, data, count, error) curColor = ColorSeek; } continue; - } } if (looking_at(buf, &i, "\\ ")) { @@ -2529,7 +2656,7 @@ read_from_ics(isr, closure, data, count, error) case H_GOT_UNWANTED_HEADER: case H_GETTING_MOVES: /* Should not happen */ - DisplayError("Error gathering move list: two headers", 0); + DisplayError(_("Error gathering move list: two headers"), 0); ics_getting_history = H_FALSE; break; } @@ -2543,7 +2670,7 @@ read_from_ics(isr, closure, data, count, error) gameInfo.whiteRating = string_to_rating(star_match[1]); gameInfo.blackRating = string_to_rating(star_match[3]); if (appData.debugMode) - fprintf(debugFP, "Ratings from header: W %d, B %d\n", + fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), gameInfo.whiteRating, gameInfo.blackRating); } continue; @@ -2576,7 +2703,7 @@ read_from_ics(isr, closure, data, count, error) break; case H_GETTING_MOVES: /* Should not happen */ - DisplayError("Error gathering move list: nested", 0); + DisplayError(_("Error gathering move list: nested"), 0); ics_getting_history = H_FALSE; break; case H_GOT_REQ_HEADER: @@ -2601,7 +2728,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, "}*"))) { + && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book savingComment = FALSE; switch (started) { case STARTED_MOVES: @@ -2620,11 +2747,18 @@ read_from_ics(isr, closure, data, count, error) } SendTimeRemaining(&first, TRUE); } +#if 0 if (first.useColors) { SendToProgram("white\ngo\n", &first); } else { SendToProgram("go\n", &first); } +#else + if (first.useColors) { + SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent + } + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos +#endif first.maybeThinking = TRUE; } else { if (first.usePlayother) { @@ -2645,11 +2779,18 @@ read_from_ics(isr, closure, data, count, error) } SendTimeRemaining(&first, FALSE); } +#if 0 if (first.useColors) { SendToProgram("black\ngo\n", &first); } else { SendToProgram("go\n", &first); } +#else + if (first.useColors) { + SendToProgram("black\n", &first); + } + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); +#endif first.maybeThinking = TRUE; } else { if (first.usePlayother) { @@ -2702,6 +2843,17 @@ read_from_ics(isr, closure, data, count, error) default: break; } + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); + } continue; } @@ -2788,7 +2940,7 @@ read_from_ics(isr, closure, data, count, error) if (forwardMostMove > backwardMostMove) { currentMove = --forwardMostMove; DisplayMove(currentMove - 1); /* before DMError */ - DisplayMoveError("Illegal move (rejected by ICS)"); + DisplayMoveError(_("Illegal move (rejected by ICS)")); DrawPosition(FALSE, boards[currentMove]); SwitchClocks(); DisplayBothClocks(); @@ -2943,6 +3095,11 @@ read_from_ics(isr, closure, data, count, error) if (gameMode == IcsObserving && atoi(star_match[0]) == ics_gamenum) { + /* icsEngineAnalyze */ + if (appData.icsEngineAnalyze) { + ExitAnalyzeMode(); + ModeHighlight(); + } StopClocks(); gameMode = IcsIdle; ics_gamenum = -1; @@ -3084,11 +3241,12 @@ read_from_ics(isr, closure, data, count, error) i++; /* skip unparsed character and loop back */ } - if (started != STARTED_MOVES && started != STARTED_BOARD && + if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window started != STARTED_HOLDINGS && i > next_out) { SendToPlayer(&buf[next_out], i - next_out); next_out = i; } + suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above leftover_len = buf_len - leftover_start; /* if buffer ends with something we couldn't parse, @@ -3096,9 +3254,9 @@ read_from_ics(isr, closure, data, count, error) } else if (count == 0) { RemoveInputSource(isr); - DisplayFatalError("Connection closed by ICS", 0, 0); + DisplayFatalError(_("Connection closed by ICS"), 0, 0); } else { - DisplayFatalError("Error reading from ICS", error, 1); + DisplayFatalError(_("Error reading from ICS"), error, 1); } } @@ -3113,7 +3271,7 @@ read_from_ics(isr, closure, data, count, error) * Additional trailing fields may be added in the future. */ -#define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d" +#define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d" #define RELATION_OBSERVING_PLAYED 0 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */ @@ -3128,10 +3286,10 @@ ParseBoard12(string) char *string; { GameMode newGameMode; - int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0; - int j, k, n, moveNum, white_stren, black_stren, white_time, black_time; + int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i; + int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback; int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count; - char to_play, board_chars[72]; + char to_play, board_chars[200]; char move_str[500], str[500], elapsed_time[500]; char black[32], white[32]; Board board; @@ -3140,25 +3298,38 @@ ParseBoard12(string) ChessMove moveType; int fromX, fromY, toX, toY; char promoChar; + int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */ + char *bookHit = NULL; // [HGM] book fromX = fromY = toX = toY = -1; newGame = FALSE; if (appData.debugMode) - fprintf(debugFP, "Parsing board: %s\n", string); + fprintf(debugFP, _("Parsing board: %s\n"), string); move_str[0] = NULLCHAR; elapsed_time[0] = NULLCHAR; - n = sscanf(string, PATTERN, board_chars, &to_play, &double_push, + { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */ + int i = 0, j; + while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) { + if(string[i] == ' ') { ranks++; files = 0; } + else files++; + i++; + } + for(j = 0; j = MAX_MOVES) { - DisplayFatalError("Game too long; increase MAX_MOVES and recompile", + DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), 0, 1); return; } @@ -3229,7 +3400,7 @@ ParseBoard12(string) return; case H_GETTING_MOVES: /* Should not happen */ - DisplayError("Error gathering move list: extra board", 0); + DisplayError(_("Error gathering move list: extra board"), 0); ics_getting_history = H_FALSE; return; } @@ -3352,10 +3523,13 @@ ParseBoard12(string) } } + if (appData.debugMode) { + fprintf(debugFP, "load %dx%d board\n", files, ranks); + } /* Parse the board */ - for (k = 0; k < 8; k++) { - for (j = 0; j < 8; j++) - board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]); + for (k = 0; k < ranks; k++) { + for (j = 0; j < files; j++) + board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]); if(gameInfo.holdingsWidth > 1) { board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare; board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;; @@ -3380,7 +3554,7 @@ ParseBoard12(string) but in ICS mode that is not its job anyway. */ if(moveNum == 0 || gameInfo.variant != VariantFischeRandom) - { int i, j; + { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing; for(i=BOARD_LEFT, j= -1; i forwardMostMove; + + /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */ + if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) { + takeback = forwardMostMove - moveNum; + for (i = 0; i < takeback; i++) { + if (appData.debugMode) fprintf(debugFP, "take back move\n"); + SendToProgram("undo\n", &first); + } + } + if (newGame) { forwardMostMove = backwardMostMove = currentMove = moveNum; if (gameMode == IcsExamining && moveNum == 0) { @@ -3495,8 +3680,37 @@ ParseBoard12(string) strcat(parseList[moveNum - 1], " "); strcat(parseList[moveNum - 1], elapsed_time); moveList[moveNum - 1][0] = NULLCHAR; - } else if (ParseOneMove(move_str, moveNum - 1, &moveType, - &fromX, &fromY, &toX, &toY, &promoChar)) { + } else if (strcmp(move_str, "none") == 0) { + // [HGM] long SAN: swapped order; test for 'none' before parsing move + /* Again, we don't know what the board looked like; + this is really the start of the game. */ + parseList[moveNum - 1][0] = NULLCHAR; + moveList[moveNum - 1][0] = NULLCHAR; + backwardMostMove = moveNum; + startedFromSetupPosition = TRUE; + fromX = fromY = toX = toY = -1; + } else { + // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. + // So we parse the long-algebraic move string in stead of the SAN move + int valid; char buf[MSG_SIZ], *prom; + + // str looks something like "Q/a1-a2"; kill the slash + if(str[1] == '/') + sprintf(buf, "%c%s", str[0], str+2); + else strcpy(buf, str); // might be castling + if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) + strcat(buf, prom); // long move lacks promo specification! + if(!appData.testLegality) { + if(appData.debugMode) + fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf); + strcpy(move_str, buf); + } + valid = ParseOneMove(move_str, moveNum - 1, &moveType, + &fromX, &fromY, &toX, &toY, &promoChar) + || ParseOneMove(buf, moveNum - 1, &moveType, + &fromX, &fromY, &toX, &toY, &promoChar); + // end of long SAN patch + if (valid) { (void) CoordsToAlgebraic(boards[moveNum - 1], PosFlags(moveNum - 1), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, @@ -3520,15 +3734,7 @@ ParseBoard12(string) /* currentMoveString is set as a side-effect of ParseOneMove */ strcpy(moveList[moveNum - 1], currentMoveString); strcat(moveList[moveNum - 1], "\n"); - } else if (strcmp(move_str, "none") == 0) { - /* Again, we don't know what the board looked like; - this is really the start of the game. */ - parseList[moveNum - 1][0] = NULLCHAR; - moveList[moveNum - 1][0] = NULLCHAR; - backwardMostMove = moveNum; - startedFromSetupPosition = TRUE; - fromX = fromY = toX = toY = -1; - } else { + } else { /* Move from ICS was illegal!? Punt. */ if (appData.debugMode) { fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str); @@ -3545,6 +3751,7 @@ ParseBoard12(string) strcat(parseList[moveNum - 1], elapsed_time); moveList[moveNum - 1][0] = NULLCHAR; fromX = fromY = toX = toY = -1; + } } if (appData.debugMode) { fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]); @@ -3559,15 +3766,15 @@ ParseBoard12(string) if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) || (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) { if (moveList[moveNum - 1][0] == NULLCHAR) { - sprintf(str, "Couldn't parse move \"%s\" from ICS", + sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str); DisplayError(str, 0); } else { if (first.sendTime) { SendTimeRemaining(&first, gameMode == IcsPlayingWhite); } - SendMoveToProgram(moveNum - 1, &first); - if (firstMove) { + bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book + if (firstMove && !bookHit) { firstMove = FALSE; if (first.useColors) { SendToProgram(gameMode == IcsPlayingWhite ? @@ -3581,7 +3788,7 @@ ParseBoard12(string) } } else if (gameMode == IcsObserving || gameMode == IcsExamining) { if (moveList[moveNum - 1][0] == NULLCHAR) { - sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str); + sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str); DisplayError(str, 0); } else { if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand! @@ -3665,6 +3872,19 @@ ParseBoard12(string) } HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); +#if ZIPPY + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); + } +#endif } void @@ -3718,14 +3938,16 @@ SendMoveToProgram(moveNum, cps) } SendToProgram(buf, cps); } else { + if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */ + AlphaRank(moveList[moveNum], 4); + SendToProgram(moveList[moveNum], cps); + AlphaRank(moveList[moveNum], 4); // and back + } else /* Added by Tord: Send castle moves in "O-O" in FRC games if required by * the engine. It would be nice to have a better way to identify castle * moves here. */ if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && cps->useOOCastle) { - if (appData.debugMode) { - fprintf(debugFP, "Tord's FRC castling code\n"); - } int fromX = moveList[moveNum][0] - AAA; int fromY = moveList[moveNum][1] - ONE; int toX = moveList[moveNum][2] - AAA; @@ -3768,7 +3990,7 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) switch (moveType) { default: - sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)", + sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"), (int)moveType, fromX, fromY, toX, toY); DisplayError(user_move + strlen("say "), 0); break; @@ -3806,10 +4028,14 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: - if(gameInfo.variant == VariantShatranj) + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) sprintf(user_move, "%c%c%c%c=%c\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, PieceToChar(WhiteFerz)); + else if(gameInfo.variant == VariantGreat) + sprintf(user_move, "%c%c%c%c=%c\n", + AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, + PieceToChar(WhiteMan)); else sprintf(user_move, "%c%c%c%c=%c\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY, @@ -3850,7 +4076,6 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); } } - AlphaRank(move, 4); } void @@ -3873,8 +4098,6 @@ AlphaRank(char *move, int n) { char *p = move, c; int x, y; - if( !appData.alphaRank ) return; - if (appData.debugMode) { fprintf(debugFP, "alphaRank(%s,%d)\n", move, n); } @@ -3882,6 +4105,7 @@ AlphaRank(char *move, int n) if(move[1]=='*' && move[2]>='0' && move[2]<='9' && move[3]>='a' && move[3]<='x' ) { + move[1] = '@'; move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA; move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE; } else @@ -4126,6 +4350,19 @@ int squaresLeft[4]; int piecesLeft[(int)BlackPawn]; long long int seed, nrOfShuffles; +void GetPositionNumber() +{ // sets global variable seed + int i; + + seed = appData.defaultFrcPosition; + if(seed < 0) { // randomize based on time for negative FRC position numbers + srandom(time(0)); + for(i=0; i<50; i++) seed += random(); + seed = random() ^ random() >> 8 ^ random() << 8; + if(seed<0) seed = -seed; + } +} + 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 { @@ -4174,12 +4411,7 @@ void SetUpShuffle(Board board, int number) { int i, p, first=1; - seed = number, nrOfShuffles = 1; - if(number < 0) { - srand(time(0)); - for(i=0; i<50; i++) seed += rand(); - seed = rand() ^ rand()<<16; - } + GetPositionNumber(); nrOfShuffles = 1; squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2; squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT; @@ -4265,11 +4497,11 @@ void SetUpShuffle(Board board, int number) #endif -BOOL 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 */ { - BOOL result = FALSE; int NrPieces; + int result = FALSE; int NrPieces; if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare && NrPieces >= 12 && !(NrPieces&1)) { @@ -4289,6 +4521,42 @@ BOOL SetCharTable( char *table, const char * map ) return result; } +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 }; + + GetPositionNumber(); // use FRC position number + + if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table + SetCharTable(pieceToChar, appData.pieceToCharTable); + for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) + if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i; + } + + j = seed%4; seed /= 4; + p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p); + board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++; + board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++; + j = seed%3 + (seed%3 >= j); seed /= 3; + p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p); + board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++; + board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++; + j = seed%3; seed /= 3; + p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p); + board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++; + board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++; + j = seed%2 + (seed%2 >= j); seed /= 2; + p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p); + board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++; + board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++; + j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY); + j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY); + j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY); + put(board, exoPieces[0], 0, 0, ANY); + for(i=BOARD_LEFT; i BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE) - DisplayFatalError("Recompile to support this BOARD_SIZE!", 0, 2); + DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2); pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */ if(pawnRow < 1) pawnRow = 1; @@ -4502,6 +4782,13 @@ InitPosition(redraw) castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1; } + if(gameInfo.variant == VariantSuper) Prelude(initialPosition); + if(gameInfo.variant == VariantGreat) { // promotion commoners + initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan; + initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9; + initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan; + initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9; + } #if 0 if(gameInfo.variant == VariantFischeRandom) { if( appData.defaultFrcPosition < 0 ) { @@ -4583,7 +4870,7 @@ SendBoard(cps, moveNum) PieceToChar((ChessSquare)(DEMOTED *bp)), AAA + j, ONE + i); } - if(appData.alphaRank) { + if(cps->alphaRank) { /* [HGM] shogi: translate coords */ message[1] = BOARD_RGHT - 1 - j + '1'; message[2] = BOARD_HEIGHT - 1 - i + 'a'; } @@ -4605,7 +4892,7 @@ SendBoard(cps, moveNum) PieceToChar((ChessSquare)(DEMOTED *bp)), AAA + j, ONE + i); } - if(appData.alphaRank) { + if(cps->alphaRank) { /* [HGM] shogi: translate coords */ message[1] = BOARD_RGHT - 1 - j + '1'; message[2] = BOARD_HEIGHT - 1 - i + 'a'; } @@ -4705,7 +4992,7 @@ OKToStartUserMove(x, y) case IcsPlayingBlack: if (appData.zippyPlay) return FALSE; if (white_piece) { - DisplayMoveError("You are playing Black"); + DisplayMoveError(_("You are playing Black")); return FALSE; } break; @@ -4714,18 +5001,18 @@ OKToStartUserMove(x, y) case IcsPlayingWhite: if (appData.zippyPlay) return FALSE; if (!white_piece) { - DisplayMoveError("You are playing White"); + DisplayMoveError(_("You are playing White")); return FALSE; } break; case EditGame: if (!white_piece && WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); + DisplayMoveError(_("It is White's turn")); return FALSE; } if (white_piece && !WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); + DisplayMoveError(_("It is Black's turn")); return FALSE; } if (cmailMsgLoaded && (currentMove < cmailOldMove)) { @@ -4745,7 +5032,7 @@ OKToStartUserMove(x, y) if (appData.icsActive) return FALSE; if (!appData.noChessProgram) { if (!white_piece) { - DisplayMoveError("You are playing White"); + DisplayMoveError(_("You are playing White")); return FALSE; } } @@ -4753,11 +5040,11 @@ OKToStartUserMove(x, y) case Training: if (!white_piece && WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); + DisplayMoveError(_("It is White's turn")); return FALSE; } if (white_piece && !WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); + DisplayMoveError(_("It is Black's turn")); return FALSE; } break; @@ -4768,7 +5055,7 @@ OKToStartUserMove(x, y) } if (currentMove != forwardMostMove && gameMode != AnalyzeMode && gameMode != AnalyzeFile && gameMode != Training) { - DisplayMoveError("Displayed position is not current"); + DisplayMoveError(_("Displayed position is not current")); return FALSE; } return TRUE; @@ -4838,7 +5125,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) case MachinePlaysWhite: /* User is moving for Black */ if (WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); + DisplayMoveError(_("It is White's turn")); return ImpossibleMove; } break; @@ -4846,7 +5133,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) case MachinePlaysBlack: /* User is moving for White */ if (!WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); + DisplayMoveError(_("It is Black's turn")); return ImpossibleMove; } break; @@ -4860,13 +5147,13 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) { /* User is moving for Black */ if (WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); + DisplayMoveError(_("It is White's turn")); return ImpossibleMove; } } else { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); + DisplayMoveError(_("It is Black's turn")); return ImpossibleMove; } } @@ -4876,7 +5163,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) /* User is moving for Black */ if (WhiteOnMove(currentMove)) { if (!appData.premove) { - DisplayMoveError("It is White's turn"); + DisplayMoveError(_("It is White's turn")); } else if (toX >= 0 && toY >= 0) { premoveToX = toX; premoveToY = toY; @@ -4897,7 +5184,7 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) /* User is moving for White */ if (!WhiteOnMove(currentMove)) { if (!appData.premove) { - DisplayMoveError("It is Black's turn"); + DisplayMoveError(_("It is Black's turn")); } else if (toX >= 0 && toY >= 0) { premoveToX = toX; premoveToY = toY; @@ -4922,11 +5209,11 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) click-click move is possible */ if (toX == -2 || toY == -2) { boards[0][fromY][fromX] = EmptySquare; - DrawPosition(FALSE, boards[currentMove]); + return AmbiguousMove; } else if (toX >= 0 && toY >= 0) { boards[0][toY][toX] = boards[0][fromY][fromX]; boards[0][fromY][fromX] = EmptySquare; - DrawPosition(FALSE, boards[currentMove]); + return AmbiguousMove; } return ImpossibleMove; } @@ -4956,11 +5243,11 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { if (moveType == IllegalMove || moveType == ImpossibleMove) { - DisplayMoveError("Illegal move"); + DisplayMoveError(_("Illegal move")); return ImpossibleMove; } } - +if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar); return moveType; /* [HGM] in stead of calling FinishMove directly, this function is made into one that returns an OK move type if FinishMove @@ -4972,17 +5259,29 @@ UserMoveTest(fromX, fromY, toX, toY, promoChar) } /* Common tail of UserMoveEvent and DropMenuEvent */ -void +int FinishMove(moveType, fromX, fromY, toX, toY, promoChar) ChessMove moveType; int fromX, fromY, toX, toY; /*char*/int promoChar; { - /* [HGM] kludge to avoid having know the exact promotion + char *bookHit = 0; +if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar); + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { + // [HGM] superchess: suppress promotions to non-available piece + int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); + if(WhiteOnMove(currentMove)) { + if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0; + } else { + if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0; + } + } + + /* [HGM] kludge to avoid having to know the exact promotion move type in caller when we know the move is a legal promotion */ - if(moveType == NormalMove) + if(moveType == NormalMove && promoChar) moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); - +if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar); /* [HGM] convert drag-and-drop piece drops to standard form */ if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop; @@ -5021,12 +5320,12 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) gameMode = PlayFromGameFile; ModeHighlight(); SetTrainingModeOff(); - DisplayInformation("End of game"); + DisplayInformation(_("End of game")); } } else { - DisplayError("Incorrect move", 0); + DisplayError(_("Incorrect move"), 0); } - return; + return 1; } /* Ok, now we know that the move is good, so we can kill @@ -5046,6 +5345,19 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/ + if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) + && promoChar != NULLCHAR && gameInfo.holdingsSize) { + // [HGM] superchess: take promotion piece out of holdings + int k = PieceToNumber(CharToPiece(ToUpper(promoChar))); + if(WhiteOnMove(forwardMostMove-1)) { + if(!--boards[forwardMostMove][k][BOARD_WIDTH-2]) + boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare; + } else { + if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1]) + boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare; + } + } + if (gameMode == BeginningOfGame) { if (appData.noChessProgram) { gameMode = EditGame; @@ -5053,6 +5365,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } else { char buf[MSG_SIZ]; gameMode = MachinePlaysBlack; + StartClocks(); SetGameInfo(); sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black); DisplayTitle(buf); @@ -5064,7 +5377,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) } ModeHighlight(); } - +if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar); /* Relay move to ICS or chess engine */ if (appData.icsActive) { if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || @@ -5078,10 +5391,11 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) gameMode == MachinePlaysBlack)) { SendTimeRemaining(&first, gameMode != MachinePlaysBlack); } - SendMoveToProgram(forwardMostMove-1, &first); if (gameMode != EditGame && gameMode != PlayFromGameFile) { - first.maybeThinking = TRUE; - } + // [HGM] book: if program might be playing, let it use book + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE); + first.maybeThinking = TRUE; + } else SendMoveToProgram(forwardMostMove-1, &first); if (currentMove == cmailOldMove + 1) { cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; } @@ -5118,6 +5432,19 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) default: break; } + + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); + } + return 1; } void @@ -5135,7 +5462,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) to do anything in between, can call this routine the old way. */ ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar); - +if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar); if(moveType != ImpossibleMove) FinishMove(moveType, fromX, fromY, toX, toY, promoChar); } @@ -5164,6 +5491,50 @@ void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cp SetProgramStats( &stats ); } +char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) +{ // [HGM] book: this routine intercepts moves to simulate book replies + char *bookHit = NULL; + + //first determine if the incoming move brings opponent into his book + if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI)) + bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move + if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)"); + if(bookHit != NULL && !cps->bookSuspend) { + // make sure opponent is not going to reply after receiving move to book position + SendToProgram("force\n", cps); + cps->bookSuspend = TRUE; // flag indicating it has to be restarted + } + if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move + // now arrange restart after book miss + 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]; + if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :( + sprintf(buf, "%s\n", bookHit); // 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 + SendToProgram("go\n", cps); + cps->bookSuspend = FALSE; // after a 'go' we are never suspended + } else { // 'go' might be sent based on 'firstMove' after this routine returns + if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return + SendToProgram("go\n", cps); + cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss + } + return bookHit; // notify caller of hit, so it can take action to send move to opponent +} + +char *savedMessage; +ChessProgramState *savedState; +void DeferredBookMove(void) +{ + if(savedState->lastPing != savedState->lastPong) + ScheduleDelayedEvent(DeferredBookMove, 10); + else + HandleMachineMove(savedMessage, savedState); +} + void HandleMachineMove(message, cps) char *message; @@ -5176,7 +5547,9 @@ HandleMachineMove(message, cps) char promoChar; char *p; int machineWhite; + char *bookHit; +FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit /* * Kludge to ignore BEL characters */ @@ -5283,11 +5656,11 @@ HandleMachineMove(message, cps) fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f, castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]); } - AlphaRank(machineMove, 4); + if(cps->alphaRank) AlphaRank(machineMove, 4); if (!ParseOneMove(machineMove, forwardMostMove, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) { /* Machine move could not be parsed; ignore it. */ - sprintf(buf1, "Illegal move \"%s\" from %s machine", + sprintf(buf1, _("Illegal move \"%s\" from %s machine"), machineMove, cps->which); DisplayError(buf1, 0); sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c", @@ -5351,7 +5724,7 @@ HandleMachineMove(message, cps) first.initDone) { SendMoveToICS(moveType, fromX, fromY, toX, toY); ics_user_moved = 1; - if(appData.autoKibitz) { /* [HGM] kibitz: send most-recent PV info to ICS */ + if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */ char buf[3*MSG_SIZ]; sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n", @@ -5409,12 +5782,12 @@ HandleMachineMove(message, cps) } } -#ifdef ADJUDICATE // [HGM] some adjudications useful with buggy engines - - if( gameMode == TwoMachinesPlay && gameInfo.holdingsSize == 0) { - int count = 0, epFile = epStatus[forwardMostMove]; + if( gameMode == TwoMachinesPlay ) { + // [HGM] some adjudications useful with buggy engines + int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1; + if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { - if(appData.testLegality && appData.checkMates) + if(appData.testLegality) // don't wait for engine to announce game end if we can judge ourselves switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile, @@ -5424,40 +5797,50 @@ HandleMachineMove(message, cps) default: break; case MT_STALEMATE: - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", - GE_XBOARD ); + epStatus[forwardMostMove] = EP_STALEMATE; + if(appData.checkMates) { + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate", + GE_XBOARD ); + } break; case MT_CHECKMATE: - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, - "Xboard adjudication: Checkmate", - GE_XBOARD ); + epStatus[forwardMostMove] = EP_CHECKMATE; + if(appData.checkMates) { + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, + "Xboard adjudication: Checkmate", + GE_XBOARD ); + } break; } - if( appData.testLegality ) - { /* [HGM] Some more adjudications for obstinate engines */ - int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, - NrWQ=0, NrBQ=0, bishopsColor = 0, + if( appData.testLegality ) + { /* [HGM] Some more adjudications for obstinate engines */ + int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0, + NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0, NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k; - static int moveCount; + static int moveCount = 6; /* First absolutely insufficient mating material. Count what is on board. */ - for(i=0; iother); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD ); return; @@ -5499,8 +5891,9 @@ HandleMachineMove(message, cps) if(--bare < 0 && appData.checkMates) { /* but only adjudicate them if adjudication enabled */ + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( NrW > 1 ? WhiteWins : NrPiece - NrW > 1 ? BlackWins : GameIsDrawn, + GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, "Xboard adjudication: Bare king", GE_XBOARD ); return; } @@ -5515,12 +5908,16 @@ HandleMachineMove(message, cps) ) ) { if(--moveCount < 0 && appData.trivialDraws) { /* if the first 3 moves do not show a tactical win, declare draw */ + SendToProgram("force\n", cps->other); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD ); return; } } else moveCount = 6; -#if 0 + } + } +#if 1 if (appData.debugMode) { int i; fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", forwardMostMove, backwardMostMove, epStatus[backwardMostMove], @@ -5579,7 +5976,30 @@ HandleMachineMove(message, cps) if( rights == 0 && ++count > appData.drawRepeats-2 && appData.drawRepeats > 1) { /* adjudicate after user-specified nr of repeats */ + SendToProgram("force\n", cps->other); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + if(gameInfo.variant == VariantXiangqi && appData.testLegality) { + // [HGM] xiangqi: check for forbidden perpetuals + int m, ourPerpetual = 1, hisPerpetual = 1; + for(m=forwardMostMove; m>k; m-=2) { + if(MateTest(boards[m], PosFlags(m), + EP_NONE, castlingRights[m]) != MT_CHECK) + ourPerpetual = 0; // the current mover did not always check + if(MateTest(boards[m-1], PosFlags(m-1), + EP_NONE, castlingRights[m-1]) != MT_CHECK) + hisPerpetual = 0; // the opponent did not always check + } + if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication: perpetual checking", GE_XBOARD ); + return; + } + if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet + break; // (or we would have caught him before). Abort repetition-checking loop. + // if neither of us is checking all the time, or both are, it is draw + // (illegal-chase forfeits not implemented yet!) + } GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); return; } @@ -5601,6 +6021,8 @@ HandleMachineMove(message, cps) epStatus[forwardMostMove] = EP_RULE_DRAW; /* this is used to judge if draw claims are legal */ if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) { + SendToProgram("force\n", cps->other); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD ); return; @@ -5620,25 +6042,27 @@ HandleMachineMove(message, cps) if(epStatus[forwardMostMove] == EP_INSUF_DRAW) p = "Draw claim: insufficient mating material"; if( p != NULL ) { + SendToProgram("force\n", cps->other); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ GameEnds( GameIsDrawn, p, GE_XBOARD ); ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ return; } } - } + if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { + SendToProgram("force\n", cps->other); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - } -#endif - if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - - GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); + GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); - return; + return; + } } + bookHit = NULL; if (gameMode == TwoMachinesPlay) { /* [HGM] relaying draw offers moved to after reception of move */ /* and interpreting offer as claim if it brings draw condition */ @@ -5649,8 +6073,8 @@ HandleMachineMove(message, cps) SendTimeRemaining(cps->other, cps->other->twoMachinesColor[0] == 'w'); } - SendMoveToProgram(forwardMostMove-1, cps->other); - if (firstMove) { + bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE); + if (firstMove && !bookHit) { firstMove = FALSE; if (cps->other->useColors) { SendToProgram(cps->other->twoMachinesColor, cps->other); @@ -5673,6 +6097,29 @@ HandleMachineMove(message, cps) if (gameMode != TwoMachinesPlay) SetUserThinkingEnables(); + // [HGM] book: after book hit opponent has received move and is now in force mode + // force the book reply into it, and then fake that it outputted this move by jumping + // back to the beginning of HandleMachineMove, with cps toggled and message set to this move + if(bookHit) { + static char bookMove[MSG_SIZ]; // a bit generous? + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + message = bookMove; + cps = cps->other; + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + if(cps->lastPing != cps->lastPong) { + savedMessage = message; // args for deferred call + savedState = cps; + ScheduleDelayedEvent(DeferredBookMove, 10); + return; + } + goto FakeBookMove; + } + return; } @@ -5695,7 +6142,7 @@ HandleMachineMove(message, cps) GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD); if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) { - DisplayError("Bad FEN received from engine", 0); + DisplayError(_("Bad FEN received from engine"), 0); return ; } else { Reset(FALSE, FALSE); @@ -5837,7 +6284,7 @@ HandleMachineMove(message, cps) cps->analysisSupport = FALSE; cps->analyzing = FALSE; Reset(FALSE, TRUE); - sprintf(buf2, "%s does not support analysis", cps->tidy); + sprintf(buf2, _("%s does not support analysis"), cps->tidy); DisplayError(buf2, 0); return; } @@ -5883,7 +6330,7 @@ HandleMachineMove(message, cps) DisplayMove(currentMove-1); /* before DisplayMoveError */ SwitchClocks(); DisplayBothClocks(); - sprintf(buf1, "Illegal move \"%s\" (rejected by %s chess program)", + sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"), parseList[currentMove], cps->which); DisplayMoveError(buf1); DrawPosition(FALSE, boards[currentMove]); @@ -5915,7 +6362,7 @@ HandleMachineMove(message, cps) || (StrStr(message, "Permission denied") != NULL)) { cps->maybeThinking = FALSE; - sprintf(buf1, "Failed to start %s chess program %s on %s: %s\n", + sprintf(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); @@ -5933,12 +6380,12 @@ HandleMachineMove(message, cps) (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, buf1); - sprintf(buf2, "Hint: %s", buf1); + sprintf(buf2, _("Hint: %s"), buf1); DisplayInformation(buf2); } else { /* Hint move could not be parsed!? */ sprintf(buf2, - "Illegal hint move \"%s\"\nfrom %s chess program", + _("Illegal hint move \"%s\"\nfrom %s chess program"), buf1, cps->which); DisplayError(buf2, 0); } @@ -6120,10 +6567,10 @@ HandleMachineMove(message, cps) } else if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack) { if (userOfferedDraw) { - DisplayInformation("Machine accepts your draw offer"); + DisplayInformation(_("Machine accepts your draw offer")); GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD); } else { - DisplayInformation("Machine offers a draw\nSelect Action / Draw to agree"); + DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree")); } } } @@ -6132,10 +6579,12 @@ HandleMachineMove(message, cps) /* * Look for thinking output */ - if ( appData.showThinking) { + if ( appData.showThinking // [HGM] thinking: test all options that cause this output + || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp() + ) { int plylev, mvleft, mvtot, curscore, time; char mvname[MOVE_LEN]; - unsigned long nodes; + u64 nodes; // [DM] char plyext; int ignore = FALSE; int prefixHint = FALSE; @@ -6152,6 +6601,9 @@ HandleMachineMove(message, cps) break; case AnalyzeMode: case AnalyzeFile: + break; + case IcsObserving: /* [DM] icsEngineAnalyze */ + if (!appData.icsEngineAnalyze) ignore = TRUE; break; case TwoMachinesPlay: if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) { @@ -6165,7 +6617,7 @@ HandleMachineMove(message, cps) if (!ignore) { buf1[0] = NULLCHAR; - if (sscanf(message, "%d%c %d %d %lu %[^\n]\n", + if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n", &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) { if (plyext != ' ' && plyext != '\t') { @@ -6249,7 +6701,8 @@ HandleMachineMove(message, cps) strcat( thinkOutput, buf1 ); } - if (currentMove == forwardMostMove || gameMode == AnalyzeMode || gameMode == AnalyzeFile) { + if (currentMove == forwardMostMove || gameMode == AnalyzeMode + || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); DisplayAnalysis(); } @@ -6276,12 +6729,13 @@ HandleMachineMove(message, cps) SendProgramStatsToFrontend( cps, &programStats ); - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || + gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); DisplayAnalysis(); } return; - } else if (sscanf(message,"stat01: %d %lu %d %d %d %s", + } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s", &time, &nodes, &plylev, &mvleft, &mvtot, mvname) >= 5) { /* The stat01: line is from Crafty (9.29+) in response @@ -6336,7 +6790,8 @@ HandleMachineMove(message, cps) strcat(programStats.movelist, p); } - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || + gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); DisplayAnalysis(); } @@ -6480,7 +6935,7 @@ ParseGameHistory(game) break; case AmbiguousMove: /* bug? */ - sprintf(buf, "Ambiguous move in ICS output: \"%s\"", yy_text); + sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text); if (appData.debugMode) { fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text); fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); @@ -6490,7 +6945,7 @@ ParseGameHistory(game) return; case ImpossibleMove: /* bug? */ - sprintf(buf, "Illegal move in ICS output: \"%s\"", yy_text); + sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text); if (appData.debugMode) { fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text); fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); @@ -6501,7 +6956,7 @@ ParseGameHistory(game) case (ChessMove) 0: /* end of file */ if (boardIndex < backwardMostMove) { /* Oops, gap. How did that happen? */ - DisplayError("Gap in move list", 0); + DisplayError(_("Gap in move list"), 0); return; } backwardMostMove = blackPlaysFirst ? 1 : 0; @@ -6601,24 +7056,28 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) epStatus[p] = EP_CAPTURE; if( board[fromY][fromX] == WhitePawn ) { - epStatus[p] = EP_PAWN_MOVE; - if( toY-fromY==2) + if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers + epStatus[p] = EP_PAWN_MOVE; + if( toY-fromY==2) { if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn && gameInfo.variant != VariantBerolina || toX < fromX) epStatus[p] = toX | berolina; if(toX fromX) epStatus[p] = toX; + } } else if( board[fromY][fromX] == BlackPawn ) { - epStatus[p] = EP_PAWN_MOVE; - if( toY-fromY== -2) + if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers + epStatus[p] = EP_PAWN_MOVE; + if( toY-fromY== -2) { if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn && gameInfo.variant != VariantBerolina || toX < fromX) epStatus[p] = toX | berolina; if(toX fromX) epStatus[p] = toX; + } } for(i=0; i 0 && gameInfo.variant != VariantBughouse ) { - /* Add to holdings, if holdings exist */ + /* [HGM] holdings: Add to holdings, if holdings exist */ + if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + // [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; + } p = (int) captured; if (p >= (int) BlackPawn) { p -= (int)BlackPawn; @@ -6827,8 +7290,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; } board[p][BOARD_WIDTH-2]++; - board[p][BOARD_WIDTH-1] = - BLACK_TO_WHITE captured; + board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured; } else { p -= (int)WhitePawn; if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { @@ -6838,8 +7300,7 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) p = PieceToNumber((ChessSquare)p); if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; } board[BOARD_HEIGHT-1-p][1]++; - board[BOARD_HEIGHT-1-p][0] = - WHITE_TO_BLACK captured; + board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured; } } @@ -6915,7 +7376,7 @@ MakeMove(fromX, fromY, toX, toY, promoChar) } if (forwardMostMove >= MAX_MOVES) { - DisplayFatalError("Game too long; increase MAX_MOVES and recompile", + DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), 0, 1); return; } @@ -6966,6 +7427,7 @@ ShowMove(fromX, fromY, toX, toY) { int instant = (gameMode == PlayFromGameFile) ? (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing; + if(appData.noGUI) return; if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { if (!instant) { if (forwardMostMove == currentMove + 1) { @@ -6987,6 +7449,38 @@ ShowMove(fromX, fromY, toX, toY) HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); } +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; + + if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return; + + while(*p) { + char c, *q = name+1, *r, *s; + + name[0] = ','; // extract next format name from feature and copy with prefixed ',' + while(*p && *p != ',') *q++ = *p++; + *q++ = ':'; *q = 0; + if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && + strcmp(name, ",nalimov:") == 0 ) { + // take nalimov path from the menu-changeable option first, if it is defined + sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB); + SendToProgram(buf,cps); // send egtbpath command for nalimov + } else + if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats || + (s = StrStr(appData.egtFormats, name)) != NULL) { + // format name occurs amongst user-supplied formats, at beginning or immediately after comma + s = r = StrStr(s, ":") + 1; // beginning of path info + while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string + c = *r; *r = 0; // temporarily null-terminate path info + *--q = 0; // strip of trailig ':' from name + sprintf(buf, "egtbpath %s %s\n", name+1, s); + *r = c; + SendToProgram(buf,cps); // send egtbpath command for this format + } + if(*p == ',') p++; // read away comma to position for next format name + } +} void InitChessProgram(cps, setup) @@ -6997,6 +7491,19 @@ InitChessProgram(cps, setup) if (appData.noChessProgram) return; hintRequested = FALSE; bookRequested = FALSE; + + /* [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 */ + sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB); + SendToProgram(buf, cps); + } + SendEgtPath(cps); /* [HGM] EGT */ + if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */ + sprintf(buf, "cores %d\n", appData.smpCores); + SendToProgram(buf, cps); + } + SendToProgram(cps->initString, cps); if (gameInfo.variant != VariantNormal && gameInfo.variant != VariantLoadable @@ -7006,7 +7513,7 @@ InitChessProgram(cps, setup) char *v = VariantName(gameInfo.variant); if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) { /* [HGM] in protocol 1 we have to assume all variants valid */ - sprintf(buf, "Variant %s not supported by %s", v, cps->tidy); + sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy); DisplayFatalError(buf, 0, 1); return; } @@ -7024,6 +7531,10 @@ InitChessProgram(cps, setup) overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; if( gameInfo.variant == VariantCourier ) overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantSuper ) + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; + if( gameInfo.variant == VariantGreat ) + overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8; if(overruled) { sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, @@ -7066,7 +7577,10 @@ InitChessProgram(cps, setup) timeIncrement, appData.searchDepth, searchTime); } - if (appData.showThinking) { + if (appData.showThinking + // [HGM] thinking: four options require thinking output to be sent + || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp() + ) { SendToProgram("post\n", cps); } SendToProgram("hard\n", cps); @@ -7111,7 +7625,7 @@ StartChessProgram(cps) } if (err != 0) { - sprintf(buf, "Startup failure on '%s'", cps->program); + sprintf(buf, _("Startup failure on '%s'"), cps->program); DisplayFatalError(buf, err, 1); cps->pr = NoProc; cps->isr = NULL; @@ -7121,6 +7635,8 @@ StartChessProgram(cps) cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps); if (cps->protocolVersion > 1) { sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion); + cps->nrOptions = 0; // [HGM] options: clear all engine-specific options + cps->comboCnt = 0; // and values of combo boxes SendToProgram(buf, cps); } else { SendToProgram("xboard\n", cps); @@ -7132,13 +7648,13 @@ void TwoMachinesEventIfReady P((void)) { if (first.lastPing != first.lastPong) { - DisplayMessage("", "Waiting for first chess program"); - ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000); + DisplayMessage("", _("Waiting for first chess program")); + ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000 return; } if (second.lastPing != second.lastPong) { - DisplayMessage("", "Waiting for second chess program"); - ScheduleDelayedEvent(TwoMachinesEventIfReady, 1000); + DisplayMessage("", _("Waiting for second chess program")); + ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000 return; } ThawUI(); @@ -7148,14 +7664,25 @@ TwoMachinesEventIfReady P((void)) void NextMatchGame P((void)) { + int index; /* [HGM] autoinc: step lod 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, - appData.loadGameIndex, + 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; + } LoadPositionFromFile(appData.loadPositionFile, - appData.loadPositionIndex, + index, appData.loadPositionFile); } TwoMachinesEventIfReady(); @@ -7236,57 +7763,72 @@ GameEnds(result, resultDetails, whosays) if (!isIcsGame || whosays == GE_ICS) { /* OK -- not an ICS game, or ICS said it was done */ StopClocks(); - if (appData.debugMode) { - fprintf(debugFP, "GameEnds(%d, %s, %d) clock stopped\n", - result, resultDetails ? resultDetails : "(null)", whosays); - } if (!isIcsGame && !appData.noChessProgram) SetUserThinkingEnables(); /* [HGM] if a machine claims the game end we verify this claim */ - if( appData.testLegality && gameMode == TwoMachinesPlay && - appData.testClaims && whosays >= GE_ENGINE1 ) { + if(gameMode == TwoMachinesPlay && appData.testClaims) { + if(appData.testLegality && whosays >= GE_ENGINE1 ) { char claimer; - if (appData.debugMode) { - fprintf(debugFP, "GameEnds(%d, %s, %d) test claims\n", - result, resultDetails ? resultDetails : "(null)", whosays); - } claimer = whosays == GE_ENGINE1 ? /* color of claimer */ first.twoMachinesColor[0] : second.twoMachinesColor[0] ; - if( gameInfo.holdingsWidth == 0 && + if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && (result == WhiteWins && claimer == 'w' || result == BlackWins && claimer == 'b' ) ) { - /* Xboard immediately adjudicates all mates, so win claims must be false */ - sprintf(buf, "False win claim: '%s'", resultDetails); - result = claimer == 'w' ? BlackWins : WhiteWins; - resultDetails = buf; + if (appData.debugMode) { + fprintf(debugFP, "result=%d sp=%d move=%d\n", + result, epStatus[forwardMostMove], forwardMostMove); + } + /* [HGM] verify: engine mate claims accepted if they were flagged */ + if(epStatus[forwardMostMove] != EP_CHECKMATE && + result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) { + sprintf(buf, "False win claim: '%s'", resultDetails); + result = claimer == 'w' ? BlackWins : WhiteWins; + resultDetails = buf; + } } else if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS && (forwardMostMove <= backwardMostMove || epStatus[forwardMostMove-1] > EP_DRAWS || (claimer=='b')==(forwardMostMove&1)) ) { - /* Draw that was not flagged by Xboard is false */ + /* [HGM] verify: draws that were not flagged are false claims */ sprintf(buf, "False draw claim: '%s'", resultDetails); result = claimer == 'w' ? BlackWins : WhiteWins; resultDetails = buf; } /* (Claiming a loss is accepted no questions asked!) */ + } + /* [HGM] bare: don't allow bare King to win */ + if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) + && result != GameIsDrawn) + { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn); + for(j=BOARD_LEFT; j= 0 && p <= (int)WhiteKing) k++; + } + if (appData.debugMode) { + fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n", + result, resultDetails ? resultDetails : "(null)", whosays, k, color); + } + if(k <= 1) { + result = GameIsDrawn; + sprintf(buf, "%s but bare king", resultDetails); + resultDetails = buf; + } + } } + if(serverMoves != NULL && !loadFlag) { char c = '='; if(result==WhiteWins) c = '+'; if(result==BlackWins) c = '-'; if(resultDetails != NULL) fprintf(serverMoves, ";%c;%s\n", c, resultDetails); } - if (appData.debugMode) { - fprintf(debugFP, "GameEnds(%d, %s, %d) after test\n", - result, resultDetails ? resultDetails : "(null)", whosays); - } - if (resultDetails != NULL) { + if (resultDetails != NULL) { gameInfo.result = result; gameInfo.resultDetails = StrSave(resultDetails); @@ -7470,9 +8012,11 @@ GameEnds(result, resultDetails, whosays) } if (matchGame < appData.matchGames) { char *tmp; - tmp = first.twoMachinesColor; - first.twoMachinesColor = second.twoMachinesColor; - second.twoMachinesColor = tmp; + if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */ + tmp = first.twoMachinesColor; + first.twoMachinesColor = second.twoMachinesColor; + second.twoMachinesColor = tmp; + } gameMode = nextGameMode; matchGame++; if(appData.matchPause>10000 || appData.matchPause<10) @@ -7483,7 +8027,7 @@ GameEnds(result, resultDetails, whosays) } else { char buf[MSG_SIZ]; gameMode = nextGameMode; - sprintf(buf, "Match %s vs. %s: final score %d-%d-%d", + sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"), first.tidy, second.tidy, first.matchWins, second.matchWins, appData.matchGames - (first.matchWins + second.matchWins)); @@ -7552,8 +8096,8 @@ ResurrectChessProgram() timeRemaining[1][currentMove] = blackTimeRemaining; } - if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) && - first.analysisSupport) { + if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile || + appData.icsEngineAnalyze) && first.analysisSupport) { SendToProgram("analyze\n", &first); first.analyzing = TRUE; } @@ -7580,6 +8124,8 @@ Reset(redraw, init) hintRequested = bookRequested = FALSE; first.maybeThinking = FALSE; second.maybeThinking = FALSE; + first.bookSuspend = FALSE; // [HGM] book + second.bookSuspend = FALSE; thinkOutput[0] = NULLCHAR; lastHint[0] = NULLCHAR; ClearGameInfo(&gameInfo); @@ -7589,6 +8135,7 @@ Reset(redraw, init) ics_gamenum = -1; white_holding[0] = black_holding[0] = NULLCHAR; ClearProgramStats(); + opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode ResetFrontEnd(); ClearHighlights(); @@ -7750,6 +8297,8 @@ LoadGameOneMove(readAhead) case BlackPromotionChancellor: case WhitePromotionArchbishop: case BlackPromotionArchbishop: + case WhitePromotionCentaur: + case BlackPromotionCentaur: case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -7892,7 +8441,7 @@ LoadGameOneMove(readAhead) if (appData.testLegality) { if (appData.debugMode) fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text); - sprintf(move, "Illegal move: %d.%s%s", + sprintf(move, _("Illegal move: %d.%s%s"), (forwardMostMove / 2) + 1, WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text); DisplayError(move, 0); @@ -7912,7 +8461,7 @@ LoadGameOneMove(readAhead) case AmbiguousMove: if (appData.debugMode) fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text); - sprintf(move, "Ambiguous move: %d.%s%s", + sprintf(move, _("Ambiguous move: %d.%s%s"), (forwardMostMove / 2) + 1, WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text); DisplayError(move, 0); @@ -7922,8 +8471,8 @@ LoadGameOneMove(readAhead) default: case ImpossibleMove: if (appData.debugMode) - fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text); - sprintf(move, "Illegal move: %d.%s%s", + fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text); + sprintf(move, _("Illegal move: %d.%s%s"), (forwardMostMove / 2) + 1, WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text); DisplayError(move, 0); @@ -7971,7 +8520,7 @@ LoadGameFromFile(filename, n, title, useList) } else { f = fopen(filename, "rb"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + sprintf(buf, _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } @@ -7983,7 +8532,7 @@ LoadGameFromFile(filename, n, title, useList) if (useList && n == 0) { int error = GameListBuild(f); if (error) { - DisplayError("Cannot build game list", error); + DisplayError(_("Cannot build game list"), error); } else if (!ListEmpty(&gameList) && ((ListGame *) gameList.tailPred)->number > 1) { GameListPopUp(f, title); @@ -8072,7 +8621,7 @@ CmailLoadGame(f, gameNumber, title, useList) int retVal; if (gameNumber > nCmailGames) { - DisplayError("No more games in this message", 0); + DisplayError(_("No more games in this message"), 0); return FALSE; } if (f == lastLoadGameFP) { @@ -8113,11 +8662,11 @@ ReloadGame(offset) { int gameNumber = lastLoadGameNumber + offset; if (lastLoadGameFP == NULL) { - DisplayError("No game has been loaded yet", 0); + DisplayError(_("No game has been loaded yet"), 0); return FALSE; } if (gameNumber <= 0) { - DisplayError("Can't back up any further", 0); + DisplayError(_("Can't back up any further"), 0); return FALSE; } if (cmailMsgLoaded) { @@ -8173,7 +8722,7 @@ LoadGame(f, gameNumber, title, useList) gn = 1; } else { - DisplayError("Game number out of range", 0); + DisplayError(_("Game number out of range"), 0); return FALSE; } } else { @@ -8184,7 +8733,7 @@ LoadGame(f, gameNumber, title, useList) gameNumber == 1) { gn = 1; } else { - DisplayError("Can't seek on game file", 0); + DisplayError(_("Can't seek on game file"), 0); return FALSE; } } @@ -8242,7 +8791,7 @@ LoadGame(f, gameNumber, title, useList) nCmailGames = CMAIL_MAX_GAMES - gn; } else { Reset(TRUE, TRUE); - DisplayError("Game not found in file", 0); + DisplayError(_("Game not found in file"), 0); } return FALSE; @@ -8369,17 +8918,10 @@ LoadGame(f, gameNumber, title, useList) startedFromSetupPosition = TRUE; if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) { Reset(TRUE, TRUE); - DisplayError("Bad FEN position in file", 0); + DisplayError(_("Bad FEN position in file"), 0); return FALSE; } CopyBoard(boards[0], initial_position); - /* [HGM] copy FEN attributes as well */ - { int i; - initialRulePlies = FENrulePlies; - epStatus[0] = FENepStatus; - for( i=0; i< nrCastlingRights; i++ ) - initialRights[i] = castlingRights[0][i] = FENcastlingRights[i]; - } if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; CopyBoard(boards[1], initial_position); @@ -8394,6 +8936,13 @@ LoadGame(f, gameNumber, title, useList) } else { currentMove = forwardMostMove = backwardMostMove = 0; } + /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */ + { int i; + initialRulePlies = FENrulePlies; + epStatus[forwardMostMove] = FENepStatus; + for( i=0; i< nrCastlingRights; i++ ) + initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i]; + } yyboardindex = forwardMostMove; free(gameInfo.fen); gameInfo.fen = NULL; @@ -8527,7 +9076,7 @@ LoadGame(f, gameNumber, title, useList) if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) || cm == WhiteWins || cm == BlackWins || cm == GameIsDrawn || cm == GameUnfinished) { - DisplayMessage("", "No moves in game"); + DisplayMessage("", _("No moves in game")); if (cmailMsgLoaded) { if (appData.debugMode) fprintf(debugFP, "Setting flipView to %d.\n", FALSE); @@ -8598,11 +9147,11 @@ ReloadPosition(offset) { int positionNumber = lastLoadPositionNumber + offset; if (lastLoadPositionFP == NULL) { - DisplayError("No position has been loaded yet", 0); + DisplayError(_("No position has been loaded yet"), 0); return FALSE; } if (positionNumber <= 0) { - DisplayError("Can't back up any further", 0); + DisplayError(_("Can't back up any further"), 0); return FALSE; } return LoadPosition(lastLoadPositionFP, positionNumber, @@ -8624,7 +9173,7 @@ LoadPositionFromFile(filename, n, title) } else { f = fopen(filename, "rb"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + sprintf(buf, _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } else { @@ -8665,7 +9214,7 @@ LoadPosition(f, positionNumber, title) if (positionNumber < 0) { /* Negative position number means to seek to that byte offset */ if (fseek(f, -positionNumber, 0) == -1) { - DisplayError("Can't seek on position file", 0); + DisplayError(_("Can't seek on position file"), 0); return FALSE; }; pn = 1; @@ -8676,14 +9225,14 @@ LoadPosition(f, positionNumber, title) positionNumber == 1) { pn = 1; } else { - DisplayError("Can't seek on position file", 0); + DisplayError(_("Can't seek on position file"), 0); return FALSE; } } } /* See if this file is FEN or old-style xboard */ if (fgets(line, MSG_SIZ, f) == NULL) { - DisplayError("Position not found in file", 0); + DisplayError(_("Position not found in file"), 0); return FALSE; } #if 0 @@ -8710,10 +9259,10 @@ LoadPosition(f, positionNumber, title) if (pn >= 2) { if (fenMode || line[0] == '#') pn--; while (pn > 0) { - /* skip postions before number pn */ + /* skip positions before number pn */ if (fgets(line, MSG_SIZ, f) == NULL) { Reset(TRUE, TRUE); - DisplayError("Position not found in file", 0); + DisplayError(_("Position not found in file"), 0); return FALSE; } if (fenMode || line[0] == '#') pn--; @@ -8722,7 +9271,7 @@ LoadPosition(f, positionNumber, title) if (fenMode) { if (!ParseFEN(initial_position, &blackPlaysFirst, line)) { - DisplayError("Bad FEN position in file", 0); + DisplayError(_("Bad FEN position in file"), 0); return FALSE; } } else { @@ -8749,25 +9298,28 @@ LoadPosition(f, positionNumber, title) SendToProgram("force\n", &first); CopyBoard(boards[0], initial_position); - /* [HGM] copy FEN attributes as well */ - { int i; - initialRulePlies = FENrulePlies; - epStatus[0] = FENepStatus; - for( i=0; i< nrCastlingRights; i++ ) - castlingRights[0][i] = FENcastlingRights[i]; - } if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; strcpy(moveList[0], ""); strcpy(parseList[0], ""); CopyBoard(boards[1], initial_position); - DisplayMessage("", "Black to play"); + DisplayMessage("", _("Black to play")); } else { currentMove = forwardMostMove = backwardMostMove = 0; - DisplayMessage("", "White to play"); + DisplayMessage("", _("White to play")); } + /* [HGM] copy FEN attributes as well */ + { int i; + initialRulePlies = FENrulePlies; + epStatus[forwardMostMove] = FENepStatus; + for( i=0; i< nrCastlingRights; i++ ) + castlingRights[forwardMostMove][i] = FENcastlingRights[i]; + } SendBoard(&first, forwardMostMove); if (appData.debugMode) { +int i, j; + for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");} + for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n"); fprintf(debugFP, "Load Position\n"); } @@ -8835,7 +9387,7 @@ SaveGameToFile(filename, append) } else { f = fopen(filename, append ? "a" : "w"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + sprintf(buf, _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } else { @@ -9019,7 +9571,21 @@ SaveGamePGN(f) linelen += numlen; /* Get move */ - movetext = SavePart(parseList[i]); + movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */ + + /* Print move */ + blank = linelen > 0 && movelen > 0; + if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) { + fprintf(f, "\n"); + linelen = 0; + blank = 0; + } + if (blank) { + fprintf(f, " "); + linelen++; + } + fprintf(f, parseList[i]); + linelen += movelen; /* [AS] Add PV info if present */ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) { @@ -9028,49 +9594,53 @@ SaveGamePGN(f) #if 0 if(i >= backwardMostMove) { - /* take the time that changed */ - seconds = timeRemaining[0][i] - timeRemaining[0][i+1]; - if(seconds <= 0) - seconds = timeRemaining[1][i] - timeRemaining[1][i+1]; + if(WhiteOnMove(i)) + seconds = timeRemaining[0][i] - timeRemaining[0][i+1] + + GetTimeQuota(i/2) / WhitePlayer()->timeOdds; + else + seconds = timeRemaining[1][i] - timeRemaining[1][i+1] + + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds; } - seconds /= 1000; + seconds = (seconds+50)/100; // deci-seconds, rounded to nearest +#else + seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time #endif - seconds = pvInfoList[i].time/100; // [HGM] PVtime: use engine time - if (appData.debugMode) { + if (appData.debugMode,0) { fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n", timeRemaining[0][i+1], timeRemaining[0][i], timeRemaining[1][i+1], timeRemaining[1][i], seconds ); } - if( seconds < 0 ) buf[0] = 0; else - if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); - else sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0); + if( seconds <= 0) buf[0] = 0; else + if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else { + seconds = (seconds + 4)/10; // round to full seconds + if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else + sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0); + } - sprintf( move_buffer, "%s {%s%.2f/%d%s}", - movetext, + sprintf( move_buffer, "{%s%.2f/%d%s}", pvInfoList[i].score >= 0 ? "+" : "", pvInfoList[i].score / 100.0, pvInfoList[i].depth, - buf ); - movetext = move_buffer; - } + buf ); - movelen = strlen(movetext); + movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */ - /* Print move */ - blank = linelen > 0 && movelen > 0; - if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) { - fprintf(f, "\n"); - linelen = 0; - blank = 0; - } - if (blank) { - fprintf(f, " "); - linelen++; - } - fprintf(f, movetext); - linelen += movelen; + /* Print score/depth */ + blank = linelen > 0 && movelen > 0; + if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) { + fprintf(f, "\n"); + linelen = 0; + blank = 0; + } + if (blank) { + fprintf(f, " "); + linelen++; + } + fprintf(f, move_buffer); + linelen += movelen; + } i++; } @@ -9188,7 +9758,7 @@ SavePositionToFile(filename) } else { f = fopen(filename, "a"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + sprintf(buf, _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } else { @@ -9318,17 +9888,17 @@ RegisterMove() } if (cmailOldMove == -1) { - DisplayError("You have edited the game history.\nUse Reload Same Game and make your move again.", 0); + DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0); return FALSE; } if (currentMove > cmailOldMove + 1) { - DisplayError("You have entered too many moves.\nBack up to the correct position and try again.", 0); + DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0); return FALSE; } if (currentMove < cmailOldMove) { - DisplayError("Displayed position is not current.\nStep forward to the correct position and try again.", 0); + DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0); return FALSE; } @@ -9376,7 +9946,7 @@ RegisterMove() cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE; nCmailMovesRegistered ++; } else if (nCmailGames == 1) { - DisplayError("You have not made a move yet", 0); + DisplayError(_("You have not made a move yet"), 0); return FALSE; } @@ -9397,18 +9967,18 @@ MailMoveEvent() char *arcDir; if (! cmailMsgLoaded) { - DisplayError("The cmail message is not loaded.\nUse Reload CMail Message and make your move again.", 0); + DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0); return; } if (nCmailGames == nCmailResults) { - DisplayError("No unfinished games", 0); + DisplayError(_("No unfinished games"), 0); return; } #if CMAIL_PROHIBIT_REMAIL if (cmailMailedMove) { - sprintf(msg, "You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line.", appData.cmailGameName); + sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName); DisplayError(msg, 0); return; } @@ -9420,10 +9990,10 @@ MailMoveEvent() || (nCmailMovesRegistered + nCmailResults == nCmailGames)) { sprintf(string, partCommandString, appData.debugMode ? " -v" : "", appData.cmailGameName); - commandOutput = popen(string, "rb"); + commandOutput = popen(string, "r"); if (commandOutput == NULL) { - DisplayError("Failed to invoke cmail", 0); + DisplayError(_("Failed to invoke cmail"), 0); } else { for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) { nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput); @@ -9485,7 +10055,7 @@ CmailMsg() if (!cmailMsgLoaded) return ""; if (cmailMailedMove) { - sprintf(cmailMsg, "Waiting for reply from opponent\n"); + sprintf(cmailMsg, _("Waiting for reply from opponent\n")); } else { /* Create a list of games left */ sprintf(string, "["); @@ -9508,17 +10078,17 @@ CmailMsg() switch (nCmailGames) { case 1: sprintf(cmailMsg, - "Still need to make move for game\n"); + _("Still need to make move for game\n")); break; case 2: sprintf(cmailMsg, - "Still need to make moves for both games\n"); + _("Still need to make moves for both games\n")); break; default: sprintf(cmailMsg, - "Still need to make moves for all %d games\n", + _("Still need to make moves for all %d games\n"), nCmailGames); break; } @@ -9526,21 +10096,21 @@ CmailMsg() switch (nCmailGames - nCmailMovesRegistered - nCmailResults) { case 1: sprintf(cmailMsg, - "Still need to make a move for game %s\n", + _("Still need to make a move for game %s\n"), string); break; case 0: if (nCmailResults == nCmailGames) { - sprintf(cmailMsg, "No unfinished games\n"); + sprintf(cmailMsg, _("No unfinished games\n")); } else { - sprintf(cmailMsg, "Ready to send mail\n"); + sprintf(cmailMsg, _("Ready to send mail\n")); } break; default: sprintf(cmailMsg, - "Still need to make moves for games %s\n", + _("Still need to make moves for games %s\n"), string); } } @@ -9601,7 +10171,7 @@ ExitEvent(status) GameEnds((ChessMove) 0, NULL, GE_PLAYER); #else /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */ - GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "aborted" : gameInfo.resultDetails, GE_PLAYER); + GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER); #endif /* [HGM] crash: the above GameEnds() is a dud if another one was running */ /* make sure this other one finishes before killing it! */ @@ -9708,9 +10278,9 @@ EditCommentEvent() char title[MSG_SIZ]; if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) { - strcpy(title, "Edit comment"); + strcpy(title, _("Edit comment")); } else { - sprintf(title, "Edit comment on %d.%s%s", (currentMove - 1) / 2 + 1, + sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1, WhiteOnMove(currentMove - 1) ? " " : ".. ", parseList[currentMove - 1]); } @@ -9734,17 +10304,19 @@ AnalyzeModeEvent() return; if (gameMode != AnalyzeFile) { - EditGameEvent(); - if (gameMode != EditGame) return; + if (!appData.icsEngineAnalyze) { + EditGameEvent(); + if (gameMode != EditGame) return; + } ResurrectChessProgram(); SendToProgram("analyze\n", &first); first.analyzing = TRUE; /*first.maybeThinking = TRUE;*/ first.maybeThinking = FALSE; /* avoid killing GNU Chess */ - AnalysisPopUp("Analysis", - "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."); + AnalysisPopUp(_("Analysis"), + _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.")); } - gameMode = AnalyzeMode; + if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode; pausing = FALSE; ModeHighlight(); SetGameInfo(); @@ -9768,8 +10340,8 @@ AnalyzeFileEvent() first.analyzing = TRUE; /*first.maybeThinking = TRUE;*/ first.maybeThinking = FALSE; /* avoid killing GNU Chess */ - AnalysisPopUp("Analysis", - "Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."); + AnalysisPopUp(_("Analysis"), + _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis.")); } gameMode = AnalyzeFile; pausing = FALSE; @@ -9785,6 +10357,7 @@ void MachineWhiteEvent() { char buf[MSG_SIZ]; + char *bookHit = NULL; if (appData.noChessProgram || (gameMode == MachinePlaysWhite)) return; @@ -9801,7 +10374,7 @@ MachineWhiteEvent() EditPositionDone(); if (!WhiteOnMove(currentMove)) { - DisplayError("It is not White's turn", 0); + DisplayError(_("It is not White's turn"), 0); return; } @@ -9813,6 +10386,10 @@ MachineWhiteEvent() TruncateGame(); ResurrectChessProgram(); /* in case it isn't running */ + if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */ + gameMode = MachinePlaysWhite; + ResetClocks(); + } else gameMode = MachinePlaysWhite; pausing = FALSE; ModeHighlight(); @@ -9830,10 +10407,9 @@ MachineWhiteEvent() SendTimeRemaining(&first, TRUE); } if (first.useColors) { - SendToProgram("white\ngo\n", &first); - } else { - SendToProgram("go\n", &first); + SendToProgram("white\n", &first); // [HGM] book: send 'go' separately } + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move SetMachineThinkingEnables(); first.maybeThinking = TRUE; StartClocks(); @@ -9841,6 +10417,19 @@ MachineWhiteEvent() if (appData.autoFlipView && !flipView) { flipView = !flipView; DrawPosition(FALSE, NULL); + DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged; + } + + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); } } @@ -9848,6 +10437,7 @@ void MachineBlackEvent() { char buf[MSG_SIZ]; + char *bookHit = NULL; if (appData.noChessProgram || (gameMode == MachinePlaysBlack)) return; @@ -9864,7 +10454,7 @@ MachineBlackEvent() EditPositionDone(); if (WhiteOnMove(currentMove)) { - DisplayError("It is not Black's turn", 0); + DisplayError(_("It is not Black's turn"), 0); return; } @@ -9893,10 +10483,9 @@ MachineBlackEvent() SendTimeRemaining(&first, FALSE); } if (first.useColors) { - SendToProgram("black\ngo\n", &first); - } else { - SendToProgram("go\n", &first); + SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately } + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move SetMachineThinkingEnables(); first.maybeThinking = TRUE; StartClocks(); @@ -9904,6 +10493,18 @@ MachineBlackEvent() if (appData.autoFlipView && flipView) { flipView = !flipView; DrawPosition(FALSE, NULL); + DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged; + } + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); } } @@ -9936,6 +10537,7 @@ TwoMachinesEvent P((void)) int i; char buf[MSG_SIZ]; ChessProgramState *onmove; + char *bookHit = NULL; if (appData.noChessProgram) return; @@ -9945,7 +10547,7 @@ TwoMachinesEvent P((void)) case MachinePlaysWhite: case MachinePlaysBlack: if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) { - DisplayError("Wait until your turn,\nor select Move Now", 0); + DisplayError(_("Wait until your turn,\nor select Move Now"), 0); return; } /* fall through */ @@ -9977,7 +10579,7 @@ TwoMachinesEvent P((void)) } else { /* kludge: allow timeout for initial "feature" command */ FreezeUI(); - DisplayMessage("", "Starting second chess program"); + DisplayMessage("", _("Starting second chess program")); ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT); } return; @@ -10018,8 +10620,8 @@ TwoMachinesEvent P((void)) SendToProgram(buf, &second); } + ResetClocks(); if (!first.sendTime || !second.sendTime) { - ResetClocks(); timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; } @@ -10032,11 +10634,24 @@ TwoMachinesEvent P((void)) if (onmove->useColors) { SendToProgram(onmove->twoMachinesColor, onmove); } - SendToProgram("go\n", onmove); + bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move +// SendToProgram("go\n", onmove); onmove->maybeThinking = TRUE; SetMachineThinkingEnables(); StartClocks(); + + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.depth = programStats.nodes = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); + } } void @@ -10045,7 +10660,7 @@ TrainingEvent() if (gameMode == Training) { SetTrainingModeOff(); gameMode = PlayFromGameFile; - DisplayMessage("", "Training mode off"); + DisplayMessage("", _("Training mode off")); } else { gameMode = Training; animateTraining = appData.animate; @@ -10053,10 +10668,10 @@ TrainingEvent() /* make sure we are not already at the end of the game */ if (currentMove < forwardMostMove) { SetTrainingModeOn(); - DisplayMessage("", "Training mode on"); + DisplayMessage("", _("Training mode on")); } else { gameMode = PlayFromGameFile; - DisplayError("Already at end of game", 0); + DisplayError(_("Already at end of game"), 0); } } ModeHighlight(); @@ -10137,13 +10752,13 @@ EditGameEvent() break; case IcsPlayingBlack: case IcsPlayingWhite: - DisplayError("Warning: You are still playing a game", 0); + DisplayError(_("Warning: You are still playing a game"), 0); break; case IcsObserving: - DisplayError("Warning: You are still observing a game", 0); + DisplayError(_("Warning: You are still observing a game"), 0); break; case IcsExamining: - DisplayError("Warning: You are still examining a game", 0); + DisplayError(_("Warning: You are still examining a game"), 0); break; case IcsIdle: break; @@ -10213,6 +10828,12 @@ EditPositionEvent() void ExitAnalyzeMode() { + /* [DM] icsEngineAnalyze - possible call from other functions */ + if (appData.icsEngineAnalyze) { + appData.icsEngineAnalyze = FALSE; + + DisplayMessage("",_("Close ICS engine analyze...")); + } if (first.analysisSupport && first.analyzing) { SendToProgram("exit\n", &first); first.analyzing = FALSE; @@ -10232,6 +10853,11 @@ EditPositionDone() strcpy(parseList[0], ""); currentMove = forwardMostMove = backwardMostMove = 1; CopyBoard(boards[1], boards[0]); + /* [HGM] copy rights as well, as this code is also used after pasting a FEN */ + { int i; + epStatus[1] = epStatus[0]; + for(i=0; i 0 ) { int score = 0; int depth = 0; - int time = -1, sec = 0; + int time = -1, sec = 0, deci; char * s_eval = FindStr( text, "[%eval " ); char * s_emt = FindStr( text, "[%emt " ); @@ -11362,8 +11989,9 @@ char *GetInfoFromComment( int index, char * text ) return text; } - time = -1; sec = -1; + time = -1; sec = -1; deci = -1; if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 && + sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 && sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 && sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { return text; @@ -11373,7 +12001,9 @@ char *GetInfoFromComment( int index, char * text ) return text; } - if(sec >= 0) time = 60*time + sec; + if(sec >= 0) time = 600*time + 10*sec; else + if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec + score = score >= 0 ? score*100 + score_lo : score*100 - score_lo; /* [HGM] PV time: now locate end of PV info */ @@ -11382,6 +12012,8 @@ char *GetInfoFromComment( int index, char * text ) while( *++sep >= '0' && *sep <= '9'); // strip time if(sec >= 0) while( *++sep >= '0' && *sep <= '9'); // strip seconds + if(deci >= 0) + while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds while(*sep == ' ') sep++; } @@ -11395,7 +12027,7 @@ char *GetInfoFromComment( int index, char * text ) pvInfoList[index-1].depth = depth; pvInfoList[index-1].score = score; - pvInfoList[index-1].time = time; + pvInfoList[index-1].time = 10*time; // centi-sec } return sep; } @@ -11423,7 +12055,7 @@ 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 */ - sprintf(buf, "Error writing to %s chess program", cps->which); + sprintf(buf, _("Error writing to %s chess program"), cps->which); if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */ if(epStatus[forwardMostMove] <= EP_DRAWS) { gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ @@ -11453,12 +12085,12 @@ ReceiveFromProgram(isr, closure, message, count, error) if (count <= 0) { if (count == 0) { sprintf(buf, - "Error: %s chess program (%s) exited unexpectedly", + _("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(epStatus[forwardMostMove] <= EP_DRAWS) { gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */ - sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program); + sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program); } else { gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; } @@ -11468,7 +12100,7 @@ ReceiveFromProgram(isr, closure, message, count, error) DisplayFatalError(buf, 0, 1); } else { sprintf(buf, - "Error reading from %s chess program (%s)", + _("Error reading from %s chess program (%s)"), cps->which, cps->program); RemoveInputSource(cps->isr); @@ -11500,8 +12132,7 @@ ReceiveFromProgram(isr, closure, message, count, error) sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 && sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 && sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 && - sscanf(message, "askuser%c", &c)!=1 && sscanf(message, "1-0 %c", &c)!=1 && - sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#') + sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#') { quote = "# "; print = (appData.engineComments == 2); } message[0] = start; // restore original message } @@ -11513,6 +12144,14 @@ ReceiveFromProgram(isr, closure, message, count, error) message); } } + + /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */ + if (appData.icsEngineAnalyze) { + if (strstr(message, "whisper") != NULL || + strstr(message, "kibitz") != NULL || + strstr(message, "tellics") != NULL) return; + } + HandleMachineMove(message, cps); } @@ -11587,7 +12226,8 @@ SendTimeControl(cps, mps, tc, inc, sd, st) ChessProgramState *WhitePlayer() /* [HGM] return pointer to 'first' or 'second', depending on who plays white */ { - if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || + gameMode == BeginningOfGame || gameMode == MachinePlaysBlack) return &second; return &first; } @@ -11691,6 +12331,55 @@ StringFeature(p, name, loc, cps) return FALSE; } +int +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 +{ + char *p, *q, buf[MSG_SIZ]; + int n, min = (-1)<<31, max = 1<<31, def; + + if(p = strstr(opt->name, " -spin ")) { + if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE; + if(max < min) max = min; // enforce consistency + if(def < min) def = min; + if(def > max) def = max; + opt->value = def; + opt->min = min; + opt->max = max; + opt->type = Spin; + } else if(p = strstr(opt->name, " -string ")) { + opt->textValue = p+9; + opt->type = TextBox; + } else if(p = strstr(opt->name, " -check ")) { + if(sscanf(p, " -check %d", &def) < 1) return FALSE; + opt->value = (def != 0); + opt->type = CheckBox; + } else if(p = strstr(opt->name, " -combo ")) { + opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type + cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices + opt->value = n = 0; + while(q = StrStr(q, " /// ")) { + n++; *q = 0; // count choices, and null-terminate each of them + q += 5; + if(*q == '*') { // remember default, which is marked with * prefix + q++; + opt->value = n; + } + cps->comboList[cps->comboCnt++] = q; + } + cps->comboList[cps->comboCnt++] = NULL; + opt->max = n + 1; + opt->type = ComboBox; + } else if(p = strstr(opt->name, " -button")) { + opt->type = Button; + } else if(p = strstr(opt->name, " -save")) { + opt->type = SaveButton; + } else return FALSE; + *p = 0; // terminate option name + return TRUE; +} + void FeatureDone(cps, val) ChessProgramState* cps; @@ -11748,9 +12437,6 @@ ParseFeatures(args, cps) if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue; if (BoolFeature(&p, "name", &cps->sendName, cps)) continue; if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */ - if (BoolFeature(&p, "debug", &cps->debug, cps)) continue; - if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue; - if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue; if (IntFeature(&p, "done", &val, cps)) { FeatureDone(cps, val); continue; @@ -11760,6 +12446,25 @@ ParseFeatures(args, cps) if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue; /* End of additions by Tord */ + /* [HGM] added features: */ + if (BoolFeature(&p, "debug", &cps->debug, cps)) continue; + if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue; + if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue; + if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue; + if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; + if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue; + if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) { + ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature + if(cps->nrOptions >= MAX_OPTIONS) { + cps->nrOptions--; + sprintf(buf, "%s engine has too many options\n", cps->which); + DisplayError(buf, 0); + } + continue; + } + if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue; + /* End of additions by HGM */ + /* unknown feature: complain and skip */ q = p; while (*q && *q != '=') q++; @@ -11821,12 +12526,33 @@ PonderNextMoveEvent(newState) } void -ShowThinkingEvent(newState) - int newState; +NewSettingEvent(option, command, value) + char *command; + int option, value; { - if (newState == appData.showThinking) return; + char buf[MSG_SIZ]; + if (gameMode == EditPosition) EditPositionDone(); - if (newState) { + sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value); + SendToProgram(buf, &first); + if (gameMode == TwoMachinesPlay) { + SendToProgram(buf, &second); + } +} + +void +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 + int newState = appData.showThinking + // [HGM] thinking: other features now need thinking output as well + || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp(); + + if (oldState == newState) return; + oldState = newState; + if (gameMode == EditPosition) EditPositionDone(); + if (oldState) { SendToProgram("post\n", &first); if (gameMode == TwoMachinesPlay) { SendToProgram("post\n", &second); @@ -11838,7 +12564,7 @@ ShowThinkingEvent(newState) SendToProgram("nopost\n", &second); } } - appData.showThinking = newState; +// appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine! } void @@ -11912,7 +12638,8 @@ DisplayAnalysisText(text) { char buf[MSG_SIZ]; - if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) { + if (gameMode == AnalyzeMode || gameMode == AnalyzeFile + || appData.icsEngineAnalyze) { sprintf(buf, "Analysis (%s)", first.tidy); AnalysisPopUp(buf, text); } @@ -11948,8 +12675,8 @@ DisplayAnalysis() } else { safeStrCpy( lst, programStats.movelist, sizeof(lst)); - nps = (((double)programStats.nodes) / - (((double)programStats.time)/100.0)); + nps = (u64ToDouble(programStats.nodes) / + ((double)programStats.time /100.0)); cs = programStats.time % 100; s = programStats.time / 100; @@ -11960,32 +12687,32 @@ DisplayAnalysis() if (programStats.moves_left > 0 && appData.periodicUpdates) { if (programStats.move_name[0] != NULLCHAR) { - sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d", + sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", programStats.depth, programStats.nr_moves-programStats.moves_left, programStats.nr_moves, programStats.move_name, ((float)programStats.score)/100.0, lst, only_one_move(lst)? xtra[programStats.got_fail] : "", - programStats.nodes, (int)nps, h, m, s, cs); + (u64)programStats.nodes, (int)nps, h, m, s, cs); } else { - sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d", + sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", programStats.depth, programStats.nr_moves-programStats.moves_left, programStats.nr_moves, ((float)programStats.score)/100.0, lst, only_one_move(lst)? xtra[programStats.got_fail] : "", - programStats.nodes, (int)nps, h, m, s, cs); + (u64)programStats.nodes, (int)nps, h, m, s, cs); } } else { - sprintf(buf, "depth=%d %+.2f %s%s\nNodes: %lu NPS: %d\nTime: %02d:%02d:%02d.%02d", + sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d", programStats.depth, ((float)programStats.score)/100.0, lst, only_one_move(lst)? xtra[programStats.got_fail] : "", - programStats.nodes, (int)nps, h, m, s, cs); + (u64)programStats.nodes, (int)nps, h, m, s, cs); } } DisplayAnalysisText(buf); @@ -12015,7 +12742,7 @@ DisplayComment(moveNumber, text) if(text == NULL) text = ""; score = pvInfoList[moveNumber].score; sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., - depth, pvInfoList[moveNumber].time, text); + depth, (pvInfoList[moveNumber].time+50)/100, text); CommentPopUp(title, buf); } else if (text != NULL) @@ -12070,9 +12797,9 @@ CheckFlags() } } else { if (blackFlag) { - if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell"); + if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell")); } else { - if(gameMode != TwoMachinesPlay) DisplayTitle("White's flag fell"); + if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell")); if (appData.autoCallFlag) { GameEnds(BlackWins, "Black wins on time", GE_XBOARD); return TRUE; @@ -12092,9 +12819,9 @@ CheckFlags() } } else { if (whiteFlag) { - if(gameMode != TwoMachinesPlay) DisplayTitle("Both flags fell"); + if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell")); } else { - if(gameMode != TwoMachinesPlay) DisplayTitle("Black's flag fell"); + if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell")); if (appData.autoCallFlag) { GameEnds(WhiteWins, "White wins on time", GE_XBOARD); return TRUE; @@ -12113,7 +12840,7 @@ CheckTimeControl() gameMode == PlayFromGameFile || forwardMostMove == 0) return; /* - * add time to clocks when time control is achieved ([HGM] now also used fot increment) + * add time to clocks when time control is achieved ([HGM] now also used for increment) */ if ( !WhiteOnMove(forwardMostMove) ) /* White made time control */ @@ -12326,14 +13053,14 @@ SwitchClocks() if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ - if(pvInfoList[forwardMostMove-1].time == -1) - pvInfoList[forwardMostMove-1].time = +// if(pvInfoList[forwardMostMove-1].time == -1) + pvInfoList[forwardMostMove-1].time = // use GUI time (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10; } else { if(whiteNPS >= 0) lastTickLength = 0; whiteTimeRemaining -= lastTickLength; /* [HGM] PGNtime: save time for PGN file if engine did not give it */ - if(pvInfoList[forwardMostMove-1].time == -1) +// if(pvInfoList[forwardMostMove-1].time == -1) pvInfoList[forwardMostMove-1].time = (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10; } @@ -12817,12 +13544,14 @@ ParseFEN(board, blackPlaysFirst, fen) *p++; if((int) piece >= (int) BlackPawn ) { i = (int)piece - (int)BlackPawn; - if( i >= BOARD_HEIGHT ) return FALSE; + i = PieceToNumber((ChessSquare)i); + if( i >= gameInfo.holdingsSize ) return FALSE; board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */ board[BOARD_HEIGHT-1-i][1]++; /* black counts */ } else { i = (int)piece - (int)WhitePawn; - if( i >= BOARD_HEIGHT ) return FALSE; + i = PieceToNumber((ChessSquare)i); + if( i >= gameInfo.holdingsSize ) return FALSE; board[i][BOARD_WIDTH-1] = piece; /* white holdings */ board[i][BOARD_WIDTH-2]++; /* black holdings */ } @@ -12969,7 +13698,7 @@ EditPositionPasteFEN(char *fen) Board initial_position; if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) { - DisplayError("Bad FEN position in clipboard", 0); + DisplayError(_("Bad FEN position in clipboard"), 0); return ; } else { int savedBlackPlaysFirst = blackPlaysFirst;