X-Git-Url: http://winboard.nl/cgi-bin?p=xboard.git;a=blobdiff_plain;f=backend.c;h=304acc21cc5ea023eec943d5dcc5a81fe93856b5;hp=5fbe8f01e662d5a98369cbc4e398837bcd7cd383;hb=d016fb202fe45795e630e22ba516e754cf694ea6;hpb=29df666d997ad990de5376c962b154b26eb73f2f diff --git a/backend.c b/backend.c index 5fbe8f0..304acc2 100644 --- a/backend.c +++ b/backend.c @@ -1,9 +1,13 @@ /* * backend.c -- Common back end for X and Windows NT versions of - * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $ * - * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts. - * Enhancements Copyright 1992-2001 Free Software Foundation, Inc. + * Copyright 1991 by Digital Equipment Corporation, Maynard, + * Massachusetts. + * + * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006, + * 2007, 2008, 2009 Free Software Foundation, Inc. + * + * Enhancements Copyright 2005 Alessandro Scotti * * The following terms apply to Digital Equipment Corporation's copyright * interest in XBoard: @@ -27,25 +31,25 @@ * SOFTWARE. * ------------------------------------------------------------------------ * - * The following terms apply to the enhanced version of XBoard distributed - * by the Free Software Foundation: + * The following terms apply to the enhanced version of XBoard + * distributed by the Free Software Foundation: * ------------------------------------------------------------------------ - * This program is free software; you can redistribute it and/or modify + * + * GNU XBoard is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * GNU XBoard is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * ------------------------------------------------------------------------ + * along with this program. If not, see http://www.gnu.org/licenses/. * * - * See the file ChangeLog for a revision history. */ + *------------------------------------------------------------------------ + ** See the file ChangeLog for a revision history. */ /* [AS] Also useful here for debugging */ #ifdef WIN32 @@ -55,7 +59,7 @@ #else -#define DoSleep( n ) +#define DoSleep( n ) if( (n) >= 0) sleep(n) #endif @@ -68,6 +72,7 @@ #include #include #include +#include #if STDC_HEADERS # include @@ -120,6 +125,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 { @@ -127,23 +142,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)); @@ -153,7 +151,6 @@ void SendToICS P((char *s)); void SendToICSDelayed P((char *s, long msdelay)); void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY)); -void InitPosition P((int redraw)); void HandleMachineMove P((char *message, ChessProgramState *cps)); int AutoPlayOneMove P((void)); int LoadGameOneMove P((ChessMove readAhead)); @@ -161,10 +158,10 @@ int LoadGameFromFile P((char *filename, int n, char *title, int useList)); int LoadPositionFromFile P((char *filename, int n, char *title)); int SavePositionToFile P((char *filename)); void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar, - Board board)); + Board board, char *castle, char *ep)); 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)); @@ -214,12 +211,40 @@ int string_to_rating P((char *str)); void ParseFeatures P((char* args, ChessProgramState *cps)); void InitBackEnd3 P((void)); void FeatureDone P((ChessProgramState* cps, int val)); -void InitChessProgram P((ChessProgramState *cps)); +void InitChessProgram P((ChessProgramState *cps, int setup)); +void OutputKibitz(int window, char *text); +int PerpetualChase(int first, int last); +int EngineOutputIsUp(); +void InitDrawingSizes(int x, int y); -void GetInfoFromComment( int, char * ); +#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*/; +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; +int lastSavedGame; /* [HGM] save: ID of game */ +char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */ +extern int chatCount; +int chattingPartner; /* States for ics_getting_history */ #define H_FALSE 0 @@ -235,6 +260,8 @@ static ChessProgramStats programStats; #define GE_PLAYER 2 #define GE_FILE 3 #define GE_XBOARD 4 +#define GE_ENGINE1 5 +#define GE_ENGINE2 6 /* Maximum number of games in a cmail message */ #define CMAIL_MAX_GAMES 20 @@ -272,6 +299,8 @@ static char * safeStrCpy( char * dst, const char * src, size_t count ) return dst; } +#if 0 +//[HGM] for future use? Conditioned out for now to suppress warning. static char * safeStrCat( char * dst, const char * src, size_t count ) { size_t dst_len; @@ -288,9 +317,35 @@ static char * safeStrCat( char * dst, const char * src, size_t count ) return dst; } +#endif + +/* 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 */ + availability yet. [HGM] Change of logic: the flag now only + indicates the type of castlings allowed by the rule of the game. + The actual rights themselves are maintained in the array + castlingRights, as part of the game history, and are not probed + by this function. + */ int PosFlags(index) { @@ -298,9 +353,11 @@ 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; + case VariantLosers: + flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist break; case VariantAtomic: flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE; @@ -308,7 +365,12 @@ PosFlags(index) case VariantKriegspiel: flags |= F_KRIEGSPIEL_CAPTURE; break; + case VariantCapaRandom: + case VariantFischeRandom: + flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */ case VariantNoCastle: + case VariantShatranj: + case VariantCourier: flags &= ~F_ALL_CASTLE_OK; break; default: @@ -319,7 +381,7 @@ PosFlags(index) FILE *gameFileFP, *debugFP; -/* +/* [AS] Note: sometimes, the sscanf() function is used to parse the input into a fixed-size buffer. Because of this, we must be prepared to receive strings as long as the size of the input buffer, which is currently @@ -344,12 +406,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; @@ -381,6 +439,7 @@ int have_sent_ICS_logon = 0; int movesPerSession; long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement; long timeControl_2; /* [AS] Allow separate time controls */ +char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */ long timeRemaining[2][MAX_MOVES]; int matchGame = 0; TimeMark programStartTime; @@ -399,45 +458,137 @@ GameInfo gameInfo; AppData appData; Board boards[MAX_MOVES]; -Board initialPosition = { +/* [HGM] Following 7 needed for accurate legality tests: */ +signed char epStatus[MAX_MOVES]; +signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1 +signed char castlingRank[BOARD_SIZE]; // and corresponding ranks +signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE]; +int nrCastlingRights; // For TwoKings, or to implement castling-unknown status +int initialRulePlies, FENrulePlies; +char FENepStatus; +FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option) +int loadFlag = 0; +int shuffleOpenings; +int mute; // mute all sounds + +ChessSquare FIDEArray[2][BOARD_SIZE] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, - { WhitePawn, WhitePawn, WhitePawn, WhitePawn, - WhitePawn, WhitePawn, WhitePawn, WhitePawn }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { BlackPawn, BlackPawn, BlackPawn, BlackPawn, - BlackPawn, BlackPawn, BlackPawn, BlackPawn }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackKing, BlackBishop, BlackKnight, BlackRook } }; -Board twoKingsPosition = { + +ChessSquare twoKingsArray[2][BOARD_SIZE] = { { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing, WhiteKing, WhiteKnight, WhiteRook }, - { WhitePawn, WhitePawn, WhitePawn, WhitePawn, - WhitePawn, WhitePawn, WhitePawn, WhitePawn }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { EmptySquare, EmptySquare, EmptySquare, EmptySquare, - EmptySquare, EmptySquare, EmptySquare, EmptySquare }, - { BlackPawn, BlackPawn, BlackPawn, BlackPawn, - BlackPawn, BlackPawn, BlackPawn, BlackPawn }, { BlackRook, BlackKnight, BlackBishop, BlackQueen, - BlackKing, BlackKing, BlackKnight, BlackRook } + BlackKing, BlackKing, BlackKnight, BlackRook } +}; + +ChessSquare KnightmateArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen, + WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook }, + { BlackRook, BlackMan, BlackBishop, BlackQueen, + BlackUnicorn, BlackBishop, BlackMan, BlackRook } +}; + +ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */ + { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen, + WhiteKing, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen, + BlackKing, BlackBishop, BlackKnight, BlackRook } +}; + +ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */ + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing, + WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackKing, + BlackFerz, BlackAlfil, BlackKnight, BlackRook } +}; + + +#if (BOARD_SIZE>=10) +ChessSquare ShogiArray[2][BOARD_SIZE] = { + { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir, + WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen }, + { BlackQueen, BlackKnight, BlackFerz, BlackWazir, + BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen } +}; + +ChessSquare XiangqiArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz, + WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackFerz, + BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook } +}; + +ChessSquare CapablancaArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, + WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, + 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 }, + { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, + BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook } }; +#ifdef GOTHIC +ChessSquare GothicArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, + WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, + BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook } +}; +#else // !GOTHIC +#define GothicArray CapablancaArray +#endif // !GOTHIC + +#ifdef FALCON +ChessSquare FalconArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, + WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, + BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook } +}; +#else // !FALCON +#define FalconArray CapablancaArray +#endif // !FALCON + +#else // !(BOARD_SIZE>=10) +#define XiangqiPosition FIDEArray +#define CapablancaArray FIDEArray +#define GothicArray FIDEArray +#define GreatArray FIDEArray +#endif // !(BOARD_SIZE>=10) + +#if (BOARD_SIZE>=12) +ChessSquare CourierArray[2][BOARD_SIZE] = { + { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing, + WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook }, + { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing, + BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook } +}; +#else // !(BOARD_SIZE>=12) +#define CourierArray CapablancaArray +#endif // !(BOARD_SIZE>=12) + + +Board initialPosition; + /* Convert str to a rating. Checks for special cases of "----", + "++++", etc. Also strips ()'s */ int string_to_rating(str) @@ -459,7 +610,7 @@ ClearProgramStats() programStats.nr_moves = 0; programStats.moves_left = 0; programStats.nodes = 0; - programStats.time = 100; + programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output programStats.score = 0; programStats.got_only_move = 0; programStats.got_fail = 0; @@ -471,7 +622,10 @@ InitBackEnd1() { int matched, min, sec; + ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options + GetTimeMark(&programStartTime); + srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level ClearProgramStats(); programStats.ok_to_send = 1; @@ -504,12 +658,14 @@ InitBackEnd1() appData.zippyTalk = appData.zippyPlay = FALSE; } - /* [AS] Initialize pv info list */ + /* [AS] Initialize pv info list [HGM] and game state */ { - int i; + int i, j; for( i=0; i second.timeOdds) norm = second.timeOdds; + } + first.timeOdds /= norm; + second.timeOdds /= norm; + } + + /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/ + first.accumulateTC = appData.firstAccumulateTC; + second.accumulateTC = appData.secondAccumulateTC; + first.maxNrOfSessions = second.maxNrOfSessions = 1; + + /* [HGM] debug */ + first.debug = second.debug = FALSE; + first.supportsNPS = second.supportsNPS = UNKNOWN; + + /* [HGM] options */ + first.optionSettings = appData.firstOptions; + second.optionSettings = appData.secondOptions; first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */ second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */ @@ -608,7 +793,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 { @@ -618,7 +803,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 { @@ -643,19 +828,23 @@ InitBackEnd1() #endif if (appData.noChessProgram) { - programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION) - + strlen(PATCHLEVEL)); - sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL); + programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING)); + sprintf(programVersion, "%s", PACKAGE_STRING); } else { +#if 0 char *p, *q; q = first.program; while (*q != ' ' && *q != NULLCHAR) q++; p = q; - while (p > first.program && *(p-1) != '/') p--; - programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION) - + strlen(PATCHLEVEL) + (q - p)); - sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL); + while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */ + programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p)); + sprintf(programVersion, "%s + ", PACKAGE_STRING); 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(PACKAGE_STRING) + strlen(first.tidy)); + sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy); +#endif } if (!appData.icsActive) { @@ -669,7 +858,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; @@ -684,16 +873,25 @@ 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; + case VariantXiangqi: /* [HGM] repetition rules not implemented */ + case VariantFairy: /* [HGM] TestLegality definitely off! */ + case VariantGothic: /* [HGM] should work */ + case VariantCapablanca: /* [HGM] should work */ + case VariantCourier: /* [HGM] initial forced moves not implemented */ + case VariantShogi: /* [HGM] drops not tested for legality */ + case VariantKnightmate: /* [HGM] should work */ + case VariantCylinder: /* [HGM] untested */ + case VariantFalcon: /* [HGM] untested */ + case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!) + offboard interposition not understood */ case VariantNormal: /* definitely works! */ case VariantWildCastle: /* pieces not automatically shuffled */ case VariantNoCastle: /* pieces not automatically shuffled */ - case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */ - case VariantCrazyhouse: /* holdings not shown, - offboard interposition not understood */ + case VariantFischeRandom: /* [HGM] works and shuffles pieces */ case VariantLosers: /* should work except for win condition, and doesn't know captures are mandatory */ case VariantSuicide: /* should work except for win condition, @@ -703,10 +901,18 @@ InitBackEnd1() case VariantTwoKings: /* should work */ case VariantAtomic: /* should work except for win condition */ case Variant3Check: /* should work except for win condition */ - case VariantShatranj: /* might work if TestLegality is off */ + 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 ) @@ -751,22 +957,51 @@ int NextTimeControlFromString( char ** str, long * value ) return result; } -int GetTimeControlForWhite() -{ - int result = timeControl; +int NextSessionFromString( char ** str, int *moves, long * tc, long *inc) +{ /* [HGM] routine added to read '+moves/time' for secondary time control */ + int result = -1; long temp, temp2; + if(**str != '+') return -1; // old params remain in force! + (*str)++; + if( NextTimeControlFromString( str, &temp ) ) return -1; + + if(**str != '/') { + /* time only: incremental or sudden-death time control */ + if(**str == '+') { /* increment follows; read it */ + (*str)++; + if(result = NextIntegerFromString( str, &temp2)) return -1; + *inc = temp2 * 1000; + } else *inc = 0; + *moves = 0; *tc = temp * 1000; + return 0; + } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */ + + (*str)++; /* classical time control */ + result = NextTimeControlFromString( str, &temp2); + if(result == 0) { + *moves = temp/60; + *tc = temp2 * 1000; + *inc = 0; + } return result; } -int GetTimeControlForBlack() -{ - int result = timeControl; +int GetTimeQuota(int movenr) +{ /* [HGM] get time to add from the multi-session time-control string */ + int moves=1; /* kludge to force reading of first session */ + long time, increment; + char *s = fullTimeControlString; - if( timeControl_2 > 0 ) { - result = timeControl_2; - } + if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString); + do { + if(moves) NextSessionFromString(&s, &moves, &time, &increment); + if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment); + if(movenr == -1) return time; /* last move before new session */ + if(!moves) return increment; /* current session is incremental */ + if(movenr >= 0) movenr -= moves; /* we already finished this session */ + } while(movenr >= -1); /* try again for next session */ - return result; + return 0; // no new time quota on this move } int @@ -789,6 +1024,19 @@ ParseTimeControl(tc, ti, mps) #else long tc1; long tc2; + char buf[MSG_SIZ]; + + if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0; + if(ti > 0) { + if(mps) + sprintf(buf, "+%d/%s+%d", mps, tc, ti); + else sprintf(buf, "+%s+%d", tc, ti); + } else { + if(mps) + sprintf(buf, "+%d/%s", mps, tc); + else sprintf(buf, "+%s", tc); + } + fullTimeControlString = StrSave(buf); if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) { return FALSE; @@ -841,13 +1089,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); } } @@ -859,16 +1113,21 @@ InitBackEnd3 P((void)) char buf[MSG_SIZ]; int err; - InitChessProgram(&first); + 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", + snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"), appData.icsHost, appData.icsPort); } DisplayFatalError(buf, err, 1); @@ -913,7 +1172,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; } @@ -921,24 +1180,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; } } @@ -950,7 +1213,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; } } @@ -962,53 +1225,67 @@ InitBackEnd3 P((void)) (void) LoadPositionFromFile(appData.loadPositionFile, appData.loadPositionIndex, appData.loadPositionFile); + /* [HGM] try to make self-starting even after FEN load */ + /* to allow automatic setup of fairy variants with wtm */ + if(initialMode == BeginningOfGame && !blackPlaysFirst) { + gameMode = BeginningOfGame; + setboardSpoiledMachineBlack = 1; + } + /* [HGM] loadPos: make that every new game uses the setup */ + /* from file as long as we do not switch variant */ + if(!blackPlaysFirst) { int i; + startedFromPositionFile = TRUE; + CopyBoard(filePosition, boards[0]); + for(i=0; i 0) { RemoveInputSource(isr); - DisplayFatalError("Got end of file from keyboard", 0, 0); + DisplayFatalError(_("Got end of file from keyboard"), 0, 0); } } void +KeepAlive() +{ // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out + SendToICS("date\n"); + if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); +} + +void SendToICS(s) char *s; { @@ -1189,7 +1473,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); } } @@ -1214,7 +1498,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); } } @@ -1289,7 +1573,13 @@ StringToVariant(e) char buf[MSG_SIZ]; if (!e) return v; - + + /* [HGM] skip over optional board-size prefixes */ + if( sscanf(e, "%dx%d_", &i, &i) == 2 || + sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) { + while( *e++ != '_'); + } + for (i=0; i= BlackPawn ) { + holdingsColumn = 0; + countsColumn = 1; + holdingsStartRow = BOARD_HEIGHT-1; + direction = -1; + } else { + holdingsColumn = BOARD_WIDTH-1; + countsColumn = BOARD_WIDTH-2; + holdingsStartRow = 0; + direction = 1; + } + + for(i=0; i= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */ + if(j < 0) continue; /* should not happen */ + piece = (ChessSquare) ( (int)piece + (int)lowestPiece ); + board[holdingsStartRow+j*direction][holdingsColumn] = piece; + board[holdingsStartRow+j*direction][countsColumn]++; + } + +} + + +void +VariantSwitch(Board board, VariantClass newVariant) +{ + int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j; + int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove; +// Board tempBoard; int saveCastling[BOARD_SIZE], saveEP; + + startedFromPositionFile = FALSE; + if(gameInfo.variant == newVariant) return; + + /* [HGM] This routine is called each time an assignment is made to + * gameInfo.variant during a game, to make sure the board sizes + * are set to match the new variant. If that means adding or deleting + * holdings, we shift the playing board accordingly + * This kludge is needed because in ICS observe mode, we get boards + * of an ongoing game without knowing the variant, and learn about the + * latter only later. This can be because of the move list we requested, + * in which case the game history is refilled from the beginning anyway, + * but also when receiving holdings of a crazyhouse game. In the latter + * case we want to add those holdings to the already received position. + */ + + + if (appData.debugMode) { + fprintf(debugFP, "Switch board from %s to %s\n", + VariantName(gameInfo.variant), VariantName(newVariant)); + setbuf(debugFP, NULL); + } + shuffleOpenings = 0; /* [HGM] shuffle */ + gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */ + switch(newVariant) { + case VariantShogi: + newWidth = 9; newHeight = 9; + gameInfo.holdingsSize = 7; + case VariantBughouse: + case VariantCrazyhouse: + newHoldingsWidth = 2; break; + default: + newHoldingsWidth = gameInfo.holdingsSize = 0; + } + + if(newWidth != gameInfo.boardWidth || + newHeight != gameInfo.boardHeight || + newHoldingsWidth != gameInfo.holdingsWidth ) { + + /* shift position to new playing area, if needed */ + if(newHoldingsWidth > gameInfo.holdingsWidth) { + for(i=0; i=BOARD_LEFT; j--) + board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] = + board[i][j]; + for(i=0; i 0) { /* If last read ended with a partial line that we couldn't parse, prepend it to the new read and try again. */ @@ -1655,6 +2121,11 @@ read_from_ics(isr, closure, data, count, error) for (i = 0; i < count; i++) { if (data[i] != NULLCHAR && data[i] != '\r') buf[buf_len++] = data[i]; + if(buf_len >= 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++] = ' '; // replace by space (assumes ICS does not break lines within word) + } } buf[buf_len] = NULLCHAR; @@ -1804,7 +2275,40 @@ 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(chattingPartner>=0) { + char mess[MSG_SIZ]; + sprintf(mess, "%s%s", talker, parse); + OutputChatMessage(chattingPartner, mess); + chattingPartner = -1; + } else + 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 + int depth=0; float score; + if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) { + // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph + pvInfoList[forwardMostMove-1].depth = depth; + pvInfoList[forwardMostMove-1].score = 100*score; + } + 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 */ @@ -1861,7 +2365,7 @@ read_from_ics(isr, closure, data, count, error) if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) { char buf[MSG_SIZ]; - sprintf(buf, "%s@%s", ics_handle, appData.icsHost); + snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost); DisplayIcsInteractionTitle(buf); have_set_title = TRUE; } @@ -1885,24 +2389,100 @@ 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.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i); + + // [HGM] chat: intercept tells by users for which we have an open chat window + channel = -1; + if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") || + looking_at(buf, &i, "* whispers:") || + looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) || + looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) { + int p; + sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle + chattingPartner = -1; + + if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel + for(p=0; p backwardMostMove) { + if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved currentMove = --forwardMostMove; DisplayMove(currentMove - 1); /* before DMError */ - DisplayMoveError("Illegal move (rejected by ICS)"); DrawPosition(FALSE, boards[currentMove]); SwitchClocks(); DisplayBothClocks(); } + DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg + ics_user_moved = 0; continue; } } @@ -2562,6 +3167,7 @@ read_from_ics(isr, closure, data, count, error) /* Send "new" early, in case this command takes a long time to finish, so that we'll be ready for the next challenge. */ + gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant' Reset(TRUE, TRUE); } #endif /*ZIPPY*/ @@ -2574,6 +3180,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; @@ -2635,9 +3246,9 @@ read_from_ics(isr, closure, data, count, error) ClearPremoveHighlights(); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); - UserMoveEvent(premoveFromX, premoveFromY, + UserMoveEvent(premoveFromX, premoveFromY, premoveToX, premoveToY, - premovePromoChar); + premovePromoChar); } } @@ -2654,11 +3265,17 @@ read_from_ics(isr, closure, data, count, error) started = STARTED_NONE; parse[parse_pos] = NULLCHAR; if (appData.debugMode) - fprintf(debugFP, "Parsing holdings: %s\n", parse); + fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n", + parse, currentMove); if (sscanf(parse, " game %d", &gamenum) == 1 && gamenum == ics_gamenum) { if (gameInfo.variant == VariantNormal) { - gameInfo.variant = VariantCrazyhouse; /*temp guess*/ + /* [HGM] We seem to switch variant during a game! + * Presumably no holdings were displayed, so we have + * to move the position two files to the right to + * create room for them! + */ + VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */ /* Get a move list just to see the header, which will tell us whether this is really bug or zh */ if (ics_getting_history == H_FALSE) { @@ -2673,6 +3290,9 @@ read_from_ics(isr, closure, data, count, error) new_piece); white_holding[strlen(white_holding)-1] = NULLCHAR; black_holding[strlen(black_holding)-1] = NULLCHAR; + /* [HGM] copy holdings to board holdings area */ + CopyHoldings(boards[currentMove], white_holding, WhitePawn); + CopyHoldings(boards[currentMove], black_holding, BlackPawn); #if ZIPPY if (appData.zippyPlay && first.initDone) { ZippyHoldings(white_holding, black_holding, @@ -2690,7 +3310,8 @@ read_from_ics(isr, closure, data, count, error) gameInfo.white, white_holding, gameInfo.black, black_holding); } - DrawPosition(FALSE, NULL); + + DrawPosition(FALSE, boards[currentMove]); DisplayTitle(str); } /* Suppress following prompt */ @@ -2705,11 +3326,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, @@ -2717,9 +3339,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); } } @@ -2734,7 +3356,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 */ @@ -2749,10 +3371,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; @@ -2761,25 +3383,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; } @@ -2850,7 +3485,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; } @@ -2906,7 +3541,13 @@ ParseBoard12(string) timeIncrement = increment * 1000; movesPerSession = 0; gameInfo.timeControl = TimeControlTagValue(); - gameInfo.variant = StringToVariant(gameInfo.event); + VariantSwitch(board, StringToVariant(gameInfo.event) ); + if (appData.debugMode) { + fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event); + fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant)); + setbuf(debugFP, NULL); + } + gameInfo.outOfBook = NULL; /* Do we have the ratings? */ @@ -2967,15 +3608,74 @@ 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] = 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;; + } + } CopyBoard(boards[moveNum], board); if (moveNum == 0) { startedFromSetupPosition = !CompareBoards(board, initialPosition); - } + if(startedFromSetupPosition) + initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */ + } + + /* [HGM] Set castling rights. Take the outermost Rooks, + to make it also work for FRC opening positions. Note that board12 + is really defective for later FRC positions, as it has no way to + indicate which Rook can castle if they are on the same side of King. + For the initial position we grant rights to the outermost Rooks, + and remember thos rights, and we then copy them on positions + later in an FRC game. This means WB might not recognize castlings with + Rooks that have moved back to their original position as illegal, + but in ICS mode that is not its job anyway. + */ + if(moveNum == 0 || gameInfo.variant != VariantFischeRandom) + { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing; + + for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + if(board[0][i] == WhiteRook) j = i; + initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j); + for(i=BOARD_LEFT, j= -1; i=BOARD_LEFT; i--) + if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i; + initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j); + + if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; } + for(k=BOARD_LEFT; k 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) { @@ -3038,6 +3748,16 @@ ParseBoard12(string) /* Put the move on the move list, first converting to canonical algebraic form. */ if (moveNum > 0) { + if (appData.debugMode) { + if (appData.debugMode) { int f = forwardMostMove; + fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f, + castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]); + } + fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str); + fprintf(debugFP, "moveNum = %d\n", moveNum); + fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT); + setbuf(debugFP, NULL); + } if (moveNum <= backwardMostMove) { /* We don't know what the board looked like before this move. Punt. */ @@ -3045,21 +3765,53 @@ 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 && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!) + 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, parseList[moveNum-1]); - switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){ + switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN, + castlingRights[moveNum]) ) { case MT_NONE: case MT_STALEMATE: default: break; case MT_CHECK: - strcat(parseList[moveNum - 1], "+"); + if(gameInfo.variant != VariantShogi) + strcat(parseList[moveNum - 1], "+"); break; case MT_CHECKMATE: + case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate strcat(parseList[moveNum - 1], "#"); break; } @@ -3068,16 +3820,12 @@ 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); + fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth); + } #if 0 if (appData.testLegality && appData.debugMode) { sprintf(str, "Illegal move \"%s\" from ICS", move_str); @@ -3089,7 +3837,12 @@ 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]); + setbuf(debugFP, NULL); + } #if ZIPPY /* Send move to chess program (BEFORE animating it). */ @@ -3099,15 +3852,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 ? @@ -3121,9 +3874,10 @@ 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! SendMoveToProgram(moveNum - 1, &first); } } @@ -3131,7 +3885,7 @@ ParseBoard12(string) #endif } - if (moveNum > 0 && !gotPremove) { + if (moveNum > 0 && !gotPremove && !appData.noGUI) { /* If move comes from a remote source, animate it. If it isn't remote, it will have already been animated. */ if (!pausing && !ics_user_moved && prevMove == moveNum - 1) { @@ -3161,22 +3915,35 @@ ParseBoard12(string) /* Display opponents and material strengths */ if (gameInfo.variant != VariantBughouse && - gameInfo.variant != VariantCrazyhouse) { + gameInfo.variant != VariantCrazyhouse && !appData.noGUI) { if (tinyLayout || smallLayout) { - sprintf(str, "%s(%d) %s(%d) {%d %d}", + if(gameInfo.variant == VariantNormal) + sprintf(str, "%s(%d) %s(%d) {%d %d}", gameInfo.white, white_stren, gameInfo.black, black_stren, basetime, increment); + else + sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", + gameInfo.white, white_stren, gameInfo.black, black_stren, + basetime, increment, (int) gameInfo.variant); } else { - sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", + if(gameInfo.variant == VariantNormal) + sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", gameInfo.white, white_stren, gameInfo.black, black_stren, basetime, increment); + else + sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", + gameInfo.white, white_stren, gameInfo.black, black_stren, + basetime, increment, VariantName(gameInfo.variant)); } DisplayTitle(str); + if (appData.debugMode) { + fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant); + } } /* Display the board */ - if (!pausing) { + if (!pausing && !appData.noGUI) { if (appData.premove) if (!gotPremove || @@ -3186,11 +3953,27 @@ ParseBoard12(string) DrawPosition(FALSE, boards[currentMove]); DisplayMove(moveNum - 1); - if (appData.ringBellAfterMoves && !ics_user_moved) - RingBell(); + if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move + !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) || + (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) { + if(newMove) RingBell(); else PlayIcsUnfinishedSound(); + } } HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); +#if ZIPPY + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.nodes = programStats.depth = 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 @@ -3228,6 +4011,7 @@ SendMoveToProgram(moveNum, cps) ChessProgramState *cps; { char buf[MSG_SIZ]; + if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -3243,18 +4027,24 @@ 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 + * the engine. It would be nice to have a better way to identify castle * moves here. */ - if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) { - int fromX = moveList[moveNum][0] - 'a'; - int fromY = moveList[moveNum][1] - '1'; - int toX = moveList[moveNum][2] - 'a'; - int toY = moveList[moveNum][3] - '1'; - if((boards[currentMove][fromY][fromX] == WhiteKing - && boards[currentMove][toY][toX] == WhiteRook) - || (boards[currentMove][fromY][fromX] == BlackKing - && boards[currentMove][toY][toX] == BlackRook)) { + if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) + && cps->useOOCastle) { + int fromX = moveList[moveNum][0] - AAA; + int fromY = moveList[moveNum][1] - ONE; + int toX = moveList[moveNum][2] - AAA; + int toY = moveList[moveNum][3] - ONE; + if((boards[moveNum][fromY][fromX] == WhiteKing + && boards[moveNum][toY][toX] == WhiteRook) + || (boards[moveNum][fromY][fromX] == BlackKing + && boards[moveNum][toY][toX] == BlackRook)) { if(toX > fromX) SendToProgram("O-O\n", cps); else SendToProgram("O-O-O\n", cps); } @@ -3263,6 +4053,21 @@ SendMoveToProgram(moveNum, cps) else SendToProgram(moveList[moveNum], cps); /* End of additions by Tord */ } + + /* [HGM] setting up the opening has brought engine in force mode! */ + /* Send 'go' if we are in a mode where machine should play. */ + if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) && + (gameMode == TwoMachinesPlay || +#ifdef ZIPPY + gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite || +#endif + gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) { + SendToProgram("go\n", cps); + if (appData.debugMode) { + fprintf(debugFP, "(extra)\n"); + } + } + setboardSpoiledMachineBlack = 0; } void @@ -3274,7 +4079,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; @@ -3308,25 +4113,40 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) case BlackPromotionKnight: case WhitePromotionKing: case BlackPromotionKing: - sprintf(user_move, "%c%c%c%c=%c\n", - 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY, + case WhitePromotionChancellor: + case BlackPromotionChancellor: + case WhitePromotionArchbishop: + case BlackPromotionArchbishop: + 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, PieceToChar(PromoPiece(moveType))); break; case WhiteDrop: case BlackDrop: sprintf(user_move, "%c@%c%c\n", ToUpper(PieceToChar((ChessSquare) fromX)), - 'a' + toX, '1' + toY); + AAA + toX, ONE + toY); break; case NormalMove: case WhiteCapturesEnPassant: case BlackCapturesEnPassant: case IllegalMove: /* could be a variant we don't quite understand */ sprintf(user_move, "%c%c%c%c\n", - 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY); + AAA + fromX, ONE + fromY, AAA + toX, ONE + toY); break; } SendToICS(user_move); + if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command + ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000); } void @@ -3337,14 +4157,14 @@ CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move) { if (rf == DROP_RANK) { sprintf(move, "%c@%c%c\n", - ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt); + ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt); } else { if (promoChar == 'x' || promoChar == NULLCHAR) { sprintf(move, "%c%c%c%c\n", - 'a' + ff, '1' + rf, 'a' + ft, '1' + rt); + AAA + ff, ONE + rf, AAA + ft, ONE + rt); } else { sprintf(move, "%c%c%c%c%c\n", - 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar); + AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar); } } } @@ -3363,6 +4183,56 @@ ProcessICSInitScript(f) } +/* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */ +void +AlphaRank(char *move, int n) +{ +// char *p = move, c; int x, y; + + if (appData.debugMode) { + fprintf(debugFP, "alphaRank(%s,%d)\n", move, 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 + if(move[0]>='0' && move[0]<='9' && + move[1]>='a' && move[1]<='x' && + move[2]>='0' && move[2]<='9' && + move[3]>='a' && move[3]<='x' ) { + /* input move, Shogi -> normal */ + move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA; + move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE; + move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA; + move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE; + } else + if(move[1]=='@' && + move[3]>='0' && move[3]<='9' && + move[2]>='a' && move[2]<='x' ) { + move[1] = '*'; + move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1'; + move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a'; + } else + if( + move[0]>='a' && move[0]<='x' && + move[3]>='0' && move[3]<='9' && + move[2]>='a' && move[2]<='x' ) { + /* output move, normal -> Shogi */ + move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1'; + move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a'; + move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1'; + move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a'; + if(move[4] == PieceToChar(BlackQueen)) move[4] = '+'; + } + if (appData.debugMode) { + fprintf(debugFP, " out = '%s'\n", move); + } +} + /* Parser for moves from gnuchess, ICS, or user typein box */ Boolean ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) @@ -3372,8 +4242,16 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) int *fromX, *fromY, *toX, *toY; char *promoChar; { + if (appData.debugMode) { + fprintf(debugFP, "move to parse: %s\n", move); + } *moveType = yylexstr(moveNum, move); + switch (*moveType) { + case WhitePromotionChancellor: + case BlackPromotionChancellor: + case WhitePromotionArchbishop: + case BlackPromotionArchbishop: case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -3402,13 +4280,16 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case BlackASideCastleFR: /* End of code added by Tord */ case IllegalMove: /* bug or odd chess variant */ - *fromX = currentMoveString[0] - 'a'; - *fromY = currentMoveString[1] - '1'; - *toX = currentMoveString[2] - 'a'; - *toY = currentMoveString[3] - '1'; + *fromX = currentMoveString[0] - AAA; + *fromY = currentMoveString[1] - ONE; + *toX = currentMoveString[2] - AAA; + *toY = currentMoveString[3] - ONE; *promoChar = currentMoveString[4]; - if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 || - *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) { + if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT || + *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) { + if (appData.debugMode) { + fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType); + } *fromX = *fromY = *toX = *toY = 0; return FALSE; } @@ -3422,10 +4303,10 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case BlackDrop: *fromX = *moveType == WhiteDrop ? (int) CharToPiece(ToUpper(currentMoveString[0])) : - (int) CharToPiece(ToLower(currentMoveString[0])); + (int) CharToPiece(ToLower(currentMoveString[0])); *fromY = DROP_RANK; - *toX = currentMoveString[2] - 'a'; - *toY = currentMoveString[3] - '1'; + *toX = currentMoveString[2] - AAA; + *toY = currentMoveString[3] - ONE; *promoChar = NULLCHAR; return TRUE; @@ -3440,6 +4321,9 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case BlackWins: case GameIsDrawn: default: + if (appData.debugMode) { + fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType); + } /* bug? */ *fromX = *fromY = *toX = *toY = 0; *promoChar = NULLCHAR; @@ -3447,134 +4331,490 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } -/* [AS] FRC game initialization */ -static int FindEmptySquare( Board board, int n ) -{ - int i = 0; +// [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants. +// All positions will have equal probability, but the current method will not provide a unique +// numbering scheme for arrays that contain 3 or more pieces of the same kind. +#define DARK 1 +#define LITE 2 +#define ANY 3 - while( 1 ) { - while( board[0][i] != EmptySquare ) i++; - if( n == 0 ) - break; - n--; - i++; - } +int squaresLeft[4]; +int piecesLeft[(int)BlackPawn]; +int seed, nrOfShuffles; - return i; +void GetPositionNumber() +{ // sets global variable seed + int i; + + seed = appData.defaultFrcPosition; + if(seed < 0) { // randomize based on time for negative FRC position numbers + for(i=0; i<50; i++) seed += random(); + seed = random() ^ random() >> 8 ^ random() << 8; + if(seed<0) seed = -seed; + } } -static void ShuffleFRC( Board board ) +int put(Board board, int pieceType, int rank, int n, int shade) +// put the piece on the (n-1)-th empty squares of the given shade { - int i; + int i; - srand( time(0) ); + for(i=BOARD_LEFT; i= j) i -= j--; + j = n - 1 - j; i += j; + put(board, pieceType, rank, j, ANY); + put(board, pieceType, rank, i, ANY); +} + +void SetUpShuffle(Board board, int number) { - int i; - unsigned char knights; + int i, p, first=1; - /* Bring the position index into a safe range (just in case...) */ - if( pos_index < 0 ) pos_index = 0; + GetPositionNumber(); nrOfShuffles = 1; - pos_index %= 960; + squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2; + squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT; + squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK]; - /* Clear the board */ - for( i=0; i<8; i++ ) { - board[0][i] = EmptySquare; - } + for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0; + + for(i=BOARD_LEFT; i (int) WhitePawn; p--) { + if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue; + while(piecesLeft[p] >= 2) { + AddOnePiece(board, p, 0, LITE); + AddOnePiece(board, p, 0, DARK); + } + // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares) + } + + for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) { + // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere + // but we leave King and Rooks for last, to possibly obey FRC restriction + if(p == (int)WhiteRook) continue; + while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations + if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece + } + + // now everything is placed, except perhaps King (Unicorn) and Rooks + + if(PosFlags(0) & F_FRC_TYPE_CASTLING) { + // Last King gets castling rights + while(piecesLeft[(int)WhiteUnicorn]) { + i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY); + initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i; + } - /* Place bishops and queen */ - board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */ - pos_index /= 4; + while(piecesLeft[(int)WhiteKing]) { + i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY); + initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i; + } - board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */ - pos_index /= 4; - board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen; - pos_index /= 6; + } else { + while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY); + while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY); + } - /* Place knigths */ - knights = FRC_KnightTable[ pos_index ]; + // Only Rooks can be left; simply place them all + while(piecesLeft[(int)WhiteRook]) { + i = put(board, WhiteRook, 0, 0, ANY); + if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights + if(first) { + first=0; + initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i; + } + initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i; + } + } + for(i=BOARD_LEFT; i= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize +} + +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 */ +{ + int result = FALSE; int NrPieces; - /* Place rooks and king */ - board[0][ FindEmptySquare(board, 0) ] = WhiteRook; - board[0][ FindEmptySquare(board, 0) ] = WhiteKing; - board[0][ FindEmptySquare(board, 0) ] = WhiteRook; + if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare + && NrPieces >= 12 && !(NrPieces&1)) { + int i; /* [HGM] Accept even length from 12 to 34 */ - /* Mirror piece placement for black */ - for( i=0; i<8; i++ ) { - board[7][i] = board[0][i] + BlackPawn - WhitePawn; + for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.'; + for( i=0; i= 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>1; + castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; + castlingRights[0][4] = initialRights[4] = BOARD_LEFT; + castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1; + break; + case VariantFalcon: + pieces = FalconArray; + gameInfo.boardWidth = 10; + SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); + break; + case VariantXiangqi: + pieces = XiangqiArray; + gameInfo.boardWidth = 9; + gameInfo.boardHeight = 10; + nrCastlingRights = 0; + SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); + break; + case VariantShogi: + pieces = ShogiArray; + gameInfo.boardWidth = 9; + gameInfo.boardHeight = 9; + gameInfo.holdingsSize = 7; + nrCastlingRights = 0; + SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); + break; + case VariantCourier: + pieces = CourierArray; + gameInfo.boardWidth = 12; + nrCastlingRights = 0; + SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); + for(i=0; i= 0) { + if(gameInfo.boardWidth != appData.NrFiles) overrule++; + gameInfo.boardWidth = appData.NrFiles; + } + if(appData.NrRanks >= 0) { + gameInfo.boardHeight = appData.NrRanks; + } + if(appData.holdingsSize >= 0) { + i = appData.holdingsSize; + if(i > gameInfo.boardHeight) i = gameInfo.boardHeight; + gameInfo.holdingsSize = i; + } + if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2; + if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE) + 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; + + /* User pieceToChar list overrules defaults */ + if(appData.pieceToCharTable != NULL) + SetCharTable(pieceToChar, appData.pieceToCharTable); + + for( j=0; j= BOARD_RGHT || overrule) continue; + initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth]; + initialPosition[pawnRow][j] = WhitePawn; + initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn; + if(gameInfo.variant == VariantXiangqi) { + if(j&1) { + initialPosition[pawnRow][j] = + initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare; + if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) { + initialPosition[2][j] = WhiteCannon; + initialPosition[BOARD_HEIGHT-3][j] = BlackCannon; + } + } + } + initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth]; + } + if( (gameInfo.variant == VariantShogi) && !overrule ) { + + j=BOARD_LEFT+1; + initialPosition[1][j] = WhiteBishop; + initialPosition[BOARD_HEIGHT-2][j] = BlackRook; + j=BOARD_RGHT-2; + initialPosition[1][j] = WhiteRook; + initialPosition[BOARD_HEIGHT-2][j] = BlackBishop; + } + + if( nrCastlingRights == -1) { + /* [HGM] Build normal castling rights (must be done after board sizing!) */ + /* This sets default castling rights from none to normal corners */ + /* Variants with other castling rights must set them themselves above */ + nrCastlingRights = 6; + + castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1; + castlingRights[0][1] = initialRights[1] = BOARD_LEFT; + castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1; + castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1; + castlingRights[0][4] = initialRights[4] = BOARD_LEFT; + castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1; + } + + if(gameInfo.variant == VariantSuper) Prelude(initialPosition); + if(gameInfo.variant == VariantGreat) { // promotion commoners + initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan; + initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-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 ) { - ShuffleFRC( boards[0] ); + ShuffleFRC( initialPosition ); } else { - SetupFRC( boards[0], appData.defaultFrcPosition ); + SetupFRC( initialPosition, appData.defaultFrcPosition ); } - break; + startedFromSetupPosition = TRUE; + } else +#else + if (appData.debugMode) { + fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings); + } + if(shuffleOpenings) { + SetUpShuffle(initialPosition, appData.defaultFrcPosition); + startedFromSetupPosition = TRUE; + } +#endif + if(startedFromPositionFile) { + /* [HGM] loadPos: use PositionFile for every new game */ + CopyBoard(initialPosition, filePosition); + for(i=0; iuseSetboard) { - char* fen = PositionToFEN(moveNum, cps->useFEN960); + char* fen = PositionToFEN(moveNum, cps->fenOverride); sprintf(message, "setboard %s\n", fen); SendToProgram(message, cps); free(fen); @@ -3602,25 +4842,43 @@ SendBoard(cps, moveNum) SendToProgram("edit\n", cps); SendToProgram("#\n", cps); - for (i = BOARD_SIZE - 1; i >= 0; i--) { - bp = &boards[moveNum][i][0]; - for (j = 0; j < BOARD_SIZE; j++, bp++) { + for (i = BOARD_HEIGHT - 1; i >= 0; i--) { + bp = &boards[moveNum][i][BOARD_LEFT]; + for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) { if ((int) *bp < (int) BlackPawn) { sprintf(message, "%c%c%c\n", PieceToChar(*bp), - 'a' + j, '1' + i); + AAA + j, ONE + i); + if(message[0] == '+' || message[0] == '~') { + sprintf(message, "%c%c%c+\n", + PieceToChar((ChessSquare)(DEMOTED *bp)), + AAA + j, ONE + i); + } + if(cps->alphaRank) { /* [HGM] shogi: translate coords */ + message[1] = BOARD_RGHT - 1 - j + '1'; + message[2] = BOARD_HEIGHT - 1 - i + 'a'; + } SendToProgram(message, cps); } } } SendToProgram("c\n", cps); - for (i = BOARD_SIZE - 1; i >= 0; i--) { - bp = &boards[moveNum][i][0]; - for (j = 0; j < BOARD_SIZE; j++, bp++) { + for (i = BOARD_HEIGHT - 1; i >= 0; i--) { + bp = &boards[moveNum][i][BOARD_LEFT]; + for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) { if (((int) *bp != (int) EmptySquare) && ((int) *bp >= (int) BlackPawn)) { sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)), - 'a' + j, '1' + i); + AAA + j, ONE + i); + if(message[0] == '+' || message[0] == '~') { + sprintf(message, "%c%c%c+\n", + PieceToChar((ChessSquare)(DEMOTED *bp)), + AAA + j, ONE + i); + } + if(cps->alphaRank) { /* [HGM] shogi: translate coords */ + message[1] = BOARD_RGHT - 1 - j + '1'; + message[2] = BOARD_HEIGHT - 1 - i + 'a'; + } SendToProgram(message, cps); } } @@ -3628,25 +4886,55 @@ SendBoard(cps, moveNum) SendToProgram(".\n", cps); } + setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */ } int IsPromotion(fromX, fromY, toX, toY) int fromX, fromY, toX, toY; { - return gameMode != EditPosition && - fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 && - ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) || - (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0)); + /* [HGM] add Shogi promotions */ + int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn; + ChessSquare piece; + + if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || + !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE; + /* [HGM] Note to self: line above also weeds out drops */ + piece = boards[currentMove][fromY][fromX]; + if(gameInfo.variant == VariantShogi) { + promotionZoneSize = 3; + highestPromotingPiece = (int)WhiteKing; + /* [HGM] Should be Silver = Ferz, really, but legality testing is off, + and if in normal chess we then allow promotion to King, why not + allow promotion of other piece in Shogi? */ + } + if((int)piece >= BlackPawn) { + if(toY >= promotionZoneSize && fromY >= promotionZoneSize) + return FALSE; + highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece; + } else { + if( toY < BOARD_HEIGHT - promotionZoneSize && + fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE; + } + return ( (int)piece <= highestPromotingPiece ); } +int +InPalace(row, column) + int row, column; +{ /* [HGM] for Xiangqi */ + if( (row < 3 || row > BOARD_HEIGHT-4) && + column < (BOARD_WIDTH + 4)/2 && + column > (BOARD_WIDTH - 5)/2 ) return TRUE; + return FALSE; +} int PieceForSquare (x, y) int x; int y; { - if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) + if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) return -1; else return boards[currentMove][y][x]; @@ -3670,7 +4958,7 @@ OKToStartUserMove(x, y) if (from_piece == EmptySquare) return FALSE; white_piece = (int)from_piece >= (int)WhitePawn && - (int)from_piece <= (int)WhiteKing; + (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */ switch (gameMode) { case PlayFromGameFile: @@ -3687,7 +4975,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; @@ -3696,18 +4984,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)) { @@ -3727,7 +5015,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; } } @@ -3735,11 +5023,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; @@ -3750,7 +5038,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; @@ -3763,18 +5051,38 @@ char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ]; ChessMove lastLoadGameStart = (ChessMove) 0; -void -UserMoveEvent(fromX, fromY, toX, toY, promoChar) +ChessMove +UserMoveTest(fromX, fromY, toX, toY, promoChar) int fromX, fromY, toX, toY; int promoChar; { ChessMove moveType; + ChessSquare pdown, pup; - if (fromX < 0 || fromY < 0) return; + if (fromX < 0 || fromY < 0) return ImpossibleMove; if ((fromX == toX) && (fromY == toY)) { - return; - } - + return ImpossibleMove; + } + + /* [HGM] suppress all moves into holdings area and guard band */ + if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 ) + return ImpossibleMove; + + /* [HGM] moved to here from winboard.c */ + /* note: this code seems to exist for filtering out some obviously illegal premoves */ + pdown = boards[currentMove][fromY][fromX]; + pup = boards[currentMove][toY][toX]; + if ( gameMode != EditPosition && + (WhitePawn <= pdown && pdown < BlackPawn && + WhitePawn <= pup && pup < BlackPawn || + BlackPawn <= pdown && pdown < EmptySquare && + BlackPawn <= pup && pup < EmptySquare + ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) && + (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0|| + pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ) + ) ) + return ImpossibleMove; + /* Check if the user is playing in turn. This is complicated because we let the user "pick up" a piece before it is his turn. So the piece he tried to pick up may have been captured by the time he puts it down! @@ -3795,21 +5103,21 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) case IcsIdle: /* We switched into a game mode where moves are not accepted, perhaps while the mouse button was down. */ - return; + return ImpossibleMove; case MachinePlaysWhite: /* User is moving for Black */ if (WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); - return; + DisplayMoveError(_("It is White's turn")); + return ImpossibleMove; } break; case MachinePlaysBlack: /* User is moving for White */ if (!WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); - return; + DisplayMoveError(_("It is Black's turn")); + return ImpossibleMove; } break; @@ -3819,17 +5127,17 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) case AnalyzeMode: case Training: if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn && - (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) { + (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) { /* User is moving for Black */ if (WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); - return; + DisplayMoveError(_("It is White's turn")); + return ImpossibleMove; } } else { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); - return; + DisplayMoveError(_("It is Black's turn")); + return ImpossibleMove; } } break; @@ -3838,7 +5146,7 @@ UserMoveEvent(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; @@ -3850,8 +5158,14 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) fprintf(debugFP, "Got premove: fromX %d," "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); + if(!WhiteOnMove(currentMove) && gotPremove == 1) { + // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove + if (appData.debugMode) + fprintf(debugFP, "Execute as normal move\n"); + gotPremove = 0; break; + } } - return; + return ImpossibleMove; } break; @@ -3859,7 +5173,7 @@ UserMoveEvent(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; @@ -3871,8 +5185,14 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) fprintf(debugFP, "Got premove: fromX %d," "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); + if(WhiteOnMove(currentMove) && gotPremove == 1) { + // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove + if (appData.debugMode) + fprintf(debugFP, "Execute as normal move\n"); + gotPremove = 0; break; + } } - return; + return ImpossibleMove; } break; @@ -3880,40 +5200,109 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) break; case EditPosition: + /* EditPosition, empty square, or different color piece; + 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; + return ImpossibleMove; + } + + /* [HGM] If move started in holdings, it means a drop */ + if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { + if( pup != EmptySquare ) return ImpossibleMove; + if(appData.testLegality) { + /* it would be more logical if LegalityTest() also figured out + * which drops are legal. For now we forbid pawns on back rank. + * Shogi is on its own here... + */ + if( (pdown == WhitePawn || pdown == BlackPawn) && + (toY == 0 || toY == BOARD_HEIGHT -1 ) ) + return(ImpossibleMove); /* no pawn drops on 1st/8th */ + } + return WhiteDrop; /* Not needed to specify white or black yet */ } - if (toX < 0 || toY < 0) return; userOfferedDraw = FALSE; + /* [HGM] always test for legality, to get promotion info */ + moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), + epStatus[currentMove], castlingRights[currentMove], + fromY, fromX, toY, toX, promoChar); + + /* [HGM] but possibly ignore an IllegalMove result */ if (appData.testLegality) { - moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN, fromY, fromX, toY, toX, promoChar); if (moveType == IllegalMove || moveType == ImpossibleMove) { - DisplayMoveError("Illegal move"); - return; + DisplayMoveError(_("Illegal move")); + return ImpossibleMove; } - } else { - moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar); } +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 + should be called. This to give the calling driver routine the + opportunity to finish the userMove input with a promotion popup, + without bothering the user with this for invalid or illegal moves */ + +/* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */ +} +/* Common tail of UserMoveEvent and DropMenuEvent */ +int +FinishMove(moveType, fromX, fromY, toX, toY, promoChar) + ChessMove moveType; + int fromX, fromY, toX, toY; + /*char*/int promoChar; +{ + 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 && 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; + if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", + moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]); +// fromX = boards[currentMove][fromY][fromX]; + // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here + if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down + fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings + while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; + fromY = DROP_RANK; + } + + /* [HGM] The following if has been moved here from + UserMoveEvent(). Because it seemed to belon here (why not allow + piece drops in training games?), and because it can only be + performed after it is known to what we promote. */ if (gameMode == Training) { /* compare the move played on the board to the next move in the * game. If they match, display the move and the opponent's response. * If they don't match, display an error message. */ int saveAnimate; - Board testBoard; + Board testBoard; char testRights[BOARD_SIZE]; char testStatus; CopyBoard(testBoard, boards[currentMove]); - ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard); + ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus); if (CompareBoards(testBoard, boards[currentMove+1])) { ForwardInner(currentMove+1); @@ -3932,24 +5321,14 @@ UserMoveEvent(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; } - FinishMove(moveType, fromX, fromY, toX, toY, promoChar); -} - -/* Common tail of UserMoveEvent and DropMenuEvent */ -void -FinishMove(moveType, fromX, fromY, toX, toY, promoChar) - ChessMove moveType; - int fromX, fromY, toX, toY; - /*char*/int promoChar; -{ /* Ok, now we know that the move is good, so we can kill the previous line in Analysis Mode */ if (gameMode == AnalyzeMode && currentMove < forwardMostMove) { @@ -3974,6 +5353,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); @@ -3981,10 +5361,11 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) sprintf(buf, "name %s\n", gameInfo.white); SendToProgram(buf, &first); } + StartClocks(); } 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 || @@ -3998,10 +5379,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; } @@ -4012,11 +5394,12 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) switch (gameMode) { case EditGame: switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN)) { + EP_UNKNOWN, castlingRights[currentMove]) ) { case MT_NONE: case MT_CHECK: break; case MT_CHECKMATE: + case MT_STAINMATE: if (WhiteOnMove(currentMove)) { GameEnds(BlackWins, "Black mates", GE_PLAYER); } else { @@ -4038,17 +5421,109 @@ 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.nodes = programStats.depth = 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 +UserMoveEvent(fromX, fromY, toX, toY, promoChar) + int fromX, fromY, toX, toY; + int promoChar; +{ + /* [HGM] This routine was added to allow calling of its two logical + parts from other modules in the old way. Before, UserMoveEvent() + automatically called FinishMove() if the move was OK, and returned + otherwise. I separated the two, in order to make it possible to + slip a promotion popup in between. But that it always needs two + calls, to the first part, (now called UserMoveTest() ), and to + FinishMove if the first part succeeded. Calls that do not need + 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 == AmbiguousMove) + DrawPosition(FALSE, boards[currentMove]); + else if(moveType != ImpossibleMove) + FinishMove(moveType, fromX, fromY, toX, toY, promoChar); +} + +void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats ) +{ +// char * hint = lastHint; + FrontEndProgramStats stats; + + stats.which = cps == &first ? 0 : 1; + stats.depth = cpstats->depth; + stats.nodes = cpstats->nodes; + stats.score = cpstats->score; + stats.time = cpstats->time; + stats.pv = cpstats->movelist; + stats.hint = lastHint; + stats.an_move_index = 0; + stats.an_move_count = 0; + + if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) { + stats.hint = cpstats->move_name; + stats.an_move_index = cpstats->nr_moves - cpstats->moves_left; + stats.an_move_count = cpstats->nr_moves; + } + + SetProgramStats( &stats ); } -void SendProgramStatsToFrontend( ChessProgramState * cps ) +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) { - SetProgramStats( cps == &first ? 0 : 1, - programStats.depth, - programStats.nodes, - programStats.score, - programStats.time, - programStats.movelist, - lastHint ); + if(savedState->lastPing != savedState->lastPong) + ScheduleDelayedEvent(DeferredBookMove, 10); + else + HandleMachineMove(savedMessage, savedState); } void @@ -4063,13 +5538,20 @@ 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 */ while (*message == '\007') message++; /* + * [HGM] engine debug message: ignore lines starting with '#' character + */ + if(cps->debug && *message == '#') return; + + /* * Look for book output */ if (cps == &first && bookRequested) { @@ -4095,7 +5577,7 @@ HandleMachineMove(message, cps) * Look for machine move. */ if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) || - (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) + (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) { /* This method is only useful on engines that support ping */ if (cps->lastPing != cps->lastPong) { @@ -4109,7 +5591,8 @@ HandleMachineMove(message, cps) fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n", cps->which, gameMode); } - SendToProgram("undo\n", cps); + + SendToProgram("undo\n", cps); } return; } @@ -4160,19 +5643,67 @@ HandleMachineMove(message, cps) return; } - if (!ParseOneMove(machineMove, forwardMostMove, &moveType, - &fromX, &fromY, &toX, &toY, &promoChar)) { + if (appData.debugMode) { int f = forwardMostMove; + 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]); + } + 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", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType); if (gameMode == TwoMachinesPlay) { GameEnds(machineWhite ? BlackWins : WhiteWins, - "Forfeit due to illegal move", GE_XBOARD); + buf1, GE_XBOARD); } return; } + /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */ + /* So we have to redo legality test with true e.p. status here, */ + /* to make sure an illegal e.p. capture does not slip through, */ + /* to cause a forfeit on a justified illegal-move complaint */ + /* of the opponent. */ + if( gameMode==TwoMachinesPlay && appData.testLegality + && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */ + ) { + ChessMove moveType; + moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove), + epStatus[forwardMostMove], castlingRights[forwardMostMove], + fromY, fromX, toY, toX, promoChar); + if (appData.debugMode) { + int i; + for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ", + castlingRights[forwardMostMove][i], castlingRank[i]); + fprintf(debugFP, "castling rights\n"); + } + if(moveType == IllegalMove) { + sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c", + machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0); + GameEnds(machineWhite ? BlackWins : WhiteWins, + buf1, GE_XBOARD); + return; + } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom) + /* [HGM] Kludge to handle engines that send FRC-style castling + when they shouldn't (like TSCP-Gothic) */ + switch(moveType) { + case WhiteASideCastleFR: + case BlackASideCastleFR: + toX+=2; + currentMoveString[2]++; + break; + case WhiteHSideCastleFR: + case BlackHSideCastleFR: + toX--; + currentMoveString[2]--; + break; + default: ; // nothing to do, but suppresses warning of pedantic compilers + } + } hintRequested = FALSE; lastHint[0] = NULLCHAR; bookRequested = FALSE; @@ -4186,23 +5717,36 @@ HandleMachineMove(message, cps) first.initDone) { SendMoveToICS(moveType, fromX, fromY, toX, toY); ics_user_moved = 1; + if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */ + char buf[3*MSG_SIZ]; + + sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n", + programStats.score / 100., + programStats.depth, + programStats.time / 100., + (unsigned int)programStats.nodes, + (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.), + programStats.movelist); + SendToICS(buf); +if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes); + } } #endif /* currentMoveString is set as a side-effect of ParseOneMove */ strcpy(machineMove, currentMoveString); strcat(machineMove, "\n"); strcpy(moveList[forwardMostMove], machineMove); - + /* [AS] Save move info and clear stats for next move */ pvInfoList[ forwardMostMove ].score = programStats.score; pvInfoList[ forwardMostMove ].depth = programStats.depth; - pvInfoList[ forwardMostMove ].time = -1; + pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats ClearProgramStats(); thinkOutput[0] = NULLCHAR; hiddenThinkOutputState = 0; MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/ - + /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */ if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) { int count = 0; @@ -4224,29 +5768,381 @@ HandleMachineMove(message, cps) if( count >= adjudicateLossPlies ) { ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, - "Xboard adjudication", + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication", GE_XBOARD ); return; } } - if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) { - ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + 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 ) + { /* [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, NrK=0, bishopsColor = 0, + NrPieces=0, NrPawns=0, PawnAdvance=0, i, j; + static int moveCount = 6; + ChessMove result; + char *reason = NULL; + + /* Count what is on board. */ + for(i=0; iother); // make sure opponent gets move + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, + "Xboard adjudication: King destroyed", GE_XBOARD ); + return; + } + } - return; + /* Bare King in Shatranj (loses) or Losers (wins) */ + if( NrW == 1 || NrPieces - NrW == 1) { + if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first) + epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable + 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) ? WhiteWins : BlackWins, + "Xboard adjudication: Bare king", GE_XBOARD ); + return; + } + } else + if( gameInfo.variant == VariantShatranj && --bare < 0) + { /* bare King */ + epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm + if(appData.checkMates) { + /* but only adjudicate if adjudication enabled */ + SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, + "Xboard adjudication: Bare king", GE_XBOARD ); + return; + } + } + } else bare = 1; + + + // don't wait for engine to announce game end if we can judge ourselves + switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile, + castlingRights[forwardMostMove]) ) { + case MT_CHECK: + if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time + int i, checkCnt = 0; // (should really be done by making nr of checks part of game state) + for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) { + if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK) + checkCnt++; + if(checkCnt >= 2) { + reason = "Xboard adjudication: 3rd check"; + epStatus[forwardMostMove] = EP_CHECKMATE; + break; + } + } + } + case MT_NONE: + default: + break; + case MT_STALEMATE: + case MT_STAINMATE: + reason = "Xboard adjudication: Stalemate"; + if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt + epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw + if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers: + epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win + else if(gameInfo.variant == VariantSuicide) // in suicide it depends + epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE : + ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ? + EP_CHECKMATE : EP_WINS); + else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi) + epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses + } + break; + case MT_CHECKMATE: + reason = "Xboard adjudication: Checkmate"; + epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE); + break; + } + + switch(i = epStatus[forwardMostMove]) { + case EP_STALEMATE: + result = GameIsDrawn; break; + case EP_CHECKMATE: + result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break; + case EP_WINS: + result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break; + default: + result = (ChessMove) 0; + } + if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + GameEnds( result, reason, GE_XBOARD ); + return; + } + + /* Next absolutely insufficient mating material. */ + if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && + gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible + (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 || + NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color + { /* KBK, KNK, KK of KBKB with like Bishops */ + + /* always flag draws, for judging claims */ + epStatus[forwardMostMove] = EP_INSUF_DRAW; + + if(appData.materialDraws) { + /* but only adjudicate them if adjudication enabled */ + SendToProgram("force\n", cps->other); // 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; + } + } + + /* Then some trivial draws (only adjudicate, cannot be claimed) */ + if(NrPieces == 4 && + ( NrWR == 1 && NrBR == 1 /* KRKR */ + || NrWQ==1 && NrBQ==1 /* KQKQ */ + || NrWN==2 || NrBN==2 /* KNNK */ + || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */ + ) ) { + 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 1 + if (appData.debugMode) { int i; + fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n", + forwardMostMove, backwardMostMove, epStatus[backwardMostMove], + appData.drawRepeats); + for( i=forwardMostMove; i>=backwardMostMove; i-- ) + fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]); + + } +#endif + /* Check for rep-draws */ + count = 0; + for(k = forwardMostMove-2; + k>=backwardMostMove && k>=forwardMostMove-100 && + epStatus[k] < EP_UNKNOWN && + epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE; + k-=2) + { int rights=0; +#if 0 + if (appData.debugMode) { + fprintf(debugFP, " loop\n"); + } +#endif + if(CompareBoards(boards[k], boards[forwardMostMove])) { +#if 0 + if (appData.debugMode) { + fprintf(debugFP, "match\n"); + } +#endif + /* compare castling rights */ + if( castlingRights[forwardMostMove][2] != castlingRights[k][2] && + (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) ) + rights++; /* King lost rights, while rook still had them */ + if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */ + if( castlingRights[forwardMostMove][0] != castlingRights[k][0] || + castlingRights[forwardMostMove][1] != castlingRights[k][1] ) + rights++; /* but at least one rook lost them */ + } + if( castlingRights[forwardMostMove][5] != castlingRights[k][5] && + (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) ) + rights++; + if( castlingRights[forwardMostMove][5] >= 0 ) { + if( castlingRights[forwardMostMove][3] != castlingRights[k][3] || + castlingRights[forwardMostMove][4] != castlingRights[k][4] ) + rights++; + } +#if 0 + if (appData.debugMode) { + for(i=0; i 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(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n", + ourPerpetual, hisPerpetual); + 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. + // Now check for perpetual chases + if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase + hisPerpetual = PerpetualChase(k, forwardMostMove); + ourPerpetual = PerpetualChase(k+1, forwardMostMove); + if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication: perpetual chasing", GE_XBOARD ); + return; + } + if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet + break; // Abort repetition-checking loop. + } + // if neither of us is checking or chasing all the time, or both are, it is draw + } + GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD ); + return; + } + if( rights == 0 && count > 1 ) /* occurred 2 or more times before */ + epStatus[forwardMostMove] = EP_REP_DRAW; + } + } + + /* Now we test for 50-move draws. Determine ply count */ + count = forwardMostMove; + /* look for last irreversble move */ + while( epStatus[count] <= EP_NONE && count > backwardMostMove ) + count--; + /* if we hit starting position, add initial plies */ + if( count == backwardMostMove ) + count -= initialRulePlies; + count = forwardMostMove - count; + if( count >= 100) + 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; + } + + /* if draw offer is pending, treat it as a draw claim + * when draw condition present, to allow engines a way to + * claim draws before making their move to avoid a race + * condition occurring after their move + */ + if( cps->other->offeredDraw || cps->offeredDraw ) { + char *p = NULL; + if(epStatus[forwardMostMove] == EP_RULE_DRAW) + p = "Draw claim: 50-move rule"; + if(epStatus[forwardMostMove] == EP_REP_DRAW) + p = "Draw claim: 3-fold repetition"; + 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*/ + + GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD ); + + 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 */ + if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) { + SendToProgram("draw\n", cps->other); + } if (cps->other->sendTime) { 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); @@ -4257,8 +6153,8 @@ HandleMachineMove(message, cps) } ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - - if (!pausing && appData.ringBellAfterMoves) { + + if (!pausing && appData.ringBellAfterMoves) { RingBell(); } @@ -4269,6 +6165,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.nodes = programStats.depth = 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; } @@ -4281,6 +6200,35 @@ HandleMachineMove(message, cps) cps->useSigint = FALSE; cps->useSigterm = FALSE; } + if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands + ParseFeatures(message+8, cps); + return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands! + } + + /* [HGM] Allow engine to set up a position. Don't ask me why one would + * want this, I was asked to put it in, and obliged. + */ + if (!strncmp(message, "setboard ", 9)) { + Board initial_position; int i; + + GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD); + + if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) { + DisplayError(_("Bad FEN received from engine"), 0); + return ; + } else { + Reset(FALSE, FALSE); + CopyBoard(boards[0], initial_position); + initialRulePlies = FENrulePlies; + epStatus[0] = FENepStatus; + for( i=0; ilastPong) == 1) { - return; + return; } /* * If the move is illegal, cancel it and redraw the board. @@ -4408,7 +6353,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; } @@ -4428,7 +6373,9 @@ HandleMachineMove(message, cps) searchTime); return; } - if (!StrStr(message, "llegal")) return; + if (!StrStr(message, "llegal")) { + return; + } if (gameMode == BeginningOfGame || gameMode == EndOfGame || gameMode == IcsIdle) return; if (forwardMostMove <= backwardMostMove) return; @@ -4452,10 +6399,17 @@ 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]); + + /* [HGM] illegal-move claim should forfeit game when Xboard */ + /* only passes fully legal moves */ + if( appData.testLegality && gameMode == TwoMachinesPlay ) { + GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins, + "False illegal-move claim", GE_XBOARD ); + } return; } if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) { @@ -4477,7 +6431,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", + snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"), cps->which, cps->program, cps->host, message); RemoveInputSource(cps->isr); DisplayFatalError(buf1, 0, 1); @@ -4495,12 +6449,12 @@ HandleMachineMove(message, cps) (void) CoordsToAlgebraic(boards[forwardMostMove], PosFlags(forwardMostMove), EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, buf1); - sprintf(buf2, "Hint: %s", buf1); + snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1); DisplayInformation(buf2); } else { /* Hint move could not be parsed!? */ - sprintf(buf2, - "Illegal hint move \"%s\"\nfrom %s chess program", + snprintf(buf2, sizeof(buf2), + _("Illegal hint move \"%s\"\nfrom %s chess program"), buf1, cps->which); DisplayError(buf2, 0); } @@ -4529,7 +6483,7 @@ HandleMachineMove(message, cps) r = p + 1; } } - GameEnds(WhiteWins, r, GE_ENGINE); + GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */ return; } else if (strncmp(message, "0-1", 3) == 0) { char *p, *q, *r = ""; @@ -4543,10 +6497,10 @@ HandleMachineMove(message, cps) } /* Kludge for Arasan 4.1 bug */ if (strcmp(r, "Black resigns") == 0) { - GameEnds(WhiteWins, r, GE_ENGINE); + GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); return; } - GameEnds(BlackWins, r, GE_ENGINE); + GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first)); return; } else if (strncmp(message, "1/2", 3) == 0) { char *p, *q, *r = ""; @@ -4558,40 +6512,45 @@ HandleMachineMove(message, cps) r = p + 1; } } - GameEnds(GameIsDrawn, r, GE_ENGINE); + + GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first)); return; } else if (strncmp(message, "White resign", 12) == 0) { - GameEnds(BlackWins, "White resigns", GE_ENGINE); + GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first)); return; } else if (strncmp(message, "Black resign", 12) == 0) { - GameEnds(WhiteWins, "Black resigns", GE_ENGINE); + GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first)); return; + } else if (strncmp(message, "White matches", 13) == 0 || + strncmp(message, "Black matches", 13) == 0 ) { + /* [HGM] ignore GNUShogi noises */ + return; } else if (strncmp(message, "White", 5) == 0 && message[5] != '(' && StrStr(message, "Black") == NULL) { - GameEnds(WhiteWins, "White mates", GE_ENGINE); + GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first)); return; } else if (strncmp(message, "Black", 5) == 0 && message[5] != '(') { - GameEnds(BlackWins, "Black mates", GE_ENGINE); + GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first)); return; } else if (strcmp(message, "resign") == 0 || strcmp(message, "computer resigns") == 0) { switch (gameMode) { case MachinePlaysBlack: case IcsPlayingBlack: - GameEnds(WhiteWins, "Black resigns", GE_ENGINE); + GameEnds(WhiteWins, "Black resigns", GE_ENGINE); break; case MachinePlaysWhite: case IcsPlayingWhite: - GameEnds(BlackWins, "White resigns", GE_ENGINE); + GameEnds(BlackWins, "White resigns", GE_ENGINE); break; case TwoMachinesPlay: if (cps->twoMachinesColor[0] == 'w') - GameEnds(BlackWins, "White resigns", GE_ENGINE); + GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first)); else - GameEnds(WhiteWins, "Black resigns", GE_ENGINE); + GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first)); break; default: /* can't happen */ @@ -4602,17 +6561,17 @@ HandleMachineMove(message, cps) switch (gameMode) { case MachinePlaysBlack: case IcsPlayingBlack: - GameEnds(WhiteWins, "White mates", GE_ENGINE); + GameEnds(WhiteWins, "White mates", GE_ENGINE); break; case MachinePlaysWhite: case IcsPlayingWhite: - GameEnds(BlackWins, "Black mates", GE_ENGINE); + GameEnds(BlackWins, "Black mates", GE_ENGINE); break; case TwoMachinesPlay: if (cps->twoMachinesColor[0] == 'w') - GameEnds(BlackWins, "Black mates", GE_ENGINE); + GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first)); else - GameEnds(WhiteWins, "White mates", GE_ENGINE); + GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first)); break; default: /* can't happen */ @@ -4623,17 +6582,17 @@ HandleMachineMove(message, cps) switch (gameMode) { case MachinePlaysBlack: case IcsPlayingBlack: - GameEnds(BlackWins, "Black mates", GE_ENGINE); + GameEnds(BlackWins, "Black mates", GE_ENGINE1); break; case MachinePlaysWhite: case IcsPlayingWhite: - GameEnds(WhiteWins, "White mates", GE_ENGINE); + GameEnds(WhiteWins, "White mates", GE_ENGINE); break; case TwoMachinesPlay: if (cps->twoMachinesColor[0] == 'w') - GameEnds(WhiteWins, "White mates", GE_ENGINE); + GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first)); else - GameEnds(BlackWins, "Black mates", GE_ENGINE); + GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first)); break; default: /* can't happen */ @@ -4642,14 +6601,14 @@ HandleMachineMove(message, cps) return; } else if (strncmp(message, "checkmate", 9) == 0) { if (WhiteOnMove(forwardMostMove)) { - GameEnds(BlackWins, "Black mates", GE_ENGINE); + GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first)); } else { - GameEnds(WhiteWins, "White mates", GE_ENGINE); + GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first)); } return; } else if (strstr(message, "Draw") != NULL || strstr(message, "game is a draw") != NULL) { - GameEnds(GameIsDrawn, "Draw", GE_ENGINE); + GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first)); return; } else if (strstr(message, "offer") != NULL && strstr(message, "draw") != NULL) { @@ -4664,18 +6623,23 @@ HandleMachineMove(message, cps) if (gameMode == TwoMachinesPlay) { if (cps->other->offeredDraw) { GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD); - } else { + /* [HGM] in two-machine mode we delay relaying draw offer */ + /* until after we also have move, to see if it is really claim */ + } +#if 0 + else { if (cps->other->sendDrawOffers) { SendToProgram("draw\n", cps->other); } } +#endif } 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")); } } } @@ -4684,10 +6648,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; @@ -4704,6 +6670,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)) { @@ -4717,7 +6686,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') { @@ -4725,7 +6694,7 @@ HandleMachineMove(message, cps) } /* [AS] Negate score if machine is playing black and reporting absolute scores */ - if( cps->scoreIsAbsolute && + if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) { curscore = -curscore; @@ -4738,6 +6707,16 @@ HandleMachineMove(message, cps) programStats.score = curscore; programStats.got_only_move = 0; + if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */ + int ticklen; + + if(cps->nps == 0) ticklen = 10*time; // use engine reported time + else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time + if(WhiteOnMove(forwardMostMove)) + whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen; + else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen; + } + /* Buffer overflow protection */ if (buf1[0] != NULLCHAR) { if (strlen(buf1) >= sizeof(programStats.movelist) @@ -4763,10 +6742,10 @@ HandleMachineMove(message, cps) } else { programStats.line_is_book = 0; } - - SendProgramStatsToFrontend( cps ); - /* + SendProgramStatsToFrontend( cps, &programStats ); + + /* [AS] Protect the thinkOutput buffer from overflow... this is only useful if buf1 hasn't overflowed first! */ @@ -4791,7 +6770,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(); } @@ -4815,15 +6795,16 @@ HandleMachineMove(message, cps) mean "line isn't going to change" (Crafty isn't searching, so stats won't change) */ programStats.line_is_book = 1; - - SendProgramStatsToFrontend( cps ); - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { + SendProgramStatsToFrontend( cps, &programStats ); + + 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 @@ -4841,8 +6822,9 @@ HandleMachineMove(message, cps) programStats.nr_moves = mvtot; strcpy(programStats.move_name, mvname); programStats.ok_to_send = 1; + programStats.movelist[0] = '\0'; - SendProgramStatsToFrontend( cps ); + SendProgramStatsToFrontend( cps, &programStats ); DisplayAnalysis(); return; @@ -4868,16 +6850,17 @@ HandleMachineMove(message, cps) /* [AS] Avoid buffer overflow */ if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) { - strcat(thinkOutput, " "); - strcat(thinkOutput, p); + strcat(thinkOutput, " "); + strcat(thinkOutput, p); } if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) { - strcat(programStats.movelist, " "); - strcat(programStats.movelist, p); + strcat(programStats.movelist, " "); + strcat(programStats.movelist, p); } - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || gameMode == AnalyzeFile) { + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || + gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); DisplayAnalysis(); } @@ -4887,9 +6870,11 @@ HandleMachineMove(message, cps) else { buf1[0] = NULLCHAR; - if (sscanf(message, "%d%c %d %d %lu %[^\n]\n", - &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) + if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n", + &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) { + ChessProgramStats cpstats; + if (plyext != ' ' && plyext != '\t') { time *= 100; } @@ -4899,23 +6884,23 @@ HandleMachineMove(message, cps) curscore = -curscore; } - programStats.depth = plylev; - programStats.nodes = nodes; - programStats.time = time; - programStats.score = curscore; - programStats.got_only_move = 0; - programStats.movelist[0] = '\0'; + cpstats.depth = plylev; + cpstats.nodes = nodes; + cpstats.time = time; + cpstats.score = curscore; + cpstats.got_only_move = 0; + cpstats.movelist[0] = '\0'; if (buf1[0] != NULLCHAR) { - safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) ); + safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) ); } - programStats.ok_to_send = 0; - programStats.line_is_book = 0; - programStats.nr_moves = 0; - programStats.moves_left = 0; + cpstats.ok_to_send = 0; + cpstats.line_is_book = 0; + cpstats.nr_moves = 0; + cpstats.moves_left = 0; - SendProgramStatsToFrontend( cps ); + SendProgramStatsToFrontend( cps, &cpstats ); } } } @@ -4964,6 +6949,16 @@ ParseGameHistory(game) yyboardindex = boardIndex; moveType = (ChessMove) yylex(); switch (moveType) { + case IllegalMove: /* maybe suicide chess, etc. */ + if (appData.debugMode) { + fprintf(debugFP, "Illegal 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); + setbuf(debugFP, NULL); + } + case WhitePromotionChancellor: + case BlackPromotionChancellor: + case WhitePromotionArchbishop: + case BlackPromotionArchbishop: case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -4991,11 +6986,10 @@ ParseGameHistory(game) case BlackHSideCastleFR: case BlackASideCastleFR: /* POP Fabien */ - case IllegalMove: /* maybe suicide chess, etc. */ - fromX = currentMoveString[0] - 'a'; - fromY = currentMoveString[1] - '1'; - toX = currentMoveString[2] - 'a'; - toY = currentMoveString[3] - '1'; + fromX = currentMoveString[0] - AAA; + fromY = currentMoveString[1] - ONE; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; break; case WhiteDrop: @@ -5004,24 +6998,34 @@ ParseGameHistory(game) (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); fromY = DROP_RANK; - toX = currentMoveString[2] - 'a'; - toY = currentMoveString[3] - '1'; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; promoChar = NULLCHAR; 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); + setbuf(debugFP, NULL); + } DisplayError(buf, 0); 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); + setbuf(debugFP, NULL); + } DisplayError(buf, 0); return; 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; @@ -5076,21 +7080,25 @@ ParseGameHistory(game) EP_UNKNOWN, fromY, fromX, toY, toX, promoChar, parseList[boardIndex]); CopyBoard(boards[boardIndex + 1], boards[boardIndex]); + {int i; for(i=0; iBOARD_LEFT && board[toY][toX-1] == BlackPawn && + gameInfo.variant != VariantBerolina || toX < fromX) + *ep = toX | berolina; + if(toX fromX) + *ep = toX; + } + } else + if( board[fromY][fromX] == BlackPawn ) { + if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers + *ep = EP_PAWN_MOVE; + if( toY-fromY== -2) { + if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn && + gameInfo.variant != VariantBerolina || toX < fromX) + *ep = toX | berolina; + if(toX fromX) + *ep = toX; + } + } + + for(i=0; i fromX) { - board[0][6] = WhiteKing; board[0][5] = WhiteRook; + board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook; } else { - board[0][2] = WhiteKing; board[0][3] = WhiteRook; + board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook; } } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook) { board[fromY][fromX] = EmptySquare; board[toY][toX] = EmptySquare; if(toX > fromX) { - board[7][6] = BlackKing; board[7][5] = BlackRook; + board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook; } else { - board[7][2] = BlackKing; board[7][3] = BlackRook; + board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook; } /* End of code added by Tord */ - } else if (fromY == 0 && fromX == 4 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 6) { + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ + && toY == fromY && toX > fromX+1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][7] = EmptySquare; - board[toY][5] = WhiteRook; - } else if (fromY == 0 && fromX == 4 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 2) { + board[toY][toX] = king; + board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; + board[fromY][BOARD_RGHT-1] = EmptySquare; + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ + && toY == fromY && toX < fromX-1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][0] = EmptySquare; - board[toY][3] = WhiteRook; - } else if (fromY == 0 && fromX == 3 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 5) { - board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][7] = EmptySquare; - board[toY][4] = WhiteRook; - } else if (fromY == 0 && fromX == 3 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 1) { - board[fromY][fromX] = EmptySquare; - board[toY][toX] = WhiteKing; - board[fromY][0] = EmptySquare; - board[toY][2] = WhiteRook; + board[toY][toX] = king; + board[toY][toX+1] = board[fromY][BOARD_LEFT]; + board[fromY][BOARD_LEFT] = EmptySquare; } else if (board[fromY][fromX] == WhitePawn - && toY == 7) { + && toY == BOARD_HEIGHT-1 + && gameInfo.variant != VariantXiangqi + ) { /* white pawn promotion */ - board[7][toX] = CharToPiece(ToUpper(promoChar)); - if (board[7][toX] == EmptySquare) { - board[7][toX] = WhiteQueen; + board[toY][toX] = CharToPiece(ToUpper(promoChar)); + if (board[toY][toX] == EmptySquare) { + board[toY][toX] = WhiteQueen; } + if(gameInfo.variant==VariantBughouse || + gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; - } else if ((fromY == 4) + } else if ((fromY == BOARD_HEIGHT-4) && (toX != fromX) + && gameInfo.variant != VariantXiangqi + && gameInfo.variant != VariantBerolina && (board[fromY][fromX] == WhitePawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; board[toY][toX] = WhitePawn; captured = board[toY - 1][toX]; board[toY - 1][toX] = EmptySquare; - } else if (fromY == 7 && fromX == 4 - && board[fromY][fromX] == BlackKing - && toY == 7 && toX == 6) { + } else if ((fromY == BOARD_HEIGHT-4) + && (toX == fromX) + && gameInfo.variant == VariantBerolina + && (board[fromY][fromX] == WhitePawn) + && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = BlackKing; - board[fromY][7] = EmptySquare; - board[toY][5] = BlackRook; - } else if (fromY == 7 && fromX == 4 - && board[fromY][fromX] == BlackKing - && toY == 7 && toX == 2) { + board[toY][toX] = WhitePawn; + if(oldEP & EP_BEROLIN_A) { + captured = board[fromY][fromX-1]; + board[fromY][fromX-1] = EmptySquare; + }else{ captured = board[fromY][fromX+1]; + board[fromY][fromX+1] = EmptySquare; + } + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ + && toY == fromY && toX > fromX+1) { board[fromY][fromX] = EmptySquare; - board[toY][toX] = BlackKing; - board[fromY][0] = EmptySquare; - board[toY][3] = BlackRook; + board[toY][toX] = king; + board[toY][toX-1] = board[fromY][BOARD_RGHT-1]; + board[fromY][BOARD_RGHT-1] = EmptySquare; + } else if (board[fromY][fromX] == king + && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */ + && toY == fromY && toX < fromX-1) { + board[fromY][fromX] = EmptySquare; + board[toY][toX] = king; + board[toY][toX+1] = board[fromY][BOARD_LEFT]; + board[fromY][BOARD_LEFT] = EmptySquare; } else if (fromY == 7 && fromX == 3 && board[fromY][fromX] == BlackKing && toY == 7 && toX == 5) { @@ -5208,47 +7277,111 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[fromY][0] = EmptySquare; board[toY][2] = BlackRook; } else if (board[fromY][fromX] == BlackPawn - && toY == 0) { + && toY == 0 + && gameInfo.variant != VariantXiangqi + ) { /* black pawn promotion */ board[0][toX] = CharToPiece(ToLower(promoChar)); if (board[0][toX] == EmptySquare) { board[0][toX] = BlackQueen; } + if(gameInfo.variant==VariantBughouse || + gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */ + board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]); board[fromY][fromX] = EmptySquare; } else if ((fromY == 3) && (toX != fromX) + && gameInfo.variant != VariantXiangqi + && gameInfo.variant != VariantBerolina && (board[fromY][fromX] == BlackPawn) && (board[toY][toX] == EmptySquare)) { board[fromY][fromX] = EmptySquare; board[toY][toX] = BlackPawn; captured = board[toY + 1][toX]; board[toY + 1][toX] = EmptySquare; + } else if ((fromY == 3) + && (toX == fromX) + && gameInfo.variant == VariantBerolina + && (board[fromY][fromX] == BlackPawn) + && (board[toY][toX] == EmptySquare)) { + board[fromY][fromX] = EmptySquare; + board[toY][toX] = BlackPawn; + if(oldEP & EP_BEROLIN_A) { + captured = board[fromY][fromX-1]; + board[fromY][fromX-1] = EmptySquare; + }else{ captured = board[fromY][fromX+1]; + board[fromY][fromX+1] = EmptySquare; + } } else { board[toY][toX] = board[fromY][fromX]; board[fromY][fromX] = EmptySquare; } - if (gameInfo.variant == VariantCrazyhouse) { -#if 0 - /* !!A lot more code needs to be written to support holdings */ + + /* [HGM] now we promote for Shogi, if needed */ + if(gameInfo.variant == VariantShogi && promoChar == 'q') + board[toY][toX] = (ChessSquare) (PROMOTED piece); + } + + if (gameInfo.holdingsWidth != 0) { + + /* !!A lot more code needs to be written to support holdings */ + /* [HGM] OK, so I have written it. Holdings are stored in the */ + /* penultimate board files, so they are automaticlly stored */ + /* in the game history. */ if (fromY == DROP_RANK) { - /* Delete from holdings */ - if (holdings[(int) fromX] > 0) holdings[(int) fromX]--; + /* Delete from holdings, by decreasing count */ + /* and erasing image if necessary */ + p = (int) fromX; + if(p < (int) BlackPawn) { /* white drop */ + p -= (int)WhitePawn; + if(p >= gameInfo.holdingsSize) p = 0; + if(--board[p][BOARD_WIDTH-2] == 0) + board[p][BOARD_WIDTH-1] = EmptySquare; + } else { /* black drop */ + p -= (int)BlackPawn; + if(p >= gameInfo.holdingsSize) p = 0; + if(--board[BOARD_HEIGHT-1-p][1] == 0) + board[BOARD_HEIGHT-1-p][0] = EmptySquare; + } } - if (captured != EmptySquare) { - /* Add to holdings */ - if (captured < BlackPawn) { - holdings[(int)captured - (int)BlackPawn + (int)WhitePawn]++; + if (captured != EmptySquare && gameInfo.holdingsSize > 0 + && gameInfo.variant != VariantBughouse ) { + /* [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; + if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { + /* in Shogi restore piece to its original first */ + captured = (ChessSquare) (DEMOTED captured); + p = DEMOTED p; + } + 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; } else { - holdings[(int)captured - (int)WhitePawn + (int)BlackPawn]++; + p -= (int)WhitePawn; + if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) { + captured = (ChessSquare) (DEMOTED captured); + p = DEMOTED p; + } + 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; } } -#endif + } else if (gameInfo.variant == VariantAtomic) { if (captured != EmptySquare) { int y, x; for (y = toY-1; y <= toY+1; y++) { for (x = toX-1; x <= toX+1; x++) { - if (y >= 0 && y <= 7 && x >= 0 && x <= 7 && + if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT && board[y][x] != WhitePawn && board[y][x] != BlackPawn) { board[y][x] = EmptySquare; } @@ -5257,6 +7390,24 @@ ApplyMove(fromX, fromY, toX, toY, promoChar, board) board[toY][toX] = EmptySquare; } } + if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') { + /* [HGM] Shogi promotions */ + board[toY][toX] = (ChessSquare) (PROMOTED piece); + } + + 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((int)piece < (int)BlackPawn) { // determine stm from piece color + if(!--board[k][BOARD_WIDTH-2]) + board[k][BOARD_WIDTH-1] = EmptySquare; + } else { + if(!--board[BOARD_HEIGHT-1-k][1]) + board[BOARD_HEIGHT-1-k][0] = EmptySquare; + } + } + } /* Updates forwardMostMove */ @@ -5265,21 +7416,67 @@ MakeMove(fromX, fromY, toX, toY, promoChar) int fromX, fromY, toX, toY; int promoChar; { - forwardMostMove++; - if (forwardMostMove >= MAX_MOVES) { - DisplayFatalError("Game too long; increase MAX_MOVES and recompile", +// forwardMostMove++; // [HGM] bare: moved downstream + + if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */ + int timeLeft; static int lastLoadFlag=0; int king, piece; + piece = boards[forwardMostMove][fromY][fromX]; + king = piece < (int) BlackPawn ? WhiteKing : BlackKing; + if(gameInfo.variant == VariantKnightmate) + king += (int) WhiteUnicorn - (int) WhiteKing; + if(forwardMostMove == 0) { + if(blackPlaysFirst) + fprintf(serverMoves, "%s;", second.tidy); + fprintf(serverMoves, "%s;", first.tidy); + if(!blackPlaysFirst) + fprintf(serverMoves, "%s;", second.tidy); + } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";"); + lastLoadFlag = loadFlag; + // print base move + fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY); + // print castling suffix + if( toY == fromY && piece == king ) { + if(toX-fromX > 1) + fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY); + if(fromX-toX >1) + fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY); + } + // e.p. suffix + if( (boards[forwardMostMove][fromY][fromX] == WhitePawn || + boards[forwardMostMove][fromY][fromX] == BlackPawn ) && + boards[forwardMostMove][toY][toX] == EmptySquare + && fromX != toX ) + fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY); + // promotion suffix + if(promoChar != NULLCHAR) + fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY); + if(!loadFlag) { + fprintf(serverMoves, "/%d/%d", + pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score); + if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000; + else timeLeft = blackTimeRemaining/1000; + fprintf(serverMoves, "/%d", timeLeft); + } + fflush(serverMoves); + } + + if (forwardMostMove+1 >= MAX_MOVES) { + DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"), 0, 1); return; } SwitchClocks(); - timeRemaining[0][forwardMostMove] = whiteTimeRemaining; - timeRemaining[1][forwardMostMove] = blackTimeRemaining; - if (commentList[forwardMostMove] != NULL) { - free(commentList[forwardMostMove]); - commentList[forwardMostMove] = NULL; - } - CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]); - ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]); + timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining; + timeRemaining[1][forwardMostMove+1] = blackTimeRemaining; + if (commentList[forwardMostMove+1] != NULL) { + free(commentList[forwardMostMove+1]); + commentList[forwardMostMove+1] = NULL; + } + CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]); + {int i; for(i=0; iegtFormats) == 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, "egtpath %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) +InitChessProgram(cps, setup) ChessProgramState *cps; + int setup; /* [HGM] needed to setup FRC opening position */ { - char buf[MSG_SIZ]; + char buf[MSG_SIZ], b[MSG_SIZ]; int overruled; 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) { + gameInfo.variant != VariantLoadable + /* [HGM] also send variant if board size non-standard */ + || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0 + ) { char *v = VariantName(gameInfo.variant); - if (StrStr(cps->variants, v) == NULL) { - sprintf(buf, "Variant %s not supported by %s", v, cps->tidy); + 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); DisplayFatalError(buf, 0, 1); return; } - sprintf(buf, "variant %s\n", VariantName(gameInfo.variant)); + + /* [HGM] make prefix for non-standard board size. Awkward testing... */ + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantXiangqi ) + overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0; + if( gameInfo.variant == VariantShogi ) + overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7; + if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse ) + overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5; + if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || + gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon ) + 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, + gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name + /* [HGM] varsize: try first if this defiant size variant is specifically known */ + if(StrStr(cps->variants, b) == NULL) { + // specific sized variant not known, check if general sizing allowed + if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best + if(StrStr(cps->variants, "boardsize") == NULL) { + sprintf(buf, "Board size %dx%d+%d not supported by %s", + gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy); + DisplayFatalError(buf, 0, 1); + return; + } + /* [HGM] here we really should compare with the maximum supported board size */ + } + } + } else sprintf(b, "%s", VariantName(gameInfo.variant)); + sprintf(buf, "variant %s\n", b); SendToProgram(buf, cps); } + currentlyInitializedVariant = gameInfo.variant; + + /* [HGM] send opening position in FRC to first engine */ + if(setup) { + SendToProgram("force\n", cps); + SendBoard(cps, 0); + /* engine is now in force mode! Set flag to wake it up after first move. */ + setboardSpoiledMachineBlack = 1; + } + if (cps->sendICS) { - sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-"); + snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-"); SendToProgram(buf, cps); } cps->maybeThinking = FALSE; @@ -5365,7 +7666,10 @@ InitChessProgram(cps) 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); @@ -5400,17 +7704,17 @@ StartChessProgram(cps) err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr); } else { if (*appData.remoteUser == NULLCHAR) { - sprintf(buf, "%s %s %s", appData.remoteShell, cps->host, + snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host, cps->program); } else { - sprintf(buf, "%s %s -l %s %s", appData.remoteShell, + snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell, cps->host, appData.remoteUser, cps->program); } err = StartChildProcess(buf, "", &cps->pr); } 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; @@ -5420,6 +7724,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); @@ -5431,13 +7737,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(); @@ -5447,14 +7753,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(); @@ -5477,6 +7794,28 @@ void UserAdjudicationEvent( int result ) } +// [HGM] save: calculate checksum of game to make games easily identifiable +int StringCheckSum(char *s) +{ + int i = 0; + if(s==NULL) return 0; + while(*s) i = i*259 + *s++; + return i; +} + +int GameCheckSum() +{ + int i, sum=0; + for(i=backwardMostMove; i1 && sum==0) sum++; // make sure never zero for non-empty game + return sum + StringCheckSum(commentList[i]); +} // end of save patch + void GameEnds(result, resultDetails, whosays) ChessMove result; @@ -5485,13 +7824,17 @@ GameEnds(result, resultDetails, whosays) { GameMode nextGameMode; int isIcsGame; + char buf[MSG_SIZ]; + + if(endingGame) return; /* [HGM] crash: forbid recursion */ + endingGame = 1; if (appData.debugMode) { fprintf(debugFP, "GameEnds(%d, %s, %d)\n", result, resultDetails ? resultDetails : "(null)", whosays); } - if (appData.icsActive && whosays == GE_ENGINE) { + if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) { /* If we are playing on ICS, the server decides when the game is over, but the engine can offer to draw, claim a draw, or resign. @@ -5508,7 +7851,8 @@ GameEnds(result, resultDetails, whosays) } } #endif - return; + endingGame = 0; /* [HGM] crash */ + return; } /* If we're loading the game from a file, stop */ @@ -5518,7 +7862,7 @@ GameEnds(result, resultDetails, whosays) } /* Cancel draw offers */ - first.offeredDraw = second.offeredDraw = 0; + first.offeredDraw = second.offeredDraw = 0; /* If this is an ICS game, only ICS can really say it's done; if not, anyone can. */ @@ -5533,11 +7877,115 @@ GameEnds(result, resultDetails, whosays) if (!isIcsGame && !appData.noChessProgram) SetUserThinkingEnables(); - if (resultDetails != NULL) { + /* [HGM] if a machine claims the game end we verify this claim */ + if(gameMode == TwoMachinesPlay && appData.testClaims) { + if(appData.testLegality && whosays >= GE_ENGINE1 ) { + char claimer; + ChessMove trueResult = (ChessMove) -1; + + claimer = whosays == GE_ENGINE1 ? /* color of claimer */ + first.twoMachinesColor[0] : + second.twoMachinesColor[0] ; + + // [HGM] losers: because the logic is becoming a bit hairy, determine true result first + if(epStatus[forwardMostMove] == EP_CHECKMATE) { + /* [HGM] verify: engine mate claims accepted if they were flagged */ + trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; + } else + if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win + /* [HGM] verify: engine mate claims accepted if they were flagged */ + trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; + } else + if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now + trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE + } + + // now verify win claims, but not in drop games, as we don't understand those yet + if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper + || gameInfo.variant == VariantGreat) && + (result == WhiteWins && claimer == 'w' || + result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win + if (appData.debugMode) { + fprintf(debugFP, "result=%d sp=%d move=%d\n", + result, epStatus[forwardMostMove], forwardMostMove); + } + if(result != trueResult) { + 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)) + ) { + /* [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) + && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway + && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course... + && 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 (resultDetails != NULL) { gameInfo.result = result; gameInfo.resultDetails = StrSave(resultDetails); + /* display last move only if game was not loaded from file */ + if ((whosays != GE_FILE) && (currentMove == forwardMostMove)) + DisplayMove(currentMove - 1); + + if (forwardMostMove != 0) { + if (gameMode != PlayFromGameFile && gameMode != EditGame + && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates + ) { + if (*appData.saveGameFile != NULLCHAR) { + SaveGameToFile(appData.saveGameFile, TRUE); + } else if (appData.autoSaveGames) { + AutoSaveGame(); + } + if (*appData.savePositionFile != NULLCHAR) { + SavePositionToFile(appData.savePositionFile); + } + } + } + /* Tell program how game ended in case it is learning */ + /* [HGM] Moved this to after saving the PGN, just in case */ + /* engine died and we got here through time loss. In that */ + /* case we will get a fatal error writing the pipe, which */ + /* would otherwise lose us the PGN. */ + /* [HGM] crash: not needed anymore, but doesn't hurt; */ + /* output during GameEnds should never be fatal anymore */ if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay || @@ -5555,23 +8003,6 @@ GameEnds(result, resultDetails, whosays) SendToProgram(buf, &second); } } - - /* display last move only if game was not loaded from file */ - if ((whosays != GE_FILE) && (currentMove == forwardMostMove)) - DisplayMove(currentMove - 1); - - if (forwardMostMove != 0) { - if (gameMode != PlayFromGameFile && gameMode != EditGame) { - if (*appData.saveGameFile != NULLCHAR) { - SaveGameToFile(appData.saveGameFile, TRUE); - } else if (appData.autoSaveGames) { - AutoSaveGame(); - } - if (*appData.savePositionFile != NULLCHAR) { - SavePositionToFile(appData.savePositionFile); - } - } - } } if (appData.icsActive) { @@ -5629,7 +8060,8 @@ GameEnds(result, resultDetails, whosays) if (appData.noChessProgram) { gameMode = nextGameMode; ModeHighlight(); - return; + endingGame = 0; /* [HGM] crash */ + return; } if (first.reuse) { @@ -5710,17 +8142,22 @@ 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++; - ScheduleDelayedEvent(NextMatchGame, 10000); + if(appData.matchPause>10000 || appData.matchPause<10) + appData.matchPause = 10000; /* [HGM] make pause adjustable */ + ScheduleDelayedEvent(NextMatchGame, appData.matchPause); + endingGame = 0; /* [HGM] crash */ return; } 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)); @@ -5732,6 +8169,7 @@ GameEnds(result, resultDetails, whosays) ExitAnalyzeMode(); gameMode = nextGameMode; ModeHighlight(); + endingGame = 0; /* [HGM] crash */ } /* Assumes program was just initialized (initString sent). @@ -5747,9 +8185,20 @@ FeedMovesToProgram(cps, upto) fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n", startedFromSetupPosition ? "position and " : "", backwardMostMove, upto, cps->which); + if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ]; + // [HGM] variantswitch: make engine aware of new variant + if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL) + return; // [HGM] refrain from feeding moves altogether if variant is unsupported! + sprintf(buf, "variant %s\n", VariantName(gameInfo.variant)); + SendToProgram(buf, cps); + currentlyInitializedVariant = gameInfo.variant; + } SendToProgram("force\n", cps); if (startedFromSetupPosition) { SendBoard(cps, backwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "feedMoves\n"); + } } for (i = backwardMostMove; i < upto; i++) { SendMoveToProgram(i, cps); @@ -5766,7 +8215,7 @@ ResurrectChessProgram() if (appData.noChessProgram || first.pr != NoProc) return; StartChessProgram(&first); - InitChessProgram(&first); + InitChessProgram(&first, FALSE); FeedMovesToProgram(&first, currentMove); if (!first.sendTime) { @@ -5777,8 +8226,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; } @@ -5797,7 +8246,6 @@ Reset(redraw, init) fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n", redraw, init, gameMode); } - pausing = pauseExamInvalid = FALSE; startedFromSetupPosition = blackPlaysFirst = FALSE; firstMove = TRUE; @@ -5806,6 +8254,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); @@ -5815,6 +8265,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(); @@ -5824,9 +8275,24 @@ Reset(redraw, init) alarmSounded = FALSE; GameEnds((ChessMove) 0, NULL, GE_PLAYER); + if(appData.serverMovesName != NULL) { + /* [HGM] prepare to make moves file for broadcasting */ + clock_t t = clock(); + if(serverMoves != NULL) fclose(serverMoves); + serverMoves = fopen(appData.serverMovesName, "r"); + if(serverMoves != NULL) { + fclose(serverMoves); + /* delay 15 sec before overwriting, so all clients can see end */ + while(clock()-t < appData.serverPause*CLOCKS_PER_SEC); + } + serverMoves = fopen(appData.serverMovesName, "w"); + } + ExitAnalyzeMode(); gameMode = BeginningOfGame; ModeHighlight(); + if(appData.icsActive) gameInfo.variant = VariantNormal; + currentMove = forwardMostMove = backwardMostMove = 0; InitPosition(redraw); for (i = 0; i < MAX_MOVES; i++) { if (commentList[i] != NULL) { @@ -5840,10 +8306,13 @@ Reset(redraw, init) if (first.pr == NULL) { StartChessProgram(&first); } - if (init) InitChessProgram(&first); + if (init) { + InitChessProgram(&first, startedFromSetupPosition); + } DisplayTitle(""); DisplayMessage("", ""); HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1); + lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved } void @@ -5884,16 +8353,16 @@ AutoPlayOneMove() return FALSE; } - toX = moveList[currentMove][2] - 'a'; - toY = moveList[currentMove][3] - '1'; + toX = moveList[currentMove][2] - AAA; + toY = moveList[currentMove][3] - ONE; if (moveList[currentMove][1] == '@') { if (appData.highlightLastMove) { SetHighlights(-1, -1, toX, toY); } } else { - fromX = moveList[currentMove][0] - 'a'; - fromY = moveList[currentMove][1] - '1'; + fromX = moveList[currentMove][0] - AAA; + fromY = moveList[currentMove][1] - ONE; HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */ @@ -5907,9 +8376,8 @@ AutoPlayOneMove() SendMoveToProgram(currentMove++, &first); DisplayBothClocks(); DrawPosition(FALSE, boards[currentMove]); - if (commentList[currentMove] != NULL) { - DisplayComment(currentMove - 1, commentList[currentMove]); - } + // [HGM] PV info: always display, routine tests if empty + DisplayComment(currentMove - 1, commentList[currentMove]); return TRUE; } @@ -5957,6 +8425,12 @@ LoadGameOneMove(readAhead) case WhiteCapturesEnPassant: case BlackCapturesEnPassant: + case WhitePromotionChancellor: + case BlackPromotionChancellor: + case WhitePromotionArchbishop: + case BlackPromotionArchbishop: + case WhitePromotionCentaur: + case BlackPromotionCentaur: case WhitePromotionQueen: case BlackPromotionQueen: case WhitePromotionRook: @@ -5984,10 +8458,10 @@ LoadGameOneMove(readAhead) /* POP Fabien */ if (appData.debugMode) fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString); - fromX = currentMoveString[0] - 'a'; - fromY = currentMoveString[1] - '1'; - toX = currentMoveString[2] - 'a'; - toY = currentMoveString[3] - '1'; + fromX = currentMoveString[0] - AAA; + fromY = currentMoveString[1] - ONE; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; break; @@ -5999,8 +8473,8 @@ LoadGameOneMove(readAhead) (int) CharToPiece(ToUpper(currentMoveString[0])) : (int) CharToPiece(ToLower(currentMoveString[0])); fromY = DROP_RANK; - toX = currentMoveString[2] - 'a'; - toY = currentMoveString[3] - '1'; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; break; case WhiteWins: @@ -6034,11 +8508,12 @@ LoadGameOneMove(readAhead) if (appData.debugMode) fprintf(debugFP, "Parser hit end of file\n"); switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN)) { + EP_UNKNOWN, castlingRights[currentMove]) ) { case MT_NONE: case MT_CHECK: break; case MT_CHECKMATE: + case MT_STAINMATE: if (WhiteOnMove(currentMove)) { GameEnds(BlackWins, "Black mates", GE_FILE); } else { @@ -6069,11 +8544,12 @@ LoadGameOneMove(readAhead) if (appData.debugMode) fprintf(debugFP, "Parsed start of next game: %s\n", yy_text); switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN)) { + EP_UNKNOWN, castlingRights[currentMove]) ) { case MT_NONE: case MT_CHECK: break; case MT_CHECKMATE: + case MT_STAINMATE: if (WhiteOnMove(currentMove)) { GameEnds(BlackWins, "Black mates", GE_FILE); } else { @@ -6099,7 +8575,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); @@ -6108,10 +8584,10 @@ LoadGameOneMove(readAhead) if (appData.debugMode) fprintf(debugFP, "Parsed %s into IllegalMove %s\n", yy_text, currentMoveString); - fromX = currentMoveString[0] - 'a'; - fromY = currentMoveString[1] - '1'; - toX = currentMoveString[2] - 'a'; - toY = currentMoveString[3] - '1'; + fromX = currentMoveString[0] - AAA; + fromY = currentMoveString[1] - ONE; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; } break; @@ -6119,7 +8595,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); @@ -6129,8 +8605,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); @@ -6142,7 +8618,7 @@ LoadGameOneMove(readAhead) if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) { DrawPosition(FALSE, boards[currentMove]); DisplayBothClocks(); - if (!appData.matchMode && commentList[currentMove] != NULL) + if (!appData.matchMode) // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } (void) StopLoadGameTimer(); @@ -6178,7 +8654,7 @@ LoadGameFromFile(filename, n, title, useList) } else { f = fopen(filename, "rb"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } @@ -6190,7 +8666,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); @@ -6219,21 +8695,22 @@ MakeRegisteredMove() thinkOutput[0] = NULLCHAR; strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]); - fromX = cmailMove[lastLoadGameNumber - 1][0] - 'a'; - fromY = cmailMove[lastLoadGameNumber - 1][1] - '1'; - toX = cmailMove[lastLoadGameNumber - 1][2] - 'a'; - toY = cmailMove[lastLoadGameNumber - 1][3] - '1'; + fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA; + fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE; + toX = cmailMove[lastLoadGameNumber - 1][2] - AAA; + toY = cmailMove[lastLoadGameNumber - 1][3] - ONE; promoChar = cmailMove[lastLoadGameNumber - 1][4]; MakeMove(fromX, fromY, toX, toY, promoChar); ShowMove(fromX, fromY, toX, toY); switch (MateTest(boards[currentMove], PosFlags(currentMove), - EP_UNKNOWN)) { + EP_UNKNOWN, castlingRights[currentMove]) ) { case MT_NONE: case MT_CHECK: break; case MT_CHECKMATE: + case MT_STAINMATE: if (WhiteOnMove(currentMove)) { GameEnds(BlackWins, "Black mates", GE_PLAYER); } else { @@ -6279,7 +8756,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) { @@ -6320,11 +8797,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) { @@ -6353,6 +8830,7 @@ LoadGame(f, gameNumber, title, useList) int numPGNTags = 0; int err; GameMode oldGameMode; + VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ if (appData.debugMode) fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode); @@ -6379,7 +8857,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 { @@ -6390,7 +8868,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; } } @@ -6402,9 +8880,8 @@ LoadGame(f, gameNumber, title, useList) yynewfile(f); - if (lg && lg->gameInfo.white && lg->gameInfo.black) { - sprintf(buf, "%s vs. %s", lg->gameInfo.white, + snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white, lg->gameInfo.black); DisplayTitle(buf); } else if (*title != NULLCHAR) { @@ -6449,7 +8926,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; @@ -6561,12 +9038,22 @@ LoadGame(f, gameNumber, title, useList) err = ParsePGNTag(yy_text, &gameInfo); if (!err) numPGNTags++; + /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */ + if(gameInfo.variant != oldVariant) { + startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */ + InitPosition(TRUE); + oldVariant = gameInfo.variant; + if (appData.debugMode) + fprintf(debugFP, "New variant %d\n", (int) oldVariant); + } + + if (gameInfo.fen != NULL) { Board initial_position; 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); @@ -6584,6 +9071,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; @@ -6619,10 +9113,10 @@ LoadGame(f, gameNumber, title, useList) } if (!matchMode) { if( appData.autoDisplayTags ) { - tags = PGNTags(&gameInfo); - TagsPopUp(tags, CmailMsg()); - free(tags); - } + tags = PGNTags(&gameInfo); + TagsPopUp(tags, CmailMsg()); + free(tags); + } } } else { /* Make something up, but don't display it now */ @@ -6640,8 +9134,8 @@ LoadGame(f, gameNumber, title, useList) if (!startedFromSetupPosition) { p = yy_text; - for (i = BOARD_SIZE - 1; i >= 0; i--) - for (j = 0; j < BOARD_SIZE; p++) + for (i = BOARD_HEIGHT - 1; i >= 0; i--) + for (j = BOARD_LEFT; j < BOARD_RGHT; p++) switch (*p) { case '[': case '-': @@ -6686,13 +9180,19 @@ LoadGame(f, gameNumber, title, useList) if (first.pr == NoProc) { StartChessProgram(&first); } - InitChessProgram(&first); + InitChessProgram(&first, FALSE); SendToProgram("force\n", &first); if (startedFromSetupPosition) { SendBoard(&first, forwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "Load Game\n"); + } DisplayBothClocks(); } + /* [HGM] server: flag to write setup moves in broadcast file as one */ + loadFlag = appData.suppressLoadMoves; + while (cm == Comment) { char *p; if (appData.debugMode) @@ -6711,7 +9211,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); @@ -6727,10 +9227,9 @@ LoadGame(f, gameNumber, title, useList) return TRUE; } - if (commentList[currentMove] != NULL) { - if (!matchMode && (pausing || appData.timeDelay != 0)) { + // [HGM] PV info: routine tests if comment empty + if (!matchMode && (pausing || appData.timeDelay != 0)) { DisplayComment(currentMove - 1, commentList[currentMove]); - } } if (!matchMode && appData.timeDelay != 0) DrawPosition(FALSE, boards[currentMove]); @@ -6771,6 +9270,8 @@ LoadGame(f, gameNumber, title, useList) if (appData.debugMode) fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode); + + loadFlag = 0; /* [HGM] true game starts */ return TRUE; } @@ -6781,11 +9282,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, @@ -6807,7 +9308,7 @@ LoadPositionFromFile(filename, n, title) } else { f = fopen(filename, "rb"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } else { @@ -6842,13 +9343,13 @@ LoadPosition(f, positionNumber, title) strcpy(lastLoadPositionTitle, title); if (first.pr == NoProc) { StartChessProgram(&first); - InitChessProgram(&first); + InitChessProgram(&first, FALSE); } pn = positionNumber; 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; @@ -6859,16 +9360,17 @@ 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 switch (line[0]) { case '#': case 'x': default: @@ -6877,18 +9379,25 @@ LoadPosition(f, positionNumber, title) case 'p': case 'n': case 'b': case 'r': case 'q': case 'k': case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K': case '1': case '2': case '3': case '4': case '5': case '6': - case '7': case '8': + case '7': case '8': case '9': + case 'H': case 'A': case 'M': case 'h': case 'a': case 'm': + case 'E': case 'F': case 'G': case 'e': case 'f': case 'g': + case 'C': case 'W': case 'c': case 'w': fenMode = TRUE; break; } +#else + // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces + fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare; +#endif 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--; @@ -6897,16 +9406,16 @@ 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 { (void) fgets(line, MSG_SIZ, f); (void) fgets(line, MSG_SIZ, f); - for (i = BOARD_SIZE - 1; i >= 0; i--) { + for (i = BOARD_HEIGHT - 1; i >= 0; i--) { (void) fgets(line, MSG_SIZ, f); - for (p = line, j = 0; j < BOARD_SIZE; p++) { + for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) { if (*p == ' ') continue; initial_position[i][j++] = CharToPiece(*p); @@ -6929,12 +9438,25 @@ LoadPosition(f, positionNumber, title) 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"); + } if (positionNumber > 1) { sprintf(line, "%s %d", title, positionNumber); @@ -7000,7 +9522,7 @@ SaveGameToFile(filename, append) } else { f = fopen(filename, append ? "a" : "w"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } else { @@ -7028,6 +9550,7 @@ SavePart(str) #define PGN_SIDE_WHITE 0 #define PGN_SIDE_BLACK 1 +/* [AS] */ static int FindFirstMoveOutOfBook( int side ) { int result = -1; @@ -7046,13 +9569,14 @@ static int FindFirstMoveOutOfBook( int side ) int score = pvInfoList[index].score; int in_book = 0; - if( depth == 0 ) { - in_book = 1; /* Yace */ + if( depth <= 2 ) { + in_book = 1; } - if( score == 0 ) { - if( depth <= 1 || depth == 63 /* Zappa */ ) { - in_book = 1; - } + else if( score == 0 && depth == 63 ) { + in_book = 1; /* Zappa */ + } + else if( score == 2 && depth == 99 ) { + in_book = 1; /* Abrok */ } has_book_hit += in_book; @@ -7070,6 +9594,7 @@ static int FindFirstMoveOutOfBook( int side ) return result; } +/* [AS] */ void GetOutOfBookInfo( char * buf ) { int oob[2]; @@ -7091,7 +9616,7 @@ void GetOutOfBookInfo( char * buf ) } sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" ); - sprintf( buf+strlen(buf), "%s%.2f", + sprintf( buf+strlen(buf), "%s%.2f", pvInfoList[idx].score >= 0 ? "+" : "", pvInfoList[idx].score / 100.0 ); } @@ -7106,19 +9631,19 @@ SaveGamePGN(f) { int i, offset, linelen, newblock; time_t tm; - char *movetext; +// char *movetext; char numtext[32]; int movelen, numlen, blank; char move_buffer[100]; /* [AS] Buffer for move+PV info */ - - offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ + offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ + tm = time((time_t *) NULL); PrintPGNTags(f, &gameInfo); if (backwardMostMove > 0 || startedFromSetupPosition) { - char *fen = PositionToFEN(backwardMostMove, 1); + char *fen = PositionToFEN(backwardMostMove, NULL); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); fprintf(f, "\n{--------------\n"); PrintPosition(f, backwardMostMove); @@ -7133,7 +9658,7 @@ SaveGamePGN(f) GetOutOfBookInfo( buf ); if( buf[0] != '\0' ) { - fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); + fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); } } @@ -7181,20 +9706,19 @@ SaveGamePGN(f) linelen += numlen; /* Get move */ - movetext = SavePart(parseList[i]); - - /* [AS] Add PV info if present */ + strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited + movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */ +#if 0 + // SavePart already does this! if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) { - sprintf( move_buffer, "%s {%s%.2f/%d}", - movetext, - pvInfoList[i].score >= 0 ? "+" : "", - pvInfoList[i].score / 100.0, - pvInfoList[i].depth ); - movetext = move_buffer; + int p = movelen - 1; + if(move_buffer[p] == ' ') p--; + if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info + while(p && move_buffer[--p] != '('); + if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0; + } } - - movelen = strlen(movetext); - +#endif /* Print move */ blank = linelen > 0 && movelen > 0; if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) { @@ -7206,9 +9730,58 @@ SaveGamePGN(f) fprintf(f, " "); linelen++; } - fprintf(f, movetext); + fprintf(f, move_buffer); linelen += movelen; + /* [AS] Add PV info if present */ + if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) { + /* [HGM] add time */ + char buf[MSG_SIZ]; int seconds = 0; + +#if 1 + if(i >= backwardMostMove) { + if(WhiteOnMove(i)) + seconds = timeRemaining[0][i] - timeRemaining[0][i+1] + + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds); + else + seconds = timeRemaining[1][i] - timeRemaining[1][i+1] + + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds); + } + seconds = (seconds+50)/100; // deci-seconds, rounded to nearest +#else + seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time +#endif + + 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%.2f/%d%s}", + pvInfoList[i].score >= 0 ? "+" : "", + pvInfoList[i].score / 100.0, + pvInfoList[i].depth, + buf ); + + movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */ + + /* 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++; } @@ -7230,6 +9803,7 @@ SaveGamePGN(f) } fclose(f); + lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving return TRUE; } @@ -7306,6 +9880,7 @@ SaveGame(f, dummy, dummy2) char *dummy2; { if (gameMode == EditPosition) EditPositionDone(); + lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving if (appData.oldSaveStyle) return SaveGameOldStyle(f); else @@ -7325,7 +9900,7 @@ SavePositionToFile(filename) } else { f = fopen(filename, "a"); if (f == NULL) { - sprintf(buf, "Can't open \"%s\"", filename); + snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename); DisplayError(buf, errno); return FALSE; } else { @@ -7354,7 +9929,7 @@ SavePosition(f, dummy, dummy2) PrintPosition(f, currentMove); fprintf(f, "--------------]\n"); } else { - fen = PositionToFEN(currentMove, 1); + fen = PositionToFEN(currentMove, NULL); fprintf(f, "%s\n", fen); free(fen); } @@ -7455,17 +10030,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; } @@ -7513,7 +10088,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; } @@ -7534,18 +10109,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; } @@ -7557,10 +10132,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); @@ -7622,7 +10197,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, "["); @@ -7645,17 +10220,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; } @@ -7663,21 +10238,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); } } @@ -7700,8 +10275,6 @@ ResetGameEvent() } } -static int exiting = 0; - void ExitEvent(status) int status; @@ -7724,8 +10297,10 @@ ExitEvent(status) if (icsPR != NoProc) { DestroyChildProcess(icsPR, TRUE); } +#if 0 /* Save game if resource set and not already saved by GameEnds() */ - if (gameInfo.resultDetails == NULL && forwardMostMove > 0) { + if ((gameInfo.resultDetails == NULL || errorExitFlag ) + && forwardMostMove > 0) { if (*appData.saveGameFile != NULLCHAR) { SaveGameToFile(appData.saveGameFile, TRUE); } else if (appData.autoSaveGames) { @@ -7736,11 +10311,22 @@ 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 ? "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! */ + if(endingGame) { int count = 0; + if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n"); + while(endingGame && count++ < 10) DoSleep(1); + if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n"); + } /* Kill off chess programs */ if (first.pr != NoProc) { ExitAnalyzeMode(); - + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); DoSleep( appData.delayAfterQuit ); @@ -7834,9 +10420,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]); } @@ -7860,17 +10446,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(); @@ -7894,8 +10482,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; @@ -7911,6 +10499,7 @@ void MachineWhiteEvent() { char buf[MSG_SIZ]; + char *bookHit = NULL; if (appData.noChessProgram || (gameMode == MachinePlaysWhite)) return; @@ -7927,7 +10516,7 @@ MachineWhiteEvent() EditPositionDone(); if (!WhiteOnMove(currentMove)) { - DisplayError("It is not White's turn", 0); + DisplayError(_("It is not White's turn"), 0); return; } @@ -7939,6 +10528,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(); @@ -7956,17 +10549,30 @@ 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(); + firstMove = FALSE; 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.nodes = programStats.depth = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); } } @@ -7974,6 +10580,7 @@ void MachineBlackEvent() { char buf[MSG_SIZ]; + char *bookHit = NULL; if (appData.noChessProgram || (gameMode == MachinePlaysBlack)) return; @@ -7990,7 +10597,7 @@ MachineBlackEvent() EditPositionDone(); if (WhiteOnMove(currentMove)) { - DisplayError("It is not Black's turn", 0); + DisplayError(_("It is not Black's turn"), 0); return; } @@ -8019,10 +10626,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(); @@ -8030,6 +10636,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.nodes = programStats.depth = programStats.time = + programStats.score = programStats.got_only_move = 0; + sprintf(programStats.movelist, "%s (xbook)", bookHit); + + strcpy(bookMove, "move "); + strcat(bookMove, bookHit); + HandleMachineMove(bookMove, &first); } } @@ -8062,6 +10680,7 @@ TwoMachinesEvent P((void)) int i; char buf[MSG_SIZ]; ChessProgramState *onmove; + char *bookHit = NULL; if (appData.noChessProgram) return; @@ -8071,7 +10690,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 */ @@ -8103,16 +10722,19 @@ 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; } DisplayMessage("", ""); - InitChessProgram(&second); + InitChessProgram(&second, FALSE); SendToProgram("force\n", &second); if (startedFromSetupPosition) { SendBoard(&second, backwardMostMove); + if (appData.debugMode) { + fprintf(debugFP, "Two Machines\n"); + } } for (i = backwardMostMove; i < forwardMostMove; i++) { SendMoveToProgram(i, &second); @@ -8141,8 +10763,8 @@ TwoMachinesEvent P((void)) SendToProgram(buf, &second); } + ResetClocks(); if (!first.sendTime || !second.sendTime) { - ResetClocks(); timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; } @@ -8155,11 +10777,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.nodes = programStats.depth = 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 @@ -8168,7 +10803,7 @@ TrainingEvent() if (gameMode == Training) { SetTrainingModeOff(); gameMode = PlayFromGameFile; - DisplayMessage("", "Training mode off"); + DisplayMessage("", _("Training mode off")); } else { gameMode = Training; animateTraining = appData.animate; @@ -8176,10 +10811,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(); @@ -8260,13 +10895,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; @@ -8336,6 +10971,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; @@ -8347,18 +10988,37 @@ ExitAnalyzeMode() void EditPositionDone() { + int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing; + startedFromSetupPosition = TRUE; - InitChessProgram(&first); + InitChessProgram(&first, FALSE); + castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1; + if(boards[0][0][BOARD_WIDTH>>1] == king) { + castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1; + castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1; + } else castlingRights[0][2] = -1; + if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) { + castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1; + castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1; + } else castlingRights[0][5] = -1; SendToProgram("force\n", &first); if (blackPlaysFirst) { strcpy(moveList[0], ""); 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= (int)WhitePawn && piece < (int)WhiteMan || + piece >= (int)BlackPawn && piece < (int)BlackMan ) { + selection = (ChessSquare) (PROMOTED piece); + } else if(piece == EmptySquare) selection = WhiteSilver; + else selection = (ChessSquare)((int)piece - 1); + goto defaultlabel; + + case DemotePiece: + if(piece > (int)WhiteMan && piece <= (int)WhiteKing || + piece > (int)BlackMan && piece <= (int)BlackKing ) { + selection = (ChessSquare) (DEMOTED piece); + } else if(piece == EmptySquare) selection = BlackSilver; + else selection = (ChessSquare)((int)piece + 1); + goto defaultlabel; + + case WhiteQueen: + case BlackQueen: + if(gameInfo.variant == VariantShatranj || + gameInfo.variant == VariantXiangqi || + gameInfo.variant == VariantCourier ) + selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz); + goto defaultlabel; + + case WhiteKing: + case BlackKing: + if(gameInfo.variant == VariantXiangqi) + selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir); + if(gameInfo.variant == VariantKnightmate) + selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn); default: + defaultlabel: if (gameMode == IcsExamining) { sprintf(buf, "%s%c@%c%c\n", ics_prefix, - PieceToChar(selection), 'a' + x, '1' + y); + PieceToChar(selection), AAA + x, ONE + y); SendToICS(buf); } else { boards[0][y][x] = selection; @@ -8515,7 +11208,7 @@ DropMenuEvent(selection, x, y) case IcsPlayingWhite: case MachinePlaysBlack: if (!WhiteOnMove(currentMove)) { - DisplayMoveError("It is Black's turn"); + DisplayMoveError(_("It is Black's turn")); return; } moveType = WhiteDrop; @@ -8523,7 +11216,7 @@ DropMenuEvent(selection, x, y) case IcsPlayingBlack: case MachinePlaysWhite: if (WhiteOnMove(currentMove)) { - DisplayMoveError("It is White's turn"); + DisplayMoveError(_("It is White's turn")); return; } moveType = BlackDrop; @@ -8540,7 +11233,7 @@ DropMenuEvent(selection, x, y) + (int) BlackPawn - (int) WhitePawn); } if (boards[currentMove][y][x] != EmptySquare) { - DisplayMoveError("That square is occupied"); + DisplayMoveError(_("That square is occupied")); return; } @@ -8564,7 +11257,7 @@ AcceptEvent() GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER); cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT; } else { - DisplayError("There is no pending offer on this move", 0); + DisplayError(_("There is no pending offer on this move"), 0); cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; } } else { @@ -8590,7 +11283,7 @@ DeclineEvent() DisplayComment(cmailOldMove - 1, "Draw declined"); #endif /*NOTDEF*/ } else { - DisplayError("There is no pending offer on this move", 0); + DisplayError(_("There is no pending offer on this move"), 0); } } else { /* Not used for offers from chess program */ @@ -8626,7 +11319,7 @@ CallFlagEvent() else GameEnds(BlackWins, "Black wins on time", GE_PLAYER); } else { - DisplayError("Your opponent is not out of time", 0); + DisplayError(_("Your opponent is not out of time"), 0); } break; case MachinePlaysBlack: @@ -8637,7 +11330,7 @@ CallFlagEvent() else GameEnds(WhiteWins, "White wins on time", GE_PLAYER); } else { - DisplayError("Your opponent is not out of time", 0); + DisplayError(_("Your opponent is not out of time"), 0); } break; } @@ -8672,7 +11365,7 @@ DrawEvent() DisplayComment(currentMove - 1, offer); cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW; } else { - DisplayError("You must make your move before offering a draw", 0); + DisplayError(_("You must make your move before offering a draw"), 0); cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE; } } else if (first.offeredDraw) { @@ -8787,15 +11480,15 @@ ForwardInner(target) if (target > 0 && moveList[target - 1][0]) { int fromX, fromY, toX, toY; - toX = moveList[target - 1][2] - 'a'; - toY = moveList[target - 1][3] - '1'; + toX = moveList[target - 1][2] - AAA; + toY = moveList[target - 1][3] - ONE; if (moveList[target - 1][1] == '@') { if (appData.highlightLastMove) { SetHighlights(-1, -1, toX, toY); } } else { - fromX = moveList[target - 1][0] - 'a'; - fromY = moveList[target - 1][1] - '1'; + fromX = moveList[target - 1][0] - AAA; + fromY = moveList[target - 1][1] - ONE; if (target == currentMove + 1) { AnimateMove(boards[currentMove], fromX, fromY, toX, toY); } @@ -8822,7 +11515,7 @@ ForwardInner(target) DisplayMove(currentMove - 1); DrawPosition(FALSE, boards[currentMove]); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); - if (commentList[currentMove] && !matchMode && gameMode != Training) { + if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty DisplayComment(currentMove - 1, commentList[currentMove]); } } @@ -8890,15 +11583,15 @@ BackwardInner(target) if (moveList[target][0]) { int fromX, fromY, toX, toY; - toX = moveList[target][2] - 'a'; - toY = moveList[target][3] - '1'; + toX = moveList[target][2] - AAA; + toY = moveList[target][3] - ONE; if (moveList[target][1] == '@') { if (appData.highlightLastMove) { SetHighlights(-1, -1, toX, toY); } } else { - fromX = moveList[target][0] - 'a'; - fromY = moveList[target][1] - '1'; + fromX = moveList[target][0] - AAA; + fromY = moveList[target][1] - ONE; if (target == currentMove - 1) { AnimateMove(boards[currentMove], toX, toY, fromX, fromY); } @@ -8925,9 +11618,8 @@ BackwardInner(target) DisplayMove(currentMove - 1); DrawPosition(full_redraw, boards[currentMove]); HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1); - if (commentList[currentMove] != NULL) { - DisplayComment(currentMove - 1, commentList[currentMove]); - } + // [HGM] PV info: routine tests if comment empty + DisplayComment(currentMove - 1, commentList[currentMove]); } void @@ -8987,11 +11679,11 @@ void RevertEvent() { if (gameMode != IcsExamining) { - DisplayError("You are not examining a game", 0); + DisplayError(_("You are not examining a game"), 0); return; } if (pausing) { - DisplayError("You can't revert while pausing", 0); + DisplayError(_("You can't revert while pausing"), 0); return; } SendToICS(ics_prefix); @@ -9005,7 +11697,7 @@ RetractMoveEvent() case MachinePlaysWhite: case MachinePlaysBlack: if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) { - DisplayError("Wait until your turn,\nor select Move Now", 0); + DisplayError(_("Wait until your turn,\nor select Move Now"), 0); return; } if (forwardMostMove < 2) return; @@ -9045,14 +11737,14 @@ MoveNowEvent() switch (gameMode) { case MachinePlaysWhite: if (!WhiteOnMove(forwardMostMove)) { - DisplayError("It is your turn", 0); + DisplayError(_("It is your turn"), 0); return; } cps = &first; break; case MachinePlaysBlack: if (WhiteOnMove(forwardMostMove)) { - DisplayError("It is your turn", 0); + DisplayError(_("It is your turn"), 0); return; } cps = &first; @@ -9102,19 +11794,19 @@ HintEvent() switch (gameMode) { case MachinePlaysWhite: if (WhiteOnMove(forwardMostMove)) { - DisplayError("Wait until your turn", 0); + DisplayError(_("Wait until your turn"), 0); return; } break; case BeginningOfGame: case MachinePlaysBlack: if (!WhiteOnMove(forwardMostMove)) { - DisplayError("Wait until your turn", 0); + DisplayError(_("Wait until your turn"), 0); return; } break; default: - DisplayError("No hint available", 0); + DisplayError(_("No hint available"), 0); return; } SendToProgram("hint\n", &first); @@ -9128,14 +11820,14 @@ BookEvent() switch (gameMode) { case MachinePlaysWhite: if (WhiteOnMove(forwardMostMove)) { - DisplayError("Wait until your turn", 0); + DisplayError(_("Wait until your turn"), 0); return; } break; case BeginningOfGame: case MachinePlaysBlack: if (!WhiteOnMove(forwardMostMove)) { - DisplayError("Wait until your turn", 0); + DisplayError(_("Wait until your turn"), 0); return; } break; @@ -9169,11 +11861,11 @@ PrintPosition(fp, move) { int i, j; - for (i = BOARD_SIZE - 1; i >= 0; i--) { - for (j = 0; j < BOARD_SIZE; j++) { + for (i = BOARD_HEIGHT - 1; i >= 0; i--) { + for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { char c = PieceToChar(boards[move][i][j]); fputc(c == 'x' ? '.' : c, fp); - fputc(j == BOARD_SIZE - 1 ? '\n' : ' ', fp); + fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp); } } if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0)) @@ -9214,6 +11906,7 @@ TidyProgramName(prog, host, buf) p = q; while (p >= prog && *p != '/' && *p != '\\') p--; p++; + if(p == prog && *p == '"') p++; if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4; memcpy(buf, p, q - p); buf[q - p] = NULLCHAR; @@ -9374,7 +12067,7 @@ AppendComment(index, text) int oldlen, len; char *old; - GetInfoFromComment( index, text ); + text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */ CrushCRs(text); while (*text == '\n') text++; @@ -9412,12 +12105,15 @@ static char * FindStr( char * text, char * sub_text ) } /* [AS] Try to extract PV info from PGN comment */ -void GetInfoFromComment( int index, char * text ) +/* [HGM] PV time: and then remove it, to prevent it appearing twice */ +char *GetInfoFromComment( int index, char * text ) { + char * sep = text; + if( text != NULL && index > 0 ) { int score = 0; int depth = 0; - int time = -1; + int time = -1, sec = 0, deci; char * s_eval = FindStr( text, "[%eval " ); char * s_emt = FindStr( text, "[%emt " ); @@ -9427,11 +12123,11 @@ void GetInfoFromComment( int index, char * text ) if( s_eval != NULL ) { if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) { - return; + return text; } if( delim != ']' ) { - return; + return text; } } @@ -9440,26 +12136,43 @@ void GetInfoFromComment( int index, char * text ) } else { /* We expect something like: [+|-]nnn.nn/dd */ - char * sep = strchr( text, '/' ); int score_lo = 0; + sep = strchr( text, '/' ); if( sep == NULL || sep < (text+4) ) { - return; + return text; } - if( sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) { - return; + 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; } if( score_lo < 0 || score_lo >= 100 ) { - return; + return text; } + if(sec >= 0) time = 600*time + 10*sec; else + if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec + score = score >= 0 ? score*100 + score_lo : score*100 - score_lo; + + /* [HGM] PV time: now locate end of PV info */ + while( *++sep >= '0' && *sep <= '9'); // strip depth + if(time >= 0) + 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++; } if( depth <= 0 ) { - return; + return text; } if( time < 0 ) { @@ -9468,8 +12181,9 @@ void 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; } void @@ -9493,9 +12207,19 @@ SendToProgram(message, cps) count = strlen(message); outCount = OutputToProcess(cps->pr, message, count, &error); - if (outCount < count && !exiting) { - sprintf(buf, "Error writing to %s chess program", cps->which); - DisplayFatalError(buf, error, 1); + 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); + 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); + } else { + gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + } + gameInfo.resultDetails = buf; + } + DisplayFatalError(buf, error, 1); } } @@ -9515,13 +12239,22 @@ 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); + } else { + gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins; + } + gameInfo.resultDetails = buf; + } RemoveInputSource(cps->isr); 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); @@ -9531,9 +12264,8 @@ ReceiveFromProgram(isr, closure, message, count, error) cps->pr = NoProc; } - DisplayFatalError(buf, error, 1); + DisplayFatalError(buf, error, 1); } - GameEnds((ChessMove) 0, NULL, GE_PLAYER); return; } @@ -9543,12 +12275,38 @@ ReceiveFromProgram(isr, closure, message, count, error) *end_str = NULLCHAR; if (appData.debugMode) { - TimeMark now; - GetTimeMark(&now); - fprintf(debugFP, "%ld <%-6s: %s\n", - SubtractTimeMarks(&now, &programStartTime), - cps->which, message); + TimeMark now; int print = 1; + char *quote = ""; char c; int i; + + if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */ + char start = message[0]; + if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing + if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && + sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 && + 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, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && + sscanf(message, "pong %c", &c)!=1 && start != '#') + { quote = "# "; print = (appData.engineComments == 2); } + message[0] = start; // restore original message + } + if(print) { + GetTimeMark(&now); + fprintf(debugFP, "%ld <%-6s: %s%s\n", + SubtractTimeMarks(&now, &programStartTime), cps->which, + quote, + 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); } @@ -9560,13 +12318,18 @@ SendTimeControl(cps, mps, tc, inc, sd, st) long tc; { char buf[MSG_SIZ]; - int seconds = (tc / 1000) % 60; + int seconds; if( timeControl_2 > 0 ) { if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) { tc = timeControl_2; } } + tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */ + inc /= cps->timeOdds; + st /= cps->timeOdds; + + seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */ if (st > 0) { /* Set exact time per move, normally using st command */ @@ -9605,6 +12368,23 @@ SendTimeControl(cps, mps, tc, inc, sd, st) } SendToProgram(buf, cps); } + + if(cps->nps > 0) { /* [HGM] nps */ + if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported! + else { + sprintf(buf, "nps %d\n", cps->nps); + SendToProgram(buf, cps); + } + } +} + +ChessProgramState *WhitePlayer() +/* [HGM] return pointer to 'first' or 'second', depending on who plays white */ +{ + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || + gameMode == BeginningOfGame || gameMode == MachinePlaysBlack) + return &second; + return &first; } void @@ -9626,10 +12406,19 @@ SendTimeRemaining(cps, machineWhite) time = blackTimeRemaining / 10; otime = whiteTimeRemaining / 10; } + /* [HGM] translate opponent's time by time-odds factor */ + otime = (otime * cps->other->timeOdds) / cps->timeOdds; + if (appData.debugMode) { + fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds); + } + if (time <= 0) time = 1; if (otime <= 0) otime = 1; - sprintf(message, "time %ld\notim %ld\n", time, otime); + sprintf(message, "time %ld\n", time); + SendToProgram(message, cps); + + sprintf(message, "otim %ld\n", otime); SendToProgram(message, cps); } @@ -9697,6 +12486,83 @@ 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, " -slider "))) { + // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots + if((n = sscanf(p, " -slider %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; // Slider; + } else if((p = strstr(opt->name, " -string "))) { + opt->textValue = p+9; + opt->type = TextBox; + } else if((p = strstr(opt->name, " -file "))) { + // for now -file is a synonym for -string, to already provide compatibility with future polyglots + opt->textValue = p+7; + opt->type = TextBox; // FileName; + } else if((p = strstr(opt->name, " -path "))) { + // for now -file is a synonym for -string, to already provide compatibility with future polyglots + opt->textValue = p+7; + opt->type = TextBox; // PathName; + } 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 + if(*q == '*') cps->comboList[cps->comboCnt-1]++; + 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 + // now look if the command-line options define a setting for this engine option. + if(cps->optionSettings && cps->optionSettings[0]) + p = strstr(cps->optionSettings, opt->name); else p = NULL; + if(p && (p == cps->optionSettings || p[-1] == ',')) { + sprintf(buf, "option %s", p); + if(p = strstr(buf, ",")) *p = 0; + strcat(buf, "\n"); + SendToProgram(buf, cps); + } + return TRUE; +} + void FeatureDone(cps, val) ChessProgramState* cps; @@ -9763,6 +12629,29 @@ 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)) { + if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature + sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name); + SendToProgram(buf, cps); + continue; + } + 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++; @@ -9824,12 +12713,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); @@ -9841,7 +12751,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 @@ -9861,13 +12771,15 @@ DisplayMove(moveNumber) char res[MSG_SIZ]; char cpThinkOutput[MSG_SIZ]; + if(appData.noGUI) return; // [HGM] fast: suppress display of moves + if (moveNumber == forwardMostMove - 1 || gameMode == AnalyzeMode || gameMode == AnalyzeFile) { safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)); if (strchr(cpThinkOutput, '\n')) { - *strchr(cpThinkOutput, '\n') = NULLCHAR; + *strchr(cpThinkOutput, '\n') = NULLCHAR; } } else { *cpThinkOutput = NULLCHAR; @@ -9898,7 +12810,7 @@ DisplayMove(moveNumber) } else { res[0] = NULLCHAR; } - + if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { DisplayMessage(res, cpThinkOutput); } else { @@ -9915,7 +12827,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); } @@ -9951,8 +12864,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; @@ -9963,32 +12876,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); @@ -10000,18 +12913,29 @@ DisplayComment(moveNumber, text) char *text; { char title[MSG_SIZ]; + char buf[8000]; // comment can be long! + int score, depth; if( appData.autoDisplayComment ) { - if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { - strcpy(title, "Comment"); - } else { - sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1, - WhiteOnMove(moveNumber) ? " " : ".. ", - parseList[moveNumber]); - } + if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) { + strcpy(title, "Comment"); + } else { + sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1, + WhiteOnMove(moveNumber) ? " " : ".. ", + parseList[moveNumber]); + } + // [HGM] PV info: display PV info together with (or as) comment + if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) { + if(text == NULL) text = ""; + score = pvInfoList[moveNumber].score; + sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100., + depth, (pvInfoList[moveNumber].time+50)/100, text); + text = buf; + } + } else title[0] = 0; - CommentPopUp(title, text); - } + if (text != NULL) + CommentPopUp(title, text); } /* This routine sends a ^C interrupt to gnuchess, to awaken it if it @@ -10062,9 +12986,9 @@ CheckFlags() } } else { if (blackFlag) { - DisplayTitle("Both flags fell"); + if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell")); } else { - 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; @@ -10084,9 +13008,9 @@ CheckFlags() } } else { if (whiteFlag) { - DisplayTitle("Both flags fell"); + if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell")); } else { - 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; @@ -10104,30 +13028,18 @@ CheckTimeControl() if (!appData.clockMode || appData.icsActive || gameMode == PlayFromGameFile || forwardMostMove == 0) return; - if (timeIncrement >= 0) { - if (WhiteOnMove(forwardMostMove)) { - blackTimeRemaining += timeIncrement; - } else { - whiteTimeRemaining += timeIncrement; - } - } /* - * add time to clocks when time control is achieved + * add time to clocks when time control is achieved ([HGM] now also used for increment) */ - if (movesPerSession) { - switch ((forwardMostMove + 1) % (movesPerSession * 2)) { - case 0: + if ( !WhiteOnMove(forwardMostMove) ) /* White made time control */ - whiteTimeRemaining += GetTimeControlForWhite(); - break; - case 1: + whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2) + /* [HGM] time odds: correct new time quota for time odds! */ + / WhitePlayer()->timeOdds; + else /* Black made time control */ - blackTimeRemaining += GetTimeControlForBlack(); - break; - default: - break; - } - } + blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2) + / WhitePlayer()->other->timeOdds; } void @@ -10146,6 +13058,11 @@ DisplayBothClocks() you have neither ftime nor gettimeofday. */ +/* VS 2008 requires the #include outside of the function */ +#if !HAVE_GETTIMEOFDAY && HAVE_FTIME +#include +#endif + /* Get the current time as a TimeMark */ void GetTimeMark(tm) @@ -10163,7 +13080,7 @@ GetTimeMark(tm) #else /*!HAVE_GETTIMEOFDAY*/ #if HAVE_FTIME -#include +// include / moved to just above start of function struct timeb timeB; ftime(&timeB); @@ -10218,6 +13135,15 @@ NextTickLength(timeRemaining) return nextTickLength; } +/* Adjust clock one minute up or down */ +void +AdjustClock(Boolean which, int dir) +{ + if(which) blackTimeRemaining += 60000*dir; + else whiteTimeRemaining += 60000*dir; + DisplayBothClocks(); +} + /* Stop clocks and reset to a fresh time control */ void ResetClocks() @@ -10225,9 +13151,9 @@ ResetClocks() (void) StopClockTimer(); if (appData.icsActive) { whiteTimeRemaining = blackTimeRemaining = 0; - } else { - whiteTimeRemaining = GetTimeControlForWhite(); - blackTimeRemaining = GetTimeControlForBlack(); + } else { /* [HGM] correct new time quote for time odds */ + whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds; + blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds; } if (whiteFlag || blackFlag) { DisplayTitle(""); @@ -10258,10 +13184,12 @@ DecrementClocks() if (fudge < 0 || fudge > FUDGE) fudge = 0; if (WhiteOnMove(forwardMostMove)) { + if(whiteNPS >= 0) lastTickLength = 0; timeRemaining = whiteTimeRemaining -= lastTickLength; DisplayWhiteClock(whiteTimeRemaining - fudge, WhiteOnMove(currentMove)); } else { + if(blackNPS >= 0) lastTickLength = 0; timeRemaining = blackTimeRemaining -= lastTickLength; DisplayBlackClock(blackTimeRemaining - fudge, !WhiteOnMove(currentMove)); @@ -10316,9 +13244,19 @@ SwitchClocks() if (StopClockTimer() && appData.clockMode) { lastTickLength = SubtractTimeMarks(&now, &tickStartTM); if (WhiteOnMove(forwardMostMove)) { + 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 = // use GUI time + (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10; } else { - whiteTimeRemaining -= lastTickLength; + 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) + pvInfoList[forwardMostMove-1].time = + (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10; } flagged = CheckFlags(); } @@ -10363,9 +13301,11 @@ StopClocks() lastTickLength = SubtractTimeMarks(&now, &tickStartTM); if (WhiteOnMove(forwardMostMove)) { + if(whiteNPS >= 0) lastTickLength = 0; whiteTimeRemaining -= lastTickLength; DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove)); } else { + if(blackNPS >= 0) lastTickLength = 0; blackTimeRemaining -= lastTickLength; DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove)); } @@ -10387,6 +13327,21 @@ StartClocks() GetTimeMark(&tickStartTM); intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ? whiteTimeRemaining : blackTimeRemaining); + + /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */ + whiteNPS = blackNPS = -1; + if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w' + || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white + whiteNPS = first.nps; + if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' + || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black + blackNPS = first.nps; + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode + whiteNPS = second.nps; + if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w') + blackNPS = second.nps; + if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS); + StartClockTimer(intendedTickLength); } @@ -10546,145 +13501,160 @@ PGNDate() char * -PositionToFEN(move, useFEN960) +PositionToFEN(move, overrideCastling) int move; - int useFEN960; + char *overrideCastling; { int i, j, fromX, fromY, toX, toY; int whiteToPlay; char buf[128]; char *p, *q; int emptycount; + ChessSquare piece; whiteToPlay = (gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0); p = buf; /* Piece placement data */ - for (i = BOARD_SIZE - 1; i >= 0; i--) { + for (i = BOARD_HEIGHT - 1; i >= 0; i--) { emptycount = 0; - for (j = 0; j < BOARD_SIZE; j++) { + for (j = BOARD_LEFT; j < BOARD_RGHT; j++) { if (boards[move][i][j] == EmptySquare) { emptycount++; - } else { + } else { ChessSquare piece = boards[move][i][j]; if (emptycount > 0) { - *p++ = '0' + emptycount; + if(emptycount<10) /* [HGM] can be >= 10 */ + *p++ = '0' + emptycount; + else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; } emptycount = 0; } - *p++ = PieceToChar(boards[move][i][j]); + if(PieceToChar(piece) == '+') { + /* [HGM] write promoted pieces as '+' (Shogi) */ + *p++ = '+'; + piece = (ChessSquare)(DEMOTED piece); + } + *p++ = PieceToChar(piece); + if(p[-1] == '~') { + /* [HGM] flag promoted pieces as '~' (Crazyhouse) */ + p[-1] = PieceToChar((ChessSquare)(DEMOTED piece)); + *p++ = '~'; + } } } if (emptycount > 0) { - *p++ = '0' + emptycount; + if(emptycount<10) /* [HGM] can be >= 10 */ + *p++ = '0' + emptycount; + else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; } emptycount = 0; } *p++ = '/'; } *(p - 1) = ' '; - /* Active color */ - *p++ = whiteToPlay ? 'w' : 'b'; - *p++ = ' '; - - /* HACK: we don't keep track of castling availability, so fake it! */ - - /* PUSH Fabien & Tord */ - - /* Declare all potential FRC castling rights (conservative) */ - /* outermost rook on each side of the king */ - - if( gameInfo.variant == VariantFischeRandom ) { - int fk, fr; - - q = p; - - /* White castling rights */ - - for (fk = 1; fk < 7; fk++) { - - if (boards[move][0][fk] == WhiteKing) { - - for (fr = 7; fr > fk; fr--) { /* H side */ - if (boards[move][0][fr] == WhiteRook) { - *p++ = useFEN960 ? 'A' + fr : 'K'; - break; - } - } - - for (fr = 0; fr < fk; fr++) { /* A side */ - if (boards[move][0][fr] == WhiteRook) { - *p++ = useFEN960 ? 'A' + fr : 'Q'; - break; - } - } - } - } - - /* Black castling rights */ - - for (fk = 1; fk < 7; fk++) { - - if (boards[move][7][fk] == BlackKing) { - - for (fr = 7; fr > fk; fr--) { /* H side */ - if (boards[move][7][fr] == BlackRook) { - *p++ = useFEN960 ? 'a' + fr : 'k'; - break; - } - } - - for (fr = 0; fr < fk; fr++) { /* A side */ - if (boards[move][7][fr] == BlackRook) { - *p++ = useFEN960 ? 'a' + fr : 'q'; - break; - } - } - } - } + /* [HGM] print Crazyhouse or Shogi holdings */ + if( gameInfo.holdingsWidth ) { + *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */ + q = p; + for(i=0; i= 0 && + castlingRights[move][0] >= 0 ) + *p++ = castlingRights[move][0] + AAA + 'A' - 'a'; + if(castlingRights[move][2] >= 0 && + castlingRights[move][1] >= 0 ) + *p++ = castlingRights[move][1] + AAA + 'A' - 'a'; + if(castlingRights[move][5] >= 0 && + castlingRights[move][3] >= 0 ) + *p++ = castlingRights[move][3] + AAA; + if(castlingRights[move][5] >= 0 && + castlingRights[move][4] >= 0 ) + *p++ = castlingRights[move][4] + AAA; + } else { + + /* [HGM] write true castling rights */ + if( nrCastlingRights == 6 ) { + if(castlingRights[move][0] == BOARD_RGHT-1 && + castlingRights[move][2] >= 0 ) *p++ = 'K'; + if(castlingRights[move][1] == BOARD_LEFT && + castlingRights[move][2] >= 0 ) *p++ = 'Q'; + if(castlingRights[move][3] == BOARD_RGHT-1 && + castlingRights[move][5] >= 0 ) *p++ = 'k'; + if(castlingRights[move][4] == BOARD_LEFT && + castlingRights[move][5] >= 0 ) *p++ = 'q'; + } + } + if (q == p) *p++ = '-'; /* No castling rights */ + *p++ = ' '; + } + if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi && + gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { /* En passant target square */ if (move > backwardMostMove) { - fromX = moveList[move - 1][0] - 'a'; - fromY = moveList[move - 1][1] - '1'; - toX = moveList[move - 1][2] - 'a'; - toY = moveList[move - 1][3] - '1'; - if (fromY == (whiteToPlay ? 6 : 1) && - toY == (whiteToPlay ? 4 : 3) && + fromX = moveList[move - 1][0] - AAA; + fromY = moveList[move - 1][1] - ONE; + toX = moveList[move - 1][2] - AAA; + toY = moveList[move - 1][3] - ONE; + if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) && + toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) && boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) && fromX == toX) { /* 2-square pawn move just happened */ - *p++ = toX + 'a'; - *p++ = whiteToPlay ? '6' : '3'; + *p++ = toX + AAA; + *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3'; } else { *p++ = '-'; } } else { *p++ = '-'; } + *p++ = ' '; + } + } - /* We don't keep track of halfmove clock for 50-move rule */ - strcpy(p, " 0 "); - p += 3; + /* [HGM] find reversible plies */ + { int i = 0, j=move; + if (appData.debugMode) { int k; + fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove); + for(k=backwardMostMove; k<=forwardMostMove; k++) + fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]); + + } + + while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++; + if( j == backwardMostMove ) i += initialRulePlies; + sprintf(p, "%d ", i); + p += i>=100 ? 4 : i >= 10 ? 3 : 2; + } /* Fullmove number */ sprintf(p, "%d", (move / 2) + 1); @@ -10693,32 +13663,66 @@ PositionToFEN(move, useFEN960) Boolean ParseFEN(board, blackPlaysFirst, fen) - Board board; + Board board; int *blackPlaysFirst; char *fen; { int i, j; char *p; int emptycount; + ChessSquare piece; p = fen; + /* [HGM] by default clear Crazyhouse holdings, if present */ + if(gameInfo.holdingsWidth) { + for(i=0; i= 0; i--) { + for (i = BOARD_HEIGHT - 1; i >= 0; i--) { j = 0; for (;;) { - if (*p == '/' || *p == ' ') { - if (*p == '/') p++; - emptycount = BOARD_SIZE - j; - while (emptycount--) board[i][j++] = EmptySquare; + if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) { + if (*p == '/') p++; + emptycount = gameInfo.boardWidth - j; + while (emptycount--) + board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; break; - } else if (isdigit(*p)) { +#if(BOARD_SIZE >= 10) + } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */ + p++; emptycount=10; + if (j + emptycount > gameInfo.boardWidth) return FALSE; + while (emptycount--) + board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; +#endif + } else if (isdigit(*p)) { emptycount = *p++ - '0'; - if (j + emptycount > BOARD_SIZE) return FALSE; - while (emptycount--) board[i][j++] = EmptySquare; - } else if (isalpha(*p)) { - if (j >= BOARD_SIZE) return FALSE; - board[i][j++] = CharToPiece(*p++); + while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */ + if (j + emptycount > gameInfo.boardWidth) return FALSE; + while (emptycount--) + board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare; + } else if (*p == '+' || isalpha(*p)) { + if (j >= gameInfo.boardWidth) return FALSE; + if(*p=='+') { + piece = CharToPiece(*++p); + if(piece == EmptySquare) return FALSE; /* unknown piece */ + piece = (ChessSquare) (PROMOTED piece ); p++; + if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */ + } else piece = CharToPiece(*p++); + + if(piece==EmptySquare) return FALSE; /* unknown piece */ + if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */ + piece = (ChessSquare) (PROMOTED piece); + if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */ + p++; + } + board[i][(j++)+gameInfo.holdingsWidth] = piece; } else { return FALSE; } @@ -10726,10 +13730,40 @@ ParseFEN(board, blackPlaysFirst, fen) } while (*p == '/' || *p == ' ') p++; + /* [HGM] look for Crazyhouse holdings here */ + while(*p==' ') p++; + if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') { + if(*p == '[') p++; + if(*p == '-' ) *p++; /* empty holdings */ else { + if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */ + /* if we would allow FEN reading to set board size, we would */ + /* have to add holdings and shift the board read so far here */ + while( (piece = CharToPiece(*p) ) != EmptySquare ) { + *p++; + if((int) piece >= (int) BlackPawn ) { + i = (int)piece - (int)BlackPawn; + 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; + 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 */ + } + } + } + if(*p == ']') *p++; + } + + while(*p == ' ') p++; + /* Active color */ - switch (*p) { + switch (*p++) { case 'w': - *blackPlaysFirst = FALSE; + *blackPlaysFirst = FALSE; break; case 'b': *blackPlaysFirst = TRUE; @@ -10738,7 +13772,120 @@ ParseFEN(board, blackPlaysFirst, fen) return FALSE; } - /* !!We ignore the rest of the FEN notation */ + /* [HGM] We NO LONGER ignore the rest of the FEN notation */ + /* return the extra info in global variiables */ + + /* set defaults in case FEN is incomplete */ + FENepStatus = EP_UNKNOWN; + for(i=0; i=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1; + if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1; + if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1; + if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1; + if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1; + if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1; + FENrulePlies = 0; + + while(*p==' ') p++; + if(nrCastlingRights) { + if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') { + /* castling indicator present, so default becomes no castlings */ + for(i=0; i= 'a' && *p < 'a' + gameInfo.boardWidth) || + ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) { + char c = *p++; int whiteKingFile=-1, blackKingFile=-1; + + for(i=BOARD_LEFT; iwhiteKingFile; i--); + FENcastlingRights[0] = i != whiteKingFile ? i : -1; + FENcastlingRights[2] = whiteKingFile; + break; + case'Q': + for(i=BOARD_LEFT; board[0][i]!=WhiteRook && iblackKingFile; i--); + FENcastlingRights[3] = i != blackKingFile ? i : -1; + FENcastlingRights[5] = blackKingFile; + break; + case'q': + for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i= 'a') { /* black rights */ + for(i=BOARD_LEFT; i= BlackKing ) break; + if(c > i) + FENcastlingRights[3] = c; + else + FENcastlingRights[4] = c; + } else { /* white rights */ + for(i=BOARD_LEFT; i= WhiteKing) break; + if(c > i) + FENcastlingRights[0] = c; + else + FENcastlingRights[1] = c; + } + } + } + if (appData.debugMode) { + fprintf(debugFP, "FEN castling rights:"); + for(i=0; i= BOARD_RGHT) return TRUE; + if(*p >= '0' && *p <='9') *p++; + FENepStatus = c; + } + } + + + if(sscanf(p, "%d", &i) == 1) { + FENrulePlies = i; /* 50-move ply counter */ + /* (The move number is still ignored) */ + } + return TRUE; } @@ -10749,13 +13896,20 @@ 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; EditPositionEvent(); blackPlaysFirst = savedBlackPlaysFirst; CopyBoard(boards[0], initial_position); + /* [HGM] copy FEN attributes as well */ + { int i; + initialRulePlies = FENrulePlies; + epStatus[0] = FENepStatus; + for( i=0; i