X-Git-Url: http://winboard.nl/cgi-bin?p=xboard.git;a=blobdiff_plain;f=backend.c;h=e60830754a948cc1d7d972896ae825c2ea1b2bfb;hp=84937ae62209a5ea7a0b9445ce1c489182680af3;hb=ca99bd4de57d0b079024cbdf5435de1ae61d5fd9;hpb=0b915ff9a9bf2c51ddd8e8c978125fffa3b2865c diff --git a/backend.c b/backend.c old mode 100644 new mode 100755 index 84937ae..e608307 --- a/backend.c +++ b/backend.c @@ -1,9 +1,13 @@ /* * backend.c -- Common back end for X and Windows NT versions of - * XBoard $Id$ * - * 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,38 +31,53 @@ * 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 +#include + +#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) ); + +#else + +#define DoSleep( n ) if( (n) >= 0) sleep(n) + +#endif #include "config.h" +#include #include #include #include #include #include #include +#include #if STDC_HEADERS # include # include +# include #else /* not STDC_HEADERS */ # if HAVE_STRING_H # include @@ -107,15 +126,15 @@ 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 +#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 */ @@ -124,33 +143,16 @@ 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 */ - u64 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)); void read_from_ics P((InputSourceRef isr, VOIDSTAR closure, char *buf, int count, int error)); +void ics_printf P((char *format, ...)); void SendToICS P((char *s)); void SendToICSDelayed P((char *s, long msdelay)); void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY)); -void InitPosition P((int redraw)); void HandleMachineMove P((char *message, ChessProgramState *cps)); int AutoPlayOneMove P((void)); int LoadGameOneMove P((ChessMove readAhead)); @@ -158,10 +160,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)); @@ -182,7 +184,6 @@ void FeedMovesToProgram P((ChessProgramState *cps, int upto)); void ResurrectChessProgram P((void)); void DisplayComment P((int moveNumber, char *text)); void DisplayMove P((int moveNumber)); -void DisplayAnalysis P((void)); void ParseGameHistory P((char *game)); void ParseBoard12 P((char *string)); @@ -211,15 +212,41 @@ 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)); -double u64ToDouble P((u64 value)); +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); #ifdef WIN32 - extern void ConsoleCreate(); + 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 +void ics_update_width P((int new_width)); +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 +262,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 @@ -260,6 +289,18 @@ static ChessProgramStats programStats; #define TN_SGA 0003 #define TN_PORT 23 +/* [AS] */ +static char * safeStrCpy( char * dst, const char * src, size_t count ) +{ + assert( dst != NULL ); + assert( src != NULL ); + assert( count > 0 ); + + strncpy( dst, src, count ); + dst[ count-1 ] = '\0'; + return dst; +} + /* Some compiler can't cast u64 to double * This function do the job for us: @@ -276,12 +317,17 @@ u64ToDouble(u64 value) u64 tmp = value & u64Const(0x7fffffffffffffff); r = (double)(s64)tmp; if (value & u64Const(0x8000000000000000)) - r += 9.2233720368547758080e18; /* 2^63 */ + 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) { @@ -289,9 +335,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; @@ -299,7 +347,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: @@ -310,6 +363,15 @@ 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 + set to 4K for Windows and 8K for the rest. + So, we must either allocate sufficiently large buffers here, or + reduce the size of the input buffer in the input reading part. +*/ + char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ]; char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ]; char thinkOutput1[MSG_SIZ*10]; @@ -348,12 +410,18 @@ InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL; GameMode gameMode = BeginningOfGame; char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2]; char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES]; +ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */ +int hiddenThinkOutputState = 0; /* [AS] */ +int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */ +int adjudicateLossPlies = 6; char white_holding[64], black_holding[64]; TimeMark lastNodeCountTime; long lastNodeCount=0; 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; @@ -372,45 +440,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) @@ -432,7 +592,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; @@ -444,7 +604,10 @@ InitBackEnd1() { int matched, min, sec; + ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options + GetTimeMark(&programStartTime); + srandom(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level ClearProgramStats(); programStats.ok_to_send = 1; @@ -462,7 +625,7 @@ InitBackEnd1() if (appData.icsActive) { appData.matchMode = FALSE; appData.matchGames = 0; -#if ZIPPY +#if ZIPPY appData.noChessProgram = !appData.zippyPlay; #else appData.zippyPlay = FALSE; @@ -477,13 +640,24 @@ InitBackEnd1() appData.zippyTalk = appData.zippyPlay = FALSE; } + /* [AS] Initialize pv info list [HGM] and game state */ + { + 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] */ + first.isUCI = appData.firstIsUCI; /* [AS] */ + second.isUCI = appData.secondIsUCI; /* [AS] */ + first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */ + second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */ + if (appData.firstProtocolVersion > PROTOVER || appData.firstProtocolVersion < 1) { char buf[MSG_SIZ]; @@ -580,7 +798,7 @@ InitBackEnd1() appData.clockMode = FALSE; first.sendTime = second.sendTime = 0; } - + #if ZIPPY /* Override some settings from environment variables, for backward compatibility. Unfortunately it's not feasible to have the env @@ -590,21 +808,14 @@ InitBackEnd1() ZippyInit(); } #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 { - 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); - strncat(programVersion, p, q - p); + /* [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); } if (!appData.icsActive) { @@ -617,7 +828,7 @@ InitBackEnd1() switch (variant) { case VariantBughouse: /* need four players and two boards */ case VariantKriegspiel: /* need to hide pieces and move details */ - case VariantFischeRandom: /* castling doesn't work, shuffle not done */ + /* case VariantFischeRandom: (Fabien: moved below) */ sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant); DisplayFatalError(buf, 0, 2); return; @@ -637,11 +848,21 @@ InitBackEnd1() 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 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, @@ -651,10 +872,107 @@ 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 ) +{ + int result = -1; + char * s = *str; + + while( *s == ' ' || *s == '\t' ) { + s++; + } + + *value = 0; + + if( *s >= '0' && *s <= '9' ) { + while( *s >= '0' && *s <= '9' ) { + *value = *value * 10 + (*s - '0'); + s++; + } + + result = 0; + } + + *str = s; + + return result; +} + +int NextTimeControlFromString( char ** str, long * value ) +{ + long temp; + int result = NextIntegerFromString( str, &temp ); + + if( result == 0 ) { + *value = temp * 60; /* Minutes */ + if( **str == ':' ) { + (*str)++; + result = NextIntegerFromString( str, &temp ); + *value += temp; /* Seconds */ + } + } + + return result; +} + +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 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(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 0; // no new time quota on this move } int @@ -663,25 +981,58 @@ ParseTimeControl(tc, ti, mps) int ti; int mps; { - int matched, min, sec; - - matched = sscanf(tc, "%d:%d", &min, &sec); - if (matched == 1) { - timeControl = min * 60 * 1000; - } else if (matched == 2) { - timeControl = (min * 60 + sec) * 1000; - } else { - return FALSE; + 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; + } + + if( *tc == '/' ) { + /* Parse second time control */ + tc++; + + if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) { + return FALSE; } - - if (ti >= 0) { - timeIncrement = ti * 1000; /* convert to ms */ - movesPerSession = 0; - } else { - timeIncrement = 0; - movesPerSession = mps; + + if( tc2 == 0 ) { + return FALSE; } - return TRUE; + + timeControl_2 = tc2 * 1000; + } + else { + timeControl_2 = 0; + } + + if( tc1 == 0 ) { + return FALSE; + } + + timeControl = tc1 * 1000; + + if (ti >= 0) { + timeIncrement = ti * 1000; /* convert to ms */ + movesPerSession = 0; + } else { + timeIncrement = 0; + movesPerSession = mps; + } + return TRUE; } void @@ -691,18 +1042,25 @@ InitBackEnd2() fprintf(debugFP, "%s\n", programVersion); } + set_cont_sequence(appData.wrapContSeq); if (appData.matchGames > 0) { appData.matchMode = TRUE; } 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); } } @@ -714,21 +1072,21 @@ InitBackEnd3 P((void)) char buf[MSG_SIZ]; int err; - InitChessProgram(&first); + InitChessProgram(&first, startedFromSetupPosition); - #ifdef WIN32 - /* Make a console window if needed */ - if (appData.icsActive) ConsoleCreate(); - #endif 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); @@ -751,7 +1109,7 @@ InitBackEnd3 P((void)) cmailISR = AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR); } - + ThawUI(); DisplayMessage("", ""); if (StrCaseCmp(appData.initialMode, "") == 0) { @@ -759,7 +1117,7 @@ InitBackEnd3 P((void)) } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) { initialMode = TwoMachinesPlay; } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) { - initialMode = AnalyzeFile; + initialMode = AnalyzeFile; } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) { initialMode = AnalyzeMode; } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) { @@ -788,15 +1146,19 @@ InitBackEnd3 P((void)) 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); 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); return; @@ -822,6 +1184,19 @@ 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= 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; + + 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; + case VariantGreat: + newWidth = 10; + case VariantSuper: + newHoldingsWidth = 2; + gameInfo.holdingsSize = 8; + break; + case VariantGothic: + case VariantCapablanca: + case VariantCapaRandom: + newWidth = 10; + 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. */ @@ -1508,17 +2087,71 @@ read_from_ics(isr, closure, data, count, error) buf[i] = buf[leftover_start + i]; } - /* Copy in new characters, removing nulls and \r's */ - buf_len = leftover_len; - for (i = 0; i < count; i++) { - if (data[i] != NULLCHAR && data[i] != '\r') - buf[buf_len++] = data[i]; - } + /* copy new characters into the buffer */ + bp = buf + leftover_len; + buf_len=leftover_len; + for (i=0; i=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 */ @@ -1719,7 +2385,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; } @@ -1743,32 +2409,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 ") || looking_at(buf, &i, "<12> "))) { loggedOn = TRUE; @@ -1995,7 +2729,7 @@ read_from_ics(isr, closure, data, count, error) moves and soak them up so user can step through them and/or save them. */ - Reset(FALSE, TRUE); + Reset(TRUE, TRUE); gameMode = IcsObserving; ModeHighlight(); ics_gamenum = -1; @@ -2040,7 +2774,7 @@ read_from_ics(isr, closure, data, count, error) gameInfo.whiteRating = string_to_rating(star_match[1]); gameInfo.blackRating = string_to_rating(star_match[3]); if (appData.debugMode) - fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), + fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), gameInfo.whiteRating, gameInfo.blackRating); } continue; @@ -2050,11 +2784,11 @@ read_from_ics(isr, closure, data, count, error) "* * match, initial time: * minute*, increment: * second")) { /* Header for a move list -- second line */ /* Initial board will follow if this is a wild game */ - if (gameInfo.event != NULL) free(gameInfo.event); sprintf(str, "ICS %s %s match", star_match[0], star_match[1]); gameInfo.event = StrSave(str); - gameInfo.variant = StringToVariant(gameInfo.event); + /* [HGM] we switched variant. Translate boards if needed. */ + VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event)); continue; } @@ -2094,11 +2828,11 @@ read_from_ics(isr, closure, data, count, error) break; } continue; - } - + } + if (looking_at(buf, &i, "% ") || ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE) - && looking_at(buf, &i, "}*"))) { + && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book savingComment = FALSE; switch (started) { case STARTED_MOVES: @@ -2113,15 +2847,14 @@ read_from_ics(isr, closure, data, count, error) if (WhiteOnMove(forwardMostMove)) { if (first.sendTime) { if (first.useColors) { - SendToProgram("black\n", &first); + SendToProgram("black\n", &first); } SendTimeRemaining(&first, TRUE); } if (first.useColors) { - SendToProgram("white\ngo\n", &first); - } else { - SendToProgram("go\n", &first); + SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent } + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos first.maybeThinking = TRUE; } else { if (first.usePlayother) { @@ -2143,10 +2876,9 @@ read_from_ics(isr, closure, data, count, error) SendTimeRemaining(&first, FALSE); } if (first.useColors) { - SendToProgram("black\ngo\n", &first); - } else { - SendToProgram("go\n", &first); + SendToProgram("black\n", &first); } + bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); first.maybeThinking = TRUE; } else { if (first.usePlayother) { @@ -2159,7 +2891,7 @@ read_from_ics(isr, closure, data, count, error) firstMove = TRUE; } } - } + } } #endif if (gameMode == IcsObserving && ics_gamenum == -1) { @@ -2199,16 +2931,27 @@ read_from_ics(isr, closure, data, count, error) default: break; } + if(bookHit) { // [HGM] book: simulate book reply + static char bookMove[MSG_SIZ]; // a bit generous? + + programStats.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); + } continue; } - + if ((started == STARTED_MOVES || started == STARTED_BOARD || started == STARTED_HOLDINGS || started == STARTED_MOVES_NOHIDE) && i >= leftover_len) { /* Accumulate characters in move list or board */ parse[parse_pos++] = buf[i]; } - + /* Start of game messages. Mostly we detect start of game when the first board image arrives. On some versions of the ICS, though, we need to do a "refresh" after starting @@ -2241,7 +2984,7 @@ read_from_ics(isr, closure, data, count, error) player2Rating = string_to_rating(star_match[3]); if (appData.debugMode) - fprintf(debugFP, + fprintf(debugFP, "Ratings from 'Game notification:' %s %d, %s %d\n", player1Name, player1Rating, player2Name, player2Rating); @@ -2271,25 +3014,26 @@ read_from_ics(isr, closure, data, count, error) SendToICS("refresh\n"); } continue; - } - + } + /* Error messages */ - if (ics_user_moved) { +// if (ics_user_moved) { + if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then! if (looking_at(buf, &i, "Illegal move") || looking_at(buf, &i, "Not a legal move") || looking_at(buf, &i, "Your king is in check") || looking_at(buf, &i, "It isn't your turn") || looking_at(buf, &i, "It is not your move")) { /* Illegal move */ - ics_user_moved = 0; - if (forwardMostMove > 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; } } @@ -2334,7 +3078,7 @@ read_from_ics(isr, closure, data, count, error) 2 empty, white, or black (IGNORED) 3 player 2 name (not necessarily black) 4 player 2 rating - + The names/ratings are sorted out when the game actually starts (below). */ @@ -2344,14 +3088,14 @@ read_from_ics(isr, closure, data, count, error) player2Rating = string_to_rating(star_match[4]); if (appData.debugMode) - fprintf(debugFP, + fprintf(debugFP, "Ratings from 'Creating:' %s %d, %s %d\n", player1Name, player1Rating, player2Name, player2Rating); continue; } - + /* Improved generic start/end-of-game messages */ if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) || (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){ @@ -2427,6 +3171,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*/ @@ -2439,11 +3184,11 @@ read_from_ics(isr, closure, data, count, error) if (gameMode == IcsObserving && atoi(star_match[0]) == ics_gamenum) { - /* icsEngineAnalyze */ - if (appData.icsEngineAnalyze) { - ExitAnalyzeMode(); - ModeHighlight(); - } + /* icsEngineAnalyze */ + if (appData.icsEngineAnalyze) { + ExitAnalyzeMode(); + ModeHighlight(); + } StopClocks(); gameMode = IcsIdle; ics_gamenum = -1; @@ -2505,9 +3250,9 @@ read_from_ics(isr, closure, data, count, error) ClearPremoveHighlights(); if (appData.debugMode) fprintf(debugFP, "Sending premove:\n"); - UserMoveEvent(premoveFromX, premoveFromY, - premoveToX, premoveToY, - premovePromoChar); + UserMoveEvent(premoveFromX, premoveFromY, + premoveToX, premoveToY, + premovePromoChar); } } @@ -2524,11 +3269,23 @@ 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! + */ + VariantClass newVariant; + switch(gameInfo.boardWidth) { // base guess on board width + case 9: newVariant = VariantShogi; break; + case 10: newVariant = VariantGreat; break; + default: newVariant = VariantCrazyhouse; break; + } + VariantSwitch(boards[currentMove], newVariant); /* temp guess */ /* Get a move list just to see the header, which will tell us whether this is really bug or zh */ if (ics_getting_history == H_FALSE) { @@ -2543,6 +3300,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, @@ -2560,7 +3320,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 */ @@ -2574,17 +3335,18 @@ 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, reparse it after appending the next read */ - + } else if (count == 0) { RemoveInputSource(isr); DisplayFatalError(_("Connection closed by ICS"), 0, 0); @@ -2595,16 +3357,16 @@ read_from_ics(isr, closure, data, count, error) /* Board style 12 looks like this: - + <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0 - + * The "<12> " is stripped before it gets to this routine. The two * trailing 0's (flip state and clock ticking) are later addition, and * some chess servers may not have them, or may have only the first. - * Additional trailing fields may be added in the future. + * 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 */ @@ -2617,12 +3379,12 @@ read_from_ics(isr, closure, data, count, error) void ParseBoard12(string) char *string; -{ +{ GameMode newGameMode; int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i; int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback; int 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; @@ -2631,9 +3393,12 @@ 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 + Boolean weird = FALSE; fromX = fromY = toX = toY = -1; - + newGame = FALSE; if (appData.debugMode) @@ -2641,15 +3406,48 @@ ParseBoard12(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++; + if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies + i++; + } + for(j = 0; j 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; - /* 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); - } - } + /* [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) { @@ -2880,7 +3745,7 @@ ParseBoard12(string) if (!pausing || currentMove > forwardMostMove) currentMove = forwardMostMove; } else { - /* New part of history that is not contiguous with old part */ + /* New part of history that is not contiguous with old part */ if (pausing && gameMode == IcsExamining) { pauseExamInvalid = TRUE; forwardMostMove = pauseExamForwardMostMove; @@ -2893,7 +3758,7 @@ ParseBoard12(string) SendToICS(str); } } - + /* Update the clocks */ if (strchr(elapsed_time, '.')) { /* Time is in ms */ @@ -2904,7 +3769,7 @@ ParseBoard12(string) timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000; timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000; } - + #if ZIPPY if (appData.zippyPlay && newGame && @@ -2912,10 +3777,20 @@ ParseBoard12(string) gameMode != IcsExamining) ZippyFirstBoard(moveNum, basetime, increment); #endif - + /* 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. */ @@ -2923,21 +3798,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; } @@ -2946,32 +3853,27 @@ 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 0 - if (appData.testLegality && appData.debugMode) { - sprintf(str, "Illegal move \"%s\" from ICS", move_str); - DisplayError(str, 0); - } -#endif + 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); + } strcpy(parseList[moveNum - 1], move_str); strcat(parseList[moveNum - 1], " "); 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). */ - if (appData.zippyPlay && !newGame && newMove && + if (appData.zippyPlay && !newGame && newMove && (!appData.getMoveList || backwardMostMove == 0) && first.initDone) { if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) || @@ -2984,8 +3886,8 @@ ParseBoard12(string) 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 ? @@ -3002,6 +3904,7 @@ ParseBoard12(string) 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); } } @@ -3009,7 +3912,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) { @@ -3019,7 +3922,7 @@ ParseBoard12(string) SetHighlights(fromX, fromY, toX, toY); } } - + /* Start the clocks */ whiteFlag = blackFlag = FALSE; appData.clockMode = !(basetime == 0 && increment == 0); @@ -3036,39 +3939,68 @@ ParseBoard12(string) DisplayBothClocks(); else StartClocks(); - + /* 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 || + if (!gotPremove || ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) || ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove)))) ClearPremoveHighlights(); 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 @@ -3100,12 +4032,19 @@ AnalysisPeriodicEvent(force) programStats.ok_to_send = 0; } +void ics_update_width(new_width) + int new_width; +{ + ics_printf("set width %d\n", new_width); +} + void SendMoveToProgram(moveNum, cps) int moveNum; ChessProgramState *cps; { char buf[MSG_SIZ]; + if (cps->useUsermove) { SendToProgram("usermove ", cps); } @@ -3121,8 +4060,47 @@ SendMoveToProgram(moveNum, cps) } SendToProgram(buf, cps); } else { - SendToProgram(moveList[moveNum], cps); + if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */ + AlphaRank(moveList[moveNum], 4); + SendToProgram(moveList[moveNum], cps); + AlphaRank(moveList[moveNum], 4); // and back + } else + /* Added by Tord: Send castle moves in "O-O" in FRC games if required by + * the engine. It would be nice to have a better way to identify castle + * moves here. */ + if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) + && cps->useOOCastle) { + 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); + } + else SendToProgram(moveList[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 @@ -3134,7 +4112,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; @@ -3142,12 +4120,20 @@ SendMoveToICS(moveType, fromX, fromY, toX, toY) case BlackKingSideCastle: case WhiteQueenSideCastleWild: case BlackQueenSideCastleWild: + /* PUSH Fabien */ + case WhiteHSideCastleFR: + case BlackHSideCastleFR: + /* POP Fabien */ sprintf(user_move, "o-o\n"); break; case WhiteQueenSideCastle: case BlackQueenSideCastle: case WhiteKingSideCastleWild: case BlackKingSideCastleWild: + /* PUSH Fabien */ + case WhiteASideCastleFR: + case BlackASideCastleFR: + /* POP Fabien */ sprintf(user_move, "o-o-o\n"); break; case WhitePromotionQueen: @@ -3160,25 +4146,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 @@ -3189,14 +4190,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); } } } @@ -3215,6 +4216,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) @@ -3223,9 +4274,17 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) ChessMove *moveType; 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: @@ -3247,14 +4306,23 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: + /* Code added by Tord: */ + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + 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; } @@ -3268,10 +4336,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; @@ -3286,6 +4354,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; @@ -3293,35 +4364,480 @@ ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar) } } +// [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 + +int squaresLeft[4]; +int piecesLeft[(int)BlackPawn]; +int seed, nrOfShuffles; + +void GetPositionNumber() +{ // sets global variable seed + int i; + + seed = appData.defaultFrcPosition; + if(seed < 0) { // randomize based on time for negative FRC position numbers + for(i=0; i<50; i++) seed += random(); + seed = random() ^ random() >> 8 ^ random() << 8; + if(seed<0) seed = -seed; + } +} + +int put(Board board, int pieceType, int rank, int n, int shade) +// put the piece on the (n-1)-th empty squares of the given shade +{ + int i; + + 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, p, first=1; + + GetPositionNumber(); nrOfShuffles = 1; + + squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2; + squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT; + squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK]; + + 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; + } + + 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; + } + + + } else { + while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY); + while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY); + } + + // 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; + + if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare + && NrPieces >= 12 && !(NrPieces&1)) { + int i; /* [HGM] Accept even length from 12 to 34 */ + + 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 (appData.debugMode) { + fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings); + } + if(shuffleOpenings) { + SetUpShuffle(initialPosition, appData.defaultFrcPosition); + startedFromSetupPosition = TRUE; + } + if(startedFromPositionFile) { + /* [HGM] loadPos: use PositionFile for every new game */ + CopyBoard(initialPosition, filePosition); + for(i=0; iuseSetboard) { - char* fen = PositionToFEN(moveNum); + char* fen = PositionToFEN(moveNum, cps->fenOverride); sprintf(message, "setboard %s\n", fen); SendToProgram(message, cps); free(fen); @@ -3347,51 +4863,159 @@ 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); + sprintf(message, "%c%c%c\n", PieceToChar(*bp), + 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); } } } - + 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; +HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice) { - 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] rewritten IsPromotion to only flag promotions that offer a choice */ + /* [HGM] add Shogi promotions */ + int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn; + ChessSquare piece; + ChessMove moveType; + Boolean premove; + + if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop + if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings + + if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions + !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move + return FALSE; + + piece = boards[currentMove][fromY][fromX]; + if(gameInfo.variant == VariantShogi) { + promotionZoneSize = 3; + highestPromotingPiece = (int)WhiteFerz; + } + + // next weed out all moves that do not touch the promotion zone at all + 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; + } + + if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece + + // weed out mandatory Shogi promotions + if(gameInfo.variant == VariantShogi) { + if(piece >= BlackPawn) { + if(toY == 0 && piece == BlackPawn || + toY == 0 && piece == BlackQueen || + toY <= 1 && piece == BlackKnight) { + *promoChoice = '+'; + return FALSE; + } + } else { + if(toY == BOARD_HEIGHT-1 && piece == WhitePawn || + toY == BOARD_HEIGHT-1 && piece == WhiteQueen || + toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) { + *promoChoice = '+'; + return FALSE; + } + } + } + + // weed out obviously illegal Pawn moves + if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) { + if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide + if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep + if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep + if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE; + // note we are not allowed to test for valid (non-)capture, due to premove + } + + // we either have a choice what to promote to, or (in Shogi) whether to promote + if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) { + *promoChoice = PieceToChar(BlackFerz); // no choice + return FALSE; + } + if(appData.alwaysPromoteToQueen) { // predetermined + if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers) + *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want + else *promoChoice = PieceToChar(BlackQueen); + return FALSE; + } + + // suppress promotion popup on illegal moves that are not premoves + premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) || + gameMode == IcsPlayingBlack && WhiteOnMove(currentMove); + if(appData.testLegality && !premove) { + moveType = LegalityTest(boards[currentMove], PosFlags(currentMove), + epStatus[currentMove], castlingRights[currentMove], + fromY, fromX, toY, toX, NULLCHAR); + if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen && + moveType != WhitePromotionKnight && moveType != BlackPromotionKnight) + return FALSE; + } + + return TRUE; } +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]; @@ -3415,7 +5039,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: @@ -3450,11 +5074,11 @@ OKToStartUserMove(x, y) if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); return FALSE; - } + } if (white_piece && !WhiteOnMove(currentMove)) { DisplayMoveError(_("It is Black's turn")); return FALSE; - } + } if (cmailMsgLoaded && (currentMove < cmailOldMove)) { /* Editing correspondence game history */ /* Could disallow this or prompt for confirmation */ @@ -3477,16 +5101,16 @@ OKToStartUserMove(x, y) } } break; - + case Training: if (!white_piece && WhiteOnMove(currentMove)) { DisplayMoveError(_("It is White's turn")); return FALSE; - } + } if (white_piece && !WhiteOnMove(currentMove)) { DisplayMoveError(_("It is Black's turn")); return FALSE; - } + } break; default: @@ -3507,18 +5131,14 @@ int lastLoadGameUseList = FALSE; 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, captureOwn) int fromX, fromY, toX, toY; int promoChar; + Boolean captureOwn; { ChessMove moveType; - - if (fromX < 0 || fromY < 0) return; - if ((fromX == toX) && (fromY == toY)) { - return; - } + ChessSquare pdown, pup; /* 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 @@ -3540,13 +5160,13 @@ 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; + return ImpossibleMove; } break; @@ -3554,7 +5174,7 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError(_("It is Black's turn")); - return; + return ImpossibleMove; } break; @@ -3564,17 +5184,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; + return ImpossibleMove; } } else { /* User is moving for White */ if (!WhiteOnMove(currentMove)) { DisplayMoveError(_("It is Black's turn")); - return; + return ImpossibleMove; } } break; @@ -3591,12 +5211,12 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) premoveFromY = fromY; premovePromoChar = promoChar; gotPremove = 1; - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "Got premove: fromX %d," "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } - return; + return ImpossibleMove; } break; @@ -3612,12 +5232,12 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) premoveFromY = fromY; premovePromoChar = promoChar; gotPremove = 1; - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "Got premove: fromX %d," "fromY %d, toX %d, toY %d\n", fromX, fromY, toX, toY); } - return; + return ImpossibleMove; } break; @@ -3625,40 +5245,111 @@ 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; } - if (toX < 0 || toY < 0) return; - userOfferedDraw = FALSE; + pdown = boards[currentMove][fromY][fromX]; + pup = boards[currentMove][toY][toX]; + /* [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 */ + } + + 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; + 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. + * 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); @@ -3682,19 +5373,9 @@ UserMoveEvent(fromX, fromY, toX, toY, promoChar) } else { 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) { @@ -3719,6 +5400,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); @@ -3726,10 +5408,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 || @@ -3743,10 +5426,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; } @@ -3757,11 +5441,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 { @@ -3773,7 +5458,7 @@ FinishMove(moveType, fromX, fromY, toX, toY, promoChar) break; } break; - + case MachinePlaysBlack: case MachinePlaysWhite: /* disable certain menu options while machine is thinking */ @@ -3783,6 +5468,287 @@ 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, FALSE); +if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar); + if(moveType == AmbiguousMove) + DrawPosition(FALSE, boards[currentMove]); + else if(moveType != ImpossibleMove && moveType != Comment) + FinishMove(moveType, fromX, fromY, toX, toY, promoChar); +} + +void LeftClick(ClickType clickType, int xPix, int yPix) +{ + int x, y; + Boolean saveAnimate; + static int second = 0, promotionChoice = 0; + char promoChoice = NULLCHAR; + + if (clickType == Press) ErrorPopDown(); + + x = EventToSquare(xPix, BOARD_WIDTH); + y = EventToSquare(yPix, BOARD_HEIGHT); + if (!flipView && y >= 0) { + y = BOARD_HEIGHT - 1 - y; + } + if (flipView && x >= 0) { + x = BOARD_WIDTH - 1 - x; + } + + if(promotionChoice) { // we are waiting for a click to indicate promotion piece + if(clickType == Release) return; // ignore upclick of click-click destination + promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel + if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y); + if(gameInfo.holdingsWidth && + (WhiteOnMove(currentMove) + ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0 + : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) { + // click in right holdings, for determining promotion piece + ChessSquare p = boards[currentMove][y][x]; + if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p); + if(p != EmptySquare) { + FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p))); + fromX = fromY = -1; + return; + } + } + DrawPosition(FALSE, boards[currentMove]); + return; + } + + /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */ + if(clickType == Press + && ( x == BOARD_LEFT-1 || x == BOARD_RGHT + || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize + || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) ) + return; + + if (fromX == -1) { + if (clickType == Press) { + /* First square */ + if (OKToStartUserMove(x, y)) { + fromX = x; + fromY = y; + second = 0; + DragPieceBegin(xPix, yPix); + if (appData.highlightDragging) { + SetHighlights(x, y, -1, -1); + } + } + } + return; + } + + /* fromX != -1 */ + if (clickType == Press && gameMode != EditPosition) { + ChessSquare fromP; + ChessSquare toP; + int frc; + + // ignore off-board to clicks + if(y < 0 || x < 0) return; + + /* Check if clicking again on the same color piece */ + fromP = boards[currentMove][fromY][fromX]; + toP = boards[currentMove][y][x]; + frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom; + if ((WhitePawn <= fromP && fromP <= WhiteKing && + WhitePawn <= toP && toP <= WhiteKing && + !(fromP == WhiteKing && toP == WhiteRook && frc) && + !(fromP == WhiteRook && toP == WhiteKing && frc)) || + (BlackPawn <= fromP && fromP <= BlackKing && + BlackPawn <= toP && toP <= BlackKing && + !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling + !(fromP == BlackKing && toP == BlackRook && frc))) { + /* Clicked again on same color piece -- changed his mind */ + second = (x == fromX && y == fromY); + if (appData.highlightDragging) { + SetHighlights(x, y, -1, -1); + } else { + ClearHighlights(); + } + if (OKToStartUserMove(x, y)) { + fromX = x; + fromY = y; + DragPieceBegin(xPix, yPix); + } + return; + } + // ignore to-clicks in holdings + if(x < BOARD_LEFT || x >= BOARD_RGHT) return; + } + + if (clickType == Release && (x == fromX && y == fromY || + x < BOARD_LEFT || x >= BOARD_RGHT)) { + + // treat drags into holding as click on start square + x = fromX; y = fromY; + + DragPieceEnd(xPix, yPix); + if (appData.animateDragging) { + /* Undo animation damage if any */ + DrawPosition(FALSE, NULL); + } + if (second) { + /* Second up/down in same square; just abort move */ + second = 0; + fromX = fromY = -1; + ClearHighlights(); + gotPremove = 0; + ClearPremoveHighlights(); + } else { + /* First upclick in same square; start click-click mode */ + SetHighlights(x, y, -1, -1); + } + return; + } + + /* we now have a different from- and to-square */ + /* Completed move */ + toX = x; + toY = y; + saveAnimate = appData.animate; + if (clickType == Press) { + /* Finish clickclick move */ + if (appData.animate || appData.highlightLastMove) { + SetHighlights(fromX, fromY, toX, toY); + } else { + ClearHighlights(); + } + } else { + /* Finish drag move */ + if (appData.highlightLastMove) { + SetHighlights(fromX, fromY, toX, toY); + } else { + ClearHighlights(); + } + DragPieceEnd(xPix, yPix); + /* Don't animate move and drag both */ + appData.animate = FALSE; + } + if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) { + SetHighlights(fromX, fromY, toX, toY); + if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { + // [HGM] super: promotion to captured piece selected from holdings + ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX]; + promotionChoice = TRUE; + // kludge follows to temporarily execute move on display, without promoting yet + boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank + boards[currentMove][toY][toX] = p; + DrawPosition(FALSE, boards[currentMove]); + boards[currentMove][fromY][fromX] = p; // take back, but display stays + boards[currentMove][toY][toX] = q; + DisplayMessage("Click in holdings to choose piece", ""); + return; + } + PromotionPopUp(); + } else { + UserMoveEvent(fromX, fromY, toX, toY, promoChoice); + if (!appData.highlightLastMove || gotPremove) ClearHighlights(); + if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY); + fromX = fromY = -1; + } + appData.animate = saveAnimate; + if (appData.animate || appData.animateDragging) { + /* Undo animation damage if needed */ + DrawPosition(FALSE, NULL); + } +} + +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 ); +} + +char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial) +{ // [HGM] book: this routine intercepts moves to simulate book replies + char *bookHit = NULL; + + //first determine if the incoming move brings opponent into his book + if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI)) + bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move + if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)"); + if(bookHit != NULL && !cps->bookSuspend) { + // make sure opponent is not going to reply after receiving move to book position + SendToProgram("force\n", cps); + cps->bookSuspend = TRUE; // flag indicating it has to be restarted + } + if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move + // now arrange restart after book miss + if(bookHit) { + // after a book hit we never send 'go', and the code after the call to this routine + // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove'). + char buf[MSG_SIZ]; + if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :( + sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it + SendToProgram(buf, cps); + if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go' + } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine + SendToProgram("go\n", cps); + cps->bookSuspend = FALSE; // after a 'go' we are never suspended + } else { // 'go' might be sent based on 'firstMove' after this routine returns + if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return + SendToProgram("go\n", cps); + cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss + } + return bookHit; // notify caller of hit, so it can take action to send move to opponent +} + +char *savedMessage; +ChessProgramState *savedState; +void DeferredBookMove(void) +{ + if(savedState->lastPing != savedState->lastPong) + ScheduleDelayedEvent(DeferredBookMove, 10); + else + HandleMachineMove(savedMessage, savedState); } void @@ -3797,13 +5763,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) { @@ -3828,11 +5801,9 @@ 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)) { - + if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 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) { if (gameMode == BeginningOfGame) { @@ -3845,7 +5816,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; } @@ -3896,19 +5868,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; @@ -3922,6 +5942,19 @@ 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 */ @@ -3929,15 +5962,392 @@ HandleMachineMove(message, cps) 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 = 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; + + while( count < adjudicateLossPlies ) { + int score = pvInfoList[ forwardMostMove - count - 1 ].score; + + if( count & 1 ) { + score = -score; /* Flip score for winning side */ + } + + if( score > adjudicateLossThreshold ) { + break; + } + + count++; + } + + if( count >= adjudicateLossPlies ) { + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + + GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, + "Xboard adjudication", + GE_XBOARD ); + + return; + } + } + + 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; + } + } + + /* 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 (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]); + + } + + /* 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(CompareBoards(boards[k], boards[forwardMostMove])) { + /* 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( rights == 0 && ++count > appData.drawRepeats-2 + && appData.drawRepeats > 1) { + /* adjudicate after user-specified nr of repeats */ + SendToProgram("force\n", cps->other); // suppress reply + SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */ + ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ + if(gameInfo.variant == VariantXiangqi && appData.testLegality) { + // [HGM] xiangqi: check for forbidden perpetuals + int m, ourPerpetual = 1, hisPerpetual = 1; + for(m=forwardMostMove; m>k; m-=2) { + if(MateTest(boards[m], PosFlags(m), + EP_NONE, castlingRights[m]) != MT_CHECK) + ourPerpetual = 0; // the current mover did not always check + if(MateTest(boards[m-1], PosFlags(m-1), + EP_NONE, castlingRights[m-1]) != MT_CHECK) + hisPerpetual = 0; // the opponent did not always check + } + if(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); @@ -3948,15 +6358,41 @@ HandleMachineMove(message, cps) } ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/ - if (!pausing && appData.ringBellAfterMoves) { + + if (!pausing && appData.ringBellAfterMoves) { RingBell(); } - /* + + /* * Reenable menu items that were disabled while * machine was thinking */ 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; } @@ -3969,6 +6405,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(TRUE, FALSE); + CopyBoard(boards[0], initial_position); + initialRulePlies = FENrulePlies; + epStatus[0] = FENepStatus; + for( i=0; ipr); return; } - /* Commands from the engine directly to ICS. We don't allow these to be - * sent until we are logged on. Crafty kibitzes have been known to + /* Commands from the engine directly to ICS. We don't allow these to be + * sent until we are logged on. Crafty kibitzes have been known to * interfere with the login process. */ if (loggedOn) { @@ -4048,11 +6513,8 @@ HandleMachineMove(message, cps) return; } } - if (strncmp(message, "feature ", 8) == 0) { - ParseFeatures(message+8, cps); - } if (sscanf(message, "pong %d", &cps->lastPong) == 1) { - return; + return; } /* * If the move is illegal, cancel it and redraw the board. @@ -4061,7 +6523,7 @@ HandleMachineMove(message, cps) */ if (strncmp(message + 1, "llegal move", 11) == 0 || strncmp(message, "Error", 5) == 0) { - if (StrStr(message, "name") || + if (StrStr(message, "name") || StrStr(message, "rating") || StrStr(message, "?") || StrStr(message, "result") || StrStr(message, "board") || StrStr(message, "bk") || StrStr(message, "computer") || @@ -4096,7 +6558,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; } @@ -4116,21 +6578,36 @@ 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; -#if 0 - /* Following removed: it caused a bug where a real illegal move - message in analyze mored would be ignored. */ - if (cps == &first && programStats.ok_to_send == 0) { - /* Bogus message from Crafty responding to "." This filtering - can miss some of the bad messages, but fortunately the bug - is fixed in current Crafty versions, so it doesn't matter. */ - return; - } -#endif if (pausing) PauseEvent(); + if(appData.forceIllegal) { + // [HGM] illegal: machine refused move; force position after move into it + SendToProgram("force\n", cps); + if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks. + // we have a real problem now, as SendBoard will use the a2a3 kludge + // when black is to move, while there might be nothing on a2 or black + // might already have the move. So send the board as if white has the move. + // But first we must change the stm of the engine, as it refused the last move + SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0] + if(WhiteOnMove(forwardMostMove)) { + SendToProgram("a7a6\n", cps); // for the engine black still had the move + SendBoard(cps, forwardMostMove); // kludgeless board + } else { + SendToProgram("a2a3\n", cps); // for the engine white still had the move + CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]); + SendBoard(cps, forwardMostMove+1); // kludgeless board + } + } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly + if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack || + gameMode == TwoMachinesPlay) + SendToProgram("go\n", cps); + return; + } else if (gameMode == PlayFromGameFile) { /* Stop reading this game file */ gameMode = EditGame; @@ -4144,6 +6621,13 @@ HandleMachineMove(message, cps) 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")) { @@ -4152,7 +6636,7 @@ HandleMachineMove(message, cps) Don't use it. */ cps->sendTime = 0; } - + /* * If chess program startup fails, exit with an error message. * Attempts to recover here are futile. @@ -4165,14 +6649,14 @@ 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); return; } - - /* + + /* * Look for hint output */ if (sscanf(message, "Hint: %s", buf1) == 1) { @@ -4183,11 +6667,11 @@ 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, + snprintf(buf2, sizeof(buf2), _("Illegal hint move \"%s\"\nfrom %s chess program"), buf1, cps->which); DisplayError(buf2, 0); @@ -4217,7 +6701,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 = ""; @@ -4231,10 +6715,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 = ""; @@ -4246,40 +6730,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 */ @@ -4290,17 +6779,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 */ @@ -4311,17 +6800,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 */ @@ -4330,14 +6819,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) { @@ -4352,10 +6841,8 @@ HandleMachineMove(message, cps) if (gameMode == TwoMachinesPlay) { if (cps->other->offeredDraw) { GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD); - } else { - if (cps->other->sendDrawOffers) { - SendToProgram("draw\n", cps->other); - } + /* [HGM] in two-machine mode we delay relaying draw offer */ + /* until after we also have move, to see if it is really claim */ } } else if (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack) { @@ -4368,14 +6855,16 @@ 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]; - u64 nodes; + u64 nodes; // [DM] char plyext; int ignore = FALSE; int prefixHint = FALSE; @@ -4392,14 +6881,12 @@ HandleMachineMove(message, cps) break; case AnalyzeMode: case AnalyzeFile: - break; - /* icsEngineAnalyze */ - case IcsObserving: - if (!appData.icsEngineAnalyze) ignore = TRUE; + break; + case IcsObserving: /* [DM] icsEngineAnalyze */ + if (!appData.icsEngineAnalyze) ignore = TRUE; break; case TwoMachinesPlay: - if ((cps->twoMachinesColor[0] == 'w') != - WhiteOnMove(forwardMostMove)) { + if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) { ignore = TRUE; } break; @@ -4410,18 +6897,37 @@ HandleMachineMove(message, cps) if (!ignore) { buf1[0] = NULLCHAR; - if (sscanf(message, "%d%c %d %d" u64Display "%[^\n]\n", + if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n", &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) { if (plyext != ' ' && plyext != '\t') { time *= 100; } + + /* [AS] Negate score if machine is playing black and reporting absolute scores */ + if( cps->scoreIsAbsolute && + ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) + { + curscore = -curscore; + } + + programStats.depth = plylev; programStats.nodes = nodes; programStats.time = time; 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) @@ -4430,10 +6936,8 @@ HandleMachineMove(message, cps) "PV is too long; using the first %d bytes.\n", sizeof(programStats.movelist) - 1); } - strncpy(programStats.movelist, buf1, - sizeof(programStats.movelist)); - programStats.movelist[sizeof(programStats.movelist) - 1] - = NULLCHAR; + + safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) ); } else { sprintf(programStats.movelist, " no PV\n"); } @@ -4450,18 +6954,36 @@ HandleMachineMove(message, cps) programStats.line_is_book = 0; } - sprintf(thinkOutput, "[%d]%c%+.2f %s%s%s", - plylev, + SendProgramStatsToFrontend( cps, &programStats ); + + /* + [AS] Protect the thinkOutput buffer from overflow... this + is only useful if buf1 hasn't overflowed first! + */ + sprintf(thinkOutput, "[%d]%c%+.2f %s%s", + plylev, (gameMode == TwoMachinesPlay ? ToUpper(cps->twoMachinesColor[0]) : ' '), ((double) curscore) / 100.0, prefixHint ? lastHint : "", - prefixHint ? " " : "", programStats.movelist); + prefixHint ? " " : "" ); + + if( buf1[0] != NULLCHAR ) { + unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1; + + if( strlen(buf1) > max_len ) { + if( appData.debugMode) { + fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n"); + } + buf1[max_len+1] = '\0'; + } + + strcat( thinkOutput, buf1 ); + } - if (currentMove == forwardMostMove || gameMode == AnalyzeMode - || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { + if (currentMove == forwardMostMove || gameMode == AnalyzeMode + || gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; @@ -4484,13 +7006,14 @@ HandleMachineMove(message, cps) isn't searching, so stats won't change) */ programStats.line_is_book = 1; - if (currentMove == forwardMostMove || gameMode==AnalyzeMode || - gameMode == AnalyzeFile || appData.icsEngineAnalyze) { + SendProgramStatsToFrontend( cps, &programStats ); + + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || + gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; - } else if (sscanf(message,"stat01: %d" u64Display "%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 @@ -4508,7 +7031,10 @@ HandleMachineMove(message, cps) programStats.nr_moves = mvtot; strcpy(programStats.move_name, mvname); programStats.ok_to_send = 1; - DisplayAnalysis(); + programStats.movelist[0] = '\0'; + + SendProgramStatsToFrontend( cps, &programStats ); + return; } else if (strncmp(message,"++",2) == 0) { @@ -4523,27 +7049,74 @@ HandleMachineMove(message, cps) } else if (thinkOutput[0] != NULLCHAR && strncmp(message, " ", 4) == 0) { + unsigned message_len; + p = message; while (*p && *p == ' ') p++; - strcat(thinkOutput, " "); - strcat(thinkOutput, p); - strcat(programStats.movelist, " "); - strcat(programStats.movelist, p); + + message_len = strlen( p ); + + /* [AS] Avoid buffer overflow */ + if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) { + strcat(thinkOutput, " "); + strcat(thinkOutput, p); + } + + if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) { + strcat(programStats.movelist, " "); + strcat(programStats.movelist, p); + } + if (currentMove == forwardMostMove || gameMode==AnalyzeMode || - gameMode == AnalyzeFile || appData.icsEngineAnalyze) { + gameMode == AnalyzeFile || appData.icsEngineAnalyze) { DisplayMove(currentMove - 1); - DisplayAnalysis(); } return; } } + else { + buf1[0] = NULLCHAR; + + 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; + } + + /* [AS] Negate score if machine is playing black and reporting absolute scores */ + if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) { + curscore = -curscore; + } + + 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( cpstats.movelist, buf1, sizeof(cpstats.movelist) ); + } + + cpstats.ok_to_send = 0; + cpstats.line_is_book = 0; + cpstats.nr_moves = 0; + cpstats.moves_left = 0; + + SendProgramStatsToFrontend( cps, &cpstats ); + } + } } } /* Parse a game score from the character string "game", and record it as the history of the current game. The game - score is NOT assumed to start from the standard position. + score is NOT assumed to start from the standard position. The display is not updated in any way. */ void @@ -4583,6 +7156,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: @@ -4604,11 +7187,16 @@ ParseGameHistory(game) case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: - case IllegalMove: /* maybe suicide chess, etc. */ - fromX = currentMoveString[0] - 'a'; - fromY = currentMoveString[1] - '1'; - toX = currentMoveString[2] - 'a'; - toY = currentMoveString[3] - '1'; + /* PUSH Fabien */ + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + /* POP Fabien */ + fromX = currentMoveString[0] - AAA; + fromY = currentMoveString[1] - ONE; + toX = currentMoveString[2] - AAA; + toY = currentMoveString[3] - ONE; promoChar = currentMoveString[4]; break; case WhiteDrop: @@ -4617,18 +7205,28 @@ 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); + 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); + 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 */ @@ -4689,21 +7287,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][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook; + } else { + 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[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook; + } else { + board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook; + } + /* End of code added by Tord */ + + } 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][4] = WhiteRook; - } else if (fromY == 0 && fromX == 3 - && board[fromY][fromX] == WhiteKing - && toY == 0 && toX == 1) { + 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][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) { @@ -4797,47 +7484,116 @@ 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; + p = PieceToNumber((ChessSquare)p); + if(p >= gameInfo.holdingsSize) p = 0; + if(--board[p][BOARD_WIDTH-2] <= 0) + board[p][BOARD_WIDTH-1] = EmptySquare; + if((int)board[p][BOARD_WIDTH-2] < 0) + board[p][BOARD_WIDTH-2] = 0; + } else { /* black drop */ + p -= (int)BlackPawn; + p = PieceToNumber((ChessSquare)p); + if(p >= gameInfo.holdingsSize) p = 0; + if(--board[BOARD_HEIGHT-1-p][1] <= 0) + board[BOARD_HEIGHT-1-p][0] = EmptySquare; + if((int)board[BOARD_HEIGHT-1-p][1] < 0) + board[BOARD_HEIGHT-1-p][1] = 0; + } } - 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; } @@ -4846,6 +7602,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 */ @@ -4854,21 +7628,67 @@ MakeMove(fromX, fromY, toX, toY, promoChar) int fromX, fromY, toX, toY; int promoChar; { - forwardMostMove++; - if (forwardMostMove >= MAX_MOVES) { +// 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(); + 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) { + 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; @@ -4953,7 +7878,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); @@ -4969,7 +7897,7 @@ InitChessProgram(cps) SendToProgram(buf, cps); } cps->initDone = TRUE; -} +} void @@ -4988,26 +7916,28 @@ 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; return; } - + 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); @@ -5019,13 +7949,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(); @@ -5035,19 +7965,69 @@ TwoMachinesEventIfReady P((void)) void NextMatchGame P((void)) { + int index; /* [HGM] autoinc: step load index during match */ Reset(FALSE, TRUE); if (*appData.loadGameFile != NULLCHAR) { + index = appData.loadGameIndex; + if(index < 0) { // [HGM] autoinc + lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1; + if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1; + } LoadGameFromFile(appData.loadGameFile, - 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(); } +void UserAdjudicationEvent( int result ) +{ + ChessMove gameResult = GameIsDrawn; + + if( result > 0 ) { + gameResult = WhiteWins; + } + else if( result < 0 ) { + gameResult = BlackWins; + } + + if( gameMode == TwoMachinesPlay ) { + GameEnds( gameResult, "User adjudication", GE_XBOARD ); + } +} + + +// [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; @@ -5056,16 +8036,20 @@ 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. + game is over, but the engine can offer to draw, claim + a draw, or resign. */ #if ZIPPY if (appData.zippyPlay && first.initDone) { @@ -5079,7 +8063,8 @@ GameEnds(result, resultDetails, whosays) } } #endif - return; + endingGame = 0; /* [HGM] crash */ + return; } /* If we're loading the game from a file, stop */ @@ -5089,26 +8074,130 @@ 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. */ - isIcsGame = (gameMode == IcsPlayingWhite || - gameMode == IcsPlayingBlack || - gameMode == IcsObserving || + isIcsGame = (gameMode == IcsPlayingWhite || + gameMode == IcsPlayingBlack || + gameMode == IcsObserving || gameMode == IcsExamining); if (!isIcsGame || whosays == GE_ICS) { /* OK -- not an ICS game, or ICS said it was done */ StopClocks(); - if (!isIcsGame && !appData.noChessProgram) + if (!isIcsGame && !appData.noChessProgram) SetUserThinkingEnables(); + + /* [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 (resultDetails != NULL) { + + 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 || @@ -5126,23 +8215,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) { @@ -5184,8 +8256,8 @@ GameEnds(result, resultDetails, whosays) } } } else if (gameMode == EditGame || - gameMode == PlayFromGameFile || - gameMode == AnalyzeMode || + gameMode == PlayFromGameFile || + gameMode == AnalyzeMode || gameMode == AnalyzeFile) { nextGameMode = gameMode; } else { @@ -5200,7 +8272,8 @@ GameEnds(result, resultDetails, whosays) if (appData.noChessProgram) { gameMode = nextGameMode; ModeHighlight(); - return; + endingGame = 0; /* [HGM] crash */ + return; } if (first.reuse) { @@ -5224,10 +8297,12 @@ GameEnds(result, resultDetails, whosays) if (first.isr != NULL) RemoveInputSource(first.isr); first.isr = NULL; - + if (first.pr != NoProc) { ExitAnalyzeMode(); + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); + DoSleep( appData.delayAfterQuit ); DestroyChildProcess(first.pr, first.useSigterm); } first.pr = NoProc; @@ -5248,9 +8323,11 @@ GameEnds(result, resultDetails, whosays) if (second.isr != NULL) RemoveInputSource(second.isr); second.isr = NULL; - + if (second.pr != NoProc) { + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); + DoSleep( appData.delayAfterQuit ); DestroyChildProcess(second.pr, second.useSigterm); } second.pr = NoProc; @@ -5277,12 +8354,17 @@ 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]; @@ -5299,24 +8381,36 @@ GameEnds(result, resultDetails, whosays) ExitAnalyzeMode(); gameMode = nextGameMode; ModeHighlight(); + endingGame = 0; /* [HGM] crash */ } /* Assumes program was just initialized (initString sent). Leaves program in force mode. */ void -FeedMovesToProgram(cps, upto) +FeedMovesToProgram(cps, upto) ChessProgramState *cps; int upto; { int i; - + if (appData.debugMode) 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); @@ -5331,9 +8425,9 @@ ResurrectChessProgram() If so, restart it and feed it all the moves made so far. */ if (appData.noChessProgram || first.pr != NoProc) return; - + StartChessProgram(&first); - InitChessProgram(&first); + InitChessProgram(&first, FALSE); FeedMovesToProgram(&first, currentMove); if (!first.sendTime) { @@ -5345,7 +8439,7 @@ ResurrectChessProgram() } if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile || - appData.icsEngineAnalyze) && first.analysisSupport) { + appData.icsEngineAnalyze) && first.analysisSupport) { SendToProgram("analyze\n", &first); first.analyzing = TRUE; } @@ -5364,7 +8458,6 @@ Reset(redraw, init) fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n", redraw, init, gameMode); } - pausing = pauseExamInvalid = FALSE; startedFromSetupPosition = blackPlaysFirst = FALSE; firstMove = TRUE; @@ -5373,6 +8466,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); @@ -5382,7 +8477,8 @@ 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(); flipView = appData.flipView; @@ -5391,9 +8487,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) { @@ -5407,10 +8518,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 @@ -5444,19 +8558,26 @@ AutoPlayOneMove() if (currentMove >= forwardMostMove) { gameMode = EditGame; ModeHighlight(); + + /* [AS] Clear current move marker at the end of a game */ + /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */ + 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] */ + AnimateMove(boards[currentMove], fromX, fromY, toX, toY); if (appData.highlightLastMove) { @@ -5467,9 +8588,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; } @@ -5483,13 +8603,13 @@ LoadGameOneMove(readAhead) ChessMove moveType; char move[MSG_SIZ]; char *p, *q; - - if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && + + if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && gameMode != AnalyzeMode && gameMode != Training) { gameFileFP = NULL; return FALSE; } - + yyboardindex = forwardMostMove; if (readAhead != (ChessMove)0) { moveType = readAhead; @@ -5498,11 +8618,11 @@ LoadGameOneMove(readAhead) return FALSE; moveType = (ChessMove) yylex(); } - + done = FALSE; switch (moveType) { case Comment: - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "Parsed Comment: %s\n", yy_text); p = yy_text; if (*p == '{' || *p == '[' || *p == '(') { @@ -5517,6 +8637,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: @@ -5536,12 +8662,18 @@ LoadGameOneMove(readAhead) case WhiteQueenSideCastleWild: case BlackKingSideCastleWild: case BlackQueenSideCastleWild: + /* PUSH Fabien */ + case WhiteHSideCastleFR: + case WhiteASideCastleFR: + case BlackHSideCastleFR: + case BlackASideCastleFR: + /* 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; @@ -5553,8 +8685,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: @@ -5588,11 +8720,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 { @@ -5623,11 +8756,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 { @@ -5662,10 +8796,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; @@ -5683,7 +8817,7 @@ LoadGameOneMove(readAhead) default: case ImpossibleMove: if (appData.debugMode) - fprintf(debugFP, "Parsed ImpossibleMove: %s\n", yy_text); + 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); @@ -5696,7 +8830,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(); @@ -5707,7 +8841,7 @@ LoadGameOneMove(readAhead) /* currentMoveString is set as a side-effect of yylex */ strcat(currentMoveString, "\n"); strcpy(moveList[forwardMostMove], currentMoveString); - + thinkOutput[0] = NULLCHAR; MakeMove(fromX, fromY, toX, toY, promoChar); currentMove = forwardMostMove; @@ -5732,7 +8866,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; } @@ -5770,38 +8904,39 @@ MakeRegisteredMove() if (appData.debugMode) fprintf(debugFP, "Restoring %s for game %d\n", cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber); - + 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 { GameEnds(WhiteWins, "White mates", GE_PLAYER); } break; - + case MT_STALEMATE: GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER); break; } break; - + case CMAIL_RESIGN: if (WhiteOnMove(currentMove)) { GameEnds(BlackWins, "White resigns", GE_PLAYER); @@ -5809,11 +8944,11 @@ MakeRegisteredMove() GameEnds(WhiteWins, "Black resigns", GE_PLAYER); } break; - + case CMAIL_ACCEPT: GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER); break; - + default: break; } @@ -5907,8 +9042,9 @@ LoadGame(f, gameNumber, title, useList) int numPGNTags = 0; int err; GameMode oldGameMode; + VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */ - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode); if (gameMode == Training ) @@ -5926,7 +9062,7 @@ LoadGame(f, gameNumber, title, useList) if (useList) { lg = (ListGame *) ListElem(&gameList, gameNumber-1); - + if (lg) { fseek(f, lg->offset, 0); GameListHighlight(gameNumber); @@ -5956,9 +9092,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) { @@ -5981,12 +9116,12 @@ LoadGame(f, gameNumber, title, useList) /* * Skip the first gn-1 games in the file. - * Also skip over anything that precedes an identifiable - * start of game marker, to avoid being confused by - * garbage at the start of the file. Currently + * Also skip over anything that precedes an identifiable + * start of game marker, to avoid being confused by + * garbage at the start of the file. Currently * recognized start of game markers are the move number "1", * the pattern "gnuchess .* game", the pattern - * "^[#;%] [^ ]* game file", and a PGN tag block. + * "^[#;%] [^ ]* game file", and a PGN tag block. * A game that starts with one of the latter two patterns * will also have a move number 1, possibly * following a position diagram. @@ -6012,7 +9147,7 @@ LoadGame(f, gameNumber, title, useList) gn--; lastLoadGameStart = cm; break; - + case MoveNumberOne: switch (lastLoadGameStart) { case GNUChessGame: @@ -6080,7 +9215,7 @@ LoadGame(f, gameNumber, title, useList) break; } } - + if (appData.debugMode) fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm); @@ -6106,15 +9241,25 @@ LoadGame(f, gameNumber, title, useList) free(gameInfo.event); } gameInfo.event = StrSave(yy_text); - } + } startedFromSetupPosition = FALSE; while (cm == PGNTag) { - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text); 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; @@ -6138,6 +9283,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; @@ -6149,7 +9301,7 @@ LoadGame(f, gameNumber, title, useList) /* Handle comments interspersed among the tags */ while (cm == Comment) { char *p; - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "Parsed Comment: %s\n", yy_text); p = yy_text; if (*p == '{' || *p == '[' || *p == '(') { @@ -6172,9 +9324,11 @@ LoadGame(f, gameNumber, title, useList) gameInfo.variant = StringToVariant(gameInfo.event); } if (!matchMode) { - tags = PGNTags(&gameInfo); - TagsPopUp(tags, CmailMsg()); - free(tags); + if( appData.autoDisplayTags ) { + tags = PGNTags(&gameInfo); + TagsPopUp(tags, CmailMsg()); + free(tags); + } } } else { /* Make something up, but don't display it now */ @@ -6192,8 +9346,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 '-': @@ -6208,13 +9362,13 @@ LoadGame(f, gameNumber, title, useList) } while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; - + if (strncmp(p, "black", strlen("black"))==0) blackPlaysFirst = TRUE; else blackPlaysFirst = FALSE; startedFromSetupPosition = TRUE; - + CopyBoard(boards[0], initial_position); if (blackPlaysFirst) { currentMove = forwardMostMove = backwardMostMove = 1; @@ -6238,16 +9392,22 @@ 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); - DisplayBothClocks(); + 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) + if (appData.debugMode) fprintf(debugFP, "Parsed Comment: %s\n", yy_text); p = yy_text; if (*p == '{' || *p == '[' || *p == '(') { @@ -6279,12 +9439,11 @@ 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) + if (!matchMode && appData.timeDelay != 0) DrawPosition(FALSE, boards[currentMove]); if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) { @@ -6292,7 +9451,7 @@ LoadGame(f, gameNumber, title, useList) } /* if the first token after the PGN tags is a move - * and not move number 1, retrieve it from the parser + * and not move number 1, retrieve it from the parser */ if (cm != MoveNumberOne) LoadGameOneMove(cm); @@ -6321,8 +9480,10 @@ LoadGame(f, gameNumber, title, useList) AutoPlayGameLoop(); } - if (appData.debugMode) + if (appData.debugMode) fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode); + + loadFlag = 0; /* [HGM] true game starts */ return TRUE; } @@ -6359,7 +9520,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 { @@ -6378,7 +9539,7 @@ LoadPosition(f, positionNumber, title) char *p, line[MSG_SIZ]; Board initial_position; int i, j, fenMode, pn; - + if (gameMode == Training ) SetTrainingModeOff(); @@ -6394,8 +9555,8 @@ 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 */ @@ -6421,23 +9582,13 @@ LoadPosition(f, positionNumber, title) DisplayError(_("Position not found in file"), 0); return FALSE; } - switch (line[0]) { - case '#': case 'x': - default: - fenMode = FALSE; - break; - 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': - fenMode = TRUE; - break; - } + // [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; 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); @@ -6455,16 +9606,16 @@ LoadPosition(f, positionNumber, title) } 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); } } - + blackPlaysFirst = FALSE; if (!feof(f)) { (void) fgets(line, MSG_SIZ, f); @@ -6473,7 +9624,7 @@ LoadPosition(f, positionNumber, title) } } startedFromSetupPosition = TRUE; - + SendToProgram("force\n", &first); CopyBoard(boards[0], initial_position); if (blackPlaysFirst) { @@ -6486,7 +9637,20 @@ LoadPosition(f, positionNumber, title) currentMove = forwardMostMove = backwardMostMove = 0; 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); @@ -6500,7 +9664,7 @@ LoadPosition(f, positionNumber, title) timeRemaining[0][1] = whiteTimeRemaining; timeRemaining[1][1] = blackTimeRemaining; DrawPosition(FALSE, boards[currentMove]); - + return TRUE; } @@ -6552,7 +9716,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 { @@ -6567,7 +9731,7 @@ SavePart(str) { static char buf[MSG_SIZ]; char *p; - + p = strchr(str, ' '); if (p == NULL) return str; strncpy(buf, str, p - str); @@ -6577,6 +9741,83 @@ SavePart(str) #define PGN_MAX_LINE 75 +#define PGN_SIDE_WHITE 0 +#define PGN_SIDE_BLACK 1 + +/* [AS] */ +static int FindFirstMoveOutOfBook( int side ) +{ + int result = -1; + + if( backwardMostMove == 0 && ! startedFromSetupPosition) { + int index = backwardMostMove; + int has_book_hit = 0; + + if( (index % 2) != side ) { + index++; + } + + while( index < forwardMostMove ) { + /* Check to see if engine is in book */ + int depth = pvInfoList[index].depth; + int score = pvInfoList[index].score; + int in_book = 0; + + if( depth <= 2 ) { + 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; + + if( ! in_book ) { + result = index; + + break; + } + + index += 2; + } + } + + return result; +} + +/* [AS] */ +void GetOutOfBookInfo( char * buf ) +{ + int oob[2]; + int i; + int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ + + oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE ); + oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK ); + + *buf = '\0'; + + if( oob[0] >= 0 || oob[1] >= 0 ) { + for( i=0; i<2; i++ ) { + int idx = oob[i]; + + if( idx >= 0 ) { + if( i > 0 && oob[0] >= 0 ) { + strcat( buf, " " ); + } + + sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" ); + sprintf( buf+strlen(buf), "%s%.2f", + pvInfoList[idx].score >= 0 ? "+" : "", + pvInfoList[idx].score / 100.0 ); + } + } + } +} + /* Save game in PGN style and close the file */ int SaveGamePGN(f) @@ -6584,27 +9825,41 @@ 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 */ + tm = time((time_t *) NULL); - + PrintPGNTags(f, &gameInfo); - + if (backwardMostMove > 0 || startedFromSetupPosition) { - char *fen = PositionToFEN(backwardMostMove); + char *fen = PositionToFEN(backwardMostMove, NULL); fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen); fprintf(f, "\n{--------------\n"); PrintPosition(f, backwardMostMove); fprintf(f, "--------------}\n"); free(fen); - } else { + } + else { + /* [AS] Out of book annotation */ + if( appData.saveOutOfBookInfo ) { + char buf[64]; + + GetOutOfBookInfo( buf ); + + if( buf[0] != '\0' ) { + fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); + } + } + fprintf(f, "\n"); } i = backwardMostMove; - offset = backwardMostMove & (~1L); /* output move numbers start at 1 */ linelen = 0; newblock = TRUE; @@ -6641,12 +9896,12 @@ SaveGamePGN(f) fprintf(f, " "); linelen++; } - fprintf(f, numtext); + fprintf(f, "%s", numtext); linelen += numlen; /* Get move */ - movetext = SavePart(parseList[i]); - movelen = strlen(movetext); + 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 */ /* Print move */ blank = linelen > 0 && movelen > 0; @@ -6659,12 +9914,57 @@ SaveGamePGN(f) fprintf(f, " "); linelen++; } - fprintf(f, movetext); + fprintf(f, "%s", 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(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 + + 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, "%s", move_buffer); + linelen += movelen; + } + i++; } - + /* Start a new line */ if (linelen > 0) fprintf(f, "\n"); @@ -6683,6 +9983,7 @@ SaveGamePGN(f) } fclose(f); + lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving return TRUE; } @@ -6693,12 +9994,12 @@ SaveGameOldStyle(f) { int i, offset; time_t tm; - + tm = time((time_t *) NULL); - + fprintf(f, "# %s game file -- %s", programName, ctime(&tm)); PrintOpponents(f); - + if (backwardMostMove > 0 || startedFromSetupPosition) { fprintf(f, "\n[--------------\n"); PrintPosition(f, backwardMostMove); @@ -6733,7 +10034,7 @@ SaveGameOldStyle(f) i++; } } - + if (commentList[i] != NULL) { fprintf(f, "[%s]\n", commentList[i]); } @@ -6759,6 +10060,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 @@ -6778,7 +10080,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 { @@ -6797,17 +10099,17 @@ SavePosition(f, dummy, dummy2) { time_t tm; char *fen; - + if (appData.oldSaveStyle) { tm = time((time_t *) NULL); - + fprintf(f, "# %s position file -- %s", programName, ctime(&tm)); PrintOpponents(f); fprintf(f, "[--------------\n"); PrintPosition(f, currentMove); fprintf(f, "--------------]\n"); } else { - fen = PositionToFEN(currentMove); + fen = PositionToFEN(currentMove, NULL); fprintf(f, "%s\n", fen); free(fen); } @@ -6825,7 +10127,7 @@ ReloadCmailMsgEvent(unregister) int i; struct stat inbuf, outbuf; int status; - + /* Any registered moves are unregistered if unregister is set, */ /* i.e. invoked by the signal handler */ if (unregister) { @@ -6853,7 +10155,7 @@ ReloadCmailMsgEvent(unregister) outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5); sprintf(outFilename, "%s.out", appData.cmailGameName); } - + status = stat(outFilename, &outbuf); if (status < 0) { cmailMailedMove = FALSE; @@ -6861,10 +10163,10 @@ ReloadCmailMsgEvent(unregister) status = stat(inFilename, &inbuf); cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime); } - + /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE counts the games, notes how each one terminated, etc. - + It would be nice to remove this kludge and instead gather all the information while building the game list. (And to keep it in the game list nodes instead of having a bunch of fixed-size @@ -6874,7 +10176,7 @@ ReloadCmailMsgEvent(unregister) */ cmailMsgLoaded = TRUE; LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE); - + /* Load first game in the file or popup game menu */ LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE); @@ -6900,7 +10202,7 @@ RegisterMove() cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE; nCmailMovesRegistered --; - if (cmailCommentList[lastLoadGameNumber - 1] != NULL) + if (cmailCommentList[lastLoadGameNumber - 1] != NULL) { free(cmailCommentList[lastLoadGameNumber - 1]); cmailCommentList[lastLoadGameNumber - 1] = NULL; @@ -6947,11 +10249,11 @@ RegisterMove() sprintf(string, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber); - + f = fopen(string, "w"); if (appData.oldSaveStyle) { SaveGameOldStyle(f); /* also closes the file */ - + sprintf(string, "%s.pos.out", appData.cmailGameName); f = fopen(string, "w"); SavePosition(f, 0, NULL); /* also closes the file */ @@ -6959,10 +10261,10 @@ RegisterMove() fprintf(f, "{--------------\n"); PrintPosition(f, currentMove); fprintf(f, "--------------}\n\n"); - + SaveGame(f, 0, NULL); /* also closes the file*/ } - + cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE; nCmailMovesRegistered ++; } else if (nCmailGames == 1) { @@ -7005,7 +10307,7 @@ MailMoveEvent() #endif if (! (cmailMailedMove || RegisterMove())) return; - + if ( cmailMailedMove || (nCmailMovesRegistered + nCmailResults == nCmailGames)) { sprintf(string, partCommandString, @@ -7071,7 +10373,7 @@ CmailMsg() char number[5]; char string[MSG_SIZ]; /* Space for game-list */ int i; - + if (!cmailMsgLoaded) return ""; if (cmailMailedMove) { @@ -7088,7 +10390,7 @@ CmailMsg() sprintf(number, "%d", i + 1); prependComma = 1; } - + strcat(string, number); } } @@ -7100,12 +10402,12 @@ CmailMsg() sprintf(cmailMsg, _("Still need to make move for game\n")); break; - + case 2: sprintf(cmailMsg, _("Still need to make moves for both games\n")); break; - + default: sprintf(cmailMsg, _("Still need to make moves for all %d games\n"), @@ -7119,7 +10421,7 @@ CmailMsg() _("Still need to make a move for game %s\n"), string); break; - + case 0: if (nCmailResults == nCmailGames) { sprintf(cmailMsg, _("No unfinished games\n")); @@ -7127,7 +10429,7 @@ CmailMsg() sprintf(cmailMsg, _("Ready to send mail\n")); } break; - + default: sprintf(cmailMsg, _("Still need to make moves for games %s\n"), @@ -7153,8 +10455,6 @@ ResetGameEvent() } } -static int exiting = 0; - void ExitEvent(status) int status; @@ -7177,28 +10477,32 @@ ExitEvent(status) if (icsPR != NoProc) { DestroyChildProcess(icsPR, TRUE); } - /* Save game if resource set and not already saved by GameEnds() */ - if (gameInfo.resultDetails == NULL && forwardMostMove > 0) { - if (*appData.saveGameFile != NULLCHAR) { - SaveGameToFile(appData.saveGameFile, TRUE); - } else if (appData.autoSaveGames) { - AutoSaveGame(); - } - if (*appData.savePositionFile != NULLCHAR) { - SavePositionToFile(appData.savePositionFile); - } + + /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */ + GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER); + + /* [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"); } - GameEnds((ChessMove) 0, NULL, GE_PLAYER); /* Kill off chess programs */ if (first.pr != NoProc) { ExitAnalyzeMode(); + + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &first); - DestroyChildProcess(first.pr, first.useSigterm); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ ); } if (second.pr != NoProc) { + DoSleep( appData.delayBeforeQuit ); SendToProgram("quit\n", &second); - DestroyChildProcess(second.pr, second.useSigterm); + DoSleep( appData.delayAfterQuit ); + DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ ); } if (first.isr != NULL) { RemoveInputSource(first.isr); @@ -7226,7 +10530,7 @@ PauseEvent() DisplayBothClocks(); } if (gameMode == PlayFromGameFile) { - if (appData.timeDelay >= 0) + if (appData.timeDelay >= 0) AutoPlayGameLoop(); } else if (gameMode == IcsExamining && pauseExamInvalid) { Reset(FALSE, TRUE); @@ -7308,17 +10612,16 @@ AnalyzeModeEvent() return; if (gameMode != AnalyzeFile) { - if (!appData.icsEngineAnalyze) { - 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.")); + EngineOutputPopUp(); } if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode; pausing = FALSE; @@ -7344,8 +10647,7 @@ 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.")); + EngineOutputPopUp(); } gameMode = AnalyzeFile; pausing = FALSE; @@ -7361,34 +10663,39 @@ void MachineWhiteEvent() { char buf[MSG_SIZ]; + char *bookHit = NULL; if (appData.noChessProgram || (gameMode == MachinePlaysWhite)) return; - if (gameMode == PlayFromGameFile || - gameMode == TwoMachinesPlay || - gameMode == Training || - gameMode == AnalyzeMode || + if (gameMode == PlayFromGameFile || + gameMode == TwoMachinesPlay || + gameMode == Training || + gameMode == AnalyzeMode || gameMode == EndOfGame) EditGameEvent(); - if (gameMode == EditPosition) + if (gameMode == EditPosition) EditPositionDone(); if (!WhiteOnMove(currentMove)) { DisplayError(_("It is not White's turn"), 0); return; } - + if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) ExitAnalyzeMode(); - if (gameMode == EditGame || gameMode == AnalyzeMode || + if (gameMode == EditGame || gameMode == AnalyzeMode || gameMode == AnalyzeFile) 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(); @@ -7406,17 +10713,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); } } @@ -7424,30 +10744,31 @@ void MachineBlackEvent() { char buf[MSG_SIZ]; + char *bookHit = NULL; if (appData.noChessProgram || (gameMode == MachinePlaysBlack)) return; - if (gameMode == PlayFromGameFile || - gameMode == TwoMachinesPlay || - gameMode == Training || - gameMode == AnalyzeMode || + if (gameMode == PlayFromGameFile || + gameMode == TwoMachinesPlay || + gameMode == Training || + gameMode == AnalyzeMode || gameMode == EndOfGame) EditGameEvent(); - if (gameMode == EditPosition) + if (gameMode == EditPosition) EditPositionDone(); if (WhiteOnMove(currentMove)) { DisplayError(_("It is not Black's turn"), 0); return; } - + if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) ExitAnalyzeMode(); - if (gameMode == EditGame || gameMode == AnalyzeMode || + if (gameMode == EditGame || gameMode == AnalyzeMode || gameMode == AnalyzeFile) TruncateGame(); @@ -7469,10 +10790,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(); @@ -7480,6 +10800,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); } } @@ -7512,7 +10844,8 @@ TwoMachinesEvent P((void)) int i; char buf[MSG_SIZ]; ChessProgramState *onmove; - + char *bookHit = NULL; + if (appData.noChessProgram) return; switch (gameMode) { @@ -7559,10 +10892,13 @@ TwoMachinesEvent P((void)) 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); @@ -7591,8 +10927,8 @@ TwoMachinesEvent P((void)) SendToProgram(buf, &second); } + ResetClocks(); if (!first.sendTime || !second.sendTime) { - ResetClocks(); timeRemaining[0][forwardMostMove] = whiteTimeRemaining; timeRemaining[1][forwardMostMove] = blackTimeRemaining; } @@ -7605,11 +10941,26 @@ 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); + savedMessage = bookMove; // args for deferred call + savedState = onmove; + ScheduleDelayedEvent(DeferredBookMove, 1); + } } void @@ -7659,7 +11010,7 @@ IcsClientEvent() case AnalyzeFile: ExitAnalyzeMode(); break; - + default: EditGameEvent(); break; @@ -7724,7 +11075,7 @@ EditGameEvent() default: return; } - + pausing = FALSE; StopClocks(); first.offeredDraw = second.offeredDraw = 0; @@ -7751,8 +11102,8 @@ EditGameEvent() whiteFlag = blackFlag = 0; } DisplayTitle(""); - } - + } + gameMode = EditGame; ModeHighlight(); SetGameInfo(); @@ -7766,16 +11117,16 @@ EditPositionEvent() EditGameEvent(); return; } - + EditGameEvent(); if (gameMode != EditGame) return; - + gameMode = EditPosition; ModeHighlight(); SetGameInfo(); if (currentMove > 0) CopyBoard(boards[0], boards[currentMove]); - + blackPlaysFirst = !WhiteOnMove(currentMove); ResetClocks(); currentMove = forwardMostMove = backwardMostMove = 0; @@ -7786,40 +11137,60 @@ EditPositionEvent() void ExitAnalyzeMode() { - /* icsEngineAnalyze - possible call from other functions */ - if (appData.icsEngineAnalyze) { - appData.icsEngineAnalyze = FALSE; - DisplayMessage("","Close ICS engine analyze..."); - } + /* [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; } - AnalysisPopDown(); thinkOutput[0] = NULLCHAR; } 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 MSG_SIZ) len = MSG_SIZ; - + strncpy(temp, buf, len); temp[len] = 0; @@ -7895,6 +11266,7 @@ EditPositionMenuEvent(selection, x, y) int x, y; { char buf[MSG_SIZ]; + ChessSquare piece = boards[0][y][x]; if (gameMode != EditPosition && gameMode != IcsExamining) return; @@ -7907,16 +11279,17 @@ EditPositionMenuEvent(selection, x, y) SendToICS(ics_prefix); SendToICS("clearboard\n"); } else { - for (x = 0; x < BOARD_SIZE; x++) { - for (y = 0; y < BOARD_SIZE; y++) { + for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare; + if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */ + for (y = 0; y < BOARD_HEIGHT; y++) { if (gameMode == IcsExamining) { if (boards[currentMove][y][x] != EmptySquare) { sprintf(buf, "%sx@%c%c\n", ics_prefix, - 'a' + x, '1' + y); + AAA + x, ONE + y); SendToICS(buf); } } else { - boards[0][y][x] = EmptySquare; + boards[0][y][x] = p; } } } @@ -7936,7 +11309,7 @@ EditPositionMenuEvent(selection, x, y) case EmptySquare: if (gameMode == IcsExamining) { - sprintf(buf, "%sx@%c%c\n", ics_prefix, 'a' + x, '1' + y); + sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y); SendToICS(buf); } else { boards[0][y][x] = EmptySquare; @@ -7944,10 +11317,41 @@ EditPositionMenuEvent(selection, x, y) } break; + case PromotePiece: + if(piece >= (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; @@ -8005,7 +11409,7 @@ void AcceptEvent() { /* Accept a pending offer of any kind from opponent */ - + if (appData.icsActive) { SendToICS(ics_prefix); SendToICS("accept\n"); @@ -8030,7 +11434,7 @@ void DeclineEvent() { /* Decline a pending offer of any kind from opponent */ - + if (appData.icsActive) { SendToICS(ics_prefix); SendToICS("decline\n"); @@ -8102,14 +11506,14 @@ void DrawEvent() { /* Offer draw or accept pending draw offer from opponent */ - + if (appData.icsActive) { /* Note: tournament rules require draw offers to be made after you make your move but before you punch your clock. Currently ICS doesn't let you do that; instead, you immediately punch your clock after making a move, but you can offer a draw at any time. */ - + SendToICS(ics_prefix); SendToICS("draw\n"); } else if (cmailMsgLoaded) { @@ -8143,7 +11547,7 @@ void AdjournEvent() { /* Offer Adjourn or accept pending Adjourn offer from opponent */ - + if (appData.icsActive) { SendToICS(ics_prefix); SendToICS("adjourn\n"); @@ -8157,7 +11561,7 @@ void AbortEvent() { /* Offer Abort or accept pending Abort offer from opponent */ - + if (appData.icsActive) { SendToICS(ics_prefix); SendToICS("abort\n"); @@ -8170,7 +11574,7 @@ void ResignEvent() { /* Resign. You can do this even if it's not your turn. */ - + if (appData.icsActive) { SendToICS(ics_prefix); SendToICS("resign\n"); @@ -8231,25 +11635,25 @@ ForwardInner(target) if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); - + if (gameMode == IcsExamining && pausing) limit = pauseExamForwardMostMove; else limit = forwardMostMove; - + if (target > limit) target = limit; 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); } @@ -8258,8 +11662,8 @@ ForwardInner(target) } } } - if (gameMode == EditGame || gameMode == AnalyzeMode || - gameMode == Training || gameMode == PlayFromGameFile || + if (gameMode == EditGame || gameMode == AnalyzeMode || + gameMode == Training || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) { while (currentMove < target) { SendMoveToProgram(currentMove++, &first); @@ -8267,7 +11671,7 @@ ForwardInner(target) } else { currentMove = target; } - + if (gameMode == EditGame || gameMode == EndOfGame) { whiteTimeRemaining = timeRemaining[0][currentMove]; blackTimeRemaining = timeRemaining[1][currentMove]; @@ -8276,7 +11680,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]); } } @@ -8300,13 +11704,13 @@ ToEndEvent() /* to optimze, we temporarily turn off analysis mode while we feed * the remaining moves to the engine. Otherwise we get analysis output * after each move. - */ + */ if (first.analysisSupport) { SendToProgram("exit\nforce\n", &first); first.analyzing = FALSE; } } - + if (gameMode == IcsExamining && !pausing) { SendToICS(ics_prefix); SendToICS("forward 999999\n"); @@ -8327,6 +11731,8 @@ void BackwardInner(target) int target; { + int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */ + if (appData.debugMode) fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n", target, currentMove, forwardMostMove); @@ -8334,23 +11740,23 @@ BackwardInner(target) if (gameMode == EditPosition) return; if (currentMove <= backwardMostMove) { ClearHighlights(); - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(full_redraw, boards[currentMove]); return; } if (gameMode == PlayFromGameFile && !pausing) PauseEvent(); - + 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); } @@ -8368,18 +11774,17 @@ BackwardInner(target) } else { currentMove = target; } - + if (gameMode == EditGame || gameMode == EndOfGame) { whiteTimeRemaining = timeRemaining[0][currentMove]; blackTimeRemaining = timeRemaining[1][currentMove]; } DisplayBothClocks(); DisplayMove(currentMove - 1); - DrawPosition(FALSE, boards[currentMove]); + 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 @@ -8399,7 +11804,7 @@ ToStartEvent() if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) { /* to optimze, we temporarily turn off analysis mode while we undo * all the moves. Otherwise we get analysis output after each undo. - */ + */ if (first.analysisSupport) { SendToProgram("exit\nforce\n", &first); first.analyzing = FALSE; @@ -8467,7 +11872,7 @@ RetractMoveEvent() DisplayBothClocks(); DisplayMove(currentMove - 1); ClearHighlights();/*!! could figure this out*/ - DrawPosition(FALSE, boards[currentMove]); + DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */ SendToProgram("remove\n", &first); /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */ break; @@ -8620,12 +12025,12 @@ PrintPosition(fp, move) int 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)) @@ -8666,6 +12071,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; @@ -8701,7 +12107,7 @@ SetGameInfo() switch (gameMode) { case MachinePlaysWhite: - gameInfo.event = StrSave("Computer chess game"); + gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); gameInfo.round = StrSave("-"); @@ -8711,7 +12117,7 @@ SetGameInfo() break; case MachinePlaysBlack: - gameInfo.event = StrSave("Computer chess game"); + gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); gameInfo.round = StrSave("-"); @@ -8721,7 +12127,7 @@ SetGameInfo() break; case TwoMachinesPlay: - gameInfo.event = StrSave("Computer chess game"); + gameInfo.event = StrSave( appData.pgnEventHeader ); gameInfo.site = StrSave(HostName()); gameInfo.date = PGNDate(); if (matchGame > 0) { @@ -8826,6 +12232,8 @@ AppendComment(index, text) int oldlen, len; char *old; + text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */ + CrushCRs(text); while (*text == '\n') text++; len = strlen(text); @@ -8850,6 +12258,99 @@ AppendComment(index, text) } } +static char * FindStr( char * text, char * sub_text ) +{ + char * result = strstr( text, sub_text ); + + if( result != NULL ) { + result += strlen( sub_text ); + } + + return result; +} + +/* [AS] Try to extract PV info from PGN comment */ +/* [HGM] PV time: and then remove it, to prevent it appearing twice */ +char *GetInfoFromComment( int index, char * text ) +{ + char * sep = text; + + if( text != NULL && index > 0 ) { + int score = 0; + int depth = 0; + int time = -1, sec = 0, deci; + char * s_eval = FindStr( text, "[%eval " ); + char * s_emt = FindStr( text, "[%emt " ); + + if( s_eval != NULL || s_emt != NULL ) { + /* New style */ + char delim; + + if( s_eval != NULL ) { + if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) { + return text; + } + + if( delim != ']' ) { + return text; + } + } + + if( s_emt != NULL ) { + } + } + else { + /* We expect something like: [+|-]nnn.nn/dd */ + int score_lo = 0; + + sep = strchr( text, '/' ); + if( sep == NULL || sep < (text+4) ) { + return text; + } + + 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 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 text; + } + + if( time < 0 ) { + time = -1; + } + + pvInfoList[index-1].depth = depth; + pvInfoList[index-1].score = score; + pvInfoList[index-1].time = 10*time; // centi-sec + } + return sep; +} + void SendToProgram(message, cps) char *message; @@ -8860,20 +12361,30 @@ SendToProgram(message, cps) if (cps->pr == NULL) return; Attention(cps); - + if (appData.debugMode) { TimeMark now; GetTimeMark(&now); - fprintf(debugFP, "%ld >%-6s: %s", + fprintf(debugFP, "%ld >%-6s: %s", SubtractTimeMarks(&now, &programStartTime), cps->which, message); } - + count = strlen(message); outCount = OutputToProcess(cps->pr, message, count, &error); - if (outCount < count && !exiting) { + 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); - DisplayFatalError(buf, error, 1); + 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); } } @@ -8895,6 +12406,15 @@ ReceiveFromProgram(isr, closure, message, count, error) sprintf(buf, _("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 { @@ -8902,36 +12422,57 @@ ReceiveFromProgram(isr, closure, message, count, error) _("Error reading from %s chess program (%s)"), cps->which, cps->program); RemoveInputSource(cps->isr); - DisplayFatalError(buf, error, 1); + + /* [AS] Program is misbehaving badly... kill it */ + if( count == -2 ) { + DestroyChildProcess( cps->pr, 9 ); + cps->pr = NoProc; + } + + DisplayFatalError(buf, error, 1); } - GameEnds((ChessMove) 0, NULL, GE_PLAYER); return; } - + if ((end_str = strchr(message, '\r')) != NULL) *end_str = NULLCHAR; if ((end_str = strchr(message, '\n')) != NULL) *end_str = NULLCHAR; - + if (appData.debugMode) { - TimeMark now; - GetTimeMark(&now); - fprintf(debugFP, "%ld <%-6s: %s\n", - SubtractTimeMarks(&now, &programStartTime), - cps->which, message); - } - /* if icsEngineAnalyze is active we block all - whisper and kibitz output, because nobody want - see this - */ - if (appData.icsEngineAnalyze) { - if (strstr(message, "whisper") != NULL || - strstr(message, "kibitz") != NULL || - strstr(message, "tellics") != NULL) return; - HandleMachineMove(message, cps); - } else { - HandleMachineMove(message, cps); + 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); } @@ -8942,7 +12483,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 */ @@ -8981,6 +12533,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 @@ -9002,10 +12571,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\n", time); + SendToProgram(message, cps); - sprintf(message, "time %ld\notim %ld\n", time, otime); + sprintf(message, "otim %ld\n", otime); SendToProgram(message, cps); } @@ -9073,6 +12651,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; @@ -9091,7 +12746,7 @@ FeatureDone(cps, val) void ParseFeatures(args, cps) char* args; - ChessProgramState *cps; + ChessProgramState *cps; { char *p = args; char *q; @@ -9103,10 +12758,10 @@ ParseFeatures(args, cps) if (*p == NULLCHAR) return; if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue; - if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue; - if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue; - if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue; - if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue; + if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue; + if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue; + if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue; + if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue; if (BoolFeature(&p, "reuse", &val, cps)) { /* Engine can disable reuse, but can't enable it if user said no */ if (!val) cps->reuse = FALSE; @@ -9134,6 +12789,33 @@ ParseFeatures(args, cps) FeatureDone(cps, val); continue; } + /* Added by Tord: */ + if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue; + 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; @@ -9165,7 +12847,7 @@ PeriodicUpdatesEvent(newState) appData.periodicUpdates=newState; /* Display type changes, so update it now */ - DisplayAnalysis(); +// DisplayAnalysis(); /* Get the ball rolling again... */ if (newState) { @@ -9196,12 +12878,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); @@ -9213,7 +12916,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 @@ -9233,16 +12936,34 @@ DisplayMove(moveNumber) char res[MSG_SIZ]; char cpThinkOutput[MSG_SIZ]; - if (moveNumber == forwardMostMove - 1 || + if(appData.noGUI) return; // [HGM] fast: suppress display of moves + + if (moveNumber == forwardMostMove - 1 || gameMode == AnalyzeMode || gameMode == AnalyzeFile) { - strcpy(cpThinkOutput, thinkOutput); - if (strchr(cpThinkOutput, '\n')) - *strchr(cpThinkOutput, '\n') = NULLCHAR; + safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)); + + if (strchr(cpThinkOutput, '\n')) { + *strchr(cpThinkOutput, '\n') = NULLCHAR; + } } else { *cpThinkOutput = NULLCHAR; } + /* [AS] Hide thinking from human user */ + if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) { + *cpThinkOutput = NULLCHAR; + if( thinkOutput[0] != NULLCHAR ) { + int i; + + for( i=0; i<=hiddenThinkOutputState; i++ ) { + cpThinkOutput[i] = '.'; + } + cpThinkOutput[i] = NULLCHAR; + hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3; + } + } + if (moveNumber == forwardMostMove - 1 && gameInfo.resultDetails != NULL) { if (gameInfo.resultDetails[0] == NULLCHAR) { @@ -9266,104 +12987,34 @@ DisplayMove(moveNumber) } void -DisplayAnalysisText(text) - char *text; -{ - char buf[MSG_SIZ]; - - if (gameMode == AnalyzeMode || gameMode == AnalyzeFile - || appData.icsEngineAnalyze) { - sprintf(buf, "Analysis (%s)", first.tidy); - AnalysisPopUp(buf, text); - } -} - -static int -only_one_move(str) - char *str; -{ - while (*str && isspace(*str)) ++str; - while (*str && !isspace(*str)) ++str; - if (!*str) return 1; - while (*str && isspace(*str)) ++str; - if (!*str) return 1; - return 0; -} - -void -DisplayAnalysis() -{ - char buf[MSG_SIZ]; - double nps; - static char *xtra[] = { "", " (--)", " (++)" }; - int h, m, s, cs; - - if (programStats.time == 0) { - programStats.time = 1; - } - - if (programStats.got_only_move) { - strcpy(buf, programStats.movelist); - } else { - nps = (u64ToDouble(programStats.nodes) / - ((double)programStats.time /100.0)); - - cs = programStats.time % 100; - s = programStats.time / 100; - h = (s / (60*60)); - s = s - h*60*60; - m = (s/60); - s = s - m*60; - - if (programStats.moves_left > 0 && appData.periodicUpdates) { - if (programStats.move_name[0] != NULLCHAR) { - 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, programStats.movelist, - only_one_move(programStats.movelist)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } else { - 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, - programStats.movelist, - only_one_move(programStats.movelist)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } - } else { - sprintf(buf, "depth=%d %+.2f %s%s\nNodes: "u64Display" NPS: %d\nTime: %02d:%02d:%02d.%02d", - programStats.depth, - ((float)programStats.score)/100.0, - programStats.movelist, - only_one_move(programStats.movelist)? - xtra[programStats.got_fail] : "", - (u64)programStats.nodes, (int)nps, h, m, s, cs); - } - } - DisplayAnalysisText(buf); -} - -void DisplayComment(moveNumber, text) int moveNumber; 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]); + } + // [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; - 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]); - } - - CommentPopUp(title, text); + if (text != NULL) + CommentPopUp(title, text); } /* This routine sends a ^C interrupt to gnuchess, to awaken it if it @@ -9414,9 +13065,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; @@ -9436,9 +13087,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; @@ -9456,30 +13107,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 += timeControl; - 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 += timeControl; - break; - default: - break; - } - } + blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2) + / WhitePlayer()->other->timeOdds; } void @@ -9498,6 +13137,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) @@ -9509,13 +13153,13 @@ GetTimeMark(tm) struct timezone timeZone; gettimeofday(&timeVal, &timeZone); - tm->sec = (long) timeVal.tv_sec; + tm->sec = (long) timeVal.tv_sec; tm->ms = (int) (timeVal.tv_usec / 1000L); #else /*!HAVE_GETTIMEOFDAY*/ #if HAVE_FTIME -#include +// include / moved to just above start of function struct timeb timeB; ftime(&timeB); @@ -9548,7 +13192,7 @@ SubtractTimeMarks(tm2, tm1) * We give the human user a slight advantage if he is playing white---the * clocks don't run until he makes his first move, so it takes zero time. * Also, we don't account for network lag, so we could get out of sync - * with GNU Chess's clock -- but then, referees are always right. + * with GNU Chess's clock -- but then, referees are always right. */ static TimeMark tickStartTM; @@ -9570,15 +13214,25 @@ 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() +ResetClocks() { (void) StopClockTimer(); if (appData.icsActive) { whiteTimeRemaining = blackTimeRemaining = 0; - } else { - whiteTimeRemaining = blackTimeRemaining = timeControl; + } else { /* [HGM] correct new time quote for time odds */ + whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds; + blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds; } if (whiteFlag || blackFlag) { DisplayTitle(""); @@ -9599,7 +13253,7 @@ DecrementClocks() if (!appData.clockMode) return; if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return; - + GetTimeMark(&now); lastTickLength = SubtractTimeMarks(&now, &tickStartTM); @@ -9609,17 +13263,19 @@ 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)); } if (CheckFlags()) return; - + tickStartTM = now; intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge; StartClockTimer(intendedTickLength); @@ -9627,9 +13283,9 @@ DecrementClocks() /* if the time remaining has fallen below the alarm threshold, sound the * alarm. if the alarm has sounded and (due to a takeback or time control * with increment) the time remaining has increased to a level above the - * threshold, reset the alarm so it can sound again. + * threshold, reset the alarm so it can sound again. */ - + if (appData.icsActive && appData.icsAlarm) { /* make sure we are dealing with the user's clock */ @@ -9639,7 +13295,7 @@ DecrementClocks() if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) { alarmSounded = FALSE; - } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { + } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { PlayAlarmSound(); alarmSounded = TRUE; } @@ -9667,9 +13323,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(); } @@ -9698,12 +13364,12 @@ SwitchClocks() whiteTimeRemaining : blackTimeRemaining); StartClockTimer(intendedTickLength); } - + /* Stop both clocks */ void StopClocks() -{ +{ long lastTickLength; TimeMark now; @@ -9714,15 +13380,17 @@ 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)); } CheckFlags(); } - + /* Start clock of player on move. Time may have been reset, so if clock is already running, stop and restart it. */ void @@ -9738,6 +13406,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); } @@ -9748,7 +13431,7 @@ TimeString(ms) long second, minute, hour, day; char *sign = ""; static char buf[32]; - + if (ms > 0 && ms <= 9900) { /* convert milliseconds to tenths, rounding up */ double tenths = floor( ((double)(ms + 99L)) / 100.00 ); @@ -9766,14 +13449,14 @@ TimeString(ms) sign = "-"; second = -second; } - + day = second / (60 * 60 * 24); second = second % (60 * 60 * 24); hour = second / (60 * 60); second = second % (60 * 60); minute = second / 60; second = second % 60; - + if (day > 0) sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ", sign, day, hour, minute, second); @@ -9781,7 +13464,7 @@ TimeString(ms) sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second); else sprintf(buf, " %s%2ld:%02ld ", sign, minute, second); - + return buf; } @@ -9794,13 +13477,13 @@ StrStr(string, match) char *string, *match; { int i, length; - + length = strlen(match); - + for (i = strlen(string) - length; i >= 0; i--, string++) if (!strncmp(match, string, length)) return string; - + return NULL; } @@ -9809,9 +13492,9 @@ StrCaseStr(string, match) char *string, *match; { int i, j, length; - + length = strlen(match); - + for (i = strlen(string) - length; i >= 0; i--, string++) { for (j = 0; j < length; j++) { if (ToLower(match[j]) != ToLower(string[j])) @@ -9829,7 +13512,7 @@ StrCaseCmp(s1, s2) char *s1, *s2; { char c1, c2; - + for (;;) { c1 = ToLower(*s1++); c2 = ToLower(*s2++); @@ -9897,116 +13580,236 @@ PGNDate() char * -PositionToFEN(move) +PositionToFEN(move, overrideCastling) int move; + 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) = ' '; + /* [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 if(move == backwardMostMove) { + // [HGM] perhaps we should always do it like this, and forget the above? + if(epStatus[move] >= 0) { + *p++ = epStatus[move] + 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); - + return StrSave(buf); } 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; } @@ -10014,21 +13817,165 @@ 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': + case 'b': *blackPlaysFirst = TRUE; break; default: 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; } - + void EditPositionPasteFEN(char *fen) { @@ -10043,9 +13990,123 @@ EditPositionPasteFEN(char *fen) 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 0) && (len < sizeof(cseq)); + if (ret) + strcpy(cseq, new_seq); + else if (appData.debugMode) + fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %d)\n", new_seq, sizeof(cseq)-1); + return ret; +} + +/* + reformat a source message so words don't cross the width boundary. internal + newlines are not removed. returns the wrapped size (no null character unless + included in source message). If dest is NULL, only calculate the size required + for the dest buffer. lp argument indicats line position upon entry, and it's + passed back upon exit. +*/ +int wrap(char *dest, char *src, int count, int width, int *lp) +{ + int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen; + + cseq_len = strlen(cseq); + old_line = line = *lp; + ansi = len = clen = 0; + + for (i=0; i < count; i++) + { + if (src[i] == '\033') + ansi = 1; + + // if we hit the width, back up + if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ') + { + // store i & len in case the word is too long + old_i = i, old_len = len; + + // find the end of the last word + while (i && src[i] != ' ' && src[i] != '\n') + { + i--; + len--; + } + + // word too long? restore i & len before splitting it + if ((old_i-i+clen) >= width) + { + i = old_i; + len = old_len; + } + + // extra space? + if (i && src[i-1] == ' ') + len--; + + if (src[i] != ' ' && src[i] != '\n') + { + i--; + if (len) + len--; + } + + // now append the newline and continuation sequence + if (dest) + dest[len] = '\n'; + len++; + if (dest) + strncpy(dest+len, cseq, cseq_len); + len += cseq_len; + line = cseq_len; + clen = cseq_len; + continue; + } + + if (dest) + dest[len] = src[i]; + len++; + if (!ansi) + line++; + if (src[i] == '\n') + line = 0; + if (src[i] == 'm') + ansi = 0; + } + if (dest && appData.debugMode) + { + fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ", + count, width, line, len, *lp); + show_bytes(debugFP, src, count); + fprintf(debugFP, "\ndest: "); + show_bytes(debugFP, dest, len); + fprintf(debugFP, "\n"); + } + *lp = dest ? line : old_line; + + return len; +}