-/*\r
- * backend.c -- Common back end for X and Windows NT versions of\r
- * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $\r
- *\r
- * Copyright 1991 by Digital Equipment Corporation, Maynard,\r
- * Massachusetts. Enhancements Copyright\r
- * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software\r
- * Foundation, Inc.\r
- *\r
- * The following terms apply to Digital Equipment Corporation's copyright\r
- * interest in XBoard:\r
- * ------------------------------------------------------------------------\r
- * All Rights Reserved\r
- *\r
- * Permission to use, copy, modify, and distribute this software and its\r
- * documentation for any purpose and without fee is hereby granted,\r
- * provided that the above copyright notice appear in all copies and that\r
- * both that copyright notice and this permission notice appear in\r
- * supporting documentation, and that the name of Digital not be\r
- * used in advertising or publicity pertaining to distribution of the\r
- * software without specific, written prior permission.\r
- *\r
- * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING\r
- * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL\r
- * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR\r
- * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,\r
- * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,\r
- * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS\r
- * SOFTWARE.\r
- * ------------------------------------------------------------------------\r
- *\r
- * The following terms apply to the enhanced version of XBoard\r
- * distributed by the Free Software Foundation:\r
- * ------------------------------------------------------------------------\r
- *\r
- * GNU XBoard is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 3 of the License, or (at\r
- * your option) any later version.\r
- *\r
- * GNU XBoard is distributed in the hope that it will be useful, but\r
- * WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
- * General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program. If not, see http://www.gnu.org/licenses/. *\r
- *\r
- *------------------------------------------------------------------------\r
- ** See the file ChangeLog for a revision history. */\r
-\r
-/* [AS] Also useful here for debugging */\r
-#ifdef WIN32\r
-#include <windows.h>\r
-\r
-#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );\r
-\r
-#else\r
-\r
-#define DoSleep( n ) if( (n) >= 0) sleep(n)\r
-\r
-#endif\r
-\r
-#include "config.h"\r
-\r
-#include <assert.h>\r
-#include <stdio.h>\r
-#include <ctype.h>\r
-#include <errno.h>\r
-#include <sys/types.h>\r
-#include <sys/stat.h>\r
-#include <math.h>\r
-#include <ctype.h>\r
-\r
-#if STDC_HEADERS\r
-# include <stdlib.h>\r
-# include <string.h>\r
-#else /* not STDC_HEADERS */\r
-# if HAVE_STRING_H\r
-# include <string.h>\r
-# else /* not HAVE_STRING_H */\r
-# include <strings.h>\r
-# endif /* not HAVE_STRING_H */\r
-#endif /* not STDC_HEADERS */\r
-\r
-#if HAVE_SYS_FCNTL_H\r
-# include <sys/fcntl.h>\r
-#else /* not HAVE_SYS_FCNTL_H */\r
-# if HAVE_FCNTL_H\r
-# include <fcntl.h>\r
-# endif /* HAVE_FCNTL_H */\r
-#endif /* not HAVE_SYS_FCNTL_H */\r
-\r
-#if TIME_WITH_SYS_TIME\r
-# include <sys/time.h>\r
-# include <time.h>\r
-#else\r
-# if HAVE_SYS_TIME_H\r
-# include <sys/time.h>\r
-# else\r
-# include <time.h>\r
-# endif\r
-#endif\r
-\r
-#if defined(_amigados) && !defined(__GNUC__)\r
-struct timezone {\r
- int tz_minuteswest;\r
- int tz_dsttime;\r
-};\r
-extern int gettimeofday(struct timeval *, struct timezone *);\r
-#endif\r
-\r
-#if HAVE_UNISTD_H\r
-# include <unistd.h>\r
-#endif\r
-\r
-#include "common.h"\r
-#include "frontend.h"\r
-#include "backend.h"\r
-#include "parser.h"\r
-#include "moves.h"\r
-#if ZIPPY\r
-# include "zippy.h"\r
-#endif\r
-#include "backendz.h"\r
-#include "gettext.h" \r
- \r
-#ifdef ENABLE_NLS \r
-# define _(s) gettext (s) \r
-# define N_(s) gettext_noop (s) \r
-#else \r
-# define _(s) (s) \r
-# define N_(s) s \r
-#endif \r
-\r
-\r
-/* A point in time */\r
-typedef struct {\r
- long sec; /* Assuming this is >= 32 bits */\r
- int ms; /* Assuming this is >= 16 bits */\r
-} TimeMark;\r
-\r
-int establish P((void));\r
-void read_from_player P((InputSourceRef isr, VOIDSTAR closure,\r
- char *buf, int count, int error));\r
-void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,\r
- char *buf, int count, int error));\r
-void SendToICS P((char *s));\r
-void SendToICSDelayed P((char *s, long msdelay));\r
-void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,\r
- int toX, int toY));\r
-void InitPosition P((int redraw));\r
-void HandleMachineMove P((char *message, ChessProgramState *cps));\r
-int AutoPlayOneMove P((void));\r
-int LoadGameOneMove P((ChessMove readAhead));\r
-int LoadGameFromFile P((char *filename, int n, char *title, int useList));\r
-int LoadPositionFromFile P((char *filename, int n, char *title));\r
-int SavePositionToFile P((char *filename));\r
-void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,\r
- Board board, char *castle, char *ep));\r
-void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));\r
-void ShowMove P((int fromX, int fromY, int toX, int toY));\r
-int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,\r
- /*char*/int promoChar));\r
-void BackwardInner P((int target));\r
-void ForwardInner P((int target));\r
-void GameEnds P((ChessMove result, char *resultDetails, int whosays));\r
-void EditPositionDone P((void));\r
-void PrintOpponents P((FILE *fp));\r
-void PrintPosition P((FILE *fp, int move));\r
-void StartChessProgram P((ChessProgramState *cps));\r
-void SendToProgram P((char *message, ChessProgramState *cps));\r
-void SendMoveToProgram P((int moveNum, ChessProgramState *cps));\r
-void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,\r
- char *buf, int count, int error));\r
-void SendTimeControl P((ChessProgramState *cps,\r
- int mps, long tc, int inc, int sd, int st));\r
-char *TimeControlTagValue P((void));\r
-void Attention P((ChessProgramState *cps));\r
-void FeedMovesToProgram P((ChessProgramState *cps, int upto));\r
-void ResurrectChessProgram P((void));\r
-void DisplayComment P((int moveNumber, char *text));\r
-void DisplayMove P((int moveNumber));\r
-void DisplayAnalysis P((void));\r
-\r
-void ParseGameHistory P((char *game));\r
-void ParseBoard12 P((char *string));\r
-void StartClocks P((void));\r
-void SwitchClocks P((void));\r
-void StopClocks P((void));\r
-void ResetClocks P((void));\r
-char *PGNDate P((void));\r
-void SetGameInfo P((void));\r
-Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));\r
-int RegisterMove P((void));\r
-void MakeRegisteredMove P((void));\r
-void TruncateGame P((void));\r
-int looking_at P((char *, int *, char *));\r
-void CopyPlayerNameIntoFileName P((char **, char *));\r
-char *SavePart P((char *));\r
-int SaveGameOldStyle P((FILE *));\r
-int SaveGamePGN P((FILE *));\r
-void GetTimeMark P((TimeMark *));\r
-long SubtractTimeMarks P((TimeMark *, TimeMark *));\r
-int CheckFlags P((void));\r
-long NextTickLength P((long));\r
-void CheckTimeControl P((void));\r
-void show_bytes P((FILE *, char *, int));\r
-int string_to_rating P((char *str));\r
-void ParseFeatures P((char* args, ChessProgramState *cps));\r
-void InitBackEnd3 P((void));\r
-void FeatureDone P((ChessProgramState* cps, int val));\r
-void InitChessProgram P((ChessProgramState *cps, int setup));\r
-void OutputKibitz(int window, char *text);\r
-int PerpetualChase(int first, int last);\r
-int EngineOutputIsUp();\r
-void InitDrawingSizes(int x, int y);\r
-\r
-#ifdef WIN32\r
- extern void ConsoleCreate();\r
-#endif\r
-\r
-ChessProgramState *WhitePlayer();\r
-void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c\r
-int VerifyDisplayMode P(());\r
-\r
-char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment\r
-void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c\r
-char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move\r
-char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book\r
-extern char installDir[MSG_SIZ];\r
-\r
-extern int tinyLayout, smallLayout;\r
-ChessProgramStats programStats;\r
-static int exiting = 0; /* [HGM] moved to top */\r
-static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;\r
-int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */\r
-char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */\r
-int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */\r
-VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */\r
-int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */\r
-int opponentKibitzes;\r
-\r
-/* States for ics_getting_history */\r
-#define H_FALSE 0\r
-#define H_REQUESTED 1\r
-#define H_GOT_REQ_HEADER 2\r
-#define H_GOT_UNREQ_HEADER 3\r
-#define H_GETTING_MOVES 4\r
-#define H_GOT_UNWANTED_HEADER 5\r
-\r
-/* whosays values for GameEnds */\r
-#define GE_ICS 0\r
-#define GE_ENGINE 1\r
-#define GE_PLAYER 2\r
-#define GE_FILE 3\r
-#define GE_XBOARD 4\r
-#define GE_ENGINE1 5\r
-#define GE_ENGINE2 6\r
-\r
-/* Maximum number of games in a cmail message */\r
-#define CMAIL_MAX_GAMES 20\r
-\r
-/* Different types of move when calling RegisterMove */\r
-#define CMAIL_MOVE 0\r
-#define CMAIL_RESIGN 1\r
-#define CMAIL_DRAW 2\r
-#define CMAIL_ACCEPT 3\r
-\r
-/* Different types of result to remember for each game */\r
-#define CMAIL_NOT_RESULT 0\r
-#define CMAIL_OLD_RESULT 1\r
-#define CMAIL_NEW_RESULT 2\r
-\r
-/* Telnet protocol constants */\r
-#define TN_WILL 0373\r
-#define TN_WONT 0374\r
-#define TN_DO 0375\r
-#define TN_DONT 0376\r
-#define TN_IAC 0377\r
-#define TN_ECHO 0001\r
-#define TN_SGA 0003\r
-#define TN_PORT 23\r
-\r
-/* [AS] */\r
-static char * safeStrCpy( char * dst, const char * src, size_t count )\r
-{\r
- assert( dst != NULL );\r
- assert( src != NULL );\r
- assert( count > 0 );\r
-\r
- strncpy( dst, src, count );\r
- dst[ count-1 ] = '\0';\r
- return dst;\r
-}\r
-\r
-#if 0\r
-//[HGM] for future use? Conditioned out for now to suppress warning.\r
-static char * safeStrCat( char * dst, const char * src, size_t count )\r
-{\r
- size_t dst_len;\r
-\r
- assert( dst != NULL );\r
- assert( src != NULL );\r
- assert( count > 0 );\r
-\r
- dst_len = strlen(dst);\r
-\r
- assert( count > dst_len ); /* Buffer size must be greater than current length */\r
-\r
- safeStrCpy( dst + dst_len, src, count - dst_len );\r
-\r
- return dst;\r
-}\r
-#endif\r
-\r
-/* Some compiler can't cast u64 to double\r
- * This function do the job for us:\r
-\r
- * We use the highest bit for cast, this only\r
- * works if the highest bit is not\r
- * in use (This should not happen)\r
- *\r
- * We used this for all compiler\r
- */\r
-double\r
-u64ToDouble(u64 value)\r
-{\r
- double r;\r
- u64 tmp = value & u64Const(0x7fffffffffffffff);\r
- r = (double)(s64)tmp;\r
- if (value & u64Const(0x8000000000000000))\r
- r += 9.2233720368547758080e18; /* 2^63 */\r
- return r;\r
-}\r
-\r
-/* Fake up flags for now, as we aren't keeping track of castling\r
- availability yet. [HGM] Change of logic: the flag now only\r
- indicates the type of castlings allowed by the rule of the game.\r
- The actual rights themselves are maintained in the array\r
- castlingRights, as part of the game history, and are not probed\r
- by this function.\r
- */\r
-int\r
-PosFlags(index)\r
-{\r
- int flags = F_ALL_CASTLE_OK;\r
- if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;\r
- switch (gameInfo.variant) {\r
- case VariantSuicide:\r
- flags &= ~F_ALL_CASTLE_OK;\r
- case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!\r
- flags |= F_IGNORE_CHECK;\r
- case VariantLosers:\r
- flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist\r
- break;\r
- case VariantAtomic:\r
- flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;\r
- break;\r
- case VariantKriegspiel:\r
- flags |= F_KRIEGSPIEL_CAPTURE;\r
- break;\r
- case VariantCapaRandom: \r
- case VariantFischeRandom:\r
- flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */\r
- case VariantNoCastle:\r
- case VariantShatranj:\r
- case VariantCourier:\r
- flags &= ~F_ALL_CASTLE_OK;\r
- break;\r
- default:\r
- break;\r
- }\r
- return flags;\r
-}\r
-\r
-FILE *gameFileFP, *debugFP;\r
-\r
-/* \r
- [AS] Note: sometimes, the sscanf() function is used to parse the input\r
- into a fixed-size buffer. Because of this, we must be prepared to\r
- receive strings as long as the size of the input buffer, which is currently\r
- set to 4K for Windows and 8K for the rest.\r
- So, we must either allocate sufficiently large buffers here, or\r
- reduce the size of the input buffer in the input reading part.\r
-*/\r
-\r
-char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];\r
-char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];\r
-char thinkOutput1[MSG_SIZ*10];\r
-\r
-ChessProgramState first, second;\r
-\r
-/* premove variables */\r
-int premoveToX = 0;\r
-int premoveToY = 0;\r
-int premoveFromX = 0;\r
-int premoveFromY = 0;\r
-int premovePromoChar = 0;\r
-int gotPremove = 0;\r
-Boolean alarmSounded;\r
-/* end premove variables */\r
-\r
-char *ics_prefix = "$";\r
-int ics_type = ICS_GENERIC;\r
-\r
-int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;\r
-int pauseExamForwardMostMove = 0;\r
-int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;\r
-int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];\r
-int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;\r
-int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;\r
-int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;\r
-int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;\r
-int whiteFlag = FALSE, blackFlag = FALSE;\r
-int userOfferedDraw = FALSE;\r
-int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;\r
-int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;\r
-int cmailMoveType[CMAIL_MAX_GAMES];\r
-long ics_clock_paused = 0;\r
-ProcRef icsPR = NoProc, cmailPR = NoProc;\r
-InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;\r
-GameMode gameMode = BeginningOfGame;\r
-char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];\r
-char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];\r
-ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */\r
-int hiddenThinkOutputState = 0; /* [AS] */\r
-int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */\r
-int adjudicateLossPlies = 6;\r
-char white_holding[64], black_holding[64];\r
-TimeMark lastNodeCountTime;\r
-long lastNodeCount=0;\r
-int have_sent_ICS_logon = 0;\r
-int movesPerSession;\r
-long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;\r
-long timeControl_2; /* [AS] Allow separate time controls */\r
-char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */\r
-long timeRemaining[2][MAX_MOVES];\r
-int matchGame = 0;\r
-TimeMark programStartTime;\r
-char ics_handle[MSG_SIZ];\r
-int have_set_title = 0;\r
-\r
-/* animateTraining preserves the state of appData.animate\r
- * when Training mode is activated. This allows the\r
- * response to be animated when appData.animate == TRUE and\r
- * appData.animateDragging == TRUE.\r
- */\r
-Boolean animateTraining;\r
-\r
-GameInfo gameInfo;\r
-\r
-AppData appData;\r
-\r
-Board boards[MAX_MOVES];\r
-/* [HGM] Following 7 needed for accurate legality tests: */\r
-char epStatus[MAX_MOVES];\r
-char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1\r
-char castlingRank[BOARD_SIZE]; // and corresponding ranks\r
-char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];\r
-int nrCastlingRights; // For TwoKings, or to implement castling-unknown status\r
-int initialRulePlies, FENrulePlies;\r
-char FENepStatus;\r
-FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)\r
-int loadFlag = 0; \r
-int shuffleOpenings;\r
-\r
-ChessSquare FIDEArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
- WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
- BlackKing, BlackBishop, BlackKnight, BlackRook }\r
-};\r
-\r
-ChessSquare twoKingsArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,\r
- WhiteKing, WhiteKing, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackBishop, BlackQueen,\r
- BlackKing, BlackKing, BlackKnight, BlackRook }\r
-};\r
-\r
-ChessSquare KnightmateArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,\r
- WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },\r
- { BlackRook, BlackMan, BlackBishop, BlackQueen,\r
- BlackUnicorn, BlackBishop, BlackMan, BlackRook }\r
-};\r
-\r
-ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */\r
- { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,\r
- WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },\r
- { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,\r
- BlackKing, BlackBishop, BlackKnight, BlackRook }\r
-};\r
-\r
-ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */\r
- { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,\r
- WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackAlfil, BlackKing,\r
- BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
-};\r
-\r
-\r
-#if (BOARD_SIZE>=10)\r
-ChessSquare ShogiArray[2][BOARD_SIZE] = {\r
- { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,\r
- WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },\r
- { BlackQueen, BlackKnight, BlackFerz, BlackWazir,\r
- BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }\r
-};\r
-\r
-ChessSquare XiangqiArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,\r
- WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackAlfil, BlackFerz,\r
- BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }\r
-};\r
-\r
-ChessSquare CapablancaArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen, \r
- WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen, \r
- BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }\r
-};\r
-\r
-ChessSquare GreatArray[2][BOARD_SIZE] = {\r
- { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing, \r
- WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },\r
- { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing, \r
- BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },\r
-};\r
-\r
-ChessSquare JanusArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing, \r
- WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },\r
- { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing, \r
- BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }\r
-};\r
-\r
-#ifdef GOTHIC\r
-ChessSquare GothicArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall, \r
- WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall, \r
- BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }\r
-};\r
-#else // !GOTHIC\r
-#define GothicArray CapablancaArray\r
-#endif // !GOTHIC\r
-\r
-#ifdef FALCON\r
-ChessSquare FalconArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen, \r
- WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen, \r
- BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }\r
-};\r
-#else // !FALCON\r
-#define FalconArray CapablancaArray\r
-#endif // !FALCON\r
-\r
-#else // !(BOARD_SIZE>=10)\r
-#define XiangqiPosition FIDEArray\r
-#define CapablancaArray FIDEArray\r
-#define GothicArray FIDEArray\r
-#define GreatArray FIDEArray\r
-#endif // !(BOARD_SIZE>=10)\r
-\r
-#if (BOARD_SIZE>=12)\r
-ChessSquare CourierArray[2][BOARD_SIZE] = {\r
- { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,\r
- WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },\r
- { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,\r
- BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }\r
-};\r
-#else // !(BOARD_SIZE>=12)\r
-#define CourierArray CapablancaArray\r
-#endif // !(BOARD_SIZE>=12)\r
-\r
-\r
-Board initialPosition;\r
-\r
-\r
-/* Convert str to a rating. Checks for special cases of "----",\r
-\r
- "++++", etc. Also strips ()'s */\r
-int\r
-string_to_rating(str)\r
- char *str;\r
-{\r
- while(*str && !isdigit(*str)) ++str;\r
- if (!*str)\r
- return 0; /* One of the special "no rating" cases */\r
- else\r
- return atoi(str);\r
-}\r
-\r
-void\r
-ClearProgramStats()\r
-{\r
- /* Init programStats */\r
- programStats.movelist[0] = 0;\r
- programStats.depth = 0;\r
- programStats.nr_moves = 0;\r
- programStats.moves_left = 0;\r
- programStats.nodes = 0;\r
- programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output\r
- programStats.score = 0;\r
- programStats.got_only_move = 0;\r
- programStats.got_fail = 0;\r
- programStats.line_is_book = 0;\r
-}\r
-\r
-void\r
-InitBackEnd1()\r
-{\r
- int matched, min, sec;\r
-\r
- ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options\r
-\r
- GetTimeMark(&programStartTime);\r
- srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level\r
-\r
- ClearProgramStats();\r
- programStats.ok_to_send = 1;\r
- programStats.seen_stat = 0;\r
-\r
- /*\r
- * Initialize game list\r
- */\r
- ListNew(&gameList);\r
-\r
-\r
- /*\r
- * Internet chess server status\r
- */\r
- if (appData.icsActive) {\r
- appData.matchMode = FALSE;\r
- appData.matchGames = 0;\r
-#if ZIPPY \r
- appData.noChessProgram = !appData.zippyPlay;\r
-#else\r
- appData.zippyPlay = FALSE;\r
- appData.zippyTalk = FALSE;\r
- appData.noChessProgram = TRUE;\r
-#endif\r
- if (*appData.icsHelper != NULLCHAR) {\r
- appData.useTelnet = TRUE;\r
- appData.telnetProgram = appData.icsHelper;\r
- }\r
- } else {\r
- appData.zippyTalk = appData.zippyPlay = FALSE;\r
- }\r
-\r
- /* [AS] Initialize pv info list [HGM] and game state */\r
- {\r
- int i, j;\r
-\r
- for( i=0; i<MAX_MOVES; i++ ) {\r
- pvInfoList[i].depth = -1;\r
- epStatus[i]=EP_NONE;\r
- for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
- }\r
- }\r
-\r
- /*\r
- * Parse timeControl resource\r
- */\r
- if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,\r
- appData.movesPerSession)) {\r
- char buf[MSG_SIZ];\r
- snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);\r
- DisplayFatalError(buf, 0, 2);\r
- }\r
-\r
- /*\r
- * Parse searchTime resource\r
- */\r
- if (*appData.searchTime != NULLCHAR) {\r
- matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);\r
- if (matched == 1) {\r
- searchTime = min * 60;\r
- } else if (matched == 2) {\r
- searchTime = min * 60 + sec;\r
- } else {\r
- char buf[MSG_SIZ];\r
- snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);\r
- DisplayFatalError(buf, 0, 2);\r
- }\r
- }\r
-\r
- /* [AS] Adjudication threshold */\r
- adjudicateLossThreshold = appData.adjudicateLossThreshold;\r
- \r
- first.which = "first";\r
- second.which = "second";\r
- first.maybeThinking = second.maybeThinking = FALSE;\r
- first.pr = second.pr = NoProc;\r
- first.isr = second.isr = NULL;\r
- first.sendTime = second.sendTime = 2;\r
- first.sendDrawOffers = 1;\r
- if (appData.firstPlaysBlack) {\r
- first.twoMachinesColor = "black\n";\r
- second.twoMachinesColor = "white\n";\r
- } else {\r
- first.twoMachinesColor = "white\n";\r
- second.twoMachinesColor = "black\n";\r
- }\r
- first.program = appData.firstChessProgram;\r
- second.program = appData.secondChessProgram;\r
- first.host = appData.firstHost;\r
- second.host = appData.secondHost;\r
- first.dir = appData.firstDirectory;\r
- second.dir = appData.secondDirectory;\r
- first.other = &second;\r
- second.other = &first;\r
- first.initString = appData.initString;\r
- second.initString = appData.secondInitString;\r
- first.computerString = appData.firstComputerString;\r
- second.computerString = appData.secondComputerString;\r
- first.useSigint = second.useSigint = TRUE;\r
- first.useSigterm = second.useSigterm = TRUE;\r
- first.reuse = appData.reuseFirst;\r
- second.reuse = appData.reuseSecond;\r
- first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second\r
- second.nps = appData.secondNPS;\r
- first.useSetboard = second.useSetboard = FALSE;\r
- first.useSAN = second.useSAN = FALSE;\r
- first.usePing = second.usePing = FALSE;\r
- first.lastPing = second.lastPing = 0;\r
- first.lastPong = second.lastPong = 0;\r
- first.usePlayother = second.usePlayother = FALSE;\r
- first.useColors = second.useColors = TRUE;\r
- first.useUsermove = second.useUsermove = FALSE;\r
- first.sendICS = second.sendICS = FALSE;\r
- first.sendName = second.sendName = appData.icsActive;\r
- first.sdKludge = second.sdKludge = FALSE;\r
- first.stKludge = second.stKludge = FALSE;\r
- TidyProgramName(first.program, first.host, first.tidy);\r
- TidyProgramName(second.program, second.host, second.tidy);\r
- first.matchWins = second.matchWins = 0;\r
- strcpy(first.variants, appData.variant);\r
- strcpy(second.variants, appData.variant);\r
- first.analysisSupport = second.analysisSupport = 2; /* detect */\r
- first.analyzing = second.analyzing = FALSE;\r
- first.initDone = second.initDone = FALSE;\r
-\r
- /* New features added by Tord: */\r
- first.useFEN960 = FALSE; second.useFEN960 = FALSE;\r
- first.useOOCastle = TRUE; second.useOOCastle = TRUE;\r
- /* End of new features added by Tord. */\r
- first.fenOverride = appData.fenOverride1;\r
- second.fenOverride = appData.fenOverride2;\r
-\r
- /* [HGM] time odds: set factor for each machine */\r
- first.timeOdds = appData.firstTimeOdds;\r
- second.timeOdds = appData.secondTimeOdds;\r
- { int norm = 1;\r
- if(appData.timeOddsMode) {\r
- norm = first.timeOdds;\r
- if(norm > second.timeOdds) norm = second.timeOdds;\r
- }\r
- first.timeOdds /= norm;\r
- second.timeOdds /= norm;\r
- }\r
-\r
- /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/\r
- first.accumulateTC = appData.firstAccumulateTC;\r
- second.accumulateTC = appData.secondAccumulateTC;\r
- first.maxNrOfSessions = second.maxNrOfSessions = 1;\r
-\r
- /* [HGM] debug */\r
- first.debug = second.debug = FALSE;\r
- first.supportsNPS = second.supportsNPS = UNKNOWN;\r
-\r
- /* [HGM] options */\r
- first.optionSettings = appData.firstOptions;\r
- second.optionSettings = appData.secondOptions;\r
-\r
- first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */\r
- second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */\r
- first.isUCI = appData.firstIsUCI; /* [AS] */\r
- second.isUCI = appData.secondIsUCI; /* [AS] */\r
- first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */\r
- second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */\r
-\r
- if (appData.firstProtocolVersion > PROTOVER ||\r
- appData.firstProtocolVersion < 1) {\r
- char buf[MSG_SIZ];\r
- sprintf(buf, _("protocol version %d not supported"),\r
- appData.firstProtocolVersion);\r
- DisplayFatalError(buf, 0, 2);\r
- } else {\r
- first.protocolVersion = appData.firstProtocolVersion;\r
- }\r
-\r
- if (appData.secondProtocolVersion > PROTOVER ||\r
- appData.secondProtocolVersion < 1) {\r
- char buf[MSG_SIZ];\r
- sprintf(buf, _("protocol version %d not supported"),\r
- appData.secondProtocolVersion);\r
- DisplayFatalError(buf, 0, 2);\r
- } else {\r
- second.protocolVersion = appData.secondProtocolVersion;\r
- }\r
-\r
- if (appData.icsActive) {\r
- appData.clockMode = TRUE; /* changes dynamically in ICS mode */\r
- } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {\r
- appData.clockMode = FALSE;\r
- first.sendTime = second.sendTime = 0;\r
- }\r
- \r
-#if ZIPPY\r
- /* Override some settings from environment variables, for backward\r
- compatibility. Unfortunately it's not feasible to have the env\r
- vars just set defaults, at least in xboard. Ugh.\r
- */\r
- if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {\r
- ZippyInit();\r
- }\r
-#endif\r
- \r
- if (appData.noChessProgram) {\r
- programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)\r
- + strlen(PATCHLEVEL));\r
- sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);\r
- } else {\r
-#if 0\r
- char *p, *q;\r
- q = first.program;\r
- while (*q != ' ' && *q != NULLCHAR) q++;\r
- p = q;\r
- while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */\r
- programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
- + strlen(PATCHLEVEL) + (q - p));\r
- sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);\r
- strncat(programVersion, p, q - p);\r
-#else\r
- /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */\r
- programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)\r
- + strlen(PATCHLEVEL) + strlen(first.tidy));\r
- sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);\r
-#endif\r
- }\r
-\r
- if (!appData.icsActive) {\r
- char buf[MSG_SIZ];\r
- /* Check for variants that are supported only in ICS mode,\r
- or not at all. Some that are accepted here nevertheless\r
- have bugs; see comments below.\r
- */\r
- VariantClass variant = StringToVariant(appData.variant);\r
- switch (variant) {\r
- case VariantBughouse: /* need four players and two boards */\r
- case VariantKriegspiel: /* need to hide pieces and move details */\r
- /* case VariantFischeRandom: (Fabien: moved below) */\r
- sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);\r
- DisplayFatalError(buf, 0, 2);\r
- return;\r
-\r
- case VariantUnknown:\r
- case VariantLoadable:\r
- case Variant29:\r
- case Variant30:\r
- case Variant31:\r
- case Variant32:\r
- case Variant33:\r
- case Variant34:\r
- case Variant35:\r
- case Variant36:\r
- default:\r
- sprintf(buf, _("Unknown variant name %s"), appData.variant);\r
- DisplayFatalError(buf, 0, 2);\r
- return;\r
-\r
- case VariantXiangqi: /* [HGM] repetition rules not implemented */\r
- case VariantFairy: /* [HGM] TestLegality definitely off! */\r
- case VariantGothic: /* [HGM] should work */\r
- case VariantCapablanca: /* [HGM] should work */\r
- case VariantCourier: /* [HGM] initial forced moves not implemented */\r
- case VariantShogi: /* [HGM] drops not tested for legality */\r
- case VariantKnightmate: /* [HGM] should work */\r
- case VariantCylinder: /* [HGM] untested */\r
- case VariantFalcon: /* [HGM] untested */\r
- case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)\r
- offboard interposition not understood */\r
- case VariantNormal: /* definitely works! */\r
- case VariantWildCastle: /* pieces not automatically shuffled */\r
- case VariantNoCastle: /* pieces not automatically shuffled */\r
- case VariantFischeRandom: /* [HGM] works and shuffles pieces */\r
- case VariantLosers: /* should work except for win condition,\r
- and doesn't know captures are mandatory */\r
- case VariantSuicide: /* should work except for win condition,\r
- and doesn't know captures are mandatory */\r
- case VariantGiveaway: /* should work except for win condition,\r
- and doesn't know captures are mandatory */\r
- case VariantTwoKings: /* should work */\r
- case VariantAtomic: /* should work except for win condition */\r
- case Variant3Check: /* should work except for win condition */\r
- case VariantShatranj: /* should work except for all win conditions */\r
- case VariantBerolina: /* might work if TestLegality is off */\r
- case VariantCapaRandom: /* should work */\r
- case VariantJanus: /* should work */\r
- case VariantSuper: /* experimental */\r
- case VariantGreat: /* experimental, requires legality testing to be off */\r
- break;\r
- }\r
- }\r
-\r
- InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard\r
- InitEngineUCI( installDir, &second );\r
-}\r
-\r
-int NextIntegerFromString( char ** str, long * value )\r
-{\r
- int result = -1;\r
- char * s = *str;\r
-\r
- while( *s == ' ' || *s == '\t' ) {\r
- s++;\r
- }\r
-\r
- *value = 0;\r
-\r
- if( *s >= '0' && *s <= '9' ) {\r
- while( *s >= '0' && *s <= '9' ) {\r
- *value = *value * 10 + (*s - '0');\r
- s++;\r
- }\r
-\r
- result = 0;\r
- }\r
-\r
- *str = s;\r
-\r
- return result;\r
-}\r
-\r
-int NextTimeControlFromString( char ** str, long * value )\r
-{\r
- long temp;\r
- int result = NextIntegerFromString( str, &temp );\r
-\r
- if( result == 0 ) {\r
- *value = temp * 60; /* Minutes */\r
- if( **str == ':' ) {\r
- (*str)++;\r
- result = NextIntegerFromString( str, &temp );\r
- *value += temp; /* Seconds */\r
- }\r
- }\r
-\r
- return result;\r
-}\r
-\r
-int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)\r
-{ /* [HGM] routine added to read '+moves/time' for secondary time control */\r
- int result = -1; long temp, temp2;\r
-\r
- if(**str != '+') return -1; // old params remain in force!\r
- (*str)++;\r
- if( NextTimeControlFromString( str, &temp ) ) return -1;\r
-\r
- if(**str != '/') {\r
- /* time only: incremental or sudden-death time control */\r
- if(**str == '+') { /* increment follows; read it */\r
- (*str)++;\r
- if(result = NextIntegerFromString( str, &temp2)) return -1;\r
- *inc = temp2 * 1000;\r
- } else *inc = 0;\r
- *moves = 0; *tc = temp * 1000; \r
- return 0;\r
- } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */\r
-\r
- (*str)++; /* classical time control */\r
- result = NextTimeControlFromString( str, &temp2);\r
- if(result == 0) {\r
- *moves = temp/60;\r
- *tc = temp2 * 1000;\r
- *inc = 0;\r
- }\r
- return result;\r
-}\r
-\r
-int GetTimeQuota(int movenr)\r
-{ /* [HGM] get time to add from the multi-session time-control string */\r
- int moves=1; /* kludge to force reading of first session */\r
- long time, increment;\r
- char *s = fullTimeControlString;\r
-\r
- if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);\r
- do {\r
- if(moves) NextSessionFromString(&s, &moves, &time, &increment);\r
- if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);\r
- if(movenr == -1) return time; /* last move before new session */\r
- if(!moves) return increment; /* current session is incremental */\r
- if(movenr >= 0) movenr -= moves; /* we already finished this session */\r
- } while(movenr >= -1); /* try again for next session */\r
-\r
- return 0; // no new time quota on this move\r
-}\r
-\r
-int\r
-ParseTimeControl(tc, ti, mps)\r
- char *tc;\r
- int ti;\r
- int mps;\r
-{\r
-#if 0\r
- int matched, min, sec;\r
-\r
- matched = sscanf(tc, "%d:%d", &min, &sec);\r
- if (matched == 1) {\r
- timeControl = min * 60 * 1000;\r
- } else if (matched == 2) {\r
- timeControl = (min * 60 + sec) * 1000;\r
- } else {\r
- return FALSE;\r
- }\r
-#else\r
- long tc1;\r
- long tc2;\r
- char buf[MSG_SIZ];\r
-\r
- if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;\r
- if(ti > 0) {\r
- if(mps)\r
- sprintf(buf, "+%d/%s+%d", mps, tc, ti);\r
- else sprintf(buf, "+%s+%d", tc, ti);\r
- } else {\r
- if(mps)\r
- sprintf(buf, "+%d/%s", mps, tc);\r
- else sprintf(buf, "+%s", tc);\r
- }\r
- fullTimeControlString = StrSave(buf);\r
-\r
- if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {\r
- return FALSE;\r
- }\r
-\r
- if( *tc == '/' ) {\r
- /* Parse second time control */\r
- tc++;\r
-\r
- if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {\r
- return FALSE;\r
- }\r
-\r
- if( tc2 == 0 ) {\r
- return FALSE;\r
- }\r
-\r
- timeControl_2 = tc2 * 1000;\r
- }\r
- else {\r
- timeControl_2 = 0;\r
- }\r
-\r
- if( tc1 == 0 ) {\r
- return FALSE;\r
- }\r
-\r
- timeControl = tc1 * 1000;\r
-#endif\r
-\r
- if (ti >= 0) {\r
- timeIncrement = ti * 1000; /* convert to ms */\r
- movesPerSession = 0;\r
- } else {\r
- timeIncrement = 0;\r
- movesPerSession = mps;\r
- }\r
- return TRUE;\r
-}\r
-\r
-void\r
-InitBackEnd2()\r
-{\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "%s\n", programVersion);\r
- }\r
-\r
- if (appData.matchGames > 0) {\r
- appData.matchMode = TRUE;\r
- } else if (appData.matchMode) {\r
- appData.matchGames = 1;\r
- }\r
- if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */\r
- appData.matchGames = appData.sameColorGames;\r
- if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */\r
- if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;\r
- if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;\r
- }\r
- Reset(TRUE, FALSE);\r
- if (appData.noChessProgram || first.protocolVersion == 1) {\r
- InitBackEnd3();\r
- } else {\r
- /* kludge: allow timeout for initial "feature" commands */\r
- FreezeUI();\r
- DisplayMessage("", _("Starting chess program"));\r
- ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);\r
- }\r
-}\r
-\r
-void\r
-InitBackEnd3 P((void))\r
-{\r
- GameMode initialMode;\r
- char buf[MSG_SIZ];\r
- int err;\r
-\r
- InitChessProgram(&first, startedFromSetupPosition);\r
-\r
-\r
- if (appData.icsActive) {\r
-#ifdef WIN32\r
- /* [DM] Make a console window if needed [HGM] merged ifs */\r
- ConsoleCreate(); \r
-#endif\r
- err = establish();\r
- if (err != 0) {\r
- if (*appData.icsCommPort != NULLCHAR) {\r
- sprintf(buf, _("Could not open comm port %s"), \r
- appData.icsCommPort);\r
- } else {\r
- snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"), \r
- appData.icsHost, appData.icsPort);\r
- }\r
- DisplayFatalError(buf, err, 1);\r
- return;\r
- }\r
- SetICSMode();\r
- telnetISR =\r
- AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);\r
- fromUserISR =\r
- AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);\r
- } else if (appData.noChessProgram) {\r
- SetNCPMode();\r
- } else {\r
- SetGNUMode();\r
- }\r
-\r
- if (*appData.cmailGameName != NULLCHAR) {\r
- SetCmailMode();\r
- OpenLoopback(&cmailPR);\r
- cmailISR =\r
- AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);\r
- }\r
- \r
- ThawUI();\r
- DisplayMessage("", "");\r
- if (StrCaseCmp(appData.initialMode, "") == 0) {\r
- initialMode = BeginningOfGame;\r
- } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {\r
- initialMode = TwoMachinesPlay;\r
- } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {\r
- initialMode = AnalyzeFile; \r
- } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {\r
- initialMode = AnalyzeMode;\r
- } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {\r
- initialMode = MachinePlaysWhite;\r
- } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {\r
- initialMode = MachinePlaysBlack;\r
- } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {\r
- initialMode = EditGame;\r
- } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {\r
- initialMode = EditPosition;\r
- } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {\r
- initialMode = Training;\r
- } else {\r
- sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);\r
- DisplayFatalError(buf, 0, 2);\r
- return;\r
- }\r
-\r
- if (appData.matchMode) {\r
- /* Set up machine vs. machine match */\r
- if (appData.noChessProgram) {\r
- DisplayFatalError(_("Can't have a match with no chess programs"),\r
- 0, 2);\r
- return;\r
- }\r
- matchMode = TRUE;\r
- matchGame = 1;\r
- if (*appData.loadGameFile != NULLCHAR) {\r
- int index = appData.loadGameIndex; // [HGM] autoinc\r
- if(index<0) lastIndex = index = 1;\r
- if (!LoadGameFromFile(appData.loadGameFile,\r
- index,\r
- appData.loadGameFile, FALSE)) {\r
- DisplayFatalError(_("Bad game file"), 0, 1);\r
- return;\r
- }\r
- } else if (*appData.loadPositionFile != NULLCHAR) {\r
- int index = appData.loadPositionIndex; // [HGM] autoinc\r
- if(index<0) lastIndex = index = 1;\r
- if (!LoadPositionFromFile(appData.loadPositionFile,\r
- index,\r
- appData.loadPositionFile)) {\r
- DisplayFatalError(_("Bad position file"), 0, 1);\r
- return;\r
- }\r
- }\r
- TwoMachinesEvent();\r
- } else if (*appData.cmailGameName != NULLCHAR) {\r
- /* Set up cmail mode */\r
- ReloadCmailMsgEvent(TRUE);\r
- } else {\r
- /* Set up other modes */\r
- if (initialMode == AnalyzeFile) {\r
- if (*appData.loadGameFile == NULLCHAR) {\r
- DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);\r
- return;\r
- }\r
- }\r
- if (*appData.loadGameFile != NULLCHAR) {\r
- (void) LoadGameFromFile(appData.loadGameFile,\r
- appData.loadGameIndex,\r
- appData.loadGameFile, TRUE);\r
- } else if (*appData.loadPositionFile != NULLCHAR) {\r
- (void) LoadPositionFromFile(appData.loadPositionFile,\r
- appData.loadPositionIndex,\r
- appData.loadPositionFile);\r
- /* [HGM] try to make self-starting even after FEN load */\r
- /* to allow automatic setup of fairy variants with wtm */\r
- if(initialMode == BeginningOfGame && !blackPlaysFirst) {\r
- gameMode = BeginningOfGame;\r
- setboardSpoiledMachineBlack = 1;\r
- }\r
- /* [HGM] loadPos: make that every new game uses the setup */\r
- /* from file as long as we do not switch variant */\r
- if(!blackPlaysFirst) { int i;\r
- startedFromPositionFile = TRUE;\r
- CopyBoard(filePosition, boards[0]);\r
- for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];\r
- }\r
- }\r
- if (initialMode == AnalyzeMode) {\r
- if (appData.noChessProgram) {\r
- DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);\r
- return;\r
- }\r
- if (appData.icsActive) {\r
- DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);\r
- return;\r
- }\r
- AnalyzeModeEvent();\r
- } else if (initialMode == AnalyzeFile) {\r
- appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent\r
- ShowThinkingEvent();\r
- AnalyzeFileEvent();\r
- AnalysisPeriodicEvent(1);\r
- } else if (initialMode == MachinePlaysWhite) {\r
- if (appData.noChessProgram) {\r
- DisplayFatalError(_("MachineWhite mode requires a chess engine"),\r
- 0, 2);\r
- return;\r
- }\r
- if (appData.icsActive) {\r
- DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),\r
- 0, 2);\r
- return;\r
- }\r
- MachineWhiteEvent();\r
- } else if (initialMode == MachinePlaysBlack) {\r
- if (appData.noChessProgram) {\r
- DisplayFatalError(_("MachineBlack mode requires a chess engine"),\r
- 0, 2);\r
- return;\r
- }\r
- if (appData.icsActive) {\r
- DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),\r
- 0, 2);\r
- return;\r
- }\r
- MachineBlackEvent();\r
- } else if (initialMode == TwoMachinesPlay) {\r
- if (appData.noChessProgram) {\r
- DisplayFatalError(_("TwoMachines mode requires a chess engine"),\r
- 0, 2);\r
- return;\r
- }\r
- if (appData.icsActive) {\r
- DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),\r
- 0, 2);\r
- return;\r
- }\r
- TwoMachinesEvent();\r
- } else if (initialMode == EditGame) {\r
- EditGameEvent();\r
- } else if (initialMode == EditPosition) {\r
- EditPositionEvent();\r
- } else if (initialMode == Training) {\r
- if (*appData.loadGameFile == NULLCHAR) {\r
- DisplayFatalError(_("Training mode requires a game file"), 0, 2);\r
- return;\r
- }\r
- TrainingEvent();\r
- }\r
- }\r
-}\r
-\r
-/*\r
- * Establish will establish a contact to a remote host.port.\r
- * Sets icsPR to a ProcRef for a process (or pseudo-process)\r
- * used to talk to the host.\r
- * Returns 0 if okay, error code if not.\r
- */\r
-int\r
-establish()\r
-{\r
- char buf[MSG_SIZ];\r
-\r
- if (*appData.icsCommPort != NULLCHAR) {\r
- /* Talk to the host through a serial comm port */\r
- return OpenCommPort(appData.icsCommPort, &icsPR);\r
-\r
- } else if (*appData.gateway != NULLCHAR) {\r
- if (*appData.remoteShell == NULLCHAR) {\r
- /* Use the rcmd protocol to run telnet program on a gateway host */\r
- snprintf(buf, sizeof(buf), "%s %s %s",\r
- appData.telnetProgram, appData.icsHost, appData.icsPort);\r
- return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);\r
-\r
- } else {\r
- /* Use the rsh program to run telnet program on a gateway host */\r
- if (*appData.remoteUser == NULLCHAR) {\r
- snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,\r
- appData.gateway, appData.telnetProgram,\r
- appData.icsHost, appData.icsPort);\r
- } else {\r
- snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",\r
- appData.remoteShell, appData.gateway, \r
- appData.remoteUser, appData.telnetProgram,\r
- appData.icsHost, appData.icsPort);\r
- }\r
- return StartChildProcess(buf, "", &icsPR);\r
-\r
- }\r
- } else if (appData.useTelnet) {\r
- return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);\r
-\r
- } else {\r
- /* TCP socket interface differs somewhat between\r
- Unix and NT; handle details in the front end.\r
- */\r
- return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);\r
- }\r
-}\r
-\r
-void\r
-show_bytes(fp, buf, count)\r
- FILE *fp;\r
- char *buf;\r
- int count;\r
-{\r
- while (count--) {\r
- if (*buf < 040 || *(unsigned char *) buf > 0177) {\r
- fprintf(fp, "\\%03o", *buf & 0xff);\r
- } else {\r
- putc(*buf, fp);\r
- }\r
- buf++;\r
- }\r
- fflush(fp);\r
-}\r
-\r
-/* Returns an errno value */\r
-int\r
-OutputMaybeTelnet(pr, message, count, outError)\r
- ProcRef pr;\r
- char *message;\r
- int count;\r
- int *outError;\r
-{\r
- char buf[8192], *p, *q, *buflim;\r
- int left, newcount, outcount;\r
-\r
- if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||\r
- *appData.gateway != NULLCHAR) {\r
- if (appData.debugMode) {\r
- fprintf(debugFP, ">ICS: ");\r
- show_bytes(debugFP, message, count);\r
- fprintf(debugFP, "\n");\r
- }\r
- return OutputToProcess(pr, message, count, outError);\r
- }\r
-\r
- buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */\r
- p = message;\r
- q = buf;\r
- left = count;\r
- newcount = 0;\r
- while (left) {\r
- if (q >= buflim) {\r
- if (appData.debugMode) {\r
- fprintf(debugFP, ">ICS: ");\r
- show_bytes(debugFP, buf, newcount);\r
- fprintf(debugFP, "\n");\r
- }\r
- outcount = OutputToProcess(pr, buf, newcount, outError);\r
- if (outcount < newcount) return -1; /* to be sure */\r
- q = buf;\r
- newcount = 0;\r
- }\r
- if (*p == '\n') {\r
- *q++ = '\r';\r
- newcount++;\r
- } else if (((unsigned char) *p) == TN_IAC) {\r
- *q++ = (char) TN_IAC;\r
- newcount ++;\r
- }\r
- *q++ = *p++;\r
- newcount++;\r
- left--;\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, ">ICS: ");\r
- show_bytes(debugFP, buf, newcount);\r
- fprintf(debugFP, "\n");\r
- }\r
- outcount = OutputToProcess(pr, buf, newcount, outError);\r
- if (outcount < newcount) return -1; /* to be sure */\r
- return count;\r
-}\r
-\r
-void\r
-read_from_player(isr, closure, message, count, error)\r
- InputSourceRef isr;\r
- VOIDSTAR closure;\r
- char *message;\r
- int count;\r
- int error;\r
-{\r
- int outError, outCount;\r
- static int gotEof = 0;\r
-\r
- /* Pass data read from player on to ICS */\r
- if (count > 0) {\r
- gotEof = 0;\r
- outCount = OutputMaybeTelnet(icsPR, message, count, &outError);\r
- if (outCount < count) {\r
- DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
- }\r
- } else if (count < 0) {\r
- RemoveInputSource(isr);\r
- DisplayFatalError(_("Error reading from keyboard"), error, 1);\r
- } else if (gotEof++ > 0) {\r
- RemoveInputSource(isr);\r
- DisplayFatalError(_("Got end of file from keyboard"), 0, 0);\r
- }\r
-}\r
-\r
-void\r
-SendToICS(s)\r
- char *s;\r
-{\r
- int count, outCount, outError;\r
-\r
- if (icsPR == NULL) return;\r
-\r
- count = strlen(s);\r
- outCount = OutputMaybeTelnet(icsPR, s, count, &outError);\r
- if (outCount < count) {\r
- DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
- }\r
-}\r
-\r
-/* This is used for sending logon scripts to the ICS. Sending\r
- without a delay causes problems when using timestamp on ICC\r
- (at least on my machine). */\r
-void\r
-SendToICSDelayed(s,msdelay)\r
- char *s;\r
- long msdelay;\r
-{\r
- int count, outCount, outError;\r
-\r
- if (icsPR == NULL) return;\r
-\r
- count = strlen(s);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, ">ICS: ");\r
- show_bytes(debugFP, s, count);\r
- fprintf(debugFP, "\n");\r
- }\r
- outCount = OutputToProcessDelayed(icsPR, s, count, &outError,\r
- msdelay);\r
- if (outCount < count) {\r
- DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
- }\r
-}\r
-\r
-\r
-/* Remove all highlighting escape sequences in s\r
- Also deletes any suffix starting with '(' \r
- */\r
-char *\r
-StripHighlightAndTitle(s)\r
- char *s;\r
-{\r
- static char retbuf[MSG_SIZ];\r
- char *p = retbuf;\r
-\r
- while (*s != NULLCHAR) {\r
- while (*s == '\033') {\r
- while (*s != NULLCHAR && !isalpha(*s)) s++;\r
- if (*s != NULLCHAR) s++;\r
- }\r
- while (*s != NULLCHAR && *s != '\033') {\r
- if (*s == '(' || *s == '[') {\r
- *p = NULLCHAR;\r
- return retbuf;\r
- }\r
- *p++ = *s++;\r
- }\r
- }\r
- *p = NULLCHAR;\r
- return retbuf;\r
-}\r
-\r
-/* Remove all highlighting escape sequences in s */\r
-char *\r
-StripHighlight(s)\r
- char *s;\r
-{\r
- static char retbuf[MSG_SIZ];\r
- char *p = retbuf;\r
-\r
- while (*s != NULLCHAR) {\r
- while (*s == '\033') {\r
- while (*s != NULLCHAR && !isalpha(*s)) s++;\r
- if (*s != NULLCHAR) s++;\r
- }\r
- while (*s != NULLCHAR && *s != '\033') {\r
- *p++ = *s++;\r
- }\r
- }\r
- *p = NULLCHAR;\r
- return retbuf;\r
-}\r
-\r
-char *variantNames[] = VARIANT_NAMES;\r
-char *\r
-VariantName(v)\r
- VariantClass v;\r
-{\r
- return variantNames[v];\r
-}\r
-\r
-\r
-/* Identify a variant from the strings the chess servers use or the\r
- PGN Variant tag names we use. */\r
-VariantClass\r
-StringToVariant(e)\r
- char *e;\r
-{\r
- char *p;\r
- int wnum = -1;\r
- VariantClass v = VariantNormal;\r
- int i, found = FALSE;\r
- char buf[MSG_SIZ];\r
-\r
- if (!e) return v;\r
-\r
- /* [HGM] skip over optional board-size prefixes */\r
- if( sscanf(e, "%dx%d_", &i, &i) == 2 ||\r
- sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {\r
- while( *e++ != '_');\r
- }\r
-\r
- for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {\r
- if (StrCaseStr(e, variantNames[i])) {\r
- v = (VariantClass) i;\r
- found = TRUE;\r
- break;\r
- }\r
- }\r
-\r
- if (!found) {\r
- if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))\r
- || StrCaseStr(e, "wild/fr") \r
- || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {\r
- v = VariantFischeRandom;\r
- } else if ((i = 4, p = StrCaseStr(e, "wild")) ||\r
- (i = 1, p = StrCaseStr(e, "w"))) {\r
- p += i;\r
- while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;\r
- if (isdigit(*p)) {\r
- wnum = atoi(p);\r
- } else {\r
- wnum = -1;\r
- }\r
- switch (wnum) {\r
- case 0: /* FICS only, actually */\r
- case 1:\r
- /* Castling legal even if K starts on d-file */\r
- v = VariantWildCastle;\r
- break;\r
- case 2:\r
- case 3:\r
- case 4:\r
- /* Castling illegal even if K & R happen to start in\r
- normal positions. */\r
- v = VariantNoCastle;\r
- break;\r
- case 5:\r
- case 7:\r
- case 8:\r
- case 10:\r
- case 11:\r
- case 12:\r
- case 13:\r
- case 14:\r
- case 15:\r
- case 18:\r
- case 19:\r
- /* Castling legal iff K & R start in normal positions */\r
- v = VariantNormal;\r
- break;\r
- case 6:\r
- case 20:\r
- case 21:\r
- /* Special wilds for position setup; unclear what to do here */\r
- v = VariantLoadable;\r
- break;\r
- case 9:\r
- /* Bizarre ICC game */\r
- v = VariantTwoKings;\r
- break;\r
- case 16:\r
- v = VariantKriegspiel;\r
- break;\r
- case 17:\r
- v = VariantLosers;\r
- break;\r
- case 22:\r
- v = VariantFischeRandom;\r
- break;\r
- case 23:\r
- v = VariantCrazyhouse;\r
- break;\r
- case 24:\r
- v = VariantBughouse;\r
- break;\r
- case 25:\r
- v = Variant3Check;\r
- break;\r
- case 26:\r
- /* Not quite the same as FICS suicide! */\r
- v = VariantGiveaway;\r
- break;\r
- case 27:\r
- v = VariantAtomic;\r
- break;\r
- case 28:\r
- v = VariantShatranj;\r
- break;\r
-\r
- /* Temporary names for future ICC types. The name *will* change in \r
- the next xboard/WinBoard release after ICC defines it. */\r
- case 29:\r
- v = Variant29;\r
- break;\r
- case 30:\r
- v = Variant30;\r
- break;\r
- case 31:\r
- v = Variant31;\r
- break;\r
- case 32:\r
- v = Variant32;\r
- break;\r
- case 33:\r
- v = Variant33;\r
- break;\r
- case 34:\r
- v = Variant34;\r
- break;\r
- case 35:\r
- v = Variant35;\r
- break;\r
- case 36:\r
- v = Variant36;\r
- break;\r
- case 37:\r
- v = VariantShogi;\r
- break;\r
- case 38:\r
- v = VariantXiangqi;\r
- break;\r
- case 39:\r
- v = VariantCourier;\r
- break;\r
- case 40:\r
- v = VariantGothic;\r
- break;\r
- case 41:\r
- v = VariantCapablanca;\r
- break;\r
- case 42:\r
- v = VariantKnightmate;\r
- break;\r
- case 43:\r
- v = VariantFairy;\r
- break;\r
- case 44:\r
- v = VariantCylinder;\r
- break;\r
- case 45:\r
- v = VariantFalcon;\r
- break;\r
- case 46:\r
- v = VariantCapaRandom;\r
- break;\r
- case 47:\r
- v = VariantBerolina;\r
- break;\r
- case 48:\r
- v = VariantJanus;\r
- break;\r
- case 49:\r
- v = VariantSuper;\r
- break;\r
- case 50:\r
- v = VariantGreat;\r
- break;\r
- case -1:\r
- /* Found "wild" or "w" in the string but no number;\r
- must assume it's normal chess. */\r
- v = VariantNormal;\r
- break;\r
- default:\r
- sprintf(buf, _("Unknown wild type %d"), wnum);\r
- DisplayError(buf, 0);\r
- v = VariantUnknown;\r
- break;\r
- }\r
- }\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),\r
- e, wnum, VariantName(v));\r
- }\r
- return v;\r
-}\r
-\r
-static int leftover_start = 0, leftover_len = 0;\r
-char star_match[STAR_MATCH_N][MSG_SIZ];\r
-\r
-/* Test whether pattern is present at &buf[*index]; if so, return TRUE,\r
- advance *index beyond it, and set leftover_start to the new value of\r
- *index; else return FALSE. If pattern contains the character '*', it\r
- matches any sequence of characters not containing '\r', '\n', or the\r
- character following the '*' (if any), and the matched sequence(s) are\r
- copied into star_match.\r
- */\r
-int\r
-looking_at(buf, index, pattern)\r
- char *buf;\r
- int *index;\r
- char *pattern;\r
-{\r
- char *bufp = &buf[*index], *patternp = pattern;\r
- int star_count = 0;\r
- char *matchp = star_match[0];\r
- \r
- for (;;) {\r
- if (*patternp == NULLCHAR) {\r
- *index = leftover_start = bufp - buf;\r
- *matchp = NULLCHAR;\r
- return TRUE;\r
- }\r
- if (*bufp == NULLCHAR) return FALSE;\r
- if (*patternp == '*') {\r
- if (*bufp == *(patternp + 1)) {\r
- *matchp = NULLCHAR;\r
- matchp = star_match[++star_count];\r
- patternp += 2;\r
- bufp++;\r
- continue;\r
- } else if (*bufp == '\n' || *bufp == '\r') {\r
- patternp++;\r
- if (*patternp == NULLCHAR)\r
- continue;\r
- else\r
- return FALSE;\r
- } else {\r
- *matchp++ = *bufp++;\r
- continue;\r
- }\r
- }\r
- if (*patternp != *bufp) return FALSE;\r
- patternp++;\r
- bufp++;\r
- }\r
-}\r
-\r
-void\r
-SendToPlayer(data, length)\r
- char *data;\r
- int length;\r
-{\r
- int error, outCount;\r
- outCount = OutputToProcess(NoProc, data, length, &error);\r
- if (outCount < length) {\r
- DisplayFatalError(_("Error writing to display"), error, 1);\r
- }\r
-}\r
-\r
-void\r
-PackHolding(packed, holding)\r
- char packed[];\r
- char *holding;\r
-{\r
- char *p = holding;\r
- char *q = packed;\r
- int runlength = 0;\r
- int curr = 9999;\r
- do {\r
- if (*p == curr) {\r
- runlength++;\r
- } else {\r
- switch (runlength) {\r
- case 0:\r
- break;\r
- case 1:\r
- *q++ = curr;\r
- break;\r
- case 2:\r
- *q++ = curr;\r
- *q++ = curr;\r
- break;\r
- default:\r
- sprintf(q, "%d", runlength);\r
- while (*q) q++;\r
- *q++ = curr;\r
- break;\r
- }\r
- runlength = 1;\r
- curr = *p;\r
- }\r
- } while (*p++);\r
- *q = NULLCHAR;\r
-}\r
-\r
-/* Telnet protocol requests from the front end */\r
-void\r
-TelnetRequest(ddww, option)\r
- unsigned char ddww, option;\r
-{\r
- unsigned char msg[3];\r
- int outCount, outError;\r
-\r
- if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;\r
-\r
- if (appData.debugMode) {\r
- char buf1[8], buf2[8], *ddwwStr, *optionStr;\r
- switch (ddww) {\r
- case TN_DO:\r
- ddwwStr = "DO";\r
- break;\r
- case TN_DONT:\r
- ddwwStr = "DONT";\r
- break;\r
- case TN_WILL:\r
- ddwwStr = "WILL";\r
- break;\r
- case TN_WONT:\r
- ddwwStr = "WONT";\r
- break;\r
- default:\r
- ddwwStr = buf1;\r
- sprintf(buf1, "%d", ddww);\r
- break;\r
- }\r
- switch (option) {\r
- case TN_ECHO:\r
- optionStr = "ECHO";\r
- break;\r
- default:\r
- optionStr = buf2;\r
- sprintf(buf2, "%d", option);\r
- break;\r
- }\r
- fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);\r
- }\r
- msg[0] = TN_IAC;\r
- msg[1] = ddww;\r
- msg[2] = option;\r
- outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);\r
- if (outCount < 3) {\r
- DisplayFatalError(_("Error writing to ICS"), outError, 1);\r
- }\r
-}\r
-\r
-void\r
-DoEcho()\r
-{\r
- if (!appData.icsActive) return;\r
- TelnetRequest(TN_DO, TN_ECHO);\r
-}\r
-\r
-void\r
-DontEcho()\r
-{\r
- if (!appData.icsActive) return;\r
- TelnetRequest(TN_DONT, TN_ECHO);\r
-}\r
-\r
-void\r
-CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)\r
-{\r
- /* put the holdings sent to us by the server on the board holdings area */\r
- int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;\r
- char p;\r
- ChessSquare piece;\r
-\r
- if(gameInfo.holdingsWidth < 2) return;\r
-\r
- if( (int)lowestPiece >= BlackPawn ) {\r
- holdingsColumn = 0;\r
- countsColumn = 1;\r
- holdingsStartRow = BOARD_HEIGHT-1;\r
- direction = -1;\r
- } else {\r
- holdingsColumn = BOARD_WIDTH-1;\r
- countsColumn = BOARD_WIDTH-2;\r
- holdingsStartRow = 0;\r
- direction = 1;\r
- }\r
-\r
- for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */\r
- board[i][holdingsColumn] = EmptySquare;\r
- board[i][countsColumn] = (ChessSquare) 0;\r
- }\r
- while( (p=*holdings++) != NULLCHAR ) {\r
- piece = CharToPiece( ToUpper(p) );\r
- if(piece == EmptySquare) continue;\r
- /*j = (int) piece - (int) WhitePawn;*/\r
- j = PieceToNumber(piece);\r
- if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */\r
- if(j < 0) continue; /* should not happen */\r
- piece = (ChessSquare) ( (int)piece + (int)lowestPiece );\r
- board[holdingsStartRow+j*direction][holdingsColumn] = piece;\r
- board[holdingsStartRow+j*direction][countsColumn]++;\r
- }\r
-\r
-}\r
-\r
-\r
-void\r
-VariantSwitch(Board board, VariantClass newVariant)\r
-{\r
- int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;\r
- int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;\r
-// Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;\r
-\r
- startedFromPositionFile = FALSE;\r
- if(gameInfo.variant == newVariant) return;\r
-\r
- /* [HGM] This routine is called each time an assignment is made to\r
- * gameInfo.variant during a game, to make sure the board sizes\r
- * are set to match the new variant. If that means adding or deleting\r
- * holdings, we shift the playing board accordingly\r
- * This kludge is needed because in ICS observe mode, we get boards\r
- * of an ongoing game without knowing the variant, and learn about the\r
- * latter only later. This can be because of the move list we requested,\r
- * in which case the game history is refilled from the beginning anyway,\r
- * but also when receiving holdings of a crazyhouse game. In the latter\r
- * case we want to add those holdings to the already received position.\r
- */\r
-\r
-\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Switch board from %s to %s\n",\r
- VariantName(gameInfo.variant), VariantName(newVariant));\r
- setbuf(debugFP, NULL);\r
- }\r
- shuffleOpenings = 0; /* [HGM] shuffle */\r
- gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */\r
- switch(newVariant) {\r
- case VariantShogi:\r
- newWidth = 9; newHeight = 9;\r
- gameInfo.holdingsSize = 7;\r
- case VariantBughouse:\r
- case VariantCrazyhouse:\r
- newHoldingsWidth = 2; break;\r
- default:\r
- newHoldingsWidth = gameInfo.holdingsSize = 0;\r
- }\r
-\r
- if(newWidth != gameInfo.boardWidth ||\r
- newHeight != gameInfo.boardHeight ||\r
- newHoldingsWidth != gameInfo.holdingsWidth ) {\r
-\r
- /* shift position to new playing area, if needed */\r
- if(newHoldingsWidth > gameInfo.holdingsWidth) {\r
- for(i=0; i<BOARD_HEIGHT; i++) \r
- for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)\r
- board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
- board[i][j];\r
- for(i=0; i<newHeight; i++) {\r
- board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;\r
- board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;\r
- }\r
- } else if(newHoldingsWidth < gameInfo.holdingsWidth) {\r
- for(i=0; i<BOARD_HEIGHT; i++)\r
- for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
- board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =\r
- board[i][j];\r
- }\r
-\r
- gameInfo.boardWidth = newWidth;\r
- gameInfo.boardHeight = newHeight;\r
- gameInfo.holdingsWidth = newHoldingsWidth;\r
- gameInfo.variant = newVariant;\r
- InitDrawingSizes(-2, 0);\r
-\r
- /* [HGM] The following should definitely be solved in a better way */\r
-#if 0\r
- CopyBoard(board, tempBoard); /* save position in case it is board[0] */\r
- for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];\r
- saveEP = epStatus[0];\r
-#endif\r
- InitPosition(FALSE); /* this sets up board[0], but also other stuff */\r
-#if 0\r
- epStatus[0] = saveEP;\r
- for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];\r
- CopyBoard(tempBoard, board); /* restore position received from ICS */\r
-#endif\r
- } else { gameInfo.variant = newVariant; InitPosition(FALSE); }\r
-\r
- forwardMostMove = oldForwardMostMove;\r
- backwardMostMove = oldBackwardMostMove;\r
- currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */\r
-}\r
-\r
-static int loggedOn = FALSE;\r
-\r
-/*-- Game start info cache: --*/\r
-int gs_gamenum;\r
-char gs_kind[MSG_SIZ];\r
-static char player1Name[128] = "";\r
-static char player2Name[128] = "";\r
-static int player1Rating = -1;\r
-static int player2Rating = -1;\r
-/*----------------------------*/\r
-\r
-ColorClass curColor = ColorNormal;\r
-int suppressKibitz = 0;\r
-\r
-void\r
-read_from_ics(isr, closure, data, count, error)\r
- InputSourceRef isr;\r
- VOIDSTAR closure;\r
- char *data;\r
- int count;\r
- int error;\r
-{\r
-#define BUF_SIZE 8192\r
-#define STARTED_NONE 0\r
-#define STARTED_MOVES 1\r
-#define STARTED_BOARD 2\r
-#define STARTED_OBSERVE 3\r
-#define STARTED_HOLDINGS 4\r
-#define STARTED_CHATTER 5\r
-#define STARTED_COMMENT 6\r
-#define STARTED_MOVES_NOHIDE 7\r
- \r
- static int started = STARTED_NONE;\r
- static char parse[20000];\r
- static int parse_pos = 0;\r
- static char buf[BUF_SIZE + 1];\r
- static int firstTime = TRUE, intfSet = FALSE;\r
- static ColorClass prevColor = ColorNormal;\r
- static int savingComment = FALSE;\r
- char str[500];\r
- int i, oldi;\r
- int buf_len;\r
- int next_out;\r
- int tkind;\r
- int backup; /* [DM] For zippy color lines */\r
- char *p;\r
-\r
- if (appData.debugMode) {\r
- if (!error) {\r
- fprintf(debugFP, "<ICS: ");\r
- show_bytes(debugFP, data, count);\r
- fprintf(debugFP, "\n");\r
- }\r
- }\r
-\r
- if (appData.debugMode) { int f = forwardMostMove;\r
- fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,\r
- castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
- }\r
- if (count > 0) {\r
- /* If last read ended with a partial line that we couldn't parse,\r
- prepend it to the new read and try again. */\r
- if (leftover_len > 0) {\r
- for (i=0; i<leftover_len; i++)\r
- buf[i] = buf[leftover_start + i];\r
- }\r
-\r
- /* Copy in new characters, removing nulls and \r's */\r
- buf_len = leftover_len;\r
- for (i = 0; i < count; i++) {\r
- if (data[i] != NULLCHAR && data[i] != '\r')\r
- buf[buf_len++] = data[i];\r
- if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' && \r
- buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') \r
- buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous\r
- }\r
-\r
- buf[buf_len] = NULLCHAR;\r
- next_out = leftover_len;\r
- leftover_start = 0;\r
- \r
- i = 0;\r
- while (i < buf_len) {\r
- /* Deal with part of the TELNET option negotiation\r
- protocol. We refuse to do anything beyond the\r
- defaults, except that we allow the WILL ECHO option,\r
- which ICS uses to turn off password echoing when we are\r
- directly connected to it. We reject this option\r
- if localLineEditing mode is on (always on in xboard)\r
- and we are talking to port 23, which might be a real\r
- telnet server that will try to keep WILL ECHO on permanently.\r
- */\r
- if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {\r
- static int remoteEchoOption = FALSE; /* telnet ECHO option */\r
- unsigned char option;\r
- oldi = i;\r
- switch ((unsigned char) buf[++i]) {\r
- case TN_WILL:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "\n<WILL ");\r
- switch (option = (unsigned char) buf[++i]) {\r
- case TN_ECHO:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "ECHO ");\r
- /* Reply only if this is a change, according\r
- to the protocol rules. */\r
- if (remoteEchoOption) break;\r
- if (appData.localLineEditing &&\r
- atoi(appData.icsPort) == TN_PORT) {\r
- TelnetRequest(TN_DONT, TN_ECHO);\r
- } else {\r
- EchoOff();\r
- TelnetRequest(TN_DO, TN_ECHO);\r
- remoteEchoOption = TRUE;\r
- }\r
- break;\r
- default:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "%d ", option);\r
- /* Whatever this is, we don't want it. */\r
- TelnetRequest(TN_DONT, option);\r
- break;\r
- }\r
- break;\r
- case TN_WONT:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "\n<WONT ");\r
- switch (option = (unsigned char) buf[++i]) {\r
- case TN_ECHO:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "ECHO ");\r
- /* Reply only if this is a change, according\r
- to the protocol rules. */\r
- if (!remoteEchoOption) break;\r
- EchoOn();\r
- TelnetRequest(TN_DONT, TN_ECHO);\r
- remoteEchoOption = FALSE;\r
- break;\r
- default:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "%d ", (unsigned char) option);\r
- /* Whatever this is, it must already be turned\r
- off, because we never agree to turn on\r
- anything non-default, so according to the\r
- protocol rules, we don't reply. */\r
- break;\r
- }\r
- break;\r
- case TN_DO:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "\n<DO ");\r
- switch (option = (unsigned char) buf[++i]) {\r
- default:\r
- /* Whatever this is, we refuse to do it. */\r
- if (appData.debugMode)\r
- fprintf(debugFP, "%d ", option);\r
- TelnetRequest(TN_WONT, option);\r
- break;\r
- }\r
- break;\r
- case TN_DONT:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "\n<DONT ");\r
- switch (option = (unsigned char) buf[++i]) {\r
- default:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "%d ", option);\r
- /* Whatever this is, we are already not doing\r
- it, because we never agree to do anything\r
- non-default, so according to the protocol\r
- rules, we don't reply. */\r
- break;\r
- }\r
- break;\r
- case TN_IAC:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "\n<IAC ");\r
- /* Doubled IAC; pass it through */\r
- i--;\r
- break;\r
- default:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);\r
- /* Drop all other telnet commands on the floor */\r
- break;\r
- }\r
- if (oldi > next_out)\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- if (++i > next_out)\r
- next_out = i;\r
- continue;\r
- }\r
- \r
- /* OK, this at least will *usually* work */\r
- if (!loggedOn && looking_at(buf, &i, "ics%")) {\r
- loggedOn = TRUE;\r
- }\r
- \r
- if (loggedOn && !intfSet) {\r
- if (ics_type == ICS_ICC) {\r
- sprintf(str,\r
- "/set-quietly interface %s\n/set-quietly style 12\n",\r
- programVersion);\r
-\r
- } else if (ics_type == ICS_CHESSNET) {\r
- sprintf(str, "/style 12\n");\r
- } else {\r
- strcpy(str, "alias $ @\n$set interface ");\r
- strcat(str, programVersion);\r
- strcat(str, "\n$iset startpos 1\n$iset ms 1\n");\r
-#ifdef WIN32\r
- strcat(str, "$iset nohighlight 1\n");\r
-#endif\r
- strcat(str, "$iset lock 1\n$style 12\n");\r
- }\r
- SendToICS(str);\r
- intfSet = TRUE;\r
- }\r
-\r
- if (started == STARTED_COMMENT) {\r
- /* Accumulate characters in comment */\r
- parse[parse_pos++] = buf[i];\r
- if (buf[i] == '\n') {\r
- parse[parse_pos] = NULLCHAR;\r
- if(!suppressKibitz) // [HGM] kibitz\r
- AppendComment(forwardMostMove, StripHighlight(parse));\r
- else { // [HGM kibitz: divert memorized engine kibitz to engine-output window\r
- int nrDigit = 0, nrAlph = 0, i;\r
- if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input\r
- { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }\r
- parse[parse_pos] = NULLCHAR;\r
- // try to be smart: if it does not look like search info, it should go to\r
- // ICS interaction window after all, not to engine-output window.\r
- for(i=0; i<parse_pos; i++) { // count letters and digits\r
- nrDigit += (parse[i] >= '0' && parse[i] <= '9');\r
- nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');\r
- nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');\r
- }\r
- if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info\r
- int depth=0; float score;\r
- if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {\r
- // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph\r
- pvInfoList[forwardMostMove-1].depth = depth;\r
- pvInfoList[forwardMostMove-1].score = 100*score;\r
- }\r
- OutputKibitz(suppressKibitz, parse);\r
- } else {\r
- char tmp[MSG_SIZ];\r
- sprintf(tmp, _("your opponent kibitzes: %s"), parse);\r
- SendToPlayer(tmp, strlen(tmp));\r
- }\r
- }\r
- started = STARTED_NONE;\r
- } else {\r
- /* Don't match patterns against characters in chatter */\r
- i++;\r
- continue;\r
- }\r
- }\r
- if (started == STARTED_CHATTER) {\r
- if (buf[i] != '\n') {\r
- /* Don't match patterns against characters in chatter */\r
- i++;\r
- continue;\r
- }\r
- started = STARTED_NONE;\r
- }\r
-\r
- /* Kludge to deal with rcmd protocol */\r
- if (firstTime && looking_at(buf, &i, "\001*")) {\r
- DisplayFatalError(&buf[1], 0, 1);\r
- continue;\r
- } else {\r
- firstTime = FALSE;\r
- }\r
-\r
- if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {\r
- ics_type = ICS_ICC;\r
- ics_prefix = "/";\r
- if (appData.debugMode)\r
- fprintf(debugFP, "ics_type %d\n", ics_type);\r
- continue;\r
- }\r
- if (!loggedOn && looking_at(buf, &i, "freechess.org")) {\r
- ics_type = ICS_FICS;\r
- ics_prefix = "$";\r
- if (appData.debugMode)\r
- fprintf(debugFP, "ics_type %d\n", ics_type);\r
- continue;\r
- }\r
- if (!loggedOn && looking_at(buf, &i, "chess.net")) {\r
- ics_type = ICS_CHESSNET;\r
- ics_prefix = "/";\r
- if (appData.debugMode)\r
- fprintf(debugFP, "ics_type %d\n", ics_type);\r
- continue;\r
- }\r
-\r
- if (!loggedOn &&\r
- (looking_at(buf, &i, "\"*\" is *a registered name") ||\r
- looking_at(buf, &i, "Logging you in as \"*\"") ||\r
- looking_at(buf, &i, "will be \"*\""))) {\r
- strcpy(ics_handle, star_match[0]);\r
- continue;\r
- }\r
-\r
- if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {\r
- char buf[MSG_SIZ];\r
- snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);\r
- DisplayIcsInteractionTitle(buf);\r
- have_set_title = TRUE;\r
- }\r
-\r
- /* skip finger notes */\r
- if (started == STARTED_NONE &&\r
- ((buf[i] == ' ' && isdigit(buf[i+1])) ||\r
- (buf[i] == '1' && buf[i+1] == '0')) &&\r
- buf[i+2] == ':' && buf[i+3] == ' ') {\r
- started = STARTED_CHATTER;\r
- i += 3;\r
- continue;\r
- }\r
-\r
- /* skip formula vars */\r
- if (started == STARTED_NONE &&\r
- buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {\r
- started = STARTED_CHATTER;\r
- i += 3;\r
- continue;\r
- }\r
-\r
- oldi = i;\r
- // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window\r
- if (appData.autoKibitz && started == STARTED_NONE && \r
- !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze\r
- (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {\r
- if(looking_at(buf, &i, "* kibitzes: ") &&\r
- (StrStr(star_match[0], gameInfo.white) == star_match[0] || \r
- StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent\r
- suppressKibitz = TRUE;\r
- if((StrStr(star_match[0], gameInfo.white) == star_match[0]\r
- && (gameMode == IcsPlayingWhite)) ||\r
- (StrStr(star_match[0], gameInfo.black) == star_match[0]\r
- && (gameMode == IcsPlayingBlack)) ) // opponent kibitz\r
- started = STARTED_CHATTER; // own kibitz we simply discard\r
- else {\r
- started = STARTED_COMMENT; // make sure it will be collected in parse[]\r
- parse_pos = 0; parse[0] = NULLCHAR;\r
- savingComment = TRUE;\r
- suppressKibitz = gameMode != IcsObserving ? 2 :\r
- (StrStr(star_match[0], gameInfo.white) == NULL) + 1;\r
- } \r
- continue;\r
- } else\r
- if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz\r
- started = STARTED_CHATTER;\r
- suppressKibitz = TRUE;\r
- }\r
- } // [HGM] kibitz: end of patch\r
-\r
- if (appData.zippyTalk || appData.zippyPlay) {\r
- /* [DM] Backup address for color zippy lines */\r
- backup = i;\r
-#if ZIPPY\r
- #ifdef WIN32\r
- if (loggedOn == TRUE)\r
- if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||\r
- (appData.zippyPlay && ZippyMatch(buf, &backup)));\r
- #else\r
- if (ZippyControl(buf, &i) ||\r
- ZippyConverse(buf, &i) ||\r
- (appData.zippyPlay && ZippyMatch(buf, &i))) {\r
- loggedOn = TRUE;\r
- if (!appData.colorize) continue;\r
- }\r
- #endif\r
-#endif\r
- } // [DM] 'else { ' deleted\r
- if (/* Don't color "message" or "messages" output */\r
- (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||\r
- looking_at(buf, &i, "*. * at *:*: ") ||\r
- looking_at(buf, &i, "--* (*:*): ") ||\r
- /* Regular tells and says */\r
- (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||\r
- looking_at(buf, &i, "* (your partner) tells you: ") ||\r
- looking_at(buf, &i, "* says: ") ||\r
- /* Message notifications (same color as tells) */\r
- looking_at(buf, &i, "* has left a message ") ||\r
- looking_at(buf, &i, "* just sent you a message:\n") ||\r
- /* Whispers and kibitzes */\r
- (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||\r
- looking_at(buf, &i, "* kibitzes: ") ||\r
- /* Channel tells */\r
- (tkind = 3, looking_at(buf, &i, "*(*: "))) {\r
-\r
- if (tkind == 1 && strchr(star_match[0], ':')) {\r
- /* Avoid "tells you:" spoofs in channels */\r
- tkind = 3;\r
- }\r
- if (star_match[0][0] == NULLCHAR ||\r
- strchr(star_match[0], ' ') ||\r
- (tkind == 3 && strchr(star_match[1], ' '))) {\r
- /* Reject bogus matches */\r
- i = oldi;\r
- } else {\r
- if (appData.colorize) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- switch (tkind) {\r
- case 1:\r
- Colorize(ColorTell, FALSE);\r
- curColor = ColorTell;\r
- break;\r
- case 2:\r
- Colorize(ColorKibitz, FALSE);\r
- curColor = ColorKibitz;\r
- break;\r
- case 3:\r
- p = strrchr(star_match[1], '(');\r
- if (p == NULL) {\r
- p = star_match[1];\r
- } else {\r
- p++;\r
- }\r
- if (atoi(p) == 1) {\r
- Colorize(ColorChannel1, FALSE);\r
- curColor = ColorChannel1;\r
- } else {\r
- Colorize(ColorChannel, FALSE);\r
- curColor = ColorChannel;\r
- }\r
- break;\r
- case 5:\r
- curColor = ColorNormal;\r
- break;\r
- }\r
- }\r
- if (started == STARTED_NONE && appData.autoComment &&\r
- (gameMode == IcsObserving ||\r
- gameMode == IcsPlayingWhite ||\r
- gameMode == IcsPlayingBlack)) {\r
- parse_pos = i - oldi;\r
- memcpy(parse, &buf[oldi], parse_pos);\r
- parse[parse_pos] = NULLCHAR;\r
- started = STARTED_COMMENT;\r
- savingComment = TRUE;\r
- } else {\r
- started = STARTED_CHATTER;\r
- savingComment = FALSE;\r
- }\r
- loggedOn = TRUE;\r
- continue;\r
- }\r
- }\r
-\r
- if (looking_at(buf, &i, "* s-shouts: ") ||\r
- looking_at(buf, &i, "* c-shouts: ")) {\r
- if (appData.colorize) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(ColorSShout, FALSE);\r
- curColor = ColorSShout;\r
- }\r
- loggedOn = TRUE;\r
- started = STARTED_CHATTER;\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "--->")) {\r
- loggedOn = TRUE;\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "* shouts: ") ||\r
- looking_at(buf, &i, "--> ")) {\r
- if (appData.colorize) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(ColorShout, FALSE);\r
- curColor = ColorShout;\r
- }\r
- loggedOn = TRUE;\r
- started = STARTED_CHATTER;\r
- continue;\r
- }\r
-\r
- if (looking_at( buf, &i, "Challenge:")) {\r
- if (appData.colorize) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(ColorChallenge, FALSE);\r
- curColor = ColorChallenge;\r
- }\r
- loggedOn = TRUE;\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "* offers you") ||\r
- looking_at(buf, &i, "* offers to be") ||\r
- looking_at(buf, &i, "* would like to") ||\r
- looking_at(buf, &i, "* requests to") ||\r
- looking_at(buf, &i, "Your opponent offers") ||\r
- looking_at(buf, &i, "Your opponent requests")) {\r
-\r
- if (appData.colorize) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(ColorRequest, FALSE);\r
- curColor = ColorRequest;\r
- }\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "* (*) seeking")) {\r
- if (appData.colorize) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(ColorSeek, FALSE);\r
- curColor = ColorSeek;\r
- }\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "\\ ")) {\r
- if (prevColor != ColorNormal) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(prevColor, TRUE);\r
- curColor = prevColor;\r
- }\r
- if (savingComment) {\r
- parse_pos = i - oldi;\r
- memcpy(parse, &buf[oldi], parse_pos);\r
- parse[parse_pos] = NULLCHAR;\r
- started = STARTED_COMMENT;\r
- } else {\r
- started = STARTED_CHATTER;\r
- }\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "Black Strength :") ||\r
- looking_at(buf, &i, "<<< style 10 board >>>") ||\r
- looking_at(buf, &i, "<10>") ||\r
- looking_at(buf, &i, "#@#")) {\r
- /* Wrong board style */\r
- loggedOn = TRUE;\r
- SendToICS(ics_prefix);\r
- SendToICS("set style 12\n");\r
- SendToICS(ics_prefix);\r
- SendToICS("refresh\n");\r
- continue;\r
- }\r
- \r
- if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {\r
- ICSInitScript();\r
- have_sent_ICS_logon = 1;\r
- continue;\r
- }\r
- \r
- if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && \r
- (looking_at(buf, &i, "\n<12> ") ||\r
- looking_at(buf, &i, "<12> "))) {\r
- loggedOn = TRUE;\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- }\r
- next_out = i;\r
- started = STARTED_BOARD;\r
- parse_pos = 0;\r
- continue;\r
- }\r
-\r
- if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||\r
- looking_at(buf, &i, "<b1> ")) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- }\r
- next_out = i;\r
- started = STARTED_HOLDINGS;\r
- parse_pos = 0;\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "* *vs. * *--- *")) {\r
- loggedOn = TRUE;\r
- /* Header for a move list -- first line */\r
-\r
- switch (ics_getting_history) {\r
- case H_FALSE:\r
- switch (gameMode) {\r
- case IcsIdle:\r
- case BeginningOfGame:\r
- /* User typed "moves" or "oldmoves" while we\r
- were idle. Pretend we asked for these\r
- moves and soak them up so user can step\r
- through them and/or save them.\r
- */\r
- Reset(FALSE, TRUE);\r
- gameMode = IcsObserving;\r
- ModeHighlight();\r
- ics_gamenum = -1;\r
- ics_getting_history = H_GOT_UNREQ_HEADER;\r
- break;\r
- case EditGame: /*?*/\r
- case EditPosition: /*?*/\r
- /* Should above feature work in these modes too? */\r
- /* For now it doesn't */\r
- ics_getting_history = H_GOT_UNWANTED_HEADER;\r
- break;\r
- default:\r
- ics_getting_history = H_GOT_UNWANTED_HEADER;\r
- break;\r
- }\r
- break;\r
- case H_REQUESTED:\r
- /* Is this the right one? */\r
- if (gameInfo.white && gameInfo.black &&\r
- strcmp(gameInfo.white, star_match[0]) == 0 &&\r
- strcmp(gameInfo.black, star_match[2]) == 0) {\r
- /* All is well */\r
- ics_getting_history = H_GOT_REQ_HEADER;\r
- }\r
- break;\r
- case H_GOT_REQ_HEADER:\r
- case H_GOT_UNREQ_HEADER:\r
- case H_GOT_UNWANTED_HEADER:\r
- case H_GETTING_MOVES:\r
- /* Should not happen */\r
- DisplayError(_("Error gathering move list: two headers"), 0);\r
- ics_getting_history = H_FALSE;\r
- break;\r
- }\r
-\r
- /* Save player ratings into gameInfo if needed */\r
- if ((ics_getting_history == H_GOT_REQ_HEADER ||\r
- ics_getting_history == H_GOT_UNREQ_HEADER) &&\r
- (gameInfo.whiteRating == -1 ||\r
- gameInfo.blackRating == -1)) {\r
-\r
- gameInfo.whiteRating = string_to_rating(star_match[1]);\r
- gameInfo.blackRating = string_to_rating(star_match[3]);\r
- if (appData.debugMode)\r
- fprintf(debugFP, _("Ratings from header: W %d, B %d\n"), \r
- gameInfo.whiteRating, gameInfo.blackRating);\r
- }\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i,\r
- "* * match, initial time: * minute*, increment: * second")) {\r
- /* Header for a move list -- second line */\r
- /* Initial board will follow if this is a wild game */\r
- if (gameInfo.event != NULL) free(gameInfo.event);\r
- sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);\r
- gameInfo.event = StrSave(str);\r
- /* [HGM] we switched variant. Translate boards if needed. */\r
- VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "Move ")) {\r
- /* Beginning of a move list */\r
- switch (ics_getting_history) {\r
- case H_FALSE:\r
- /* Normally should not happen */\r
- /* Maybe user hit reset while we were parsing */\r
- break;\r
- case H_REQUESTED:\r
- /* Happens if we are ignoring a move list that is not\r
- * the one we just requested. Common if the user\r
- * tries to observe two games without turning off\r
- * getMoveList */\r
- break;\r
- case H_GETTING_MOVES:\r
- /* Should not happen */\r
- DisplayError(_("Error gathering move list: nested"), 0);\r
- ics_getting_history = H_FALSE;\r
- break;\r
- case H_GOT_REQ_HEADER:\r
- ics_getting_history = H_GETTING_MOVES;\r
- started = STARTED_MOVES;\r
- parse_pos = 0;\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- }\r
- break;\r
- case H_GOT_UNREQ_HEADER:\r
- ics_getting_history = H_GETTING_MOVES;\r
- started = STARTED_MOVES_NOHIDE;\r
- parse_pos = 0;\r
- break;\r
- case H_GOT_UNWANTED_HEADER:\r
- ics_getting_history = H_FALSE;\r
- break;\r
- }\r
- continue;\r
- } \r
- \r
- if (looking_at(buf, &i, "% ") ||\r
- ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)\r
- && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book\r
- savingComment = FALSE;\r
- switch (started) {\r
- case STARTED_MOVES:\r
- case STARTED_MOVES_NOHIDE:\r
- memcpy(&parse[parse_pos], &buf[oldi], i - oldi);\r
- parse[parse_pos + i - oldi] = NULLCHAR;\r
- ParseGameHistory(parse);\r
-#if ZIPPY\r
- if (appData.zippyPlay && first.initDone) {\r
- FeedMovesToProgram(&first, forwardMostMove);\r
- if (gameMode == IcsPlayingWhite) {\r
- if (WhiteOnMove(forwardMostMove)) {\r
- if (first.sendTime) {\r
- if (first.useColors) {\r
- SendToProgram("black\n", &first); \r
- }\r
- SendTimeRemaining(&first, TRUE);\r
- }\r
-#if 0\r
- if (first.useColors) {\r
- SendToProgram("white\ngo\n", &first);\r
- } else {\r
- SendToProgram("go\n", &first);\r
- }\r
-#else\r
- if (first.useColors) {\r
- SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent\r
- }\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos\r
-#endif\r
- first.maybeThinking = TRUE;\r
- } else {\r
- if (first.usePlayother) {\r
- if (first.sendTime) {\r
- SendTimeRemaining(&first, TRUE);\r
- }\r
- SendToProgram("playother\n", &first);\r
- firstMove = FALSE;\r
- } else {\r
- firstMove = TRUE;\r
- }\r
- }\r
- } else if (gameMode == IcsPlayingBlack) {\r
- if (!WhiteOnMove(forwardMostMove)) {\r
- if (first.sendTime) {\r
- if (first.useColors) {\r
- SendToProgram("white\n", &first);\r
- }\r
- SendTimeRemaining(&first, FALSE);\r
- }\r
-#if 0\r
- if (first.useColors) {\r
- SendToProgram("black\ngo\n", &first);\r
- } else {\r
- SendToProgram("go\n", &first);\r
- }\r
-#else\r
- if (first.useColors) {\r
- SendToProgram("black\n", &first);\r
- }\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);\r
-#endif\r
- first.maybeThinking = TRUE;\r
- } else {\r
- if (first.usePlayother) {\r
- if (first.sendTime) {\r
- SendTimeRemaining(&first, FALSE);\r
- }\r
- SendToProgram("playother\n", &first);\r
- firstMove = FALSE;\r
- } else {\r
- firstMove = TRUE;\r
- }\r
- }\r
- } \r
- }\r
-#endif\r
- if (gameMode == IcsObserving && ics_gamenum == -1) {\r
- /* Moves came from oldmoves or moves command\r
- while we weren't doing anything else.\r
- */\r
- currentMove = forwardMostMove;\r
- ClearHighlights();/*!!could figure this out*/\r
- flipView = appData.flipView;\r
- DrawPosition(FALSE, boards[currentMove]);\r
- DisplayBothClocks();\r
- sprintf(str, "%s vs. %s",\r
- gameInfo.white, gameInfo.black);\r
- DisplayTitle(str);\r
- gameMode = IcsIdle;\r
- } else {\r
- /* Moves were history of an active game */\r
- if (gameInfo.resultDetails != NULL) {\r
- free(gameInfo.resultDetails);\r
- gameInfo.resultDetails = NULL;\r
- }\r
- }\r
- HistorySet(parseList, backwardMostMove,\r
- forwardMostMove, currentMove-1);\r
- DisplayMove(currentMove - 1);\r
- if (started == STARTED_MOVES) next_out = i;\r
- started = STARTED_NONE;\r
- ics_getting_history = H_FALSE;\r
- break;\r
-\r
- case STARTED_OBSERVE:\r
- started = STARTED_NONE;\r
- SendToICS(ics_prefix);\r
- SendToICS("refresh\n");\r
- break;\r
-\r
- default:\r
- break;\r
- }\r
- if(bookHit) { // [HGM] book: simulate book reply\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- HandleMachineMove(bookMove, &first);\r
- }\r
- continue;\r
- }\r
- \r
- if ((started == STARTED_MOVES || started == STARTED_BOARD ||\r
- started == STARTED_HOLDINGS ||\r
- started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {\r
- /* Accumulate characters in move list or board */\r
- parse[parse_pos++] = buf[i];\r
- }\r
- \r
- /* Start of game messages. Mostly we detect start of game\r
- when the first board image arrives. On some versions\r
- of the ICS, though, we need to do a "refresh" after starting\r
- to observe in order to get the current board right away. */\r
- if (looking_at(buf, &i, "Adding game * to observation list")) {\r
- started = STARTED_OBSERVE;\r
- continue;\r
- }\r
-\r
- /* Handle auto-observe */\r
- if (appData.autoObserve &&\r
- (gameMode == IcsIdle || gameMode == BeginningOfGame) &&\r
- looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {\r
- char *player;\r
- /* Choose the player that was highlighted, if any. */\r
- if (star_match[0][0] == '\033' ||\r
- star_match[1][0] != '\033') {\r
- player = star_match[0];\r
- } else {\r
- player = star_match[2];\r
- }\r
- sprintf(str, "%sobserve %s\n",\r
- ics_prefix, StripHighlightAndTitle(player));\r
- SendToICS(str);\r
-\r
- /* Save ratings from notify string */\r
- strcpy(player1Name, star_match[0]);\r
- player1Rating = string_to_rating(star_match[1]);\r
- strcpy(player2Name, star_match[2]);\r
- player2Rating = string_to_rating(star_match[3]);\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, \r
- "Ratings from 'Game notification:' %s %d, %s %d\n",\r
- player1Name, player1Rating,\r
- player2Name, player2Rating);\r
-\r
- continue;\r
- }\r
-\r
- /* Deal with automatic examine mode after a game,\r
- and with IcsObserving -> IcsExamining transition */\r
- if (looking_at(buf, &i, "Entering examine mode for game *") ||\r
- looking_at(buf, &i, "has made you an examiner of game *")) {\r
-\r
- int gamenum = atoi(star_match[0]);\r
- if ((gameMode == IcsIdle || gameMode == IcsObserving) &&\r
- gamenum == ics_gamenum) {\r
- /* We were already playing or observing this game;\r
- no need to refetch history */\r
- gameMode = IcsExamining;\r
- if (pausing) {\r
- pauseExamForwardMostMove = forwardMostMove;\r
- } else if (currentMove < forwardMostMove) {\r
- ForwardInner(forwardMostMove);\r
- }\r
- } else {\r
- /* I don't think this case really can happen */\r
- SendToICS(ics_prefix);\r
- SendToICS("refresh\n");\r
- }\r
- continue;\r
- } \r
- \r
- /* Error messages */\r
- if (ics_user_moved) {\r
- if (looking_at(buf, &i, "Illegal move") ||\r
- looking_at(buf, &i, "Not a legal move") ||\r
- looking_at(buf, &i, "Your king is in check") ||\r
- looking_at(buf, &i, "It isn't your turn") ||\r
- looking_at(buf, &i, "It is not your move")) {\r
- /* Illegal move */\r
- ics_user_moved = 0;\r
- if (forwardMostMove > backwardMostMove) {\r
- currentMove = --forwardMostMove;\r
- DisplayMove(currentMove - 1); /* before DMError */\r
- DisplayMoveError(_("Illegal move (rejected by ICS)"));\r
- DrawPosition(FALSE, boards[currentMove]);\r
- SwitchClocks();\r
- DisplayBothClocks();\r
- }\r
- continue;\r
- }\r
- }\r
-\r
- if (looking_at(buf, &i, "still have time") ||\r
- looking_at(buf, &i, "not out of time") ||\r
- looking_at(buf, &i, "either player is out of time") ||\r
- looking_at(buf, &i, "has timeseal; checking")) {\r
- /* We must have called his flag a little too soon */\r
- whiteFlag = blackFlag = FALSE;\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "added * seconds to") ||\r
- looking_at(buf, &i, "seconds were added to")) {\r
- /* Update the clocks */\r
- SendToICS(ics_prefix);\r
- SendToICS("refresh\n");\r
- continue;\r
- }\r
-\r
- if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {\r
- ics_clock_paused = TRUE;\r
- StopClocks();\r
- continue;\r
- }\r
-\r
- if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {\r
- ics_clock_paused = FALSE;\r
- StartClocks();\r
- continue;\r
- }\r
-\r
- /* Grab player ratings from the Creating: message.\r
- Note we have to check for the special case when\r
- the ICS inserts things like [white] or [black]. */\r
- if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||\r
- looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {\r
- /* star_matches:\r
- 0 player 1 name (not necessarily white)\r
- 1 player 1 rating\r
- 2 empty, white, or black (IGNORED)\r
- 3 player 2 name (not necessarily black)\r
- 4 player 2 rating\r
- \r
- The names/ratings are sorted out when the game\r
- actually starts (below).\r
- */\r
- strcpy(player1Name, StripHighlightAndTitle(star_match[0]));\r
- player1Rating = string_to_rating(star_match[1]);\r
- strcpy(player2Name, StripHighlightAndTitle(star_match[3]));\r
- player2Rating = string_to_rating(star_match[4]);\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, \r
- "Ratings from 'Creating:' %s %d, %s %d\n",\r
- player1Name, player1Rating,\r
- player2Name, player2Rating);\r
-\r
- continue;\r
- }\r
- \r
- /* Improved generic start/end-of-game messages */\r
- if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||\r
- (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){\r
- /* If tkind == 0: */\r
- /* star_match[0] is the game number */\r
- /* [1] is the white player's name */\r
- /* [2] is the black player's name */\r
- /* For end-of-game: */\r
- /* [3] is the reason for the game end */\r
- /* [4] is a PGN end game-token, preceded by " " */\r
- /* For start-of-game: */\r
- /* [3] begins with "Creating" or "Continuing" */\r
- /* [4] is " *" or empty (don't care). */\r
- int gamenum = atoi(star_match[0]);\r
- char *whitename, *blackname, *why, *endtoken;\r
- ChessMove endtype = (ChessMove) 0;\r
-\r
- if (tkind == 0) {\r
- whitename = star_match[1];\r
- blackname = star_match[2];\r
- why = star_match[3];\r
- endtoken = star_match[4];\r
- } else {\r
- whitename = star_match[1];\r
- blackname = star_match[3];\r
- why = star_match[5];\r
- endtoken = star_match[6];\r
- }\r
-\r
- /* Game start messages */\r
- if (strncmp(why, "Creating ", 9) == 0 ||\r
- strncmp(why, "Continuing ", 11) == 0) {\r
- gs_gamenum = gamenum;\r
- strcpy(gs_kind, strchr(why, ' ') + 1);\r
-#if ZIPPY\r
- if (appData.zippyPlay) {\r
- ZippyGameStart(whitename, blackname);\r
- }\r
-#endif /*ZIPPY*/\r
- continue;\r
- }\r
-\r
- /* Game end messages */\r
- if (gameMode == IcsIdle || gameMode == BeginningOfGame ||\r
- ics_gamenum != gamenum) {\r
- continue;\r
- }\r
- while (endtoken[0] == ' ') endtoken++;\r
- switch (endtoken[0]) {\r
- case '*':\r
- default:\r
- endtype = GameUnfinished;\r
- break;\r
- case '0':\r
- endtype = BlackWins;\r
- break;\r
- case '1':\r
- if (endtoken[1] == '/')\r
- endtype = GameIsDrawn;\r
- else\r
- endtype = WhiteWins;\r
- break;\r
- }\r
- GameEnds(endtype, why, GE_ICS);\r
-#if ZIPPY\r
- if (appData.zippyPlay && first.initDone) {\r
- ZippyGameEnd(endtype, why);\r
- if (first.pr == NULL) {\r
- /* Start the next process early so that we'll\r
- be ready for the next challenge */\r
- StartChessProgram(&first);\r
- }\r
- /* Send "new" early, in case this command takes\r
- a long time to finish, so that we'll be ready\r
- for the next challenge. */\r
- gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'\r
- Reset(TRUE, TRUE);\r
- }\r
-#endif /*ZIPPY*/\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "Removing game * from observation") ||\r
- looking_at(buf, &i, "no longer observing game *") ||\r
- looking_at(buf, &i, "Game * (*) has no examiners")) {\r
- if (gameMode == IcsObserving &&\r
- atoi(star_match[0]) == ics_gamenum)\r
- {\r
- /* icsEngineAnalyze */\r
- if (appData.icsEngineAnalyze) {\r
- ExitAnalyzeMode();\r
- ModeHighlight();\r
- }\r
- StopClocks();\r
- gameMode = IcsIdle;\r
- ics_gamenum = -1;\r
- ics_user_moved = FALSE;\r
- }\r
- continue;\r
- }\r
-\r
- if (looking_at(buf, &i, "no longer examining game *")) {\r
- if (gameMode == IcsExamining &&\r
- atoi(star_match[0]) == ics_gamenum)\r
- {\r
- gameMode = IcsIdle;\r
- ics_gamenum = -1;\r
- ics_user_moved = FALSE;\r
- }\r
- continue;\r
- }\r
-\r
- /* Advance leftover_start past any newlines we find,\r
- so only partial lines can get reparsed */\r
- if (looking_at(buf, &i, "\n")) {\r
- prevColor = curColor;\r
- if (curColor != ColorNormal) {\r
- if (oldi > next_out) {\r
- SendToPlayer(&buf[next_out], oldi - next_out);\r
- next_out = oldi;\r
- }\r
- Colorize(ColorNormal, FALSE);\r
- curColor = ColorNormal;\r
- }\r
- if (started == STARTED_BOARD) {\r
- started = STARTED_NONE;\r
- parse[parse_pos] = NULLCHAR;\r
- ParseBoard12(parse);\r
- ics_user_moved = 0;\r
-\r
- /* Send premove here */\r
- if (appData.premove) {\r
- char str[MSG_SIZ];\r
- if (currentMove == 0 &&\r
- gameMode == IcsPlayingWhite &&\r
- appData.premoveWhite) {\r
- sprintf(str, "%s%s\n", ics_prefix,\r
- appData.premoveWhiteText);\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Sending premove:\n");\r
- SendToICS(str);\r
- } else if (currentMove == 1 &&\r
- gameMode == IcsPlayingBlack &&\r
- appData.premoveBlack) {\r
- sprintf(str, "%s%s\n", ics_prefix,\r
- appData.premoveBlackText);\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Sending premove:\n");\r
- SendToICS(str);\r
- } else if (gotPremove) {\r
- gotPremove = 0;\r
- ClearPremoveHighlights();\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Sending premove:\n");\r
- UserMoveEvent(premoveFromX, premoveFromY, \r
- premoveToX, premoveToY, \r
- premovePromoChar);\r
- }\r
- }\r
-\r
- /* Usually suppress following prompt */\r
- if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {\r
- if (looking_at(buf, &i, "*% ")) {\r
- savingComment = FALSE;\r
- }\r
- }\r
- next_out = i;\r
- } else if (started == STARTED_HOLDINGS) {\r
- int gamenum;\r
- char new_piece[MSG_SIZ];\r
- started = STARTED_NONE;\r
- parse[parse_pos] = NULLCHAR;\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",\r
- parse, currentMove);\r
- if (sscanf(parse, " game %d", &gamenum) == 1 &&\r
- gamenum == ics_gamenum) {\r
- if (gameInfo.variant == VariantNormal) {\r
- /* [HGM] We seem to switch variant during a game!\r
- * Presumably no holdings were displayed, so we have\r
- * to move the position two files to the right to\r
- * create room for them!\r
- */\r
- VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */\r
- /* Get a move list just to see the header, which\r
- will tell us whether this is really bug or zh */\r
- if (ics_getting_history == H_FALSE) {\r
- ics_getting_history = H_REQUESTED;\r
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
- SendToICS(str);\r
- }\r
- }\r
- new_piece[0] = NULLCHAR;\r
- sscanf(parse, "game %d white [%s black [%s <- %s",\r
- &gamenum, white_holding, black_holding,\r
- new_piece);\r
- white_holding[strlen(white_holding)-1] = NULLCHAR;\r
- black_holding[strlen(black_holding)-1] = NULLCHAR;\r
- /* [HGM] copy holdings to board holdings area */\r
- CopyHoldings(boards[currentMove], white_holding, WhitePawn);\r
- CopyHoldings(boards[currentMove], black_holding, BlackPawn);\r
-#if ZIPPY\r
- if (appData.zippyPlay && first.initDone) {\r
- ZippyHoldings(white_holding, black_holding,\r
- new_piece);\r
- }\r
-#endif /*ZIPPY*/\r
- if (tinyLayout || smallLayout) {\r
- char wh[16], bh[16];\r
- PackHolding(wh, white_holding);\r
- PackHolding(bh, black_holding);\r
- sprintf(str, "[%s-%s] %s-%s", wh, bh,\r
- gameInfo.white, gameInfo.black);\r
- } else {\r
- sprintf(str, "%s [%s] vs. %s [%s]",\r
- gameInfo.white, white_holding,\r
- gameInfo.black, black_holding);\r
- }\r
-\r
- DrawPosition(FALSE, boards[currentMove]);\r
- DisplayTitle(str);\r
- }\r
- /* Suppress following prompt */\r
- if (looking_at(buf, &i, "*% ")) {\r
- savingComment = FALSE;\r
- }\r
- next_out = i;\r
- }\r
- continue;\r
- }\r
-\r
- i++; /* skip unparsed character and loop back */\r
- }\r
- \r
- if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window\r
- started != STARTED_HOLDINGS && i > next_out) {\r
- SendToPlayer(&buf[next_out], i - next_out);\r
- next_out = i;\r
- }\r
- suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above\r
- \r
- leftover_len = buf_len - leftover_start;\r
- /* if buffer ends with something we couldn't parse,\r
- reparse it after appending the next read */\r
- \r
- } else if (count == 0) {\r
- RemoveInputSource(isr);\r
- DisplayFatalError(_("Connection closed by ICS"), 0, 0);\r
- } else {\r
- DisplayFatalError(_("Error reading from ICS"), error, 1);\r
- }\r
-}\r
-\r
-\r
-/* Board style 12 looks like this:\r
- \r
- <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\r
- \r
- * The "<12> " is stripped before it gets to this routine. The two\r
- * trailing 0's (flip state and clock ticking) are later addition, and\r
- * some chess servers may not have them, or may have only the first.\r
- * Additional trailing fields may be added in the future. \r
- */\r
-\r
-#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"\r
-\r
-#define RELATION_OBSERVING_PLAYED 0\r
-#define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */\r
-#define RELATION_PLAYING_MYMOVE 1\r
-#define RELATION_PLAYING_NOTMYMOVE -1\r
-#define RELATION_EXAMINING 2\r
-#define RELATION_ISOLATED_BOARD -3\r
-#define RELATION_STARTING_POSITION -4 /* FICS only */\r
-\r
-void\r
-ParseBoard12(string)\r
- char *string;\r
-{ \r
- GameMode newGameMode;\r
- int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;\r
- int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;\r
- int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;\r
- char to_play, board_chars[200];\r
- char move_str[500], str[500], elapsed_time[500];\r
- char black[32], white[32];\r
- Board board;\r
- int prevMove = currentMove;\r
- int ticking = 2;\r
- ChessMove moveType;\r
- int fromX, fromY, toX, toY;\r
- char promoChar;\r
- int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */\r
- char *bookHit = NULL; // [HGM] book\r
-\r
- fromX = fromY = toX = toY = -1;\r
- \r
- newGame = FALSE;\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, _("Parsing board: %s\n"), string);\r
-\r
- move_str[0] = NULLCHAR;\r
- elapsed_time[0] = NULLCHAR;\r
- { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */\r
- int i = 0, j;\r
- while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {\r
- if(string[i] == ' ') { ranks++; files = 0; }\r
- else files++;\r
- i++;\r
- }\r
- for(j = 0; j <i; j++) board_chars[j] = string[j];\r
- board_chars[i] = '\0';\r
- string += i + 1;\r
- }\r
- n = sscanf(string, PATTERN, &to_play, &double_push,\r
- &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,\r
- &gamenum, white, black, &relation, &basetime, &increment,\r
- &white_stren, &black_stren, &white_time, &black_time,\r
- &moveNum, str, elapsed_time, move_str, &ics_flip,\r
- &ticking);\r
-\r
- if (n < 21) {\r
- snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);\r
- DisplayError(str, 0);\r
- return;\r
- }\r
-\r
- /* Convert the move number to internal form */\r
- moveNum = (moveNum - 1) * 2;\r
- if (to_play == 'B') moveNum++;\r
- if (moveNum >= MAX_MOVES) {\r
- DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
- 0, 1);\r
- return;\r
- }\r
- \r
- switch (relation) {\r
- case RELATION_OBSERVING_PLAYED:\r
- case RELATION_OBSERVING_STATIC:\r
- if (gamenum == -1) {\r
- /* Old ICC buglet */\r
- relation = RELATION_OBSERVING_STATIC;\r
- }\r
- newGameMode = IcsObserving;\r
- break;\r
- case RELATION_PLAYING_MYMOVE:\r
- case RELATION_PLAYING_NOTMYMOVE:\r
- newGameMode =\r
- ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?\r
- IcsPlayingWhite : IcsPlayingBlack;\r
- break;\r
- case RELATION_EXAMINING:\r
- newGameMode = IcsExamining;\r
- break;\r
- case RELATION_ISOLATED_BOARD:\r
- default:\r
- /* Just display this board. If user was doing something else,\r
- we will forget about it until the next board comes. */ \r
- newGameMode = IcsIdle;\r
- break;\r
- case RELATION_STARTING_POSITION:\r
- newGameMode = gameMode;\r
- break;\r
- }\r
- \r
- /* Modify behavior for initial board display on move listing\r
- of wild games.\r
- */\r
- switch (ics_getting_history) {\r
- case H_FALSE:\r
- case H_REQUESTED:\r
- break;\r
- case H_GOT_REQ_HEADER:\r
- case H_GOT_UNREQ_HEADER:\r
- /* This is the initial position of the current game */\r
- gamenum = ics_gamenum;\r
- moveNum = 0; /* old ICS bug workaround */\r
- if (to_play == 'B') {\r
- startedFromSetupPosition = TRUE;\r
- blackPlaysFirst = TRUE;\r
- moveNum = 1;\r
- if (forwardMostMove == 0) forwardMostMove = 1;\r
- if (backwardMostMove == 0) backwardMostMove = 1;\r
- if (currentMove == 0) currentMove = 1;\r
- }\r
- newGameMode = gameMode;\r
- relation = RELATION_STARTING_POSITION; /* ICC needs this */\r
- break;\r
- case H_GOT_UNWANTED_HEADER:\r
- /* This is an initial board that we don't want */\r
- return;\r
- case H_GETTING_MOVES:\r
- /* Should not happen */\r
- DisplayError(_("Error gathering move list: extra board"), 0);\r
- ics_getting_history = H_FALSE;\r
- return;\r
- }\r
- \r
- /* Take action if this is the first board of a new game, or of a\r
- different game than is currently being displayed. */\r
- if (gamenum != ics_gamenum || newGameMode != gameMode ||\r
- relation == RELATION_ISOLATED_BOARD) {\r
- \r
- /* Forget the old game and get the history (if any) of the new one */\r
- if (gameMode != BeginningOfGame) {\r
- Reset(FALSE, TRUE);\r
- }\r
- newGame = TRUE;\r
- if (appData.autoRaiseBoard) BoardToTop();\r
- prevMove = -3;\r
- if (gamenum == -1) {\r
- newGameMode = IcsIdle;\r
- } else if (moveNum > 0 && newGameMode != IcsIdle &&\r
- appData.getMoveList) {\r
- /* Need to get game history */\r
- ics_getting_history = H_REQUESTED;\r
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
- SendToICS(str);\r
- }\r
- \r
- /* Initially flip the board to have black on the bottom if playing\r
- black or if the ICS flip flag is set, but let the user change\r
- it with the Flip View button. */\r
- flipView = appData.autoFlipView ? \r
- (newGameMode == IcsPlayingBlack) || ics_flip :\r
- appData.flipView;\r
- \r
- /* Done with values from previous mode; copy in new ones */\r
- gameMode = newGameMode;\r
- ModeHighlight();\r
- ics_gamenum = gamenum;\r
- if (gamenum == gs_gamenum) {\r
- int klen = strlen(gs_kind);\r
- if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;\r
- sprintf(str, "ICS %s", gs_kind);\r
- gameInfo.event = StrSave(str);\r
- } else {\r
- gameInfo.event = StrSave("ICS game");\r
- }\r
- gameInfo.site = StrSave(appData.icsHost);\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
- gameInfo.white = StrSave(white);\r
- gameInfo.black = StrSave(black);\r
- timeControl = basetime * 60 * 1000;\r
- timeControl_2 = 0;\r
- timeIncrement = increment * 1000;\r
- movesPerSession = 0;\r
- gameInfo.timeControl = TimeControlTagValue();\r
- VariantSwitch(board, StringToVariant(gameInfo.event) );\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);\r
- fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));\r
- setbuf(debugFP, NULL);\r
- }\r
-\r
- gameInfo.outOfBook = NULL;\r
- \r
- /* Do we have the ratings? */\r
- if (strcmp(player1Name, white) == 0 &&\r
- strcmp(player2Name, black) == 0) {\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
- player1Rating, player2Rating);\r
- gameInfo.whiteRating = player1Rating;\r
- gameInfo.blackRating = player2Rating;\r
- } else if (strcmp(player2Name, white) == 0 &&\r
- strcmp(player1Name, black) == 0) {\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Remembered ratings: W %d, B %d\n",\r
- player2Rating, player1Rating);\r
- gameInfo.whiteRating = player2Rating;\r
- gameInfo.blackRating = player1Rating;\r
- }\r
- player1Name[0] = player2Name[0] = NULLCHAR;\r
-\r
- /* Silence shouts if requested */\r
- if (appData.quietPlay &&\r
- (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {\r
- SendToICS(ics_prefix);\r
- SendToICS("set shout 0\n");\r
- }\r
- }\r
- \r
- /* Deal with midgame name changes */\r
- if (!newGame) {\r
- if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {\r
- if (gameInfo.white) free(gameInfo.white);\r
- gameInfo.white = StrSave(white);\r
- }\r
- if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {\r
- if (gameInfo.black) free(gameInfo.black);\r
- gameInfo.black = StrSave(black);\r
- }\r
- }\r
- \r
- /* Throw away game result if anything actually changes in examine mode */\r
- if (gameMode == IcsExamining && !newGame) {\r
- gameInfo.result = GameUnfinished;\r
- if (gameInfo.resultDetails != NULL) {\r
- free(gameInfo.resultDetails);\r
- gameInfo.resultDetails = NULL;\r
- }\r
- }\r
- \r
- /* In pausing && IcsExamining mode, we ignore boards coming\r
- in if they are in a different variation than we are. */\r
- if (pauseExamInvalid) return;\r
- if (pausing && gameMode == IcsExamining) {\r
- if (moveNum <= pauseExamForwardMostMove) {\r
- pauseExamInvalid = TRUE;\r
- forwardMostMove = pauseExamForwardMostMove;\r
- return;\r
- }\r
- }\r
- \r
- if (appData.debugMode) {\r
- fprintf(debugFP, "load %dx%d board\n", files, ranks);\r
- }\r
- /* Parse the board */\r
- for (k = 0; k < ranks; k++) {\r
- for (j = 0; j < files; j++)\r
- board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);\r
- if(gameInfo.holdingsWidth > 1) {\r
- board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;\r
- board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;\r
- }\r
- }\r
- CopyBoard(boards[moveNum], board);\r
- if (moveNum == 0) {\r
- startedFromSetupPosition =\r
- !CompareBoards(board, initialPosition);\r
- if(startedFromSetupPosition)\r
- initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */\r
- }\r
-\r
- /* [HGM] Set castling rights. Take the outermost Rooks,\r
- to make it also work for FRC opening positions. Note that board12\r
- is really defective for later FRC positions, as it has no way to\r
- indicate which Rook can castle if they are on the same side of King.\r
- For the initial position we grant rights to the outermost Rooks,\r
- and remember thos rights, and we then copy them on positions\r
- later in an FRC game. This means WB might not recognize castlings with\r
- Rooks that have moved back to their original position as illegal,\r
- but in ICS mode that is not its job anyway.\r
- */\r
- if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)\r
- { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;\r
-\r
- for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
- if(board[0][i] == WhiteRook) j = i;\r
- initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
- for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
- if(board[0][i] == WhiteRook) j = i;\r
- initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
- for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)\r
- if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
- initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
- for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)\r
- if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;\r
- initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);\r
-\r
- if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }\r
- for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
- if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;\r
- for(k=BOARD_LEFT; k<BOARD_RGHT; k++)\r
- if(board[BOARD_HEIGHT-1][k] == bKing)\r
- initialRights[5] = castlingRights[moveNum][5] = k;\r
- } else { int r;\r
- r = castlingRights[moveNum][0] = initialRights[0];\r
- if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;\r
- r = castlingRights[moveNum][1] = initialRights[1];\r
- if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;\r
- r = castlingRights[moveNum][3] = initialRights[3];\r
- if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;\r
- r = castlingRights[moveNum][4] = initialRights[4];\r
- if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;\r
- /* wildcastle kludge: always assume King has rights */\r
- r = castlingRights[moveNum][2] = initialRights[2];\r
- r = castlingRights[moveNum][5] = initialRights[5];\r
- }\r
- /* [HGM] e.p. rights. Assume that ICS sends file number here? */\r
- epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;\r
-\r
- \r
- if (ics_getting_history == H_GOT_REQ_HEADER ||\r
- ics_getting_history == H_GOT_UNREQ_HEADER) {\r
- /* This was an initial position from a move list, not\r
- the current position */\r
- return;\r
- }\r
- \r
- /* Update currentMove and known move number limits */\r
- newMove = newGame || moveNum > forwardMostMove;\r
-\r
- /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */\r
- if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {\r
- takeback = forwardMostMove - moveNum;\r
- for (i = 0; i < takeback; i++) {\r
- if (appData.debugMode) fprintf(debugFP, "take back move\n");\r
- SendToProgram("undo\n", &first);\r
- }\r
- }\r
-\r
- if (newGame) {\r
- forwardMostMove = backwardMostMove = currentMove = moveNum;\r
- if (gameMode == IcsExamining && moveNum == 0) {\r
- /* Workaround for ICS limitation: we are not told the wild\r
- type when starting to examine a game. But if we ask for\r
- the move list, the move list header will tell us */\r
- ics_getting_history = H_REQUESTED;\r
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
- SendToICS(str);\r
- }\r
- } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove\r
- || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {\r
- forwardMostMove = moveNum;\r
- if (!pausing || currentMove > forwardMostMove)\r
- currentMove = forwardMostMove;\r
- } else {\r
- /* New part of history that is not contiguous with old part */ \r
- if (pausing && gameMode == IcsExamining) {\r
- pauseExamInvalid = TRUE;\r
- forwardMostMove = pauseExamForwardMostMove;\r
- return;\r
- }\r
- forwardMostMove = backwardMostMove = currentMove = moveNum;\r
- if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {\r
- ics_getting_history = H_REQUESTED;\r
- sprintf(str, "%smoves %d\n", ics_prefix, gamenum);\r
- SendToICS(str);\r
- }\r
- }\r
- \r
- /* Update the clocks */\r
- if (strchr(elapsed_time, '.')) {\r
- /* Time is in ms */\r
- timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;\r
- timeRemaining[1][moveNum] = blackTimeRemaining = black_time;\r
- } else {\r
- /* Time is in seconds */\r
- timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;\r
- timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;\r
- }\r
- \r
-\r
-#if ZIPPY\r
- if (appData.zippyPlay && newGame &&\r
- gameMode != IcsObserving && gameMode != IcsIdle &&\r
- gameMode != IcsExamining)\r
- ZippyFirstBoard(moveNum, basetime, increment);\r
-#endif\r
- \r
- /* Put the move on the move list, first converting\r
- to canonical algebraic form. */\r
- if (moveNum > 0) {\r
- if (appData.debugMode) {\r
- if (appData.debugMode) { int f = forwardMostMove;\r
- fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,\r
- castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
- }\r
- fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);\r
- fprintf(debugFP, "moveNum = %d\n", moveNum);\r
- fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);\r
- setbuf(debugFP, NULL);\r
- }\r
- if (moveNum <= backwardMostMove) {\r
- /* We don't know what the board looked like before\r
- this move. Punt. */\r
- strcpy(parseList[moveNum - 1], move_str);\r
- strcat(parseList[moveNum - 1], " ");\r
- strcat(parseList[moveNum - 1], elapsed_time);\r
- moveList[moveNum - 1][0] = NULLCHAR;\r
- } else if (strcmp(move_str, "none") == 0) {\r
- // [HGM] long SAN: swapped order; test for 'none' before parsing move\r
- /* Again, we don't know what the board looked like;\r
- this is really the start of the game. */\r
- parseList[moveNum - 1][0] = NULLCHAR;\r
- moveList[moveNum - 1][0] = NULLCHAR;\r
- backwardMostMove = moveNum;\r
- startedFromSetupPosition = TRUE;\r
- fromX = fromY = toX = toY = -1;\r
- } else {\r
- // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move. \r
- // So we parse the long-algebraic move string in stead of the SAN move\r
- int valid; char buf[MSG_SIZ], *prom;\r
-\r
- // str looks something like "Q/a1-a2"; kill the slash\r
- if(str[1] == '/') \r
- sprintf(buf, "%c%s", str[0], str+2);\r
- else strcpy(buf, str); // might be castling\r
- if((prom = strstr(move_str, "=")) && !strstr(buf, "=")) \r
- strcat(buf, prom); // long move lacks promo specification!\r
- if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)\r
- if(appData.debugMode) \r
- fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);\r
- strcpy(move_str, buf);\r
- }\r
- valid = ParseOneMove(move_str, moveNum - 1, &moveType,\r
- &fromX, &fromY, &toX, &toY, &promoChar)\r
- || ParseOneMove(buf, moveNum - 1, &moveType,\r
- &fromX, &fromY, &toX, &toY, &promoChar);\r
- // end of long SAN patch\r
- if (valid) {\r
- (void) CoordsToAlgebraic(boards[moveNum - 1],\r
- PosFlags(moveNum - 1), EP_UNKNOWN,\r
- fromY, fromX, toY, toX, promoChar,\r
- parseList[moveNum-1]);\r
- switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,\r
- castlingRights[moveNum]) ) {\r
- case MT_NONE:\r
- case MT_STALEMATE:\r
- default:\r
- break;\r
- case MT_CHECK:\r
- if(gameInfo.variant != VariantShogi)\r
- strcat(parseList[moveNum - 1], "+");\r
- break;\r
- case MT_CHECKMATE:\r
- case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate\r
- strcat(parseList[moveNum - 1], "#");\r
- break;\r
- }\r
- strcat(parseList[moveNum - 1], " ");\r
- strcat(parseList[moveNum - 1], elapsed_time);\r
- /* currentMoveString is set as a side-effect of ParseOneMove */\r
- strcpy(moveList[moveNum - 1], currentMoveString);\r
- strcat(moveList[moveNum - 1], "\n");\r
- } else {\r
- /* Move from ICS was illegal!? Punt. */\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);\r
- fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
- }\r
-#if 0\r
- if (appData.testLegality && appData.debugMode) {\r
- sprintf(str, "Illegal move \"%s\" from ICS", move_str);\r
- DisplayError(str, 0);\r
- }\r
-#endif\r
- strcpy(parseList[moveNum - 1], move_str);\r
- strcat(parseList[moveNum - 1], " ");\r
- strcat(parseList[moveNum - 1], elapsed_time);\r
- moveList[moveNum - 1][0] = NULLCHAR;\r
- fromX = fromY = toX = toY = -1;\r
- }\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);\r
- setbuf(debugFP, NULL);\r
- }\r
-\r
-#if ZIPPY\r
- /* Send move to chess program (BEFORE animating it). */\r
- if (appData.zippyPlay && !newGame && newMove && \r
- (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {\r
-\r
- if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||\r
- (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {\r
- if (moveList[moveNum - 1][0] == NULLCHAR) {\r
- sprintf(str, _("Couldn't parse move \"%s\" from ICS"),\r
- move_str);\r
- DisplayError(str, 0);\r
- } else {\r
- if (first.sendTime) {\r
- SendTimeRemaining(&first, gameMode == IcsPlayingWhite);\r
- }\r
- bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book\r
- if (firstMove && !bookHit) {\r
- firstMove = FALSE;\r
- if (first.useColors) {\r
- SendToProgram(gameMode == IcsPlayingWhite ?\r
- "white\ngo\n" :\r
- "black\ngo\n", &first);\r
- } else {\r
- SendToProgram("go\n", &first);\r
- }\r
- first.maybeThinking = TRUE;\r
- }\r
- }\r
- } else if (gameMode == IcsObserving || gameMode == IcsExamining) {\r
- if (moveList[moveNum - 1][0] == NULLCHAR) {\r
- sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);\r
- DisplayError(str, 0);\r
- } else {\r
- if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!\r
- SendMoveToProgram(moveNum - 1, &first);\r
- }\r
- }\r
- }\r
-#endif\r
- }\r
-\r
- if (moveNum > 0 && !gotPremove) {\r
- /* If move comes from a remote source, animate it. If it\r
- isn't remote, it will have already been animated. */\r
- if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {\r
- AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);\r
- }\r
- if (!pausing && appData.highlightLastMove) {\r
- SetHighlights(fromX, fromY, toX, toY);\r
- }\r
- }\r
- \r
- /* Start the clocks */\r
- whiteFlag = blackFlag = FALSE;\r
- appData.clockMode = !(basetime == 0 && increment == 0);\r
- if (ticking == 0) {\r
- ics_clock_paused = TRUE;\r
- StopClocks();\r
- } else if (ticking == 1) {\r
- ics_clock_paused = FALSE;\r
- }\r
- if (gameMode == IcsIdle ||\r
- relation == RELATION_OBSERVING_STATIC ||\r
- relation == RELATION_EXAMINING ||\r
- ics_clock_paused)\r
- DisplayBothClocks();\r
- else\r
- StartClocks();\r
- \r
- /* Display opponents and material strengths */\r
- if (gameInfo.variant != VariantBughouse &&\r
- gameInfo.variant != VariantCrazyhouse) {\r
- if (tinyLayout || smallLayout) {\r
- if(gameInfo.variant == VariantNormal)\r
- sprintf(str, "%s(%d) %s(%d) {%d %d}", \r
- gameInfo.white, white_stren, gameInfo.black, black_stren,\r
- basetime, increment);\r
- else\r
- sprintf(str, "%s(%d) %s(%d) {%d %d w%d}", \r
- gameInfo.white, white_stren, gameInfo.black, black_stren,\r
- basetime, increment, (int) gameInfo.variant);\r
- } else {\r
- if(gameInfo.variant == VariantNormal)\r
- sprintf(str, "%s (%d) vs. %s (%d) {%d %d}", \r
- gameInfo.white, white_stren, gameInfo.black, black_stren,\r
- basetime, increment);\r
- else\r
- sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}", \r
- gameInfo.white, white_stren, gameInfo.black, black_stren,\r
- basetime, increment, VariantName(gameInfo.variant));\r
- }\r
- DisplayTitle(str);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);\r
- }\r
- }\r
-\r
- \r
- /* Display the board */\r
- if (!pausing) {\r
- \r
- if (appData.premove)\r
- if (!gotPremove || \r
- ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||\r
- ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))\r
- ClearPremoveHighlights();\r
-\r
- DrawPosition(FALSE, boards[currentMove]);\r
- DisplayMove(moveNum - 1);\r
- if (appData.ringBellAfterMoves && !ics_user_moved)\r
- RingBell();\r
- }\r
-\r
- HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
-#if ZIPPY\r
- if(bookHit) { // [HGM] book: simulate book reply\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- HandleMachineMove(bookMove, &first);\r
- }\r
-#endif\r
-}\r
-\r
-void\r
-GetMoveListEvent()\r
-{\r
- char buf[MSG_SIZ];\r
- if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {\r
- ics_getting_history = H_REQUESTED;\r
- sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);\r
- SendToICS(buf);\r
- }\r
-}\r
-\r
-void\r
-AnalysisPeriodicEvent(force)\r
- int force;\r
-{\r
- if (((programStats.ok_to_send == 0 || programStats.line_is_book)\r
- && !force) || !appData.periodicUpdates)\r
- return;\r
-\r
- /* Send . command to Crafty to collect stats */\r
- SendToProgram(".\n", &first);\r
-\r
- /* Don't send another until we get a response (this makes\r
- us stop sending to old Crafty's which don't understand\r
- the "." command (sending illegal cmds resets node count & time,\r
- which looks bad)) */\r
- programStats.ok_to_send = 0;\r
-}\r
-\r
-void\r
-SendMoveToProgram(moveNum, cps)\r
- int moveNum;\r
- ChessProgramState *cps;\r
-{\r
- char buf[MSG_SIZ];\r
-\r
- if (cps->useUsermove) {\r
- SendToProgram("usermove ", cps);\r
- }\r
- if (cps->useSAN) {\r
- char *space;\r
- if ((space = strchr(parseList[moveNum], ' ')) != NULL) {\r
- int len = space - parseList[moveNum];\r
- memcpy(buf, parseList[moveNum], len);\r
- buf[len++] = '\n';\r
- buf[len] = NULLCHAR;\r
- } else {\r
- sprintf(buf, "%s\n", parseList[moveNum]);\r
- }\r
- SendToProgram(buf, cps);\r
- } else {\r
- if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */\r
- AlphaRank(moveList[moveNum], 4);\r
- SendToProgram(moveList[moveNum], cps);\r
- AlphaRank(moveList[moveNum], 4); // and back\r
- } else\r
- /* Added by Tord: Send castle moves in "O-O" in FRC games if required by\r
- * the engine. It would be nice to have a better way to identify castle \r
- * moves here. */\r
- if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)\r
- && cps->useOOCastle) {\r
- int fromX = moveList[moveNum][0] - AAA; \r
- int fromY = moveList[moveNum][1] - ONE;\r
- int toX = moveList[moveNum][2] - AAA; \r
- int toY = moveList[moveNum][3] - ONE;\r
- if((boards[moveNum][fromY][fromX] == WhiteKing \r
- && boards[moveNum][toY][toX] == WhiteRook)\r
- || (boards[moveNum][fromY][fromX] == BlackKing \r
- && boards[moveNum][toY][toX] == BlackRook)) {\r
- if(toX > fromX) SendToProgram("O-O\n", cps);\r
- else SendToProgram("O-O-O\n", cps);\r
- }\r
- else SendToProgram(moveList[moveNum], cps);\r
- }\r
- else SendToProgram(moveList[moveNum], cps);\r
- /* End of additions by Tord */\r
- }\r
-\r
- /* [HGM] setting up the opening has brought engine in force mode! */\r
- /* Send 'go' if we are in a mode where machine should play. */\r
- if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&\r
- (gameMode == TwoMachinesPlay ||\r
-#ifdef ZIPPY\r
- gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||\r
-#endif\r
- gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {\r
- SendToProgram("go\n", cps);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "(extra)\n");\r
- }\r
- }\r
- setboardSpoiledMachineBlack = 0;\r
-}\r
-\r
-void\r
-SendMoveToICS(moveType, fromX, fromY, toX, toY)\r
- ChessMove moveType;\r
- int fromX, fromY, toX, toY;\r
-{\r
- char user_move[MSG_SIZ];\r
-\r
- switch (moveType) {\r
- default:\r
- sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),\r
- (int)moveType, fromX, fromY, toX, toY);\r
- DisplayError(user_move + strlen("say "), 0);\r
- break;\r
- case WhiteKingSideCastle:\r
- case BlackKingSideCastle:\r
- case WhiteQueenSideCastleWild:\r
- case BlackQueenSideCastleWild:\r
- /* PUSH Fabien */\r
- case WhiteHSideCastleFR:\r
- case BlackHSideCastleFR:\r
- /* POP Fabien */\r
- sprintf(user_move, "o-o\n");\r
- break;\r
- case WhiteQueenSideCastle:\r
- case BlackQueenSideCastle:\r
- case WhiteKingSideCastleWild:\r
- case BlackKingSideCastleWild:\r
- /* PUSH Fabien */\r
- case WhiteASideCastleFR:\r
- case BlackASideCastleFR:\r
- /* POP Fabien */\r
- sprintf(user_move, "o-o-o\n");\r
- break;\r
- case WhitePromotionQueen:\r
- case BlackPromotionQueen:\r
- case WhitePromotionRook:\r
- case BlackPromotionRook:\r
- case WhitePromotionBishop:\r
- case BlackPromotionBishop:\r
- case WhitePromotionKnight:\r
- case BlackPromotionKnight:\r
- case WhitePromotionKing:\r
- case BlackPromotionKing:\r
- case WhitePromotionChancellor:\r
- case BlackPromotionChancellor:\r
- case WhitePromotionArchbishop:\r
- case BlackPromotionArchbishop:\r
- if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)\r
- sprintf(user_move, "%c%c%c%c=%c\n",\r
- AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
- PieceToChar(WhiteFerz));\r
- else if(gameInfo.variant == VariantGreat)\r
- sprintf(user_move, "%c%c%c%c=%c\n",\r
- AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
- PieceToChar(WhiteMan));\r
- else\r
- sprintf(user_move, "%c%c%c%c=%c\n",\r
- AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,\r
- PieceToChar(PromoPiece(moveType)));\r
- break;\r
- case WhiteDrop:\r
- case BlackDrop:\r
- sprintf(user_move, "%c@%c%c\n",\r
- ToUpper(PieceToChar((ChessSquare) fromX)),\r
- AAA + toX, ONE + toY);\r
- break;\r
- case NormalMove:\r
- case WhiteCapturesEnPassant:\r
- case BlackCapturesEnPassant:\r
- case IllegalMove: /* could be a variant we don't quite understand */\r
- sprintf(user_move, "%c%c%c%c\n",\r
- AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);\r
- break;\r
- }\r
- SendToICS(user_move);\r
-}\r
-\r
-void\r
-CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)\r
- int rf, ff, rt, ft;\r
- char promoChar;\r
- char move[7];\r
-{\r
- if (rf == DROP_RANK) {\r
- sprintf(move, "%c@%c%c\n",\r
- ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);\r
- } else {\r
- if (promoChar == 'x' || promoChar == NULLCHAR) {\r
- sprintf(move, "%c%c%c%c\n",\r
- AAA + ff, ONE + rf, AAA + ft, ONE + rt);\r
- } else {\r
- sprintf(move, "%c%c%c%c%c\n",\r
- AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);\r
- }\r
- }\r
-}\r
-\r
-void\r
-ProcessICSInitScript(f)\r
- FILE *f;\r
-{\r
- char buf[MSG_SIZ];\r
-\r
- while (fgets(buf, MSG_SIZ, f)) {\r
- SendToICSDelayed(buf,(long)appData.msLoginDelay);\r
- }\r
-\r
- fclose(f);\r
-}\r
-\r
-\r
-/* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */\r
-void\r
-AlphaRank(char *move, int n)\r
-{\r
-// char *p = move, c; int x, y;\r
-\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);\r
- }\r
-\r
- if(move[1]=='*' && \r
- move[2]>='0' && move[2]<='9' &&\r
- move[3]>='a' && move[3]<='x' ) {\r
- move[1] = '@';\r
- move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;\r
- move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
- } else\r
- if(move[0]>='0' && move[0]<='9' &&\r
- move[1]>='a' && move[1]<='x' &&\r
- move[2]>='0' && move[2]<='9' &&\r
- move[3]>='a' && move[3]<='x' ) {\r
- /* input move, Shogi -> normal */\r
- move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;\r
- move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;\r
- move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;\r
- move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;\r
- } else\r
- if(move[1]=='@' &&\r
- move[3]>='0' && move[3]<='9' &&\r
- move[2]>='a' && move[2]<='x' ) {\r
- move[1] = '*';\r
- move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
- move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
- } else\r
- if(\r
- move[0]>='a' && move[0]<='x' &&\r
- move[3]>='0' && move[3]<='9' &&\r
- move[2]>='a' && move[2]<='x' ) {\r
- /* output move, normal -> Shogi */\r
- move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';\r
- move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';\r
- move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';\r
- move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';\r
- if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, " out = '%s'\n", move);\r
- }\r
-}\r
-\r
-/* Parser for moves from gnuchess, ICS, or user typein box */\r
-Boolean\r
-ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)\r
- char *move;\r
- int moveNum;\r
- ChessMove *moveType;\r
- int *fromX, *fromY, *toX, *toY;\r
- char *promoChar;\r
-{ \r
- if (appData.debugMode) {\r
- fprintf(debugFP, "move to parse: %s\n", move);\r
- }\r
- *moveType = yylexstr(moveNum, move);\r
-\r
- switch (*moveType) {\r
- case WhitePromotionChancellor:\r
- case BlackPromotionChancellor:\r
- case WhitePromotionArchbishop:\r
- case BlackPromotionArchbishop:\r
- case WhitePromotionQueen:\r
- case BlackPromotionQueen:\r
- case WhitePromotionRook:\r
- case BlackPromotionRook:\r
- case WhitePromotionBishop:\r
- case BlackPromotionBishop:\r
- case WhitePromotionKnight:\r
- case BlackPromotionKnight:\r
- case WhitePromotionKing:\r
- case BlackPromotionKing:\r
- case NormalMove:\r
- case WhiteCapturesEnPassant:\r
- case BlackCapturesEnPassant:\r
- case WhiteKingSideCastle:\r
- case WhiteQueenSideCastle:\r
- case BlackKingSideCastle:\r
- case BlackQueenSideCastle:\r
- case WhiteKingSideCastleWild:\r
- case WhiteQueenSideCastleWild:\r
- case BlackKingSideCastleWild:\r
- case BlackQueenSideCastleWild:\r
- /* Code added by Tord: */\r
- case WhiteHSideCastleFR:\r
- case WhiteASideCastleFR:\r
- case BlackHSideCastleFR:\r
- case BlackASideCastleFR:\r
- /* End of code added by Tord */\r
- case IllegalMove: /* bug or odd chess variant */\r
- *fromX = currentMoveString[0] - AAA;\r
- *fromY = currentMoveString[1] - ONE;\r
- *toX = currentMoveString[2] - AAA;\r
- *toY = currentMoveString[3] - ONE;\r
- *promoChar = currentMoveString[4];\r
- if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||\r
- *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);\r
- }\r
- *fromX = *fromY = *toX = *toY = 0;\r
- return FALSE;\r
- }\r
- if (appData.testLegality) {\r
- return (*moveType != IllegalMove);\r
- } else {\r
- return !(fromX == fromY && toX == toY);\r
- }\r
-\r
- case WhiteDrop:\r
- case BlackDrop:\r
- *fromX = *moveType == WhiteDrop ?\r
- (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
- (int) CharToPiece(ToLower(currentMoveString[0]));\r
- *fromY = DROP_RANK;\r
- *toX = currentMoveString[2] - AAA;\r
- *toY = currentMoveString[3] - ONE;\r
- *promoChar = NULLCHAR;\r
- return TRUE;\r
-\r
- case AmbiguousMove:\r
- case ImpossibleMove:\r
- case (ChessMove) 0: /* end of file */\r
- case ElapsedTime:\r
- case Comment:\r
- case PGNTag:\r
- case NAG:\r
- case WhiteWins:\r
- case BlackWins:\r
- case GameIsDrawn:\r
- default:\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);\r
- }\r
- /* bug? */\r
- *fromX = *fromY = *toX = *toY = 0;\r
- *promoChar = NULLCHAR;\r
- return FALSE;\r
- }\r
-}\r
-\r
-// [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.\r
-// All positions will have equal probability, but the current method will not provide a unique\r
-// numbering scheme for arrays that contain 3 or more pieces of the same kind.\r
-#define DARK 1\r
-#define LITE 2\r
-#define ANY 3\r
-\r
-int squaresLeft[4];\r
-int piecesLeft[(int)BlackPawn];\r
-int seed, nrOfShuffles;\r
-\r
-void GetPositionNumber()\r
-{ // sets global variable seed\r
- int i;\r
-\r
- seed = appData.defaultFrcPosition;\r
- if(seed < 0) { // randomize based on time for negative FRC position numbers\r
- for(i=0; i<50; i++) seed += random();\r
- seed = random() ^ random() >> 8 ^ random() << 8;\r
- if(seed<0) seed = -seed;\r
- }\r
-}\r
-\r
-int put(Board board, int pieceType, int rank, int n, int shade)\r
-// put the piece on the (n-1)-th empty squares of the given shade\r
-{\r
- int i;\r
-\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
- if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {\r
- board[rank][i] = (ChessSquare) pieceType;\r
- squaresLeft[((i-BOARD_LEFT)&1) + 1]--;\r
- squaresLeft[ANY]--;\r
- piecesLeft[pieceType]--; \r
- return i;\r
- }\r
- }\r
- return -1;\r
-}\r
-\r
-\r
-void AddOnePiece(Board board, int pieceType, int rank, int shade)\r
-// calculate where the next piece goes, (any empty square), and put it there\r
-{\r
- int i;\r
-\r
- i = seed % squaresLeft[shade];\r
- nrOfShuffles *= squaresLeft[shade];\r
- seed /= squaresLeft[shade];\r
- put(board, pieceType, rank, i, shade);\r
-}\r
-\r
-void AddTwoPieces(Board board, int pieceType, int rank)\r
-// calculate where the next 2 identical pieces go, (any empty square), and put it there\r
-{\r
- int i, n=squaresLeft[ANY], j=n-1, k;\r
-\r
- k = n*(n-1)/2; // nr of possibilities, not counting permutations\r
- i = seed % k; // pick one\r
- nrOfShuffles *= k;\r
- seed /= k;\r
- while(i >= j) i -= j--;\r
- j = n - 1 - j; i += j;\r
- put(board, pieceType, rank, j, ANY);\r
- put(board, pieceType, rank, i, ANY);\r
-}\r
-\r
-void SetUpShuffle(Board board, int number)\r
-{\r
- int i, p, first=1;\r
-\r
- GetPositionNumber(); nrOfShuffles = 1;\r
-\r
- squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;\r
- squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;\r
- squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];\r
-\r
- for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;\r
-\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board\r
- p = (int) board[0][i];\r
- if(p < (int) BlackPawn) piecesLeft[p] ++;\r
- board[0][i] = EmptySquare;\r
- }\r
-\r
- if(PosFlags(0) & F_ALL_CASTLE_OK) {\r
- // shuffles restricted to allow normal castling put KRR first\r
- if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle\r
- put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);\r
- else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles\r
- put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);\r
- if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling\r
- put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);\r
- if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling\r
- put(board, WhiteRook, 0, 0, ANY);\r
- // in variants with super-numerary Kings and Rooks, we leave these for the shuffle\r
- }\r
-\r
- if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)\r
- // only for even boards make effort to put pairs of colorbound pieces on opposite colors\r
- for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {\r
- if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;\r
- while(piecesLeft[p] >= 2) {\r
- AddOnePiece(board, p, 0, LITE);\r
- AddOnePiece(board, p, 0, DARK);\r
- }\r
- // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)\r
- }\r
-\r
- for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {\r
- // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere\r
- // but we leave King and Rooks for last, to possibly obey FRC restriction\r
- if(p == (int)WhiteRook) continue;\r
- while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations\r
- if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece\r
- }\r
-\r
- // now everything is placed, except perhaps King (Unicorn) and Rooks\r
-\r
- if(PosFlags(0) & F_FRC_TYPE_CASTLING) {\r
- // Last King gets castling rights\r
- while(piecesLeft[(int)WhiteUnicorn]) {\r
- i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
- initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;\r
- }\r
-\r
- while(piecesLeft[(int)WhiteKing]) {\r
- i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);\r
- initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;\r
- }\r
-\r
-\r
- } else {\r
- while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);\r
- while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);\r
- }\r
-\r
- // Only Rooks can be left; simply place them all\r
- while(piecesLeft[(int)WhiteRook]) {\r
- i = put(board, WhiteRook, 0, 0, ANY);\r
- if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights\r
- if(first) {\r
- first=0;\r
- initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;\r
- }\r
- initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;\r
- }\r
- }\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white\r
- board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;\r
- }\r
-\r
- if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize\r
-}\r
-\r
-int SetCharTable( char *table, const char * map )\r
-/* [HGM] moved here from winboard.c because of its general usefulness */\r
-/* Basically a safe strcpy that uses the last character as King */\r
-{\r
- int result = FALSE; int NrPieces;\r
-\r
- if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare \r
- && NrPieces >= 12 && !(NrPieces&1)) {\r
- int i; /* [HGM] Accept even length from 12 to 34 */\r
-\r
- for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';\r
- for( i=0; i<NrPieces/2-1; i++ ) {\r
- table[i] = map[i];\r
- table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];\r
- }\r
- table[(int) WhiteKing] = map[NrPieces/2-1];\r
- table[(int) BlackKing] = map[NrPieces-1];\r
-\r
- result = TRUE;\r
- }\r
-\r
- return result;\r
-}\r
-\r
-void Prelude(Board board)\r
-{ // [HGM] superchess: random selection of exo-pieces\r
- int i, j, k; ChessSquare p; \r
- static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };\r
-\r
- GetPositionNumber(); // use FRC position number\r
-\r
- if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table\r
- SetCharTable(pieceToChar, appData.pieceToCharTable);\r
- for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++) \r
- if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;\r
- }\r
-\r
- j = seed%4; seed /= 4; \r
- p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
- board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;\r
- board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;\r
- j = seed%3 + (seed%3 >= j); seed /= 3; \r
- p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);\r
- board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;\r
- board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;\r
- j = seed%3; seed /= 3; \r
- p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
- board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;\r
- board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;\r
- j = seed%2 + (seed%2 >= j); seed /= 2; \r
- p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);\r
- board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;\r
- board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;\r
- j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);\r
- j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);\r
- j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);\r
- put(board, exoPieces[0], 0, 0, ANY);\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];\r
-}\r
-\r
-void\r
-InitPosition(redraw)\r
- int redraw;\r
-{\r
- ChessSquare (* pieces)[BOARD_SIZE];\r
- int i, j, pawnRow, overrule,\r
- oldx = gameInfo.boardWidth,\r
- oldy = gameInfo.boardHeight,\r
- oldh = gameInfo.holdingsWidth,\r
- oldv = gameInfo.variant;\r
-\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request\r
-\r
- /* [AS] Initialize pv info list [HGM] and game status */\r
- {\r
- for( i=0; i<MAX_MOVES; i++ ) {\r
- pvInfoList[i].depth = 0;\r
- epStatus[i]=EP_NONE;\r
- for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;\r
- }\r
-\r
- initialRulePlies = 0; /* 50-move counter start */\r
-\r
- castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;\r
- castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;\r
- }\r
-\r
- \r
- /* [HGM] logic here is completely changed. In stead of full positions */\r
- /* the initialized data only consist of the two backranks. The switch */\r
- /* selects which one we will use, which is than copied to the Board */\r
- /* initialPosition, which for the rest is initialized by Pawns and */\r
- /* empty squares. This initial position is then copied to boards[0], */\r
- /* possibly after shuffling, so that it remains available. */\r
-\r
- gameInfo.holdingsWidth = 0; /* default board sizes */\r
- gameInfo.boardWidth = 8;\r
- gameInfo.boardHeight = 8;\r
- gameInfo.holdingsSize = 0;\r
- nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */\r
- for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */\r
- SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k"); \r
-\r
- switch (gameInfo.variant) {\r
- case VariantFischeRandom:\r
- shuffleOpenings = TRUE;\r
- default:\r
- pieces = FIDEArray;\r
- break;\r
- case VariantShatranj:\r
- pieces = ShatranjArray;\r
- nrCastlingRights = 0;\r
- SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k"); \r
- break;\r
- case VariantTwoKings:\r
- pieces = twoKingsArray;\r
- break;\r
- case VariantCapaRandom:\r
- shuffleOpenings = TRUE;\r
- case VariantCapablanca:\r
- pieces = CapablancaArray;\r
- gameInfo.boardWidth = 10;\r
- SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
- break;\r
- case VariantGothic:\r
- pieces = GothicArray;\r
- gameInfo.boardWidth = 10;\r
- SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack"); \r
- break;\r
- case VariantJanus:\r
- pieces = JanusArray;\r
- gameInfo.boardWidth = 10;\r
- SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk"); \r
- nrCastlingRights = 6;\r
- castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
- castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
- castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;\r
- castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
- castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
- castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;\r
- break;\r
- case VariantFalcon:\r
- pieces = FalconArray;\r
- gameInfo.boardWidth = 10;\r
- SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk"); \r
- break;\r
- case VariantXiangqi:\r
- pieces = XiangqiArray;\r
- gameInfo.boardWidth = 9;\r
- gameInfo.boardHeight = 10;\r
- nrCastlingRights = 0;\r
- SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c."); \r
- break;\r
- case VariantShogi:\r
- pieces = ShogiArray;\r
- gameInfo.boardWidth = 9;\r
- gameInfo.boardHeight = 9;\r
- gameInfo.holdingsSize = 7;\r
- nrCastlingRights = 0;\r
- SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k"); \r
- break;\r
- case VariantCourier:\r
- pieces = CourierArray;\r
- gameInfo.boardWidth = 12;\r
- nrCastlingRights = 0;\r
- SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk"); \r
- for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
- break;\r
- case VariantKnightmate:\r
- pieces = KnightmateArray;\r
- SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k."); \r
- break;\r
- case VariantFairy:\r
- pieces = fairyArray;\r
- SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk"); \r
- break;\r
- case VariantGreat:\r
- pieces = GreatArray;\r
- gameInfo.boardWidth = 10;\r
- SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");\r
- gameInfo.holdingsSize = 8;\r
- break;\r
- case VariantSuper:\r
- pieces = FIDEArray;\r
- SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");\r
- gameInfo.holdingsSize = 8;\r
- startedFromSetupPosition = TRUE;\r
- break;\r
- case VariantCrazyhouse:\r
- case VariantBughouse:\r
- pieces = FIDEArray;\r
- SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k"); \r
- gameInfo.holdingsSize = 5;\r
- break;\r
- case VariantWildCastle:\r
- pieces = FIDEArray;\r
- /* !!?shuffle with kings guaranteed to be on d or e file */\r
- shuffleOpenings = 1;\r
- break;\r
- case VariantNoCastle:\r
- pieces = FIDEArray;\r
- nrCastlingRights = 0;\r
- for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;\r
- /* !!?unconstrained back-rank shuffle */\r
- shuffleOpenings = 1;\r
- break;\r
- }\r
-\r
- overrule = 0;\r
- if(appData.NrFiles >= 0) {\r
- if(gameInfo.boardWidth != appData.NrFiles) overrule++;\r
- gameInfo.boardWidth = appData.NrFiles;\r
- }\r
- if(appData.NrRanks >= 0) {\r
- gameInfo.boardHeight = appData.NrRanks;\r
- }\r
- if(appData.holdingsSize >= 0) {\r
- i = appData.holdingsSize;\r
- if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;\r
- gameInfo.holdingsSize = i;\r
- }\r
- if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;\r
- if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)\r
- DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);\r
-\r
- pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */\r
- if(pawnRow < 1) pawnRow = 1;\r
-\r
- /* User pieceToChar list overrules defaults */\r
- if(appData.pieceToCharTable != NULL)\r
- SetCharTable(pieceToChar, appData.pieceToCharTable);\r
-\r
- for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;\r
-\r
- if(j==BOARD_LEFT-1 || j==BOARD_RGHT)\r
- s = (ChessSquare) 0; /* account holding counts in guard band */\r
- for( i=0; i<BOARD_HEIGHT; i++ )\r
- initialPosition[i][j] = s;\r
-\r
- if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;\r
- initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];\r
- initialPosition[pawnRow][j] = WhitePawn;\r
- initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;\r
- if(gameInfo.variant == VariantXiangqi) {\r
- if(j&1) {\r
- initialPosition[pawnRow][j] = \r
- initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;\r
- if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {\r
- initialPosition[2][j] = WhiteCannon;\r
- initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;\r
- }\r
- }\r
- }\r
- initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];\r
- }\r
- if( (gameInfo.variant == VariantShogi) && !overrule ) {\r
-\r
- j=BOARD_LEFT+1;\r
- initialPosition[1][j] = WhiteBishop;\r
- initialPosition[BOARD_HEIGHT-2][j] = BlackRook;\r
- j=BOARD_RGHT-2;\r
- initialPosition[1][j] = WhiteRook;\r
- initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;\r
- }\r
-\r
- if( nrCastlingRights == -1) {\r
- /* [HGM] Build normal castling rights (must be done after board sizing!) */\r
- /* This sets default castling rights from none to normal corners */\r
- /* Variants with other castling rights must set them themselves above */\r
- nrCastlingRights = 6;\r
- \r
- castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;\r
- castlingRights[0][1] = initialRights[1] = BOARD_LEFT;\r
- castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;\r
- castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;\r
- castlingRights[0][4] = initialRights[4] = BOARD_LEFT;\r
- castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;\r
- }\r
-\r
- if(gameInfo.variant == VariantSuper) Prelude(initialPosition);\r
- if(gameInfo.variant == VariantGreat) { // promotion commoners\r
- initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;\r
- initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;\r
- initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;\r
- initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;\r
- }\r
-#if 0\r
- if(gameInfo.variant == VariantFischeRandom) {\r
- if( appData.defaultFrcPosition < 0 ) {\r
- ShuffleFRC( initialPosition );\r
- }\r
- else {\r
- SetupFRC( initialPosition, appData.defaultFrcPosition );\r
- }\r
- startedFromSetupPosition = TRUE;\r
- } else \r
-#else\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);\r
- }\r
- if(shuffleOpenings) {\r
- SetUpShuffle(initialPosition, appData.defaultFrcPosition);\r
- startedFromSetupPosition = TRUE;\r
- }\r
-#endif\r
- if(startedFromPositionFile) {\r
- /* [HGM] loadPos: use PositionFile for every new game */\r
- CopyBoard(initialPosition, filePosition);\r
- for(i=0; i<nrCastlingRights; i++)\r
- castlingRights[0][i] = initialRights[i] = fileRights[i];\r
- startedFromSetupPosition = TRUE;\r
- }\r
-\r
- CopyBoard(boards[0], initialPosition);\r
-\r
- if(oldx != gameInfo.boardWidth ||\r
- oldy != gameInfo.boardHeight ||\r
- oldh != gameInfo.holdingsWidth\r
-#ifdef GOTHIC\r
- || oldv == VariantGothic || // For licensing popups\r
- gameInfo.variant == VariantGothic\r
-#endif\r
-#ifdef FALCON\r
- || oldv == VariantFalcon ||\r
- gameInfo.variant == VariantFalcon\r
-#endif\r
- )\r
- InitDrawingSizes(-2 ,0);\r
-\r
- if (redraw)\r
- DrawPosition(TRUE, boards[currentMove]);\r
-}\r
-\r
-void\r
-SendBoard(cps, moveNum)\r
- ChessProgramState *cps;\r
- int moveNum;\r
-{\r
- char message[MSG_SIZ];\r
- \r
- if (cps->useSetboard) {\r
- char* fen = PositionToFEN(moveNum, cps->fenOverride);\r
- sprintf(message, "setboard %s\n", fen);\r
- SendToProgram(message, cps);\r
- free(fen);\r
-\r
- } else {\r
- ChessSquare *bp;\r
- int i, j;\r
- /* Kludge to set black to move, avoiding the troublesome and now\r
- * deprecated "black" command.\r
- */\r
- if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);\r
-\r
- SendToProgram("edit\n", cps);\r
- SendToProgram("#\n", cps);\r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
- bp = &boards[moveNum][i][BOARD_LEFT];\r
- for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
- if ((int) *bp < (int) BlackPawn) {\r
- sprintf(message, "%c%c%c\n", PieceToChar(*bp), \r
- AAA + j, ONE + i);\r
- if(message[0] == '+' || message[0] == '~') {\r
- sprintf(message, "%c%c%c+\n",\r
- PieceToChar((ChessSquare)(DEMOTED *bp)),\r
- AAA + j, ONE + i);\r
- }\r
- if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
- message[1] = BOARD_RGHT - 1 - j + '1';\r
- message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
- }\r
- SendToProgram(message, cps);\r
- }\r
- }\r
- }\r
- \r
- SendToProgram("c\n", cps);\r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
- bp = &boards[moveNum][i][BOARD_LEFT];\r
- for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {\r
- if (((int) *bp != (int) EmptySquare)\r
- && ((int) *bp >= (int) BlackPawn)) {\r
- sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),\r
- AAA + j, ONE + i);\r
- if(message[0] == '+' || message[0] == '~') {\r
- sprintf(message, "%c%c%c+\n",\r
- PieceToChar((ChessSquare)(DEMOTED *bp)),\r
- AAA + j, ONE + i);\r
- }\r
- if(cps->alphaRank) { /* [HGM] shogi: translate coords */\r
- message[1] = BOARD_RGHT - 1 - j + '1';\r
- message[2] = BOARD_HEIGHT - 1 - i + 'a';\r
- }\r
- SendToProgram(message, cps);\r
- }\r
- }\r
- }\r
- \r
- SendToProgram(".\n", cps);\r
- }\r
- setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */\r
-}\r
-\r
-int\r
-IsPromotion(fromX, fromY, toX, toY)\r
- int fromX, fromY, toX, toY;\r
-{\r
- /* [HGM] add Shogi promotions */\r
- int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;\r
- ChessSquare piece;\r
-\r
- if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||\r
- !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;\r
- /* [HGM] Note to self: line above also weeds out drops */\r
- piece = boards[currentMove][fromY][fromX];\r
- if(gameInfo.variant == VariantShogi) {\r
- promotionZoneSize = 3;\r
- highestPromotingPiece = (int)WhiteKing;\r
- /* [HGM] Should be Silver = Ferz, really, but legality testing is off,\r
- and if in normal chess we then allow promotion to King, why not\r
- allow promotion of other piece in Shogi? */\r
- }\r
- if((int)piece >= BlackPawn) {\r
- if(toY >= promotionZoneSize && fromY >= promotionZoneSize)\r
- return FALSE;\r
- highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;\r
- } else {\r
- if( toY < BOARD_HEIGHT - promotionZoneSize &&\r
- fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;\r
- }\r
- return ( (int)piece <= highestPromotingPiece );\r
-}\r
-\r
-int\r
-InPalace(row, column)\r
- int row, column;\r
-{ /* [HGM] for Xiangqi */\r
- if( (row < 3 || row > BOARD_HEIGHT-4) &&\r
- column < (BOARD_WIDTH + 4)/2 &&\r
- column > (BOARD_WIDTH - 5)/2 ) return TRUE;\r
- return FALSE;\r
-}\r
-\r
-int\r
-PieceForSquare (x, y)\r
- int x;\r
- int y;\r
-{\r
- if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)\r
- return -1;\r
- else\r
- return boards[currentMove][y][x];\r
-}\r
-\r
-int\r
-OKToStartUserMove(x, y)\r
- int x, y;\r
-{\r
- ChessSquare from_piece;\r
- int white_piece;\r
-\r
- if (matchMode) return FALSE;\r
- if (gameMode == EditPosition) return TRUE;\r
-\r
- if (x >= 0 && y >= 0)\r
- from_piece = boards[currentMove][y][x];\r
- else\r
- from_piece = EmptySquare;\r
-\r
- if (from_piece == EmptySquare) return FALSE;\r
-\r
- white_piece = (int)from_piece >= (int)WhitePawn &&\r
- (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */\r
-\r
- switch (gameMode) {\r
- case PlayFromGameFile:\r
- case AnalyzeFile:\r
- case TwoMachinesPlay:\r
- case EndOfGame:\r
- return FALSE;\r
-\r
- case IcsObserving:\r
- case IcsIdle:\r
- return FALSE;\r
-\r
- case MachinePlaysWhite:\r
- case IcsPlayingBlack:\r
- if (appData.zippyPlay) return FALSE;\r
- if (white_piece) {\r
- DisplayMoveError(_("You are playing Black"));\r
- return FALSE;\r
- }\r
- break;\r
-\r
- case MachinePlaysBlack:\r
- case IcsPlayingWhite:\r
- if (appData.zippyPlay) return FALSE;\r
- if (!white_piece) {\r
- DisplayMoveError(_("You are playing White"));\r
- return FALSE;\r
- }\r
- break;\r
-\r
- case EditGame:\r
- if (!white_piece && WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is White's turn"));\r
- return FALSE;\r
- } \r
- if (white_piece && !WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is Black's turn"));\r
- return FALSE;\r
- } \r
- if (cmailMsgLoaded && (currentMove < cmailOldMove)) {\r
- /* Editing correspondence game history */\r
- /* Could disallow this or prompt for confirmation */\r
- cmailOldMove = -1;\r
- }\r
- if (currentMove < forwardMostMove) {\r
- /* Discarding moves */\r
- /* Could prompt for confirmation here,\r
- but I don't think that's such a good idea */\r
- forwardMostMove = currentMove;\r
- }\r
- break;\r
-\r
- case BeginningOfGame:\r
- if (appData.icsActive) return FALSE;\r
- if (!appData.noChessProgram) {\r
- if (!white_piece) {\r
- DisplayMoveError(_("You are playing White"));\r
- return FALSE;\r
- }\r
- }\r
- break;\r
- \r
- case Training:\r
- if (!white_piece && WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is White's turn"));\r
- return FALSE;\r
- } \r
- if (white_piece && !WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is Black's turn"));\r
- return FALSE;\r
- } \r
- break;\r
-\r
- default:\r
- case IcsExamining:\r
- break;\r
- }\r
- if (currentMove != forwardMostMove && gameMode != AnalyzeMode\r
- && gameMode != AnalyzeFile && gameMode != Training) {\r
- DisplayMoveError(_("Displayed position is not current"));\r
- return FALSE;\r
- }\r
- return TRUE;\r
-}\r
-\r
-FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;\r
-int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;\r
-int lastLoadGameUseList = FALSE;\r
-char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];\r
-ChessMove lastLoadGameStart = (ChessMove) 0;\r
-\r
-\r
-ChessMove\r
-UserMoveTest(fromX, fromY, toX, toY, promoChar)\r
- int fromX, fromY, toX, toY;\r
- int promoChar;\r
-{\r
- ChessMove moveType;\r
- ChessSquare pdown, pup;\r
-\r
- if (fromX < 0 || fromY < 0) return ImpossibleMove;\r
- if ((fromX == toX) && (fromY == toY)) {\r
- return ImpossibleMove;\r
- }\r
-\r
- /* [HGM] suppress all moves into holdings area and guard band */\r
- if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )\r
- return ImpossibleMove;\r
-\r
- /* [HGM] <sameColor> moved to here from winboard.c */\r
- /* note: this code seems to exist for filtering out some obviously illegal premoves */\r
- pdown = boards[currentMove][fromY][fromX];\r
- pup = boards[currentMove][toY][toX];\r
- if ( gameMode != EditPosition &&\r
- (WhitePawn <= pdown && pdown < BlackPawn &&\r
- WhitePawn <= pup && pup < BlackPawn ||\r
- BlackPawn <= pdown && pdown < EmptySquare &&\r
- BlackPawn <= pup && pup < EmptySquare \r
- ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
- (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||\r
- pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ) \r
- ) )\r
- return ImpossibleMove;\r
-\r
- /* Check if the user is playing in turn. This is complicated because we\r
- let the user "pick up" a piece before it is his turn. So the piece he\r
- tried to pick up may have been captured by the time he puts it down!\r
- Therefore we use the color the user is supposed to be playing in this\r
- test, not the color of the piece that is currently on the starting\r
- square---except in EditGame mode, where the user is playing both\r
- sides; fortunately there the capture race can't happen. (It can\r
- now happen in IcsExamining mode, but that's just too bad. The user\r
- will get a somewhat confusing message in that case.)\r
- */\r
-\r
- switch (gameMode) {\r
- case PlayFromGameFile:\r
- case AnalyzeFile:\r
- case TwoMachinesPlay:\r
- case EndOfGame:\r
- case IcsObserving:\r
- case IcsIdle:\r
- /* We switched into a game mode where moves are not accepted,\r
- perhaps while the mouse button was down. */\r
- return ImpossibleMove;\r
-\r
- case MachinePlaysWhite:\r
- /* User is moving for Black */\r
- if (WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is White's turn"));\r
- return ImpossibleMove;\r
- }\r
- break;\r
-\r
- case MachinePlaysBlack:\r
- /* User is moving for White */\r
- if (!WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is Black's turn"));\r
- return ImpossibleMove;\r
- }\r
- break;\r
-\r
- case EditGame:\r
- case IcsExamining:\r
- case BeginningOfGame:\r
- case AnalyzeMode:\r
- case Training:\r
- if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&\r
- (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {\r
- /* User is moving for Black */\r
- if (WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is White's turn"));\r
- return ImpossibleMove;\r
- }\r
- } else {\r
- /* User is moving for White */\r
- if (!WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is Black's turn"));\r
- return ImpossibleMove;\r
- }\r
- }\r
- break;\r
-\r
- case IcsPlayingBlack:\r
- /* User is moving for Black */\r
- if (WhiteOnMove(currentMove)) {\r
- if (!appData.premove) {\r
- DisplayMoveError(_("It is White's turn"));\r
- } else if (toX >= 0 && toY >= 0) {\r
- premoveToX = toX;\r
- premoveToY = toY;\r
- premoveFromX = fromX;\r
- premoveFromY = fromY;\r
- premovePromoChar = promoChar;\r
- gotPremove = 1;\r
- if (appData.debugMode) \r
- fprintf(debugFP, "Got premove: fromX %d,"\r
- "fromY %d, toX %d, toY %d\n",\r
- fromX, fromY, toX, toY);\r
- }\r
- return ImpossibleMove;\r
- }\r
- break;\r
-\r
- case IcsPlayingWhite:\r
- /* User is moving for White */\r
- if (!WhiteOnMove(currentMove)) {\r
- if (!appData.premove) {\r
- DisplayMoveError(_("It is Black's turn"));\r
- } else if (toX >= 0 && toY >= 0) {\r
- premoveToX = toX;\r
- premoveToY = toY;\r
- premoveFromX = fromX;\r
- premoveFromY = fromY;\r
- premovePromoChar = promoChar;\r
- gotPremove = 1;\r
- if (appData.debugMode) \r
- fprintf(debugFP, "Got premove: fromX %d,"\r
- "fromY %d, toX %d, toY %d\n",\r
- fromX, fromY, toX, toY);\r
- }\r
- return ImpossibleMove;\r
- }\r
- break;\r
-\r
- default:\r
- break;\r
-\r
- case EditPosition:\r
- /* EditPosition, empty square, or different color piece;\r
- click-click move is possible */\r
- if (toX == -2 || toY == -2) {\r
- boards[0][fromY][fromX] = EmptySquare;\r
- return AmbiguousMove;\r
- } else if (toX >= 0 && toY >= 0) {\r
- boards[0][toY][toX] = boards[0][fromY][fromX];\r
- boards[0][fromY][fromX] = EmptySquare;\r
- return AmbiguousMove;\r
- }\r
- return ImpossibleMove;\r
- }\r
-\r
- /* [HGM] If move started in holdings, it means a drop */\r
- if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { \r
- if( pup != EmptySquare ) return ImpossibleMove;\r
- if(appData.testLegality) {\r
- /* it would be more logical if LegalityTest() also figured out\r
- * which drops are legal. For now we forbid pawns on back rank.\r
- * Shogi is on its own here...\r
- */\r
- if( (pdown == WhitePawn || pdown == BlackPawn) &&\r
- (toY == 0 || toY == BOARD_HEIGHT -1 ) )\r
- return(ImpossibleMove); /* no pawn drops on 1st/8th */\r
- }\r
- return WhiteDrop; /* Not needed to specify white or black yet */\r
- }\r
-\r
- userOfferedDraw = FALSE;\r
- \r
- /* [HGM] always test for legality, to get promotion info */\r
- moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),\r
- epStatus[currentMove], castlingRights[currentMove],\r
- fromY, fromX, toY, toX, promoChar);\r
-\r
- /* [HGM] but possibly ignore an IllegalMove result */\r
- if (appData.testLegality) {\r
- if (moveType == IllegalMove || moveType == ImpossibleMove) {\r
- DisplayMoveError(_("Illegal move"));\r
- return ImpossibleMove;\r
- }\r
- }\r
-if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);\r
- return moveType;\r
- /* [HGM] <popupFix> in stead of calling FinishMove directly, this\r
- function is made into one that returns an OK move type if FinishMove\r
- should be called. This to give the calling driver routine the\r
- opportunity to finish the userMove input with a promotion popup,\r
- without bothering the user with this for invalid or illegal moves */\r
-\r
-/* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */\r
-}\r
-\r
-/* Common tail of UserMoveEvent and DropMenuEvent */\r
-int\r
-FinishMove(moveType, fromX, fromY, toX, toY, promoChar)\r
- ChessMove moveType;\r
- int fromX, fromY, toX, toY;\r
- /*char*/int promoChar;\r
-{\r
- char *bookHit = 0;\r
-if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);\r
- if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) { \r
- // [HGM] superchess: suppress promotions to non-available piece\r
- int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
- if(WhiteOnMove(currentMove)) {\r
- if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;\r
- } else {\r
- if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;\r
- }\r
- }\r
-\r
- /* [HGM] <popupFix> kludge to avoid having to know the exact promotion\r
- move type in caller when we know the move is a legal promotion */\r
- if(moveType == NormalMove && promoChar)\r
- moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);\r
-if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);\r
- /* [HGM] convert drag-and-drop piece drops to standard form */\r
- if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {\r
- moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
- fromX = boards[currentMove][fromY][fromX];\r
- fromY = DROP_RANK;\r
- }\r
-\r
- /* [HGM] <popupFix> The following if has been moved here from\r
- UserMoveEvent(). Because it seemed to belon here (why not allow\r
- piece drops in training games?), and because it can only be\r
- performed after it is known to what we promote. */\r
- if (gameMode == Training) {\r
- /* compare the move played on the board to the next move in the\r
- * game. If they match, display the move and the opponent's response. \r
- * If they don't match, display an error message.\r
- */\r
- int saveAnimate;\r
- Board testBoard; char testRights[BOARD_SIZE]; char testStatus;\r
- CopyBoard(testBoard, boards[currentMove]);\r
- ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);\r
-\r
- if (CompareBoards(testBoard, boards[currentMove+1])) {\r
- ForwardInner(currentMove+1);\r
-\r
- /* Autoplay the opponent's response.\r
- * if appData.animate was TRUE when Training mode was entered,\r
- * the response will be animated.\r
- */\r
- saveAnimate = appData.animate;\r
- appData.animate = animateTraining;\r
- ForwardInner(currentMove+1);\r
- appData.animate = saveAnimate;\r
-\r
- /* check for the end of the game */\r
- if (currentMove >= forwardMostMove) {\r
- gameMode = PlayFromGameFile;\r
- ModeHighlight();\r
- SetTrainingModeOff();\r
- DisplayInformation(_("End of game"));\r
- }\r
- } else {\r
- DisplayError(_("Incorrect move"), 0);\r
- }\r
- return 1;\r
- }\r
-\r
- /* Ok, now we know that the move is good, so we can kill\r
- the previous line in Analysis Mode */\r
- if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {\r
- forwardMostMove = currentMove;\r
- }\r
-\r
- /* If we need the chess program but it's dead, restart it */\r
- ResurrectChessProgram();\r
-\r
- /* A user move restarts a paused game*/\r
- if (pausing)\r
- PauseEvent();\r
-\r
- thinkOutput[0] = NULLCHAR;\r
-\r
- MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/\r
-\r
- if (gameMode == BeginningOfGame) {\r
- if (appData.noChessProgram) {\r
- gameMode = EditGame;\r
- SetGameInfo();\r
- } else {\r
- char buf[MSG_SIZ];\r
- gameMode = MachinePlaysBlack;\r
- StartClocks();\r
- SetGameInfo();\r
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
- DisplayTitle(buf);\r
- if (first.sendName) {\r
- sprintf(buf, "name %s\n", gameInfo.white);\r
- SendToProgram(buf, &first);\r
- }\r
- StartClocks();\r
- }\r
- ModeHighlight();\r
- }\r
-if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);\r
- /* Relay move to ICS or chess engine */\r
- if (appData.icsActive) {\r
- if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
- gameMode == IcsExamining) {\r
- SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
- ics_user_moved = 1;\r
- }\r
- } else {\r
- if (first.sendTime && (gameMode == BeginningOfGame ||\r
- gameMode == MachinePlaysWhite ||\r
- gameMode == MachinePlaysBlack)) {\r
- SendTimeRemaining(&first, gameMode != MachinePlaysBlack);\r
- }\r
- if (gameMode != EditGame && gameMode != PlayFromGameFile) {\r
- // [HGM] book: if program might be playing, let it use book\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);\r
- first.maybeThinking = TRUE;\r
- } else SendMoveToProgram(forwardMostMove-1, &first);\r
- if (currentMove == cmailOldMove + 1) {\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
- }\r
- }\r
-\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
-\r
- switch (gameMode) {\r
- case EditGame:\r
- switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
- EP_UNKNOWN, castlingRights[currentMove]) ) {\r
- case MT_NONE:\r
- case MT_CHECK:\r
- break;\r
- case MT_CHECKMATE:\r
- case MT_STAINMATE:\r
- if (WhiteOnMove(currentMove)) {\r
- GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
- } else {\r
- GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
- }\r
- break;\r
- case MT_STALEMATE:\r
- GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
- break;\r
- }\r
- break;\r
- \r
- case MachinePlaysBlack:\r
- case MachinePlaysWhite:\r
- /* disable certain menu options while machine is thinking */\r
- SetMachineThinkingEnables();\r
- break;\r
-\r
- default:\r
- break;\r
- }\r
-\r
- if(bookHit) { // [HGM] book: simulate book reply\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- HandleMachineMove(bookMove, &first);\r
- }\r
- return 1;\r
-}\r
-\r
-void\r
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)\r
- int fromX, fromY, toX, toY;\r
- int promoChar;\r
-{\r
- /* [HGM] This routine was added to allow calling of its two logical\r
- parts from other modules in the old way. Before, UserMoveEvent()\r
- automatically called FinishMove() if the move was OK, and returned\r
- otherwise. I separated the two, in order to make it possible to\r
- slip a promotion popup in between. But that it always needs two\r
- calls, to the first part, (now called UserMoveTest() ), and to\r
- FinishMove if the first part succeeded. Calls that do not need\r
- to do anything in between, can call this routine the old way. \r
- */\r
- ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);\r
-if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);\r
- if(moveType != ImpossibleMove)\r
- FinishMove(moveType, fromX, fromY, toX, toY, promoChar);\r
-}\r
-\r
-void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )\r
-{\r
-// char * hint = lastHint;\r
- FrontEndProgramStats stats;\r
-\r
- stats.which = cps == &first ? 0 : 1;\r
- stats.depth = cpstats->depth;\r
- stats.nodes = cpstats->nodes;\r
- stats.score = cpstats->score;\r
- stats.time = cpstats->time;\r
- stats.pv = cpstats->movelist;\r
- stats.hint = lastHint;\r
- stats.an_move_index = 0;\r
- stats.an_move_count = 0;\r
-\r
- if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {\r
- stats.hint = cpstats->move_name;\r
- stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;\r
- stats.an_move_count = cpstats->nr_moves;\r
- }\r
-\r
- SetProgramStats( &stats );\r
-}\r
-\r
-char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)\r
-{ // [HGM] book: this routine intercepts moves to simulate book replies\r
- char *bookHit = NULL;\r
-\r
- //first determine if the incoming move brings opponent into his book\r
- if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))\r
- bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move\r
- if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");\r
- if(bookHit != NULL && !cps->bookSuspend) {\r
- // make sure opponent is not going to reply after receiving move to book position\r
- SendToProgram("force\n", cps);\r
- cps->bookSuspend = TRUE; // flag indicating it has to be restarted\r
- }\r
- if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move\r
- // now arrange restart after book miss\r
- if(bookHit) {\r
- // after a book hit we never send 'go', and the code after the call to this routine\r
- // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').\r
- char buf[MSG_SIZ];\r
- if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(\r
- sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it\r
- SendToProgram(buf, cps);\r
- if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'\r
- } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine\r
- SendToProgram("go\n", cps);\r
- cps->bookSuspend = FALSE; // after a 'go' we are never suspended\r
- } else { // 'go' might be sent based on 'firstMove' after this routine returns\r
- if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return\r
- SendToProgram("go\n", cps); \r
- cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss\r
- }\r
- return bookHit; // notify caller of hit, so it can take action to send move to opponent\r
-}\r
-\r
-char *savedMessage;\r
-ChessProgramState *savedState;\r
-void DeferredBookMove(void)\r
-{\r
- if(savedState->lastPing != savedState->lastPong)\r
- ScheduleDelayedEvent(DeferredBookMove, 10);\r
- else\r
- HandleMachineMove(savedMessage, savedState);\r
-}\r
-\r
-void\r
-HandleMachineMove(message, cps)\r
- char *message;\r
- ChessProgramState *cps;\r
-{\r
- char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];\r
- char realname[MSG_SIZ];\r
- int fromX, fromY, toX, toY;\r
- ChessMove moveType;\r
- char promoChar;\r
- char *p;\r
- int machineWhite;\r
- char *bookHit;\r
-\r
-FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit\r
- /*\r
- * Kludge to ignore BEL characters\r
- */\r
- while (*message == '\007') message++;\r
-\r
- /*\r
- * [HGM] engine debug message: ignore lines starting with '#' character\r
- */\r
- if(cps->debug && *message == '#') return;\r
-\r
- /*\r
- * Look for book output\r
- */\r
- if (cps == &first && bookRequested) {\r
- if (message[0] == '\t' || message[0] == ' ') {\r
- /* Part of the book output is here; append it */\r
- strcat(bookOutput, message);\r
- strcat(bookOutput, " \n");\r
- return;\r
- } else if (bookOutput[0] != NULLCHAR) {\r
- /* All of book output has arrived; display it */\r
- char *p = bookOutput;\r
- while (*p != NULLCHAR) {\r
- if (*p == '\t') *p = ' ';\r
- p++;\r
- }\r
- DisplayInformation(bookOutput);\r
- bookRequested = FALSE;\r
- /* Fall through to parse the current output */\r
- }\r
- }\r
-\r
- /*\r
- * Look for machine move.\r
- */\r
- if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||\r
- (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0)) \r
- {\r
- /* This method is only useful on engines that support ping */\r
- if (cps->lastPing != cps->lastPong) {\r
- if (gameMode == BeginningOfGame) {\r
- /* Extra move from before last new; ignore */\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
- }\r
- } else {\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
- cps->which, gameMode);\r
- }\r
-\r
- SendToProgram("undo\n", cps);\r
- }\r
- return;\r
- }\r
-\r
- switch (gameMode) {\r
- case BeginningOfGame:\r
- /* Extra move from before last reset; ignore */\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);\r
- }\r
- return;\r
-\r
- case EndOfGame:\r
- case IcsIdle:\r
- default:\r
- /* Extra move after we tried to stop. The mode test is\r
- not a reliable way of detecting this problem, but it's\r
- the best we can do on engines that don't support ping.\r
- */\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",\r
- cps->which, gameMode);\r
- }\r
- SendToProgram("undo\n", cps);\r
- return;\r
-\r
- case MachinePlaysWhite:\r
- case IcsPlayingWhite:\r
- machineWhite = TRUE;\r
- break;\r
-\r
- case MachinePlaysBlack:\r
- case IcsPlayingBlack:\r
- machineWhite = FALSE;\r
- break;\r
-\r
- case TwoMachinesPlay:\r
- machineWhite = (cps->twoMachinesColor[0] == 'w');\r
- break;\r
- }\r
- if (WhiteOnMove(forwardMostMove) != machineWhite) {\r
- if (appData.debugMode) {\r
- fprintf(debugFP,\r
- "Ignoring move out of turn by %s, gameMode %d"\r
- ", forwardMost %d\n",\r
- cps->which, gameMode, forwardMostMove);\r
- }\r
- return;\r
- }\r
-\r
- if (appData.debugMode) { int f = forwardMostMove;\r
- fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,\r
- castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);\r
- }\r
- if(cps->alphaRank) AlphaRank(machineMove, 4);\r
- if (!ParseOneMove(machineMove, forwardMostMove, &moveType,\r
- &fromX, &fromY, &toX, &toY, &promoChar)) {\r
- /* Machine move could not be parsed; ignore it. */\r
- sprintf(buf1, _("Illegal move \"%s\" from %s machine"),\r
- machineMove, cps->which);\r
- DisplayError(buf1, 0);\r
- sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",\r
- machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);\r
- if (gameMode == TwoMachinesPlay) {\r
- GameEnds(machineWhite ? BlackWins : WhiteWins,\r
- buf1, GE_XBOARD);\r
- }\r
- return;\r
- }\r
-\r
- /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */\r
- /* So we have to redo legality test with true e.p. status here, */\r
- /* to make sure an illegal e.p. capture does not slip through, */\r
- /* to cause a forfeit on a justified illegal-move complaint */\r
- /* of the opponent. */\r
- if( gameMode==TwoMachinesPlay && appData.testLegality\r
- && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */\r
- ) {\r
- ChessMove moveType;\r
- moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
- epStatus[forwardMostMove], castlingRights[forwardMostMove],\r
- fromY, fromX, toY, toX, promoChar);\r
- if (appData.debugMode) {\r
- int i;\r
- for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",\r
- castlingRights[forwardMostMove][i], castlingRank[i]);\r
- fprintf(debugFP, "castling rights\n");\r
- }\r
- if(moveType == IllegalMove) {\r
- sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",\r
- machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);\r
- GameEnds(machineWhite ? BlackWins : WhiteWins,\r
- buf1, GE_XBOARD);\r
- return;\r
- } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)\r
- /* [HGM] Kludge to handle engines that send FRC-style castling\r
- when they shouldn't (like TSCP-Gothic) */\r
- switch(moveType) {\r
- case WhiteASideCastleFR:\r
- case BlackASideCastleFR:\r
- toX+=2;\r
- currentMoveString[2]++;\r
- break;\r
- case WhiteHSideCastleFR:\r
- case BlackHSideCastleFR:\r
- toX--;\r
- currentMoveString[2]--;\r
- break;\r
- default: ; // nothing to do, but suppresses warning of pedantic compilers\r
- }\r
- }\r
- hintRequested = FALSE;\r
- lastHint[0] = NULLCHAR;\r
- bookRequested = FALSE;\r
- /* Program may be pondering now */\r
- cps->maybeThinking = TRUE;\r
- if (cps->sendTime == 2) cps->sendTime = 1;\r
- if (cps->offeredDraw) cps->offeredDraw--;\r
-\r
-#if ZIPPY\r
- if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&\r
- first.initDone) {\r
- SendMoveToICS(moveType, fromX, fromY, toX, toY);\r
- ics_user_moved = 1;\r
- if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */\r
- char buf[3*MSG_SIZ];\r
-\r
- sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",\r
- programStats.score / 100.,\r
- programStats.depth,\r
- programStats.time / 100.,\r
- u64ToDouble(programStats.nodes),\r
- u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),\r
- programStats.movelist);\r
- SendToICS(buf);\r
- }\r
- }\r
-#endif\r
- /* currentMoveString is set as a side-effect of ParseOneMove */\r
- strcpy(machineMove, currentMoveString);\r
- strcat(machineMove, "\n");\r
- strcpy(moveList[forwardMostMove], machineMove);\r
-\r
- /* [AS] Save move info and clear stats for next move */\r
- pvInfoList[ forwardMostMove ].score = programStats.score;\r
- pvInfoList[ forwardMostMove ].depth = programStats.depth;\r
- pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats\r
- ClearProgramStats();\r
- thinkOutput[0] = NULLCHAR;\r
- hiddenThinkOutputState = 0;\r
-\r
- MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/\r
-\r
- /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */\r
- if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {\r
- int count = 0;\r
-\r
- while( count < adjudicateLossPlies ) {\r
- int score = pvInfoList[ forwardMostMove - count - 1 ].score;\r
-\r
- if( count & 1 ) {\r
- score = -score; /* Flip score for winning side */\r
- }\r
-\r
- if( score > adjudicateLossThreshold ) {\r
- break;\r
- }\r
-\r
- count++;\r
- }\r
-\r
- if( count >= adjudicateLossPlies ) {\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
-\r
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
- "Xboard adjudication", \r
- GE_XBOARD );\r
-\r
- return;\r
- }\r
- }\r
-\r
- if( gameMode == TwoMachinesPlay ) {\r
- // [HGM] some adjudications useful with buggy engines\r
- int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;\r
- if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {\r
-\r
-\r
- if( appData.testLegality )\r
- { /* [HGM] Some more adjudications for obstinate engines */\r
- int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,\r
- NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,\r
- NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;\r
- static int moveCount = 6;\r
- ChessMove result;\r
- char *reason = NULL;\r
-\r
- /* Count what is on board. */\r
- for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)\r
- { ChessSquare p = boards[forwardMostMove][i][j];\r
- int m=i;\r
-\r
- switch((int) p)\r
- { /* count B,N,R and other of each side */\r
- case WhiteKing:\r
- case BlackKing:\r
- NrK++; break; // [HGM] atomic: count Kings\r
- case WhiteKnight:\r
- NrWN++; break;\r
- case WhiteBishop:\r
- case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj\r
- bishopsColor |= 1 << ((i^j)&1);\r
- NrWB++; break;\r
- case BlackKnight:\r
- NrBN++; break;\r
- case BlackBishop:\r
- case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj\r
- bishopsColor |= 1 << ((i^j)&1);\r
- NrBB++; break;\r
- case WhiteRook:\r
- NrWR++; break;\r
- case BlackRook:\r
- NrBR++; break;\r
- case WhiteQueen:\r
- NrWQ++; break;\r
- case BlackQueen:\r
- NrBQ++; break;\r
- case EmptySquare: \r
- break;\r
- case BlackPawn:\r
- m = 7-i;\r
- case WhitePawn:\r
- PawnAdvance += m; NrPawns++;\r
- }\r
- NrPieces += (p != EmptySquare);\r
- NrW += ((int)p < (int)BlackPawn);\r
- if(gameInfo.variant == VariantXiangqi && \r
- (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {\r
- NrPieces--; // [HGM] XQ: do not count purely defensive pieces\r
- NrW -= ((int)p < (int)BlackPawn);\r
- }\r
- }\r
-\r
- /* Some material-based adjudications that have to be made before stalemate test */\r
- if(gameInfo.variant == VariantAtomic && NrK < 2) {\r
- // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal\r
- epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated\r
- if(appData.checkMates) {\r
- SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins, \r
- "Xboard adjudication: King destroyed", GE_XBOARD );\r
- return;\r
- }\r
- }\r
-\r
- /* Bare King in Shatranj (loses) or Losers (wins) */\r
- if( NrW == 1 || NrPieces - NrW == 1) {\r
- if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)\r
- epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable\r
- if(appData.checkMates) {\r
- SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
- "Xboard adjudication: Bare king", GE_XBOARD );\r
- return;\r
- }\r
- } else\r
- if( gameInfo.variant == VariantShatranj && --bare < 0)\r
- { /* bare King */\r
- epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm\r
- if(appData.checkMates) {\r
- /* but only adjudicate if adjudication enabled */\r
- SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn, \r
- "Xboard adjudication: Bare king", GE_XBOARD );\r
- return;\r
- }\r
- }\r
- } else bare = 1;\r
-\r
-\r
- // don't wait for engine to announce game end if we can judge ourselves\r
- switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,\r
- castlingRights[forwardMostMove]) ) {\r
- case MT_CHECK:\r
- if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time\r
- int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)\r
- for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {\r
- if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)\r
- checkCnt++;\r
- if(checkCnt >= 2) {\r
- reason = "Xboard adjudication: 3rd check";\r
- epStatus[forwardMostMove] = EP_CHECKMATE;\r
- break;\r
- }\r
- }\r
- }\r
- case MT_NONE:\r
- default:\r
- break;\r
- case MT_STALEMATE:\r
- case MT_STAINMATE:\r
- reason = "Xboard adjudication: Stalemate";\r
- if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt\r
- epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw\r
- if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:\r
- epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win\r
- else if(gameInfo.variant == VariantSuicide) // in suicide it depends\r
- epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :\r
- ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?\r
- EP_CHECKMATE : EP_WINS);\r
- else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)\r
- epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses\r
- }\r
- break;\r
- case MT_CHECKMATE:\r
- reason = "Xboard adjudication: Checkmate";\r
- epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);\r
- break;\r
- }\r
-\r
- switch(i = epStatus[forwardMostMove]) {\r
- case EP_STALEMATE:\r
- result = GameIsDrawn; break;\r
- case EP_CHECKMATE:\r
- result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;\r
- case EP_WINS:\r
- result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;\r
- default:\r
- result = (ChessMove) 0;\r
- }\r
- if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( result, reason, GE_XBOARD );\r
- return;\r
- }\r
-\r
- /* Next absolutely insufficient mating material. */\r
- if( NrPieces == 2 || gameInfo.variant != VariantXiangqi && \r
- gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible\r
- (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||\r
- NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color\r
- { /* KBK, KNK, KK of KBKB with like Bishops */\r
-\r
- /* always flag draws, for judging claims */\r
- epStatus[forwardMostMove] = EP_INSUF_DRAW;\r
-\r
- if(appData.materialDraws) {\r
- /* but only adjudicate them if adjudication enabled */\r
- SendToProgram("force\n", cps->other); // suppress reply\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );\r
- return;\r
- }\r
- }\r
-\r
- /* Then some trivial draws (only adjudicate, cannot be claimed) */\r
- if(NrPieces == 4 && \r
- ( NrWR == 1 && NrBR == 1 /* KRKR */\r
- || NrWQ==1 && NrBQ==1 /* KQKQ */\r
- || NrWN==2 || NrBN==2 /* KNNK */\r
- || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */\r
- ) ) {\r
- if(--moveCount < 0 && appData.trivialDraws)\r
- { /* if the first 3 moves do not show a tactical win, declare draw */\r
- SendToProgram("force\n", cps->other); // suppress reply\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );\r
- return;\r
- }\r
- } else moveCount = 6;\r
- }\r
- }\r
-#if 1\r
- if (appData.debugMode) { int i;\r
- fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",\r
- forwardMostMove, backwardMostMove, epStatus[backwardMostMove],\r
- appData.drawRepeats);\r
- for( i=forwardMostMove; i>=backwardMostMove; i-- )\r
- fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);\r
-\r
- }\r
-#endif\r
- /* Check for rep-draws */\r
- count = 0;\r
- for(k = forwardMostMove-2;\r
- k>=backwardMostMove && k>=forwardMostMove-100 &&\r
- epStatus[k] < EP_UNKNOWN &&\r
- epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;\r
- k-=2)\r
- { int rights=0;\r
-#if 0\r
- if (appData.debugMode) {\r
- fprintf(debugFP, " loop\n");\r
- }\r
-#endif\r
- if(CompareBoards(boards[k], boards[forwardMostMove])) {\r
-#if 0\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "match\n");\r
- }\r
-#endif\r
- /* compare castling rights */\r
- if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&\r
- (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )\r
- rights++; /* King lost rights, while rook still had them */\r
- if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */\r
- if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||\r
- castlingRights[forwardMostMove][1] != castlingRights[k][1] )\r
- rights++; /* but at least one rook lost them */\r
- }\r
- if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&\r
- (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )\r
- rights++; \r
- if( castlingRights[forwardMostMove][5] >= 0 ) {\r
- if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||\r
- castlingRights[forwardMostMove][4] != castlingRights[k][4] )\r
- rights++;\r
- }\r
-#if 0\r
- if (appData.debugMode) {\r
- for(i=0; i<nrCastlingRights; i++)\r
- fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);\r
- }\r
-\r
- if (appData.debugMode) {\r
- fprintf(debugFP, " %d %d\n", rights, k);\r
- }\r
-#endif\r
- if( rights == 0 && ++count > appData.drawRepeats-2\r
- && appData.drawRepeats > 1) {\r
- /* adjudicate after user-specified nr of repeats */\r
- SendToProgram("force\n", cps->other); // suppress reply\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- if(gameInfo.variant == VariantXiangqi && appData.testLegality) { \r
- // [HGM] xiangqi: check for forbidden perpetuals\r
- int m, ourPerpetual = 1, hisPerpetual = 1;\r
- for(m=forwardMostMove; m>k; m-=2) {\r
- if(MateTest(boards[m], PosFlags(m), \r
- EP_NONE, castlingRights[m]) != MT_CHECK)\r
- ourPerpetual = 0; // the current mover did not always check\r
- if(MateTest(boards[m-1], PosFlags(m-1), \r
- EP_NONE, castlingRights[m-1]) != MT_CHECK)\r
- hisPerpetual = 0; // the opponent did not always check\r
- }\r
- if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",\r
- ourPerpetual, hisPerpetual);\r
- if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit\r
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
- "Xboard adjudication: perpetual checking", GE_XBOARD );\r
- return;\r
- }\r
- if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet\r
- break; // (or we would have caught him before). Abort repetition-checking loop.\r
- // Now check for perpetual chases\r
- if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase\r
- hisPerpetual = PerpetualChase(k, forwardMostMove);\r
- ourPerpetual = PerpetualChase(k+1, forwardMostMove);\r
- if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit\r
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins, \r
- "Xboard adjudication: perpetual chasing", GE_XBOARD );\r
- return;\r
- }\r
- if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet\r
- break; // Abort repetition-checking loop.\r
- }\r
- // if neither of us is checking or chasing all the time, or both are, it is draw\r
- }\r
- GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );\r
- return;\r
- }\r
- if( rights == 0 && count > 1 ) /* occurred 2 or more times before */\r
- epStatus[forwardMostMove] = EP_REP_DRAW;\r
- }\r
- }\r
-\r
- /* Now we test for 50-move draws. Determine ply count */\r
- count = forwardMostMove;\r
- /* look for last irreversble move */\r
- while( epStatus[count] <= EP_NONE && count > backwardMostMove )\r
- count--;\r
- /* if we hit starting position, add initial plies */\r
- if( count == backwardMostMove )\r
- count -= initialRulePlies;\r
- count = forwardMostMove - count; \r
- if( count >= 100)\r
- epStatus[forwardMostMove] = EP_RULE_DRAW;\r
- /* this is used to judge if draw claims are legal */\r
- if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {\r
- SendToProgram("force\n", cps->other); // suppress reply\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );\r
- return;\r
- }\r
-\r
- /* if draw offer is pending, treat it as a draw claim\r
- * when draw condition present, to allow engines a way to\r
- * claim draws before making their move to avoid a race\r
- * condition occurring after their move\r
- */\r
- if( cps->other->offeredDraw || cps->offeredDraw ) {\r
- char *p = NULL;\r
- if(epStatus[forwardMostMove] == EP_RULE_DRAW)\r
- p = "Draw claim: 50-move rule";\r
- if(epStatus[forwardMostMove] == EP_REP_DRAW)\r
- p = "Draw claim: 3-fold repetition";\r
- if(epStatus[forwardMostMove] == EP_INSUF_DRAW)\r
- p = "Draw claim: insufficient mating material";\r
- if( p != NULL ) {\r
- SendToProgram("force\n", cps->other); // suppress reply\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
- GameEnds( GameIsDrawn, p, GE_XBOARD );\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- return;\r
- }\r
- }\r
-\r
-\r
- if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {\r
- SendToProgram("force\n", cps->other); // suppress reply\r
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
-\r
- GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );\r
-\r
- return;\r
- }\r
- }\r
-\r
- bookHit = NULL;\r
- if (gameMode == TwoMachinesPlay) {\r
- /* [HGM] relaying draw offers moved to after reception of move */\r
- /* and interpreting offer as claim if it brings draw condition */\r
- if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {\r
- SendToProgram("draw\n", cps->other);\r
- }\r
- if (cps->other->sendTime) {\r
- SendTimeRemaining(cps->other,\r
- cps->other->twoMachinesColor[0] == 'w');\r
- }\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);\r
- if (firstMove && !bookHit) {\r
- firstMove = FALSE;\r
- if (cps->other->useColors) {\r
- SendToProgram(cps->other->twoMachinesColor, cps->other);\r
- }\r
- SendToProgram("go\n", cps->other);\r
- }\r
- cps->other->maybeThinking = TRUE;\r
- }\r
-\r
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/\r
- \r
- if (!pausing && appData.ringBellAfterMoves) {\r
- RingBell();\r
- }\r
-\r
- /* \r
- * Reenable menu items that were disabled while\r
- * machine was thinking\r
- */\r
- if (gameMode != TwoMachinesPlay)\r
- SetUserThinkingEnables();\r
-\r
- // [HGM] book: after book hit opponent has received move and is now in force mode\r
- // force the book reply into it, and then fake that it outputted this move by jumping\r
- // back to the beginning of HandleMachineMove, with cps toggled and message set to this move\r
- if(bookHit) {\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- message = bookMove;\r
- cps = cps->other;\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- if(cps->lastPing != cps->lastPong) {\r
- savedMessage = message; // args for deferred call\r
- savedState = cps;\r
- ScheduleDelayedEvent(DeferredBookMove, 10);\r
- return;\r
- }\r
- goto FakeBookMove;\r
- }\r
-\r
- return;\r
- }\r
-\r
- /* Set special modes for chess engines. Later something general\r
- * could be added here; for now there is just one kludge feature,\r
- * needed because Crafty 15.10 and earlier don't ignore SIGINT\r
- * when "xboard" is given as an interactive command.\r
- */\r
- if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {\r
- cps->useSigint = FALSE;\r
- cps->useSigterm = FALSE;\r
- }\r
-\r
- /* [HGM] Allow engine to set up a position. Don't ask me why one would\r
- * want this, I was asked to put it in, and obliged.\r
- */\r
- if (!strncmp(message, "setboard ", 9)) {\r
- Board initial_position; int i;\r
-\r
- GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);\r
-\r
- if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {\r
- DisplayError(_("Bad FEN received from engine"), 0);\r
- return ;\r
- } else {\r
- Reset(FALSE, FALSE);\r
- CopyBoard(boards[0], initial_position);\r
- initialRulePlies = FENrulePlies;\r
- epStatus[0] = FENepStatus;\r
- for( i=0; i<nrCastlingRights; i++ )\r
- castlingRights[0][i] = FENcastlingRights[i];\r
- if(blackPlaysFirst) gameMode = MachinePlaysWhite;\r
- else gameMode = MachinePlaysBlack; \r
- DrawPosition(FALSE, boards[currentMove]);\r
- }\r
- return;\r
- }\r
-\r
- /*\r
- * Look for communication commands\r
- */\r
- if (!strncmp(message, "telluser ", 9)) {\r
- DisplayNote(message + 9);\r
- return;\r
- }\r
- if (!strncmp(message, "tellusererror ", 14)) {\r
- DisplayError(message + 14, 0);\r
- return;\r
- }\r
- if (!strncmp(message, "tellopponent ", 13)) {\r
- if (appData.icsActive) {\r
- if (loggedOn) {\r
- snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);\r
- SendToICS(buf1);\r
- }\r
- } else {\r
- DisplayNote(message + 13);\r
- }\r
- return;\r
- }\r
- if (!strncmp(message, "tellothers ", 11)) {\r
- if (appData.icsActive) {\r
- if (loggedOn) {\r
- snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);\r
- SendToICS(buf1);\r
- }\r
- }\r
- return;\r
- }\r
- if (!strncmp(message, "tellall ", 8)) {\r
- if (appData.icsActive) {\r
- if (loggedOn) {\r
- snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);\r
- SendToICS(buf1);\r
- }\r
- } else {\r
- DisplayNote(message + 8);\r
- }\r
- return;\r
- }\r
- if (strncmp(message, "warning", 7) == 0) {\r
- /* Undocumented feature, use tellusererror in new code */\r
- DisplayError(message, 0);\r
- return;\r
- }\r
- if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {\r
- strcpy(realname, cps->tidy);\r
- strcat(realname, " query");\r
- AskQuestion(realname, buf2, buf1, cps->pr);\r
- return;\r
- }\r
- /* Commands from the engine directly to ICS. We don't allow these to be \r
- * sent until we are logged on. Crafty kibitzes have been known to \r
- * interfere with the login process.\r
- */\r
- if (loggedOn) {\r
- if (!strncmp(message, "tellics ", 8)) {\r
- SendToICS(message + 8);\r
- SendToICS("\n");\r
- return;\r
- }\r
- if (!strncmp(message, "tellicsnoalias ", 15)) {\r
- SendToICS(ics_prefix);\r
- SendToICS(message + 15);\r
- SendToICS("\n");\r
- return;\r
- }\r
- /* The following are for backward compatibility only */\r
- if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||\r
- !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {\r
- SendToICS(ics_prefix);\r
- SendToICS(message);\r
- SendToICS("\n");\r
- return;\r
- }\r
- }\r
- if (strncmp(message, "feature ", 8) == 0) {\r
- ParseFeatures(message+8, cps);\r
- }\r
- if (sscanf(message, "pong %d", &cps->lastPong) == 1) {\r
- return;\r
- }\r
- /*\r
- * If the move is illegal, cancel it and redraw the board.\r
- * Also deal with other error cases. Matching is rather loose\r
- * here to accommodate engines written before the spec.\r
- */\r
- if (strncmp(message + 1, "llegal move", 11) == 0 ||\r
- strncmp(message, "Error", 5) == 0) {\r
- if (StrStr(message, "name") || \r
- StrStr(message, "rating") || StrStr(message, "?") ||\r
- StrStr(message, "result") || StrStr(message, "board") ||\r
- StrStr(message, "bk") || StrStr(message, "computer") ||\r
- StrStr(message, "variant") || StrStr(message, "hint") ||\r
- StrStr(message, "random") || StrStr(message, "depth") ||\r
- StrStr(message, "accepted")) {\r
- return;\r
- }\r
- if (StrStr(message, "protover")) {\r
- /* Program is responding to input, so it's apparently done\r
- initializing, and this error message indicates it is\r
- protocol version 1. So we don't need to wait any longer\r
- for it to initialize and send feature commands. */\r
- FeatureDone(cps, 1);\r
- cps->protocolVersion = 1;\r
- return;\r
- }\r
- cps->maybeThinking = FALSE;\r
-\r
- if (StrStr(message, "draw")) {\r
- /* Program doesn't have "draw" command */\r
- cps->sendDrawOffers = 0;\r
- return;\r
- }\r
- if (cps->sendTime != 1 &&\r
- (StrStr(message, "time") || StrStr(message, "otim"))) {\r
- /* Program apparently doesn't have "time" or "otim" command */\r
- cps->sendTime = 0;\r
- return;\r
- }\r
- if (StrStr(message, "analyze")) {\r
- cps->analysisSupport = FALSE;\r
- cps->analyzing = FALSE;\r
- Reset(FALSE, TRUE);\r
- sprintf(buf2, _("%s does not support analysis"), cps->tidy);\r
- DisplayError(buf2, 0);\r
- return;\r
- }\r
- if (StrStr(message, "(no matching move)st")) {\r
- /* Special kludge for GNU Chess 4 only */\r
- cps->stKludge = TRUE;\r
- SendTimeControl(cps, movesPerSession, timeControl,\r
- timeIncrement, appData.searchDepth,\r
- searchTime);\r
- return;\r
- }\r
- if (StrStr(message, "(no matching move)sd")) {\r
- /* Special kludge for GNU Chess 4 only */\r
- cps->sdKludge = TRUE;\r
- SendTimeControl(cps, movesPerSession, timeControl,\r
- timeIncrement, appData.searchDepth,\r
- searchTime);\r
- return;\r
- }\r
- if (!StrStr(message, "llegal")) {\r
- return;\r
- }\r
- if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
- gameMode == IcsIdle) return;\r
- if (forwardMostMove <= backwardMostMove) return;\r
-#if 0\r
- /* Following removed: it caused a bug where a real illegal move\r
- message in analyze mored would be ignored. */\r
- if (cps == &first && programStats.ok_to_send == 0) {\r
- /* Bogus message from Crafty responding to "." This filtering\r
- can miss some of the bad messages, but fortunately the bug \r
- is fixed in current Crafty versions, so it doesn't matter. */\r
- return;\r
- }\r
-#endif\r
- if (pausing) PauseEvent();\r
- if (gameMode == PlayFromGameFile) {\r
- /* Stop reading this game file */\r
- gameMode = EditGame;\r
- ModeHighlight();\r
- }\r
- currentMove = --forwardMostMove;\r
- DisplayMove(currentMove-1); /* before DisplayMoveError */\r
- SwitchClocks();\r
- DisplayBothClocks();\r
- sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),\r
- parseList[currentMove], cps->which);\r
- DisplayMoveError(buf1);\r
- DrawPosition(FALSE, boards[currentMove]);\r
-\r
- /* [HGM] illegal-move claim should forfeit game when Xboard */\r
- /* only passes fully legal moves */\r
- if( appData.testLegality && gameMode == TwoMachinesPlay ) {\r
- GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,\r
- "False illegal-move claim", GE_XBOARD );\r
- }\r
- return;\r
- }\r
- if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {\r
- /* Program has a broken "time" command that\r
- outputs a string not ending in newline.\r
- Don't use it. */\r
- cps->sendTime = 0;\r
- }\r
- \r
- /*\r
- * If chess program startup fails, exit with an error message.\r
- * Attempts to recover here are futile.\r
- */\r
- if ((StrStr(message, "unknown host") != NULL)\r
- || (StrStr(message, "No remote directory") != NULL)\r
- || (StrStr(message, "not found") != NULL)\r
- || (StrStr(message, "No such file") != NULL)\r
- || (StrStr(message, "can't alloc") != NULL)\r
- || (StrStr(message, "Permission denied") != NULL)) {\r
-\r
- cps->maybeThinking = FALSE;\r
- snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),\r
- cps->which, cps->program, cps->host, message);\r
- RemoveInputSource(cps->isr);\r
- DisplayFatalError(buf1, 0, 1);\r
- return;\r
- }\r
- \r
- /* \r
- * Look for hint output\r
- */\r
- if (sscanf(message, "Hint: %s", buf1) == 1) {\r
- if (cps == &first && hintRequested) {\r
- hintRequested = FALSE;\r
- if (ParseOneMove(buf1, forwardMostMove, &moveType,\r
- &fromX, &fromY, &toX, &toY, &promoChar)) {\r
- (void) CoordsToAlgebraic(boards[forwardMostMove],\r
- PosFlags(forwardMostMove), EP_UNKNOWN,\r
- fromY, fromX, toY, toX, promoChar, buf1);\r
- snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);\r
- DisplayInformation(buf2);\r
- } else {\r
- /* Hint move could not be parsed!? */\r
- snprintf(buf2, sizeof(buf2),\r
- _("Illegal hint move \"%s\"\nfrom %s chess program"),\r
- buf1, cps->which);\r
- DisplayError(buf2, 0);\r
- }\r
- } else {\r
- strcpy(lastHint, buf1);\r
- }\r
- return;\r
- }\r
-\r
- /*\r
- * Ignore other messages if game is not in progress\r
- */\r
- if (gameMode == BeginningOfGame || gameMode == EndOfGame ||\r
- gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;\r
-\r
- /*\r
- * look for win, lose, draw, or draw offer\r
- */\r
- if (strncmp(message, "1-0", 3) == 0) {\r
- char *p, *q, *r = "";\r
- p = strchr(message, '{');\r
- if (p) {\r
- q = strchr(p, '}');\r
- if (q) {\r
- *q = NULLCHAR;\r
- r = p + 1;\r
- }\r
- }\r
- GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */\r
- return;\r
- } else if (strncmp(message, "0-1", 3) == 0) {\r
- char *p, *q, *r = "";\r
- p = strchr(message, '{');\r
- if (p) {\r
- q = strchr(p, '}');\r
- if (q) {\r
- *q = NULLCHAR;\r
- r = p + 1;\r
- }\r
- }\r
- /* Kludge for Arasan 4.1 bug */\r
- if (strcmp(r, "Black resigns") == 0) {\r
- GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));\r
- return;\r
- }\r
- GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));\r
- return;\r
- } else if (strncmp(message, "1/2", 3) == 0) {\r
- char *p, *q, *r = "";\r
- p = strchr(message, '{');\r
- if (p) {\r
- q = strchr(p, '}');\r
- if (q) {\r
- *q = NULLCHAR;\r
- r = p + 1;\r
- }\r
- }\r
- \r
- GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));\r
- return;\r
-\r
- } else if (strncmp(message, "White resign", 12) == 0) {\r
- GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
- return;\r
- } else if (strncmp(message, "Black resign", 12) == 0) {\r
- GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
- return;\r
- } else if (strncmp(message, "White matches", 13) == 0 ||\r
- strncmp(message, "Black matches", 13) == 0 ) {\r
- /* [HGM] ignore GNUShogi noises */\r
- return;\r
- } else if (strncmp(message, "White", 5) == 0 &&\r
- message[5] != '(' &&\r
- StrStr(message, "Black") == NULL) {\r
- GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
- return;\r
- } else if (strncmp(message, "Black", 5) == 0 &&\r
- message[5] != '(') {\r
- GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
- return;\r
- } else if (strcmp(message, "resign") == 0 ||\r
- strcmp(message, "computer resigns") == 0) {\r
- switch (gameMode) {\r
- case MachinePlaysBlack:\r
- case IcsPlayingBlack:\r
- GameEnds(WhiteWins, "Black resigns", GE_ENGINE);\r
- break;\r
- case MachinePlaysWhite:\r
- case IcsPlayingWhite:\r
- GameEnds(BlackWins, "White resigns", GE_ENGINE);\r
- break;\r
- case TwoMachinesPlay:\r
- if (cps->twoMachinesColor[0] == 'w')\r
- GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));\r
- else\r
- GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));\r
- break;\r
- default:\r
- /* can't happen */\r
- break;\r
- }\r
- return;\r
- } else if (strncmp(message, "opponent mates", 14) == 0) {\r
- switch (gameMode) {\r
- case MachinePlaysBlack:\r
- case IcsPlayingBlack:\r
- GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
- break;\r
- case MachinePlaysWhite:\r
- case IcsPlayingWhite:\r
- GameEnds(BlackWins, "Black mates", GE_ENGINE);\r
- break;\r
- case TwoMachinesPlay:\r
- if (cps->twoMachinesColor[0] == 'w')\r
- GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
- else\r
- GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
- break;\r
- default:\r
- /* can't happen */\r
- break;\r
- }\r
- return;\r
- } else if (strncmp(message, "computer mates", 14) == 0) {\r
- switch (gameMode) {\r
- case MachinePlaysBlack:\r
- case IcsPlayingBlack:\r
- GameEnds(BlackWins, "Black mates", GE_ENGINE1);\r
- break;\r
- case MachinePlaysWhite:\r
- case IcsPlayingWhite:\r
- GameEnds(WhiteWins, "White mates", GE_ENGINE);\r
- break;\r
- case TwoMachinesPlay:\r
- if (cps->twoMachinesColor[0] == 'w')\r
- GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
- else\r
- GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
- break;\r
- default:\r
- /* can't happen */\r
- break;\r
- }\r
- return;\r
- } else if (strncmp(message, "checkmate", 9) == 0) {\r
- if (WhiteOnMove(forwardMostMove)) {\r
- GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));\r
- } else {\r
- GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));\r
- }\r
- return;\r
- } else if (strstr(message, "Draw") != NULL ||\r
- strstr(message, "game is a draw") != NULL) {\r
- GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));\r
- return;\r
- } else if (strstr(message, "offer") != NULL &&\r
- strstr(message, "draw") != NULL) {\r
-#if ZIPPY\r
- if (appData.zippyPlay && first.initDone) {\r
- /* Relay offer to ICS */\r
- SendToICS(ics_prefix);\r
- SendToICS("draw\n");\r
- }\r
-#endif\r
- cps->offeredDraw = 2; /* valid until this engine moves twice */\r
- if (gameMode == TwoMachinesPlay) {\r
- if (cps->other->offeredDraw) {\r
- GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
- /* [HGM] in two-machine mode we delay relaying draw offer */\r
- /* until after we also have move, to see if it is really claim */\r
- }\r
-#if 0\r
- else {\r
- if (cps->other->sendDrawOffers) {\r
- SendToProgram("draw\n", cps->other);\r
- }\r
- }\r
-#endif\r
- } else if (gameMode == MachinePlaysWhite ||\r
- gameMode == MachinePlaysBlack) {\r
- if (userOfferedDraw) {\r
- DisplayInformation(_("Machine accepts your draw offer"));\r
- GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
- } else {\r
- DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));\r
- }\r
- }\r
- }\r
-\r
- \r
- /*\r
- * Look for thinking output\r
- */\r
- if ( appData.showThinking // [HGM] thinking: test all options that cause this output\r
- || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
- ) {\r
- int plylev, mvleft, mvtot, curscore, time;\r
- char mvname[MOVE_LEN];\r
- u64 nodes; // [DM]\r
- char plyext;\r
- int ignore = FALSE;\r
- int prefixHint = FALSE;\r
- mvname[0] = NULLCHAR;\r
-\r
- switch (gameMode) {\r
- case MachinePlaysBlack:\r
- case IcsPlayingBlack:\r
- if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
- break;\r
- case MachinePlaysWhite:\r
- case IcsPlayingWhite:\r
- if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;\r
- break;\r
- case AnalyzeMode:\r
- case AnalyzeFile:\r
- break;\r
- case IcsObserving: /* [DM] icsEngineAnalyze */\r
- if (!appData.icsEngineAnalyze) ignore = TRUE;\r
- break;\r
- case TwoMachinesPlay:\r
- if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {\r
- ignore = TRUE;\r
- }\r
- break;\r
- default:\r
- ignore = TRUE;\r
- break;\r
- }\r
-\r
- if (!ignore) {\r
- buf1[0] = NULLCHAR;\r
- if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
- &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {\r
-\r
- if (plyext != ' ' && plyext != '\t') {\r
- time *= 100;\r
- }\r
-\r
- /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
- if( cps->scoreIsAbsolute && \r
- ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )\r
- {\r
- curscore = -curscore;\r
- }\r
-\r
-\r
- programStats.depth = plylev;\r
- programStats.nodes = nodes;\r
- programStats.time = time;\r
- programStats.score = curscore;\r
- programStats.got_only_move = 0;\r
-\r
- if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */\r
- int ticklen;\r
-\r
- if(cps->nps == 0) ticklen = 10*time; // use engine reported time\r
- else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time\r
- if(WhiteOnMove(forwardMostMove)) \r
- whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;\r
- else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;\r
- }\r
-\r
- /* Buffer overflow protection */\r
- if (buf1[0] != NULLCHAR) {\r
- if (strlen(buf1) >= sizeof(programStats.movelist)\r
- && appData.debugMode) {\r
- fprintf(debugFP,\r
- "PV is too long; using the first %d bytes.\n",\r
- sizeof(programStats.movelist) - 1);\r
- }\r
-\r
- safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );\r
- } else {\r
- sprintf(programStats.movelist, " no PV\n");\r
- }\r
-\r
- if (programStats.seen_stat) {\r
- programStats.ok_to_send = 1;\r
- }\r
-\r
- if (strchr(programStats.movelist, '(') != NULL) {\r
- programStats.line_is_book = 1;\r
- programStats.nr_moves = 0;\r
- programStats.moves_left = 0;\r
- } else {\r
- programStats.line_is_book = 0;\r
- }\r
-\r
- SendProgramStatsToFrontend( cps, &programStats );\r
-\r
- /* \r
- [AS] Protect the thinkOutput buffer from overflow... this\r
- is only useful if buf1 hasn't overflowed first!\r
- */\r
- sprintf(thinkOutput, "[%d]%c%+.2f %s%s",\r
- plylev, \r
- (gameMode == TwoMachinesPlay ?\r
- ToUpper(cps->twoMachinesColor[0]) : ' '),\r
- ((double) curscore) / 100.0,\r
- prefixHint ? lastHint : "",\r
- prefixHint ? " " : "" );\r
-\r
- if( buf1[0] != NULLCHAR ) {\r
- unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;\r
-\r
- if( strlen(buf1) > max_len ) {\r
- if( appData.debugMode) {\r
- fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");\r
- }\r
- buf1[max_len+1] = '\0';\r
- }\r
-\r
- strcat( thinkOutput, buf1 );\r
- }\r
-\r
- if (currentMove == forwardMostMove || gameMode == AnalyzeMode\r
- || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
- DisplayMove(currentMove - 1);\r
- DisplayAnalysis();\r
- }\r
- return;\r
-\r
- } else if ((p=StrStr(message, "(only move)")) != NULL) {\r
- /* crafty (9.25+) says "(only move) <move>"\r
- * if there is only 1 legal move\r
- */\r
- sscanf(p, "(only move) %s", buf1);\r
- sprintf(thinkOutput, "%s (only move)", buf1);\r
- sprintf(programStats.movelist, "%s (only move)", buf1);\r
- programStats.depth = 1;\r
- programStats.nr_moves = 1;\r
- programStats.moves_left = 1;\r
- programStats.nodes = 1;\r
- programStats.time = 1;\r
- programStats.got_only_move = 1;\r
-\r
- /* Not really, but we also use this member to\r
- mean "line isn't going to change" (Crafty\r
- isn't searching, so stats won't change) */\r
- programStats.line_is_book = 1;\r
-\r
- SendProgramStatsToFrontend( cps, &programStats );\r
- \r
- if (currentMove == forwardMostMove || gameMode==AnalyzeMode || \r
- gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
- DisplayMove(currentMove - 1);\r
- DisplayAnalysis();\r
- }\r
- return;\r
- } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",\r
- &time, &nodes, &plylev, &mvleft,\r
- &mvtot, mvname) >= 5) {\r
- /* The stat01: line is from Crafty (9.29+) in response\r
- to the "." command */\r
- programStats.seen_stat = 1;\r
- cps->maybeThinking = TRUE;\r
-\r
- if (programStats.got_only_move || !appData.periodicUpdates)\r
- return;\r
-\r
- programStats.depth = plylev;\r
- programStats.time = time;\r
- programStats.nodes = nodes;\r
- programStats.moves_left = mvleft;\r
- programStats.nr_moves = mvtot;\r
- strcpy(programStats.move_name, mvname);\r
- programStats.ok_to_send = 1;\r
- programStats.movelist[0] = '\0';\r
-\r
- SendProgramStatsToFrontend( cps, &programStats );\r
-\r
- DisplayAnalysis();\r
- return;\r
-\r
- } else if (strncmp(message,"++",2) == 0) {\r
- /* Crafty 9.29+ outputs this */\r
- programStats.got_fail = 2;\r
- return;\r
-\r
- } else if (strncmp(message,"--",2) == 0) {\r
- /* Crafty 9.29+ outputs this */\r
- programStats.got_fail = 1;\r
- return;\r
-\r
- } else if (thinkOutput[0] != NULLCHAR &&\r
- strncmp(message, " ", 4) == 0) {\r
- unsigned message_len;\r
-\r
- p = message;\r
- while (*p && *p == ' ') p++;\r
-\r
- message_len = strlen( p );\r
-\r
- /* [AS] Avoid buffer overflow */\r
- if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {\r
- strcat(thinkOutput, " ");\r
- strcat(thinkOutput, p);\r
- }\r
-\r
- if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {\r
- strcat(programStats.movelist, " ");\r
- strcat(programStats.movelist, p);\r
- }\r
-\r
- if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||\r
- gameMode == AnalyzeFile || appData.icsEngineAnalyze) {\r
- DisplayMove(currentMove - 1);\r
- DisplayAnalysis();\r
- }\r
- return;\r
- }\r
- }\r
- else {\r
- buf1[0] = NULLCHAR;\r
-\r
- if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",\r
- &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) \r
- {\r
- ChessProgramStats cpstats;\r
-\r
- if (plyext != ' ' && plyext != '\t') {\r
- time *= 100;\r
- }\r
-\r
- /* [AS] Negate score if machine is playing black and reporting absolute scores */\r
- if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {\r
- curscore = -curscore;\r
- }\r
-\r
- cpstats.depth = plylev;\r
- cpstats.nodes = nodes;\r
- cpstats.time = time;\r
- cpstats.score = curscore;\r
- cpstats.got_only_move = 0;\r
- cpstats.movelist[0] = '\0';\r
-\r
- if (buf1[0] != NULLCHAR) {\r
- safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );\r
- }\r
-\r
- cpstats.ok_to_send = 0;\r
- cpstats.line_is_book = 0;\r
- cpstats.nr_moves = 0;\r
- cpstats.moves_left = 0;\r
-\r
- SendProgramStatsToFrontend( cps, &cpstats );\r
- }\r
- }\r
- }\r
-}\r
-\r
-\r
-/* Parse a game score from the character string "game", and\r
- record it as the history of the current game. The game\r
- score is NOT assumed to start from the standard position. \r
- The display is not updated in any way.\r
- */\r
-void\r
-ParseGameHistory(game)\r
- char *game;\r
-{\r
- ChessMove moveType;\r
- int fromX, fromY, toX, toY, boardIndex;\r
- char promoChar;\r
- char *p, *q;\r
- char buf[MSG_SIZ];\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsing game history: %s\n", game);\r
-\r
- if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");\r
- gameInfo.site = StrSave(appData.icsHost);\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
-\r
- /* Parse out names of players */\r
- while (*game == ' ') game++;\r
- p = buf;\r
- while (*game != ' ') *p++ = *game++;\r
- *p = NULLCHAR;\r
- gameInfo.white = StrSave(buf);\r
- while (*game == ' ') game++;\r
- p = buf;\r
- while (*game != ' ' && *game != '\n') *p++ = *game++;\r
- *p = NULLCHAR;\r
- gameInfo.black = StrSave(buf);\r
-\r
- /* Parse moves */\r
- boardIndex = blackPlaysFirst ? 1 : 0;\r
- yynewstr(game);\r
- for (;;) {\r
- yyboardindex = boardIndex;\r
- moveType = (ChessMove) yylex();\r
- switch (moveType) {\r
- case IllegalMove: /* maybe suicide chess, etc. */\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);\r
- fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
- setbuf(debugFP, NULL);\r
- }\r
- case WhitePromotionChancellor:\r
- case BlackPromotionChancellor:\r
- case WhitePromotionArchbishop:\r
- case BlackPromotionArchbishop:\r
- case WhitePromotionQueen:\r
- case BlackPromotionQueen:\r
- case WhitePromotionRook:\r
- case BlackPromotionRook:\r
- case WhitePromotionBishop:\r
- case BlackPromotionBishop:\r
- case WhitePromotionKnight:\r
- case BlackPromotionKnight:\r
- case WhitePromotionKing:\r
- case BlackPromotionKing:\r
- case NormalMove:\r
- case WhiteCapturesEnPassant:\r
- case BlackCapturesEnPassant:\r
- case WhiteKingSideCastle:\r
- case WhiteQueenSideCastle:\r
- case BlackKingSideCastle:\r
- case BlackQueenSideCastle:\r
- case WhiteKingSideCastleWild:\r
- case WhiteQueenSideCastleWild:\r
- case BlackKingSideCastleWild:\r
- case BlackQueenSideCastleWild:\r
- /* PUSH Fabien */\r
- case WhiteHSideCastleFR:\r
- case WhiteASideCastleFR:\r
- case BlackHSideCastleFR:\r
- case BlackASideCastleFR:\r
- /* POP Fabien */\r
- fromX = currentMoveString[0] - AAA;\r
- fromY = currentMoveString[1] - ONE;\r
- toX = currentMoveString[2] - AAA;\r
- toY = currentMoveString[3] - ONE;\r
- promoChar = currentMoveString[4];\r
- break;\r
- case WhiteDrop:\r
- case BlackDrop:\r
- fromX = moveType == WhiteDrop ?\r
- (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
- (int) CharToPiece(ToLower(currentMoveString[0]));\r
- fromY = DROP_RANK;\r
- toX = currentMoveString[2] - AAA;\r
- toY = currentMoveString[3] - ONE;\r
- promoChar = NULLCHAR;\r
- break;\r
- case AmbiguousMove:\r
- /* bug? */\r
- sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);\r
- fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
- setbuf(debugFP, NULL);\r
- }\r
- DisplayError(buf, 0);\r
- return;\r
- case ImpossibleMove:\r
- /* bug? */\r
- sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);\r
- fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);\r
- setbuf(debugFP, NULL);\r
- }\r
- DisplayError(buf, 0);\r
- return;\r
- case (ChessMove) 0: /* end of file */\r
- if (boardIndex < backwardMostMove) {\r
- /* Oops, gap. How did that happen? */\r
- DisplayError(_("Gap in move list"), 0);\r
- return;\r
- }\r
- backwardMostMove = blackPlaysFirst ? 1 : 0;\r
- if (boardIndex > forwardMostMove) {\r
- forwardMostMove = boardIndex;\r
- }\r
- return;\r
- case ElapsedTime:\r
- if (boardIndex > (blackPlaysFirst ? 1 : 0)) {\r
- strcat(parseList[boardIndex-1], " ");\r
- strcat(parseList[boardIndex-1], yy_text);\r
- }\r
- continue;\r
- case Comment:\r
- case PGNTag:\r
- case NAG:\r
- default:\r
- /* ignore */\r
- continue;\r
- case WhiteWins:\r
- case BlackWins:\r
- case GameIsDrawn:\r
- case GameUnfinished:\r
- if (gameMode == IcsExamining) {\r
- if (boardIndex < backwardMostMove) {\r
- /* Oops, gap. How did that happen? */\r
- return;\r
- }\r
- backwardMostMove = blackPlaysFirst ? 1 : 0;\r
- return;\r
- }\r
- gameInfo.result = moveType;\r
- p = strchr(yy_text, '{');\r
- if (p == NULL) p = strchr(yy_text, '(');\r
- if (p == NULL) {\r
- p = yy_text;\r
- if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
- } else {\r
- q = strchr(p, *p == '{' ? '}' : ')');\r
- if (q != NULL) *q = NULLCHAR;\r
- p++;\r
- }\r
- gameInfo.resultDetails = StrSave(p);\r
- continue;\r
- }\r
- if (boardIndex >= forwardMostMove &&\r
- !(gameMode == IcsObserving && ics_gamenum == -1)) {\r
- backwardMostMove = blackPlaysFirst ? 1 : 0;\r
- return;\r
- }\r
- (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),\r
- EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,\r
- parseList[boardIndex]);\r
- CopyBoard(boards[boardIndex + 1], boards[boardIndex]);\r
- {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}\r
- /* currentMoveString is set as a side-effect of yylex */\r
- strcpy(moveList[boardIndex], currentMoveString);\r
- strcat(moveList[boardIndex], "\n");\r
- boardIndex++;\r
- ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex], \r
- castlingRights[boardIndex], &epStatus[boardIndex]);\r
- switch (MateTest(boards[boardIndex], PosFlags(boardIndex),\r
- EP_UNKNOWN, castlingRights[boardIndex]) ) {\r
- case MT_NONE:\r
- case MT_STALEMATE:\r
- default:\r
- break;\r
- case MT_CHECK:\r
- if(gameInfo.variant != VariantShogi)\r
- strcat(parseList[boardIndex - 1], "+");\r
- break;\r
- case MT_CHECKMATE:\r
- case MT_STAINMATE:\r
- strcat(parseList[boardIndex - 1], "#");\r
- break;\r
- }\r
- }\r
-}\r
-\r
-\r
-/* Apply a move to the given board */\r
-void\r
-ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)\r
- int fromX, fromY, toX, toY;\r
- int promoChar;\r
- Board board;\r
- char *castling;\r
- char *ep;\r
-{\r
- ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;\r
-\r
- /* [HGM] compute & store e.p. status and castling rights for new position */\r
- /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */\r
- { int i;\r
-\r
- if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;\r
- oldEP = *ep;\r
- *ep = EP_NONE;\r
-\r
- if( board[toY][toX] != EmptySquare ) \r
- *ep = EP_CAPTURE; \r
-\r
- if( board[fromY][fromX] == WhitePawn ) {\r
- if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
- *ep = EP_PAWN_MOVE;\r
- if( toY-fromY==2) {\r
- if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&\r
- gameInfo.variant != VariantBerolina || toX < fromX)\r
- *ep = toX | berolina;\r
- if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&\r
- gameInfo.variant != VariantBerolina || toX > fromX) \r
- *ep = toX;\r
- }\r
- } else \r
- if( board[fromY][fromX] == BlackPawn ) {\r
- if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers\r
- *ep = EP_PAWN_MOVE; \r
- if( toY-fromY== -2) {\r
- if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&\r
- gameInfo.variant != VariantBerolina || toX < fromX)\r
- *ep = toX | berolina;\r
- if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&\r
- gameInfo.variant != VariantBerolina || toX > fromX) \r
- *ep = toX;\r
- }\r
- }\r
-\r
- for(i=0; i<nrCastlingRights; i++) {\r
- if(castling[i] == fromX && castlingRank[i] == fromY ||\r
- castling[i] == toX && castlingRank[i] == toY \r
- ) castling[i] = -1; // revoke for moved or captured piece\r
- }\r
-\r
- }\r
-\r
- /* [HGM] In Shatranj and Courier all promotions are to Ferz */\r
- if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)\r
- && promoChar != 0) promoChar = PieceToChar(WhiteFerz);\r
- \r
- if (fromX == toX && fromY == toY) return;\r
-\r
- if (fromY == DROP_RANK) {\r
- /* must be first */\r
- piece = board[toY][toX] = (ChessSquare) fromX;\r
- } else {\r
- piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */\r
- king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */\r
- if(gameInfo.variant == VariantKnightmate)\r
- king += (int) WhiteUnicorn - (int) WhiteKing;\r
-\r
- /* Code added by Tord: */\r
- /* FRC castling assumed when king captures friendly rook. */\r
- if (board[fromY][fromX] == WhiteKing &&\r
- board[toY][toX] == WhiteRook) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = EmptySquare;\r
- if(toX > fromX) {\r
- board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;\r
- } else {\r
- board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;\r
- }\r
- } else if (board[fromY][fromX] == BlackKing &&\r
- board[toY][toX] == BlackRook) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = EmptySquare;\r
- if(toX > fromX) {\r
- board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;\r
- } else {\r
- board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;\r
- }\r
- /* End of code added by Tord */\r
-\r
- } else if (board[fromY][fromX] == king\r
- && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
- && toY == fromY && toX > fromX+1) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = king;\r
- board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
- board[fromY][BOARD_RGHT-1] = EmptySquare;\r
- } else if (board[fromY][fromX] == king\r
- && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
- && toY == fromY && toX < fromX-1) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = king;\r
- board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
- board[fromY][BOARD_LEFT] = EmptySquare;\r
- } else if (board[fromY][fromX] == WhitePawn\r
- && toY == BOARD_HEIGHT-1\r
- && gameInfo.variant != VariantXiangqi\r
- ) {\r
- /* white pawn promotion */\r
- board[toY][toX] = CharToPiece(ToUpper(promoChar));\r
- if (board[toY][toX] == EmptySquare) {\r
- board[toY][toX] = WhiteQueen;\r
- }\r
- if(gameInfo.variant==VariantBughouse ||\r
- gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
- board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
- board[fromY][fromX] = EmptySquare;\r
- } else if ((fromY == BOARD_HEIGHT-4)\r
- && (toX != fromX)\r
- && gameInfo.variant != VariantXiangqi\r
- && gameInfo.variant != VariantBerolina\r
- && (board[fromY][fromX] == WhitePawn)\r
- && (board[toY][toX] == EmptySquare)) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = WhitePawn;\r
- captured = board[toY - 1][toX];\r
- board[toY - 1][toX] = EmptySquare;\r
- } else if ((fromY == BOARD_HEIGHT-4)\r
- && (toX == fromX)\r
- && gameInfo.variant == VariantBerolina\r
- && (board[fromY][fromX] == WhitePawn)\r
- && (board[toY][toX] == EmptySquare)) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = WhitePawn;\r
- if(oldEP & EP_BEROLIN_A) {\r
- captured = board[fromY][fromX-1];\r
- board[fromY][fromX-1] = EmptySquare;\r
- }else{ captured = board[fromY][fromX+1];\r
- board[fromY][fromX+1] = EmptySquare;\r
- }\r
- } else if (board[fromY][fromX] == king\r
- && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
- && toY == fromY && toX > fromX+1) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = king;\r
- board[toY][toX-1] = board[fromY][BOARD_RGHT-1];\r
- board[fromY][BOARD_RGHT-1] = EmptySquare;\r
- } else if (board[fromY][fromX] == king\r
- && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */\r
- && toY == fromY && toX < fromX-1) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = king;\r
- board[toY][toX+1] = board[fromY][BOARD_LEFT];\r
- board[fromY][BOARD_LEFT] = EmptySquare;\r
- } else if (fromY == 7 && fromX == 3\r
- && board[fromY][fromX] == BlackKing\r
- && toY == 7 && toX == 5) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = BlackKing;\r
- board[fromY][7] = EmptySquare;\r
- board[toY][4] = BlackRook;\r
- } else if (fromY == 7 && fromX == 3\r
- && board[fromY][fromX] == BlackKing\r
- && toY == 7 && toX == 1) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = BlackKing;\r
- board[fromY][0] = EmptySquare;\r
- board[toY][2] = BlackRook;\r
- } else if (board[fromY][fromX] == BlackPawn\r
- && toY == 0\r
- && gameInfo.variant != VariantXiangqi\r
- ) {\r
- /* black pawn promotion */\r
- board[0][toX] = CharToPiece(ToLower(promoChar));\r
- if (board[0][toX] == EmptySquare) {\r
- board[0][toX] = BlackQueen;\r
- }\r
- if(gameInfo.variant==VariantBughouse ||\r
- gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */\r
- board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);\r
- board[fromY][fromX] = EmptySquare;\r
- } else if ((fromY == 3)\r
- && (toX != fromX)\r
- && gameInfo.variant != VariantXiangqi\r
- && gameInfo.variant != VariantBerolina\r
- && (board[fromY][fromX] == BlackPawn)\r
- && (board[toY][toX] == EmptySquare)) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = BlackPawn;\r
- captured = board[toY + 1][toX];\r
- board[toY + 1][toX] = EmptySquare;\r
- } else if ((fromY == 3)\r
- && (toX == fromX)\r
- && gameInfo.variant == VariantBerolina\r
- && (board[fromY][fromX] == BlackPawn)\r
- && (board[toY][toX] == EmptySquare)) {\r
- board[fromY][fromX] = EmptySquare;\r
- board[toY][toX] = BlackPawn;\r
- if(oldEP & EP_BEROLIN_A) {\r
- captured = board[fromY][fromX-1];\r
- board[fromY][fromX-1] = EmptySquare;\r
- }else{ captured = board[fromY][fromX+1];\r
- board[fromY][fromX+1] = EmptySquare;\r
- }\r
- } else {\r
- board[toY][toX] = board[fromY][fromX];\r
- board[fromY][fromX] = EmptySquare;\r
- }\r
-\r
- /* [HGM] now we promote for Shogi, if needed */\r
- if(gameInfo.variant == VariantShogi && promoChar == 'q')\r
- board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
- }\r
-\r
- if (gameInfo.holdingsWidth != 0) {\r
-\r
- /* !!A lot more code needs to be written to support holdings */\r
- /* [HGM] OK, so I have written it. Holdings are stored in the */\r
- /* penultimate board files, so they are automaticlly stored */\r
- /* in the game history. */\r
- if (fromY == DROP_RANK) {\r
- /* Delete from holdings, by decreasing count */\r
- /* and erasing image if necessary */\r
- p = (int) fromX;\r
- if(p < (int) BlackPawn) { /* white drop */\r
- p -= (int)WhitePawn;\r
- if(p >= gameInfo.holdingsSize) p = 0;\r
- if(--board[p][BOARD_WIDTH-2] == 0)\r
- board[p][BOARD_WIDTH-1] = EmptySquare;\r
- } else { /* black drop */\r
- p -= (int)BlackPawn;\r
- if(p >= gameInfo.holdingsSize) p = 0;\r
- if(--board[BOARD_HEIGHT-1-p][1] == 0)\r
- board[BOARD_HEIGHT-1-p][0] = EmptySquare;\r
- }\r
- }\r
- if (captured != EmptySquare && gameInfo.holdingsSize > 0\r
- && gameInfo.variant != VariantBughouse ) {\r
- /* [HGM] holdings: Add to holdings, if holdings exist */\r
- if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) { \r
- // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip\r
- captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;\r
- }\r
- p = (int) captured;\r
- if (p >= (int) BlackPawn) {\r
- p -= (int)BlackPawn;\r
- if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
- /* in Shogi restore piece to its original first */\r
- captured = (ChessSquare) (DEMOTED captured);\r
- p = DEMOTED p;\r
- }\r
- p = PieceToNumber((ChessSquare)p);\r
- if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }\r
- board[p][BOARD_WIDTH-2]++;\r
- board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;\r
- } else {\r
- p -= (int)WhitePawn;\r
- if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {\r
- captured = (ChessSquare) (DEMOTED captured);\r
- p = DEMOTED p;\r
- }\r
- p = PieceToNumber((ChessSquare)p);\r
- if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }\r
- board[BOARD_HEIGHT-1-p][1]++;\r
- board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;\r
- }\r
- }\r
-\r
- } else if (gameInfo.variant == VariantAtomic) {\r
- if (captured != EmptySquare) {\r
- int y, x;\r
- for (y = toY-1; y <= toY+1; y++) {\r
- for (x = toX-1; x <= toX+1; x++) {\r
- if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&\r
- board[y][x] != WhitePawn && board[y][x] != BlackPawn) {\r
- board[y][x] = EmptySquare;\r
- }\r
- }\r
- }\r
- board[toY][toX] = EmptySquare;\r
- }\r
- }\r
- if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {\r
- /* [HGM] Shogi promotions */\r
- board[toY][toX] = (ChessSquare) (PROMOTED piece);\r
- }\r
-\r
- if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) \r
- && promoChar != NULLCHAR && gameInfo.holdingsSize) { \r
- // [HGM] superchess: take promotion piece out of holdings\r
- int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));\r
- if((int)piece < (int)BlackPawn) { // determine stm from piece color\r
- if(!--board[k][BOARD_WIDTH-2])\r
- board[k][BOARD_WIDTH-1] = EmptySquare;\r
- } else {\r
- if(!--board[BOARD_HEIGHT-1-k][1])\r
- board[BOARD_HEIGHT-1-k][0] = EmptySquare;\r
- }\r
- }\r
-\r
-}\r
-\r
-/* Updates forwardMostMove */\r
-void\r
-MakeMove(fromX, fromY, toX, toY, promoChar)\r
- int fromX, fromY, toX, toY;\r
- int promoChar;\r
-{\r
-// forwardMostMove++; // [HGM] bare: moved downstream\r
-\r
- if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */\r
- int timeLeft; static int lastLoadFlag=0; int king, piece;\r
- piece = boards[forwardMostMove][fromY][fromX];\r
- king = piece < (int) BlackPawn ? WhiteKing : BlackKing;\r
- if(gameInfo.variant == VariantKnightmate)\r
- king += (int) WhiteUnicorn - (int) WhiteKing;\r
- if(forwardMostMove == 0) {\r
- if(blackPlaysFirst) \r
- fprintf(serverMoves, "%s;", second.tidy);\r
- fprintf(serverMoves, "%s;", first.tidy);\r
- if(!blackPlaysFirst) \r
- fprintf(serverMoves, "%s;", second.tidy);\r
- } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");\r
- lastLoadFlag = loadFlag;\r
- // print base move\r
- fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);\r
- // print castling suffix\r
- if( toY == fromY && piece == king ) {\r
- if(toX-fromX > 1)\r
- fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);\r
- if(fromX-toX >1)\r
- fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);\r
- }\r
- // e.p. suffix\r
- if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||\r
- boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&\r
- boards[forwardMostMove][toY][toX] == EmptySquare\r
- && fromX != toX )\r
- fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);\r
- // promotion suffix\r
- if(promoChar != NULLCHAR)\r
- fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);\r
- if(!loadFlag) {\r
- fprintf(serverMoves, "/%d/%d",\r
- pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);\r
- if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;\r
- else timeLeft = blackTimeRemaining/1000;\r
- fprintf(serverMoves, "/%d", timeLeft);\r
- }\r
- fflush(serverMoves);\r
- }\r
-\r
- if (forwardMostMove+1 >= MAX_MOVES) {\r
- DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),\r
- 0, 1);\r
- return;\r
- }\r
- SwitchClocks();\r
- timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;\r
- timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;\r
- if (commentList[forwardMostMove+1] != NULL) {\r
- free(commentList[forwardMostMove+1]);\r
- commentList[forwardMostMove+1] = NULL;\r
- }\r
- CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);\r
- {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}\r
- ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1], \r
- castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);\r
- forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board\r
- gameInfo.result = GameUnfinished;\r
- if (gameInfo.resultDetails != NULL) {\r
- free(gameInfo.resultDetails);\r
- gameInfo.resultDetails = NULL;\r
- }\r
- CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,\r
- moveList[forwardMostMove - 1]);\r
- (void) CoordsToAlgebraic(boards[forwardMostMove - 1],\r
- PosFlags(forwardMostMove - 1), EP_UNKNOWN,\r
- fromY, fromX, toY, toX, promoChar,\r
- parseList[forwardMostMove - 1]);\r
- switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),\r
- epStatus[forwardMostMove], /* [HGM] use true e.p. */\r
- castlingRights[forwardMostMove]) ) {\r
- case MT_NONE:\r
- case MT_STALEMATE:\r
- default:\r
- break;\r
- case MT_CHECK:\r
- if(gameInfo.variant != VariantShogi)\r
- strcat(parseList[forwardMostMove - 1], "+");\r
- break;\r
- case MT_CHECKMATE:\r
- case MT_STAINMATE:\r
- strcat(parseList[forwardMostMove - 1], "#");\r
- break;\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);\r
- }\r
-\r
-}\r
-\r
-/* Updates currentMove if not pausing */\r
-void\r
-ShowMove(fromX, fromY, toX, toY)\r
-{\r
- int instant = (gameMode == PlayFromGameFile) ?\r
- (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;\r
- if(appData.noGUI) return;\r
- if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
- if (!instant) {\r
- if (forwardMostMove == currentMove + 1) {\r
- AnimateMove(boards[forwardMostMove - 1],\r
- fromX, fromY, toX, toY);\r
- }\r
- if (appData.highlightLastMove) {\r
- SetHighlights(fromX, fromY, toX, toY);\r
- }\r
- }\r
- currentMove = forwardMostMove;\r
- }\r
-\r
- if (instant) return;\r
-\r
- DisplayMove(currentMove - 1);\r
- DrawPosition(FALSE, boards[currentMove]);\r
- DisplayBothClocks();\r
- HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
-}\r
-\r
-void SendEgtPath(ChessProgramState *cps)\r
-{ /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */\r
- char buf[MSG_SIZ], name[MSG_SIZ], *p;\r
-\r
- if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;\r
-\r
- while(*p) {\r
- char c, *q = name+1, *r, *s;\r
-\r
- name[0] = ','; // extract next format name from feature and copy with prefixed ','\r
- while(*p && *p != ',') *q++ = *p++;\r
- *q++ = ':'; *q = 0;\r
- if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] && \r
- strcmp(name, ",nalimov:") == 0 ) {\r
- // take nalimov path from the menu-changeable option first, if it is defined\r
- sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);\r
- SendToProgram(buf,cps); // send egtbpath command for nalimov\r
- } else\r
- if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||\r
- (s = StrStr(appData.egtFormats, name)) != NULL) {\r
- // format name occurs amongst user-supplied formats, at beginning or immediately after comma\r
- s = r = StrStr(s, ":") + 1; // beginning of path info\r
- while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string\r
- c = *r; *r = 0; // temporarily null-terminate path info\r
- *--q = 0; // strip of trailig ':' from name\r
- sprintf(buf, "egtbpath %s %s\n", name+1, s);\r
- *r = c;\r
- SendToProgram(buf,cps); // send egtbpath command for this format\r
- }\r
- if(*p == ',') p++; // read away comma to position for next format name\r
- }\r
-}\r
-\r
-void\r
-InitChessProgram(cps, setup)\r
- ChessProgramState *cps;\r
- int setup; /* [HGM] needed to setup FRC opening position */\r
-{\r
- char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;\r
- if (appData.noChessProgram) return;\r
- hintRequested = FALSE;\r
- bookRequested = FALSE;\r
-\r
- /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */\r
- /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */\r
- if(cps->memSize) { /* [HGM] memory */\r
- sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);\r
- SendToProgram(buf, cps);\r
- }\r
- SendEgtPath(cps); /* [HGM] EGT */\r
- if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */\r
- sprintf(buf, "cores %d\n", appData.smpCores);\r
- SendToProgram(buf, cps);\r
- }\r
-\r
- SendToProgram(cps->initString, cps);\r
- if (gameInfo.variant != VariantNormal &&\r
- gameInfo.variant != VariantLoadable\r
- /* [HGM] also send variant if board size non-standard */\r
- || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0\r
- ) {\r
- char *v = VariantName(gameInfo.variant);\r
- if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {\r
- /* [HGM] in protocol 1 we have to assume all variants valid */\r
- sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);\r
- DisplayFatalError(buf, 0, 1);\r
- return;\r
- }\r
-\r
- /* [HGM] make prefix for non-standard board size. Awkward testing... */\r
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
- if( gameInfo.variant == VariantXiangqi )\r
- overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;\r
- if( gameInfo.variant == VariantShogi )\r
- overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;\r
- if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )\r
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;\r
- if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom || \r
- gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )\r
- overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
- if( gameInfo.variant == VariantCourier )\r
- overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;\r
- if( gameInfo.variant == VariantSuper )\r
- overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
- if( gameInfo.variant == VariantGreat )\r
- overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;\r
-\r
- if(overruled) {\r
- sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight, \r
- gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name\r
- /* [HGM] varsize: try first if this defiant size variant is specifically known */\r
- if(StrStr(cps->variants, b) == NULL) { \r
- // specific sized variant not known, check if general sizing allowed\r
- if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best\r
- if(StrStr(cps->variants, "boardsize") == NULL) {\r
- sprintf(buf, "Board size %dx%d+%d not supported by %s",\r
- gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);\r
- DisplayFatalError(buf, 0, 1);\r
- return;\r
- }\r
- /* [HGM] here we really should compare with the maximum supported board size */\r
- }\r
- }\r
- } else sprintf(b, "%s", VariantName(gameInfo.variant));\r
- sprintf(buf, "variant %s\n", b);\r
- SendToProgram(buf, cps);\r
- }\r
- currentlyInitializedVariant = gameInfo.variant;\r
-\r
- /* [HGM] send opening position in FRC to first engine */\r
- if(setup) {\r
- SendToProgram("force\n", cps);\r
- SendBoard(cps, 0);\r
- /* engine is now in force mode! Set flag to wake it up after first move. */\r
- setboardSpoiledMachineBlack = 1;\r
- }\r
-\r
- if (cps->sendICS) {\r
- snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");\r
- SendToProgram(buf, cps);\r
- }\r
- cps->maybeThinking = FALSE;\r
- cps->offeredDraw = 0;\r
- if (!appData.icsActive) {\r
- SendTimeControl(cps, movesPerSession, timeControl,\r
- timeIncrement, appData.searchDepth,\r
- searchTime);\r
- }\r
- if (appData.showThinking \r
- // [HGM] thinking: four options require thinking output to be sent\r
- || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()\r
- ) {\r
- SendToProgram("post\n", cps);\r
- }\r
- SendToProgram("hard\n", cps);\r
- if (!appData.ponderNextMove) {\r
- /* Warning: "easy" is a toggle in GNU Chess, so don't send\r
- it without being sure what state we are in first. "hard"\r
- is not a toggle, so that one is OK.\r
- */\r
- SendToProgram("easy\n", cps);\r
- }\r
- if (cps->usePing) {\r
- sprintf(buf, "ping %d\n", ++cps->lastPing);\r
- SendToProgram(buf, cps);\r
- }\r
- cps->initDone = TRUE;\r
-} \r
-\r
-\r
-void\r
-StartChessProgram(cps)\r
- ChessProgramState *cps;\r
-{\r
- char buf[MSG_SIZ];\r
- int err;\r
-\r
- if (appData.noChessProgram) return;\r
- cps->initDone = FALSE;\r
-\r
- if (strcmp(cps->host, "localhost") == 0) {\r
- err = StartChildProcess(cps->program, cps->dir, &cps->pr);\r
- } else if (*appData.remoteShell == NULLCHAR) {\r
- err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);\r
- } else {\r
- if (*appData.remoteUser == NULLCHAR) {\r
- snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,\r
- cps->program);\r
- } else {\r
- snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,\r
- cps->host, appData.remoteUser, cps->program);\r
- }\r
- err = StartChildProcess(buf, "", &cps->pr);\r
- }\r
- \r
- if (err != 0) {\r
- sprintf(buf, _("Startup failure on '%s'"), cps->program);\r
- DisplayFatalError(buf, err, 1);\r
- cps->pr = NoProc;\r
- cps->isr = NULL;\r
- return;\r
- }\r
- \r
- cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);\r
- if (cps->protocolVersion > 1) {\r
- sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);\r
- cps->nrOptions = 0; // [HGM] options: clear all engine-specific options\r
- cps->comboCnt = 0; // and values of combo boxes\r
- SendToProgram(buf, cps);\r
- } else {\r
- SendToProgram("xboard\n", cps);\r
- }\r
-}\r
-\r
-\r
-void\r
-TwoMachinesEventIfReady P((void))\r
-{\r
- if (first.lastPing != first.lastPong) {\r
- DisplayMessage("", _("Waiting for first chess program"));\r
- ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
- return;\r
- }\r
- if (second.lastPing != second.lastPong) {\r
- DisplayMessage("", _("Waiting for second chess program"));\r
- ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000\r
- return;\r
- }\r
- ThawUI();\r
- TwoMachinesEvent();\r
-}\r
-\r
-void\r
-NextMatchGame P((void))\r
-{\r
- int index; /* [HGM] autoinc: step lod index during match */\r
- Reset(FALSE, TRUE);\r
- if (*appData.loadGameFile != NULLCHAR) {\r
- index = appData.loadGameIndex;\r
- if(index < 0) { // [HGM] autoinc\r
- lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
- if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
- } \r
- LoadGameFromFile(appData.loadGameFile,\r
- index,\r
- appData.loadGameFile, FALSE);\r
- } else if (*appData.loadPositionFile != NULLCHAR) {\r
- index = appData.loadPositionIndex;\r
- if(index < 0) { // [HGM] autoinc\r
- lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;\r
- if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;\r
- } \r
- LoadPositionFromFile(appData.loadPositionFile,\r
- index,\r
- appData.loadPositionFile);\r
- }\r
- TwoMachinesEventIfReady();\r
-}\r
-\r
-void UserAdjudicationEvent( int result )\r
-{\r
- ChessMove gameResult = GameIsDrawn;\r
-\r
- if( result > 0 ) {\r
- gameResult = WhiteWins;\r
- }\r
- else if( result < 0 ) {\r
- gameResult = BlackWins;\r
- }\r
-\r
- if( gameMode == TwoMachinesPlay ) {\r
- GameEnds( gameResult, "User adjudication", GE_XBOARD );\r
- }\r
-}\r
-\r
-\r
-void\r
-GameEnds(result, resultDetails, whosays)\r
- ChessMove result;\r
- char *resultDetails;\r
- int whosays;\r
-{\r
- GameMode nextGameMode;\r
- int isIcsGame;\r
- char buf[MSG_SIZ];\r
-\r
- if(endingGame) return; /* [HGM] crash: forbid recursion */\r
- endingGame = 1;\r
-\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "GameEnds(%d, %s, %d)\n",\r
- result, resultDetails ? resultDetails : "(null)", whosays);\r
- }\r
-\r
- if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {\r
- /* If we are playing on ICS, the server decides when the\r
- game is over, but the engine can offer to draw, claim \r
- a draw, or resign. \r
- */\r
-#if ZIPPY\r
- if (appData.zippyPlay && first.initDone) {\r
- if (result == GameIsDrawn) {\r
- /* In case draw still needs to be claimed */\r
- SendToICS(ics_prefix);\r
- SendToICS("draw\n");\r
- } else if (StrCaseStr(resultDetails, "resign")) {\r
- SendToICS(ics_prefix);\r
- SendToICS("resign\n");\r
- }\r
- }\r
-#endif\r
- endingGame = 0; /* [HGM] crash */\r
- return;\r
- }\r
-\r
- /* If we're loading the game from a file, stop */\r
- if (whosays == GE_FILE) {\r
- (void) StopLoadGameTimer();\r
- gameFileFP = NULL;\r
- }\r
-\r
- /* Cancel draw offers */\r
- first.offeredDraw = second.offeredDraw = 0;\r
-\r
- /* If this is an ICS game, only ICS can really say it's done;\r
- if not, anyone can. */\r
- isIcsGame = (gameMode == IcsPlayingWhite || \r
- gameMode == IcsPlayingBlack || \r
- gameMode == IcsObserving || \r
- gameMode == IcsExamining);\r
-\r
- if (!isIcsGame || whosays == GE_ICS) {\r
- /* OK -- not an ICS game, or ICS said it was done */\r
- StopClocks();\r
- if (!isIcsGame && !appData.noChessProgram) \r
- SetUserThinkingEnables();\r
- \r
- /* [HGM] if a machine claims the game end we verify this claim */\r
- if(gameMode == TwoMachinesPlay && appData.testClaims) {\r
- if(appData.testLegality && whosays >= GE_ENGINE1 ) {\r
- char claimer;\r
- ChessMove trueResult = (ChessMove) -1;\r
-\r
- claimer = whosays == GE_ENGINE1 ? /* color of claimer */\r
- first.twoMachinesColor[0] :\r
- second.twoMachinesColor[0] ;\r
-\r
- // [HGM] losers: because the logic is becoming a bit hairy, determine true result first\r
- if(epStatus[forwardMostMove] == EP_CHECKMATE) {\r
- /* [HGM] verify: engine mate claims accepted if they were flagged */\r
- trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;\r
- } else\r
- if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win\r
- /* [HGM] verify: engine mate claims accepted if they were flagged */\r
- trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;\r
- } else\r
- if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now\r
- trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE\r
- }\r
-\r
- // now verify win claims, but not in drop games, as we don't understand those yet\r
- if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper\r
- || gameInfo.variant == VariantGreat) &&\r
- (result == WhiteWins && claimer == 'w' ||\r
- result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "result=%d sp=%d move=%d\n",\r
- result, epStatus[forwardMostMove], forwardMostMove);\r
- }\r
- if(result != trueResult) {\r
- sprintf(buf, "False win claim: '%s'", resultDetails);\r
- result = claimer == 'w' ? BlackWins : WhiteWins;\r
- resultDetails = buf;\r
- }\r
- } else\r
- if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS\r
- && (forwardMostMove <= backwardMostMove ||\r
- epStatus[forwardMostMove-1] > EP_DRAWS ||\r
- (claimer=='b')==(forwardMostMove&1))\r
- ) {\r
- /* [HGM] verify: draws that were not flagged are false claims */\r
- sprintf(buf, "False draw claim: '%s'", resultDetails);\r
- result = claimer == 'w' ? BlackWins : WhiteWins;\r
- resultDetails = buf;\r
- }\r
- /* (Claiming a loss is accepted no questions asked!) */\r
- }\r
- /* [HGM] bare: don't allow bare King to win */\r
- if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)\r
- && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway \r
- && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...\r
- && result != GameIsDrawn)\r
- { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);\r
- for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {\r
- int p = (int)boards[forwardMostMove][i][j] - color;\r
- if(p >= 0 && p <= (int)WhiteKing) k++;\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",\r
- result, resultDetails ? resultDetails : "(null)", whosays, k, color);\r
- }\r
- if(k <= 1) {\r
- result = GameIsDrawn;\r
- sprintf(buf, "%s but bare king", resultDetails);\r
- resultDetails = buf;\r
- }\r
- }\r
- }\r
-\r
-\r
- if(serverMoves != NULL && !loadFlag) { char c = '=';\r
- if(result==WhiteWins) c = '+';\r
- if(result==BlackWins) c = '-';\r
- if(resultDetails != NULL)\r
- fprintf(serverMoves, ";%c;%s\n", c, resultDetails);\r
- }\r
- if (resultDetails != NULL) {\r
- gameInfo.result = result;\r
- gameInfo.resultDetails = StrSave(resultDetails);\r
-\r
- /* display last move only if game was not loaded from file */\r
- if ((whosays != GE_FILE) && (currentMove == forwardMostMove))\r
- DisplayMove(currentMove - 1);\r
- \r
- if (forwardMostMove != 0) {\r
- if (gameMode != PlayFromGameFile && gameMode != EditGame) {\r
- if (*appData.saveGameFile != NULLCHAR) {\r
- SaveGameToFile(appData.saveGameFile, TRUE);\r
- } else if (appData.autoSaveGames) {\r
- AutoSaveGame();\r
- }\r
- if (*appData.savePositionFile != NULLCHAR) {\r
- SavePositionToFile(appData.savePositionFile);\r
- }\r
- }\r
- }\r
-\r
- /* Tell program how game ended in case it is learning */\r
- /* [HGM] Moved this to after saving the PGN, just in case */\r
- /* engine died and we got here through time loss. In that */\r
- /* case we will get a fatal error writing the pipe, which */\r
- /* would otherwise lose us the PGN. */\r
- /* [HGM] crash: not needed anymore, but doesn't hurt; */\r
- /* output during GameEnds should never be fatal anymore */\r
- if (gameMode == MachinePlaysWhite ||\r
- gameMode == MachinePlaysBlack ||\r
- gameMode == TwoMachinesPlay ||\r
- gameMode == IcsPlayingWhite ||\r
- gameMode == IcsPlayingBlack ||\r
- gameMode == BeginningOfGame) {\r
- char buf[MSG_SIZ];\r
- sprintf(buf, "result %s {%s}\n", PGNResult(result),\r
- resultDetails);\r
- if (first.pr != NoProc) {\r
- SendToProgram(buf, &first);\r
- }\r
- if (second.pr != NoProc &&\r
- gameMode == TwoMachinesPlay) {\r
- SendToProgram(buf, &second);\r
- }\r
- }\r
- }\r
-\r
- if (appData.icsActive) {\r
- if (appData.quietPlay &&\r
- (gameMode == IcsPlayingWhite ||\r
- gameMode == IcsPlayingBlack)) {\r
- SendToICS(ics_prefix);\r
- SendToICS("set shout 1\n");\r
- }\r
- nextGameMode = IcsIdle;\r
- ics_user_moved = FALSE;\r
- /* clean up premove. It's ugly when the game has ended and the\r
- * premove highlights are still on the board.\r
- */\r
- if (gotPremove) {\r
- gotPremove = FALSE;\r
- ClearPremoveHighlights();\r
- DrawPosition(FALSE, boards[currentMove]);\r
- }\r
- if (whosays == GE_ICS) {\r
- switch (result) {\r
- case WhiteWins:\r
- if (gameMode == IcsPlayingWhite)\r
- PlayIcsWinSound();\r
- else if(gameMode == IcsPlayingBlack)\r
- PlayIcsLossSound();\r
- break;\r
- case BlackWins:\r
- if (gameMode == IcsPlayingBlack)\r
- PlayIcsWinSound();\r
- else if(gameMode == IcsPlayingWhite)\r
- PlayIcsLossSound();\r
- break;\r
- case GameIsDrawn:\r
- PlayIcsDrawSound();\r
- break;\r
- default:\r
- PlayIcsUnfinishedSound();\r
- }\r
- }\r
- } else if (gameMode == EditGame ||\r
- gameMode == PlayFromGameFile || \r
- gameMode == AnalyzeMode || \r
- gameMode == AnalyzeFile) {\r
- nextGameMode = gameMode;\r
- } else {\r
- nextGameMode = EndOfGame;\r
- }\r
- pausing = FALSE;\r
- ModeHighlight();\r
- } else {\r
- nextGameMode = gameMode;\r
- }\r
-\r
- if (appData.noChessProgram) {\r
- gameMode = nextGameMode;\r
- ModeHighlight();\r
- endingGame = 0; /* [HGM] crash */\r
- return;\r
- }\r
-\r
- if (first.reuse) {\r
- /* Put first chess program into idle state */\r
- if (first.pr != NoProc &&\r
- (gameMode == MachinePlaysWhite ||\r
- gameMode == MachinePlaysBlack ||\r
- gameMode == TwoMachinesPlay ||\r
- gameMode == IcsPlayingWhite ||\r
- gameMode == IcsPlayingBlack ||\r
- gameMode == BeginningOfGame)) {\r
- SendToProgram("force\n", &first);\r
- if (first.usePing) {\r
- char buf[MSG_SIZ];\r
- sprintf(buf, "ping %d\n", ++first.lastPing);\r
- SendToProgram(buf, &first);\r
- }\r
- }\r
- } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
- /* Kill off first chess program */\r
- if (first.isr != NULL)\r
- RemoveInputSource(first.isr);\r
- first.isr = NULL;\r
- \r
- if (first.pr != NoProc) {\r
- ExitAnalyzeMode();\r
- DoSleep( appData.delayBeforeQuit );\r
- SendToProgram("quit\n", &first);\r
- DoSleep( appData.delayAfterQuit );\r
- DestroyChildProcess(first.pr, first.useSigterm);\r
- }\r
- first.pr = NoProc;\r
- }\r
- if (second.reuse) {\r
- /* Put second chess program into idle state */\r
- if (second.pr != NoProc &&\r
- gameMode == TwoMachinesPlay) {\r
- SendToProgram("force\n", &second);\r
- if (second.usePing) {\r
- char buf[MSG_SIZ];\r
- sprintf(buf, "ping %d\n", ++second.lastPing);\r
- SendToProgram(buf, &second);\r
- }\r
- }\r
- } else if (result != GameUnfinished || nextGameMode == IcsIdle) {\r
- /* Kill off second chess program */\r
- if (second.isr != NULL)\r
- RemoveInputSource(second.isr);\r
- second.isr = NULL;\r
- \r
- if (second.pr != NoProc) {\r
- DoSleep( appData.delayBeforeQuit );\r
- SendToProgram("quit\n", &second);\r
- DoSleep( appData.delayAfterQuit );\r
- DestroyChildProcess(second.pr, second.useSigterm);\r
- }\r
- second.pr = NoProc;\r
- }\r
-\r
- if (matchMode && gameMode == TwoMachinesPlay) {\r
- switch (result) {\r
- case WhiteWins:\r
- if (first.twoMachinesColor[0] == 'w') {\r
- first.matchWins++;\r
- } else {\r
- second.matchWins++;\r
- }\r
- break;\r
- case BlackWins:\r
- if (first.twoMachinesColor[0] == 'b') {\r
- first.matchWins++;\r
- } else {\r
- second.matchWins++;\r
- }\r
- break;\r
- default:\r
- break;\r
- }\r
- if (matchGame < appData.matchGames) {\r
- char *tmp;\r
- if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */\r
- tmp = first.twoMachinesColor;\r
- first.twoMachinesColor = second.twoMachinesColor;\r
- second.twoMachinesColor = tmp;\r
- }\r
- gameMode = nextGameMode;\r
- matchGame++;\r
- if(appData.matchPause>10000 || appData.matchPause<10)\r
- appData.matchPause = 10000; /* [HGM] make pause adjustable */\r
- ScheduleDelayedEvent(NextMatchGame, appData.matchPause);\r
- endingGame = 0; /* [HGM] crash */\r
- return;\r
- } else {\r
- char buf[MSG_SIZ];\r
- gameMode = nextGameMode;\r
- sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),\r
- first.tidy, second.tidy,\r
- first.matchWins, second.matchWins,\r
- appData.matchGames - (first.matchWins + second.matchWins));\r
- DisplayFatalError(buf, 0, 0);\r
- }\r
- }\r
- if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&\r
- !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))\r
- ExitAnalyzeMode();\r
- gameMode = nextGameMode;\r
- ModeHighlight();\r
- endingGame = 0; /* [HGM] crash */\r
-}\r
-\r
-/* Assumes program was just initialized (initString sent).\r
- Leaves program in force mode. */\r
-void\r
-FeedMovesToProgram(cps, upto) \r
- ChessProgramState *cps;\r
- int upto;\r
-{\r
- int i;\r
- \r
- if (appData.debugMode)\r
- fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",\r
- startedFromSetupPosition ? "position and " : "",\r
- backwardMostMove, upto, cps->which);\r
- if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];\r
- // [HGM] variantswitch: make engine aware of new variant\r
- if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)\r
- return; // [HGM] refrain from feeding moves altogether if variant is unsupported!\r
- sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));\r
- SendToProgram(buf, cps);\r
- currentlyInitializedVariant = gameInfo.variant;\r
- }\r
- SendToProgram("force\n", cps);\r
- if (startedFromSetupPosition) {\r
- SendBoard(cps, backwardMostMove);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "feedMoves\n");\r
- }\r
- }\r
- for (i = backwardMostMove; i < upto; i++) {\r
- SendMoveToProgram(i, cps);\r
- }\r
-}\r
-\r
-\r
-void\r
-ResurrectChessProgram()\r
-{\r
- /* The chess program may have exited.\r
- If so, restart it and feed it all the moves made so far. */\r
-\r
- if (appData.noChessProgram || first.pr != NoProc) return;\r
- \r
- StartChessProgram(&first);\r
- InitChessProgram(&first, FALSE);\r
- FeedMovesToProgram(&first, currentMove);\r
-\r
- if (!first.sendTime) {\r
- /* can't tell gnuchess what its clock should read,\r
- so we bow to its notion. */\r
- ResetClocks();\r
- timeRemaining[0][currentMove] = whiteTimeRemaining;\r
- timeRemaining[1][currentMove] = blackTimeRemaining;\r
- }\r
-\r
- if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||\r
- appData.icsEngineAnalyze) && first.analysisSupport) {\r
- SendToProgram("analyze\n", &first);\r
- first.analyzing = TRUE;\r
- }\r
-}\r
-\r
-/*\r
- * Button procedures\r
- */\r
-void\r
-Reset(redraw, init)\r
- int redraw, init;\r
-{\r
- int i;\r
-\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",\r
- redraw, init, gameMode);\r
- }\r
- pausing = pauseExamInvalid = FALSE;\r
- startedFromSetupPosition = blackPlaysFirst = FALSE;\r
- firstMove = TRUE;\r
- whiteFlag = blackFlag = FALSE;\r
- userOfferedDraw = FALSE;\r
- hintRequested = bookRequested = FALSE;\r
- first.maybeThinking = FALSE;\r
- second.maybeThinking = FALSE;\r
- first.bookSuspend = FALSE; // [HGM] book\r
- second.bookSuspend = FALSE;\r
- thinkOutput[0] = NULLCHAR;\r
- lastHint[0] = NULLCHAR;\r
- ClearGameInfo(&gameInfo);\r
- gameInfo.variant = StringToVariant(appData.variant);\r
- ics_user_moved = ics_clock_paused = FALSE;\r
- ics_getting_history = H_FALSE;\r
- ics_gamenum = -1;\r
- white_holding[0] = black_holding[0] = NULLCHAR;\r
- ClearProgramStats();\r
- opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode\r
- \r
- ResetFrontEnd();\r
- ClearHighlights();\r
- flipView = appData.flipView;\r
- ClearPremoveHighlights();\r
- gotPremove = FALSE;\r
- alarmSounded = FALSE;\r
-\r
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
- if(appData.serverMovesName != NULL) {\r
- /* [HGM] prepare to make moves file for broadcasting */\r
- clock_t t = clock();\r
- if(serverMoves != NULL) fclose(serverMoves);\r
- serverMoves = fopen(appData.serverMovesName, "r");\r
- if(serverMoves != NULL) {\r
- fclose(serverMoves);\r
- /* delay 15 sec before overwriting, so all clients can see end */\r
- while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);\r
- }\r
- serverMoves = fopen(appData.serverMovesName, "w");\r
- }\r
-\r
- ExitAnalyzeMode();\r
- gameMode = BeginningOfGame;\r
- ModeHighlight();\r
- if(appData.icsActive) gameInfo.variant = VariantNormal;\r
- InitPosition(redraw);\r
- for (i = 0; i < MAX_MOVES; i++) {\r
- if (commentList[i] != NULL) {\r
- free(commentList[i]);\r
- commentList[i] = NULL;\r
- }\r
- }\r
- ResetClocks();\r
- timeRemaining[0][0] = whiteTimeRemaining;\r
- timeRemaining[1][0] = blackTimeRemaining;\r
- if (first.pr == NULL) {\r
- StartChessProgram(&first);\r
- }\r
- if (init) {\r
- InitChessProgram(&first, startedFromSetupPosition);\r
- }\r
- DisplayTitle("");\r
- DisplayMessage("", "");\r
- HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
-}\r
-\r
-void\r
-AutoPlayGameLoop()\r
-{\r
- for (;;) {\r
- if (!AutoPlayOneMove())\r
- return;\r
- if (matchMode || appData.timeDelay == 0)\r
- continue;\r
- if (appData.timeDelay < 0 || gameMode == AnalyzeFile)\r
- return;\r
- StartLoadGameTimer((long)(1000.0 * appData.timeDelay));\r
- break;\r
- }\r
-}\r
-\r
-\r
-int\r
-AutoPlayOneMove()\r
-{\r
- int fromX, fromY, toX, toY;\r
-\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);\r
- }\r
-\r
- if (gameMode != PlayFromGameFile)\r
- return FALSE;\r
-\r
- if (currentMove >= forwardMostMove) {\r
- gameMode = EditGame;\r
- ModeHighlight();\r
-\r
- /* [AS] Clear current move marker at the end of a game */\r
- /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */\r
-\r
- return FALSE;\r
- }\r
- \r
- toX = moveList[currentMove][2] - AAA;\r
- toY = moveList[currentMove][3] - ONE;\r
-\r
- if (moveList[currentMove][1] == '@') {\r
- if (appData.highlightLastMove) {\r
- SetHighlights(-1, -1, toX, toY);\r
- }\r
- } else {\r
- fromX = moveList[currentMove][0] - AAA;\r
- fromY = moveList[currentMove][1] - ONE;\r
-\r
- HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */\r
-\r
- AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
-\r
- if (appData.highlightLastMove) {\r
- SetHighlights(fromX, fromY, toX, toY);\r
- }\r
- }\r
- DisplayMove(currentMove);\r
- SendMoveToProgram(currentMove++, &first);\r
- DisplayBothClocks();\r
- DrawPosition(FALSE, boards[currentMove]);\r
- // [HGM] PV info: always display, routine tests if empty\r
- DisplayComment(currentMove - 1, commentList[currentMove]);\r
- return TRUE;\r
-}\r
-\r
-\r
-int\r
-LoadGameOneMove(readAhead)\r
- ChessMove readAhead;\r
-{\r
- int fromX = 0, fromY = 0, toX = 0, toY = 0, done;\r
- char promoChar = NULLCHAR;\r
- ChessMove moveType;\r
- char move[MSG_SIZ];\r
- char *p, *q;\r
- \r
- if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile && \r
- gameMode != AnalyzeMode && gameMode != Training) {\r
- gameFileFP = NULL;\r
- return FALSE;\r
- }\r
- \r
- yyboardindex = forwardMostMove;\r
- if (readAhead != (ChessMove)0) {\r
- moveType = readAhead;\r
- } else {\r
- if (gameFileFP == NULL)\r
- return FALSE;\r
- moveType = (ChessMove) yylex();\r
- }\r
- \r
- done = FALSE;\r
- switch (moveType) {\r
- case Comment:\r
- if (appData.debugMode) \r
- fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
- p = yy_text;\r
- if (*p == '{' || *p == '[' || *p == '(') {\r
- p[strlen(p) - 1] = NULLCHAR;\r
- p++;\r
- }\r
-\r
- /* append the comment but don't display it */\r
- while (*p == '\n') p++;\r
- AppendComment(currentMove, p);\r
- return TRUE;\r
-\r
- case WhiteCapturesEnPassant:\r
- case BlackCapturesEnPassant:\r
- case WhitePromotionChancellor:\r
- case BlackPromotionChancellor:\r
- case WhitePromotionArchbishop:\r
- case BlackPromotionArchbishop:\r
- case WhitePromotionCentaur:\r
- case BlackPromotionCentaur:\r
- case WhitePromotionQueen:\r
- case BlackPromotionQueen:\r
- case WhitePromotionRook:\r
- case BlackPromotionRook:\r
- case WhitePromotionBishop:\r
- case BlackPromotionBishop:\r
- case WhitePromotionKnight:\r
- case BlackPromotionKnight:\r
- case WhitePromotionKing:\r
- case BlackPromotionKing:\r
- case NormalMove:\r
- case WhiteKingSideCastle:\r
- case WhiteQueenSideCastle:\r
- case BlackKingSideCastle:\r
- case BlackQueenSideCastle:\r
- case WhiteKingSideCastleWild:\r
- case WhiteQueenSideCastleWild:\r
- case BlackKingSideCastleWild:\r
- case BlackQueenSideCastleWild:\r
- /* PUSH Fabien */\r
- case WhiteHSideCastleFR:\r
- case WhiteASideCastleFR:\r
- case BlackHSideCastleFR:\r
- case BlackASideCastleFR:\r
- /* POP Fabien */\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
- fromX = currentMoveString[0] - AAA;\r
- fromY = currentMoveString[1] - ONE;\r
- toX = currentMoveString[2] - AAA;\r
- toY = currentMoveString[3] - ONE;\r
- promoChar = currentMoveString[4];\r
- break;\r
-\r
- case WhiteDrop:\r
- case BlackDrop:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);\r
- fromX = moveType == WhiteDrop ?\r
- (int) CharToPiece(ToUpper(currentMoveString[0])) :\r
- (int) CharToPiece(ToLower(currentMoveString[0]));\r
- fromY = DROP_RANK;\r
- toX = currentMoveString[2] - AAA;\r
- toY = currentMoveString[3] - ONE;\r
- break;\r
-\r
- case WhiteWins:\r
- case BlackWins:\r
- case GameIsDrawn:\r
- case GameUnfinished:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed game end: %s\n", yy_text);\r
- p = strchr(yy_text, '{');\r
- if (p == NULL) p = strchr(yy_text, '(');\r
- if (p == NULL) {\r
- p = yy_text;\r
- if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";\r
- } else {\r
- q = strchr(p, *p == '{' ? '}' : ')');\r
- if (q != NULL) *q = NULLCHAR;\r
- p++;\r
- }\r
- GameEnds(moveType, p, GE_FILE);\r
- done = TRUE;\r
- if (cmailMsgLoaded) {\r
- ClearHighlights();\r
- flipView = WhiteOnMove(currentMove);\r
- if (moveType == GameUnfinished) flipView = !flipView;\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Setting flipView to %d\n", flipView) ;\r
- }\r
- break;\r
-\r
- case (ChessMove) 0: /* end of file */\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parser hit end of file\n");\r
- switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
- EP_UNKNOWN, castlingRights[currentMove]) ) {\r
- case MT_NONE:\r
- case MT_CHECK:\r
- break;\r
- case MT_CHECKMATE:\r
- case MT_STAINMATE:\r
- if (WhiteOnMove(currentMove)) {\r
- GameEnds(BlackWins, "Black mates", GE_FILE);\r
- } else {\r
- GameEnds(WhiteWins, "White mates", GE_FILE);\r
- }\r
- break;\r
- case MT_STALEMATE:\r
- GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
- break;\r
- }\r
- done = TRUE;\r
- break;\r
-\r
- case MoveNumberOne:\r
- if (lastLoadGameStart == GNUChessGame) {\r
- /* GNUChessGames have numbers, but they aren't move numbers */\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
- yy_text, (int) moveType);\r
- return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
- }\r
- /* else fall thru */\r
-\r
- case XBoardGame:\r
- case GNUChessGame:\r
- case PGNTag:\r
- /* Reached start of next game in file */\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);\r
- switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
- EP_UNKNOWN, castlingRights[currentMove]) ) {\r
- case MT_NONE:\r
- case MT_CHECK:\r
- break;\r
- case MT_CHECKMATE:\r
- case MT_STAINMATE:\r
- if (WhiteOnMove(currentMove)) {\r
- GameEnds(BlackWins, "Black mates", GE_FILE);\r
- } else {\r
- GameEnds(WhiteWins, "White mates", GE_FILE);\r
- }\r
- break;\r
- case MT_STALEMATE:\r
- GameEnds(GameIsDrawn, "Stalemate", GE_FILE);\r
- break;\r
- }\r
- done = TRUE;\r
- break;\r
-\r
- case PositionDiagram: /* should not happen; ignore */\r
- case ElapsedTime: /* ignore */\r
- case NAG: /* ignore */\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",\r
- yy_text, (int) moveType);\r
- return LoadGameOneMove((ChessMove)0); /* tail recursion */\r
-\r
- case IllegalMove:\r
- if (appData.testLegality) {\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);\r
- sprintf(move, _("Illegal move: %d.%s%s"),\r
- (forwardMostMove / 2) + 1,\r
- WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
- DisplayError(move, 0);\r
- done = TRUE;\r
- } else {\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed %s into IllegalMove %s\n",\r
- yy_text, currentMoveString);\r
- fromX = currentMoveString[0] - AAA;\r
- fromY = currentMoveString[1] - ONE;\r
- toX = currentMoveString[2] - AAA;\r
- toY = currentMoveString[3] - ONE;\r
- promoChar = currentMoveString[4];\r
- }\r
- break;\r
-\r
- case AmbiguousMove:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);\r
- sprintf(move, _("Ambiguous move: %d.%s%s"),\r
- (forwardMostMove / 2) + 1,\r
- WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
- DisplayError(move, 0);\r
- done = TRUE;\r
- break;\r
-\r
- default:\r
- case ImpossibleMove:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);\r
- sprintf(move, _("Illegal move: %d.%s%s"),\r
- (forwardMostMove / 2) + 1,\r
- WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);\r
- DisplayError(move, 0);\r
- done = TRUE;\r
- break;\r
- }\r
-\r
- if (done) {\r
- if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {\r
- DrawPosition(FALSE, boards[currentMove]);\r
- DisplayBothClocks();\r
- if (!appData.matchMode) // [HGM] PV info: routine tests if empty\r
- DisplayComment(currentMove - 1, commentList[currentMove]);\r
- }\r
- (void) StopLoadGameTimer();\r
- gameFileFP = NULL;\r
- cmailOldMove = forwardMostMove;\r
- return FALSE;\r
- } else {\r
- /* currentMoveString is set as a side-effect of yylex */\r
- strcat(currentMoveString, "\n");\r
- strcpy(moveList[forwardMostMove], currentMoveString);\r
- \r
- thinkOutput[0] = NULLCHAR;\r
- MakeMove(fromX, fromY, toX, toY, promoChar);\r
- currentMove = forwardMostMove;\r
- return TRUE;\r
- }\r
-}\r
-\r
-/* Load the nth game from the given file */\r
-int\r
-LoadGameFromFile(filename, n, title, useList)\r
- char *filename;\r
- int n;\r
- char *title;\r
- /*Boolean*/ int useList;\r
-{\r
- FILE *f;\r
- char buf[MSG_SIZ];\r
-\r
- if (strcmp(filename, "-") == 0) {\r
- f = stdin;\r
- title = "stdin";\r
- } else {\r
- f = fopen(filename, "rb");\r
- if (f == NULL) {\r
- snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);\r
- DisplayError(buf, errno);\r
- return FALSE;\r
- }\r
- }\r
- if (fseek(f, 0, 0) == -1) {\r
- /* f is not seekable; probably a pipe */\r
- useList = FALSE;\r
- }\r
- if (useList && n == 0) {\r
- int error = GameListBuild(f);\r
- if (error) {\r
- DisplayError(_("Cannot build game list"), error);\r
- } else if (!ListEmpty(&gameList) &&\r
- ((ListGame *) gameList.tailPred)->number > 1) {\r
- GameListPopUp(f, title);\r
- return TRUE;\r
- }\r
- GameListDestroy();\r
- n = 1;\r
- }\r
- if (n == 0) n = 1;\r
- return LoadGame(f, n, title, FALSE);\r
-}\r
-\r
-\r
-void\r
-MakeRegisteredMove()\r
-{\r
- int fromX, fromY, toX, toY;\r
- char promoChar;\r
- if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
- switch (cmailMoveType[lastLoadGameNumber - 1]) {\r
- case CMAIL_MOVE:\r
- case CMAIL_DRAW:\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Restoring %s for game %d\n",\r
- cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
- \r
- thinkOutput[0] = NULLCHAR;\r
- strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);\r
- fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;\r
- fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;\r
- toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;\r
- toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;\r
- promoChar = cmailMove[lastLoadGameNumber - 1][4];\r
- MakeMove(fromX, fromY, toX, toY, promoChar);\r
- ShowMove(fromX, fromY, toX, toY);\r
- \r
- switch (MateTest(boards[currentMove], PosFlags(currentMove),\r
- EP_UNKNOWN, castlingRights[currentMove]) ) {\r
- case MT_NONE:\r
- case MT_CHECK:\r
- break;\r
- \r
- case MT_CHECKMATE:\r
- case MT_STAINMATE:\r
- if (WhiteOnMove(currentMove)) {\r
- GameEnds(BlackWins, "Black mates", GE_PLAYER);\r
- } else {\r
- GameEnds(WhiteWins, "White mates", GE_PLAYER);\r
- }\r
- break;\r
- \r
- case MT_STALEMATE:\r
- GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);\r
- break;\r
- }\r
-\r
- break;\r
- \r
- case CMAIL_RESIGN:\r
- if (WhiteOnMove(currentMove)) {\r
- GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
- } else {\r
- GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
- }\r
- break;\r
- \r
- case CMAIL_ACCEPT:\r
- GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
- break;\r
- \r
- default:\r
- break;\r
- }\r
- }\r
-\r
- return;\r
-}\r
-\r
-/* Wrapper around LoadGame for use when a Cmail message is loaded */\r
-int\r
-CmailLoadGame(f, gameNumber, title, useList)\r
- FILE *f;\r
- int gameNumber;\r
- char *title;\r
- int useList;\r
-{\r
- int retVal;\r
-\r
- if (gameNumber > nCmailGames) {\r
- DisplayError(_("No more games in this message"), 0);\r
- return FALSE;\r
- }\r
- if (f == lastLoadGameFP) {\r
- int offset = gameNumber - lastLoadGameNumber;\r
- if (offset == 0) {\r
- cmailMsg[0] = NULLCHAR;\r
- if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
- cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
- nCmailMovesRegistered--;\r
- }\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
- if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {\r
- cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;\r
- }\r
- } else {\r
- if (! RegisterMove()) return FALSE;\r
- }\r
- }\r
-\r
- retVal = LoadGame(f, gameNumber, title, useList);\r
-\r
- /* Make move registered during previous look at this game, if any */\r
- MakeRegisteredMove();\r
-\r
- if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {\r
- commentList[currentMove]\r
- = StrSave(cmailCommentList[lastLoadGameNumber - 1]);\r
- DisplayComment(currentMove - 1, commentList[currentMove]);\r
- }\r
-\r
- return retVal;\r
-}\r
-\r
-/* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */\r
-int\r
-ReloadGame(offset)\r
- int offset;\r
-{\r
- int gameNumber = lastLoadGameNumber + offset;\r
- if (lastLoadGameFP == NULL) {\r
- DisplayError(_("No game has been loaded yet"), 0);\r
- return FALSE;\r
- }\r
- if (gameNumber <= 0) {\r
- DisplayError(_("Can't back up any further"), 0);\r
- return FALSE;\r
- }\r
- if (cmailMsgLoaded) {\r
- return CmailLoadGame(lastLoadGameFP, gameNumber,\r
- lastLoadGameTitle, lastLoadGameUseList);\r
- } else {\r
- return LoadGame(lastLoadGameFP, gameNumber,\r
- lastLoadGameTitle, lastLoadGameUseList);\r
- }\r
-}\r
-\r
-\r
-\r
-/* Load the nth game from open file f */\r
-int\r
-LoadGame(f, gameNumber, title, useList)\r
- FILE *f;\r
- int gameNumber;\r
- char *title;\r
- int useList;\r
-{\r
- ChessMove cm;\r
- char buf[MSG_SIZ];\r
- int gn = gameNumber;\r
- ListGame *lg = NULL;\r
- int numPGNTags = 0;\r
- int err;\r
- GameMode oldGameMode;\r
- VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */\r
-\r
- if (appData.debugMode) \r
- fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);\r
-\r
- if (gameMode == Training )\r
- SetTrainingModeOff();\r
-\r
- oldGameMode = gameMode;\r
- if (gameMode != BeginningOfGame) {\r
- Reset(FALSE, TRUE);\r
- }\r
-\r
- gameFileFP = f;\r
- if (lastLoadGameFP != NULL && lastLoadGameFP != f) {\r
- fclose(lastLoadGameFP);\r
- }\r
-\r
- if (useList) {\r
- lg = (ListGame *) ListElem(&gameList, gameNumber-1);\r
- \r
- if (lg) {\r
- fseek(f, lg->offset, 0);\r
- GameListHighlight(gameNumber);\r
- gn = 1;\r
- }\r
- else {\r
- DisplayError(_("Game number out of range"), 0);\r
- return FALSE;\r
- }\r
- } else {\r
- GameListDestroy();\r
- if (fseek(f, 0, 0) == -1) {\r
- if (f == lastLoadGameFP ?\r
- gameNumber == lastLoadGameNumber + 1 :\r
- gameNumber == 1) {\r
- gn = 1;\r
- } else {\r
- DisplayError(_("Can't seek on game file"), 0);\r
- return FALSE;\r
- }\r
- }\r
- }\r
- lastLoadGameFP = f;\r
- lastLoadGameNumber = gameNumber;\r
- strcpy(lastLoadGameTitle, title);\r
- lastLoadGameUseList = useList;\r
-\r
- yynewfile(f);\r
-\r
- if (lg && lg->gameInfo.white && lg->gameInfo.black) {\r
- snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,\r
- lg->gameInfo.black);\r
- DisplayTitle(buf);\r
- } else if (*title != NULLCHAR) {\r
- if (gameNumber > 1) {\r
- sprintf(buf, "%s %d", title, gameNumber);\r
- DisplayTitle(buf);\r
- } else {\r
- DisplayTitle(title);\r
- }\r
- }\r
-\r
- if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {\r
- gameMode = PlayFromGameFile;\r
- ModeHighlight();\r
- }\r
-\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- CopyBoard(boards[0], initialPosition);\r
- StopClocks();\r
-\r
- /*\r
- * Skip the first gn-1 games in the file.\r
- * Also skip over anything that precedes an identifiable \r
- * start of game marker, to avoid being confused by \r
- * garbage at the start of the file. Currently \r
- * recognized start of game markers are the move number "1",\r
- * the pattern "gnuchess .* game", the pattern\r
- * "^[#;%] [^ ]* game file", and a PGN tag block. \r
- * A game that starts with one of the latter two patterns\r
- * will also have a move number 1, possibly\r
- * following a position diagram.\r
- * 5-4-02: Let's try being more lenient and allowing a game to\r
- * start with an unnumbered move. Does that break anything?\r
- */\r
- cm = lastLoadGameStart = (ChessMove) 0;\r
- while (gn > 0) {\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
- switch (cm) {\r
- case (ChessMove) 0:\r
- if (cmailMsgLoaded) {\r
- nCmailGames = CMAIL_MAX_GAMES - gn;\r
- } else {\r
- Reset(TRUE, TRUE);\r
- DisplayError(_("Game not found in file"), 0);\r
- }\r
- return FALSE;\r
-\r
- case GNUChessGame:\r
- case XBoardGame:\r
- gn--;\r
- lastLoadGameStart = cm;\r
- break;\r
- \r
- case MoveNumberOne:\r
- switch (lastLoadGameStart) {\r
- case GNUChessGame:\r
- case XBoardGame:\r
- case PGNTag:\r
- break;\r
- case MoveNumberOne:\r
- case (ChessMove) 0:\r
- gn--; /* count this game */\r
- lastLoadGameStart = cm;\r
- break;\r
- default:\r
- /* impossible */\r
- break;\r
- }\r
- break;\r
-\r
- case PGNTag:\r
- switch (lastLoadGameStart) {\r
- case GNUChessGame:\r
- case PGNTag:\r
- case MoveNumberOne:\r
- case (ChessMove) 0:\r
- gn--; /* count this game */\r
- lastLoadGameStart = cm;\r
- break;\r
- case XBoardGame:\r
- lastLoadGameStart = cm; /* game counted already */\r
- break;\r
- default:\r
- /* impossible */\r
- break;\r
- }\r
- if (gn > 0) {\r
- do {\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
- } while (cm == PGNTag || cm == Comment);\r
- }\r
- break;\r
-\r
- case WhiteWins:\r
- case BlackWins:\r
- case GameIsDrawn:\r
- if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {\r
- if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]\r
- != CMAIL_OLD_RESULT) {\r
- nCmailResults ++ ;\r
- cmailResult[ CMAIL_MAX_GAMES\r
- - gn - 1] = CMAIL_OLD_RESULT;\r
- }\r
- }\r
- break;\r
-\r
- case NormalMove:\r
- /* Only a NormalMove can be at the start of a game\r
- * without a position diagram. */\r
- if (lastLoadGameStart == (ChessMove) 0) {\r
- gn--;\r
- lastLoadGameStart = MoveNumberOne;\r
- }\r
- break;\r
-\r
- default:\r
- break;\r
- }\r
- }\r
- \r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);\r
-\r
- if (cm == XBoardGame) {\r
- /* Skip any header junk before position diagram and/or move 1 */\r
- for (;;) {\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
-\r
- if (cm == (ChessMove) 0 ||\r
- cm == GNUChessGame || cm == XBoardGame) {\r
- /* Empty game; pretend end-of-file and handle later */\r
- cm = (ChessMove) 0;\r
- break;\r
- }\r
-\r
- if (cm == MoveNumberOne || cm == PositionDiagram ||\r
- cm == PGNTag || cm == Comment)\r
- break;\r
- }\r
- } else if (cm == GNUChessGame) {\r
- if (gameInfo.event != NULL) {\r
- free(gameInfo.event);\r
- }\r
- gameInfo.event = StrSave(yy_text);\r
- } \r
-\r
- startedFromSetupPosition = FALSE;\r
- while (cm == PGNTag) {\r
- if (appData.debugMode) \r
- fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);\r
- err = ParsePGNTag(yy_text, &gameInfo);\r
- if (!err) numPGNTags++;\r
-\r
- /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */\r
- if(gameInfo.variant != oldVariant) {\r
- startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */\r
- InitPosition(TRUE);\r
- oldVariant = gameInfo.variant;\r
- if (appData.debugMode) \r
- fprintf(debugFP, "New variant %d\n", (int) oldVariant);\r
- }\r
-\r
-\r
- if (gameInfo.fen != NULL) {\r
- Board initial_position;\r
- startedFromSetupPosition = TRUE;\r
- if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {\r
- Reset(TRUE, TRUE);\r
- DisplayError(_("Bad FEN position in file"), 0);\r
- return FALSE;\r
- }\r
- CopyBoard(boards[0], initial_position);\r
- if (blackPlaysFirst) {\r
- currentMove = forwardMostMove = backwardMostMove = 1;\r
- CopyBoard(boards[1], initial_position);\r
- strcpy(moveList[0], "");\r
- strcpy(parseList[0], "");\r
- timeRemaining[0][1] = whiteTimeRemaining;\r
- timeRemaining[1][1] = blackTimeRemaining;\r
- if (commentList[0] != NULL) {\r
- commentList[1] = commentList[0];\r
- commentList[0] = NULL;\r
- }\r
- } else {\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- }\r
- /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */\r
- { int i;\r
- initialRulePlies = FENrulePlies;\r
- epStatus[forwardMostMove] = FENepStatus;\r
- for( i=0; i< nrCastlingRights; i++ )\r
- initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
- }\r
- yyboardindex = forwardMostMove;\r
- free(gameInfo.fen);\r
- gameInfo.fen = NULL;\r
- }\r
-\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
-\r
- /* Handle comments interspersed among the tags */\r
- while (cm == Comment) {\r
- char *p;\r
- if (appData.debugMode) \r
- fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
- p = yy_text;\r
- if (*p == '{' || *p == '[' || *p == '(') {\r
- p[strlen(p) - 1] = NULLCHAR;\r
- p++;\r
- }\r
- while (*p == '\n') p++;\r
- AppendComment(currentMove, p);\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
- }\r
- }\r
-\r
- /* don't rely on existence of Event tag since if game was\r
- * pasted from clipboard the Event tag may not exist\r
- */\r
- if (numPGNTags > 0){\r
- char *tags;\r
- if (gameInfo.variant == VariantNormal) {\r
- gameInfo.variant = StringToVariant(gameInfo.event);\r
- }\r
- if (!matchMode) {\r
- if( appData.autoDisplayTags ) {\r
- tags = PGNTags(&gameInfo);\r
- TagsPopUp(tags, CmailMsg());\r
- free(tags);\r
- }\r
- }\r
- } else {\r
- /* Make something up, but don't display it now */\r
- SetGameInfo();\r
- TagsPopDown();\r
- }\r
-\r
- if (cm == PositionDiagram) {\r
- int i, j;\r
- char *p;\r
- Board initial_position;\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);\r
-\r
- if (!startedFromSetupPosition) {\r
- p = yy_text;\r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--)\r
- for (j = BOARD_LEFT; j < BOARD_RGHT; p++)\r
- switch (*p) {\r
- case '[':\r
- case '-':\r
- case ' ':\r
- case '\t':\r
- case '\n':\r
- case '\r':\r
- break;\r
- default:\r
- initial_position[i][j++] = CharToPiece(*p);\r
- break;\r
- }\r
- while (*p == ' ' || *p == '\t' ||\r
- *p == '\n' || *p == '\r') p++;\r
- \r
- if (strncmp(p, "black", strlen("black"))==0)\r
- blackPlaysFirst = TRUE;\r
- else\r
- blackPlaysFirst = FALSE;\r
- startedFromSetupPosition = TRUE;\r
- \r
- CopyBoard(boards[0], initial_position);\r
- if (blackPlaysFirst) {\r
- currentMove = forwardMostMove = backwardMostMove = 1;\r
- CopyBoard(boards[1], initial_position);\r
- strcpy(moveList[0], "");\r
- strcpy(parseList[0], "");\r
- timeRemaining[0][1] = whiteTimeRemaining;\r
- timeRemaining[1][1] = blackTimeRemaining;\r
- if (commentList[0] != NULL) {\r
- commentList[1] = commentList[0];\r
- commentList[0] = NULL;\r
- }\r
- } else {\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- }\r
- }\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
- }\r
-\r
- if (first.pr == NoProc) {\r
- StartChessProgram(&first);\r
- }\r
- InitChessProgram(&first, FALSE);\r
- SendToProgram("force\n", &first);\r
- if (startedFromSetupPosition) {\r
- SendBoard(&first, forwardMostMove);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Load Game\n");\r
- }\r
- DisplayBothClocks();\r
- } \r
-\r
- /* [HGM] server: flag to write setup moves in broadcast file as one */\r
- loadFlag = appData.suppressLoadMoves;\r
-\r
- while (cm == Comment) {\r
- char *p;\r
- if (appData.debugMode) \r
- fprintf(debugFP, "Parsed Comment: %s\n", yy_text);\r
- p = yy_text;\r
- if (*p == '{' || *p == '[' || *p == '(') {\r
- p[strlen(p) - 1] = NULLCHAR;\r
- p++;\r
- }\r
- while (*p == '\n') p++;\r
- AppendComment(currentMove, p);\r
- yyboardindex = forwardMostMove;\r
- cm = (ChessMove) yylex();\r
- }\r
-\r
- if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||\r
- cm == WhiteWins || cm == BlackWins ||\r
- cm == GameIsDrawn || cm == GameUnfinished) {\r
- DisplayMessage("", _("No moves in game"));\r
- if (cmailMsgLoaded) {\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Setting flipView to %d.\n", FALSE);\r
- ClearHighlights();\r
- flipView = FALSE;\r
- }\r
- DrawPosition(FALSE, boards[currentMove]);\r
- DisplayBothClocks();\r
- gameMode = EditGame;\r
- ModeHighlight();\r
- gameFileFP = NULL;\r
- cmailOldMove = 0;\r
- return TRUE;\r
- }\r
-\r
- // [HGM] PV info: routine tests if comment empty\r
- if (!matchMode && (pausing || appData.timeDelay != 0)) {\r
- DisplayComment(currentMove - 1, commentList[currentMove]);\r
- }\r
- if (!matchMode && appData.timeDelay != 0) \r
- DrawPosition(FALSE, boards[currentMove]);\r
-\r
- if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {\r
- programStats.ok_to_send = 1;\r
- }\r
-\r
- /* if the first token after the PGN tags is a move\r
- * and not move number 1, retrieve it from the parser \r
- */\r
- if (cm != MoveNumberOne)\r
- LoadGameOneMove(cm);\r
-\r
- /* load the remaining moves from the file */\r
- while (LoadGameOneMove((ChessMove)0)) {\r
- timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
- timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
- }\r
-\r
- /* rewind to the start of the game */\r
- currentMove = backwardMostMove;\r
-\r
- HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
-\r
- if (oldGameMode == AnalyzeFile ||\r
- oldGameMode == AnalyzeMode) {\r
- AnalyzeFileEvent();\r
- }\r
-\r
- if (matchMode || appData.timeDelay == 0) {\r
- ToEndEvent();\r
- gameMode = EditGame;\r
- ModeHighlight();\r
- } else if (appData.timeDelay > 0) {\r
- AutoPlayGameLoop();\r
- }\r
-\r
- if (appData.debugMode) \r
- fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);\r
-\r
- loadFlag = 0; /* [HGM] true game starts */\r
- return TRUE;\r
-}\r
-\r
-/* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */\r
-int\r
-ReloadPosition(offset)\r
- int offset;\r
-{\r
- int positionNumber = lastLoadPositionNumber + offset;\r
- if (lastLoadPositionFP == NULL) {\r
- DisplayError(_("No position has been loaded yet"), 0);\r
- return FALSE;\r
- }\r
- if (positionNumber <= 0) {\r
- DisplayError(_("Can't back up any further"), 0);\r
- return FALSE;\r
- }\r
- return LoadPosition(lastLoadPositionFP, positionNumber,\r
- lastLoadPositionTitle);\r
-}\r
-\r
-/* Load the nth position from the given file */\r
-int\r
-LoadPositionFromFile(filename, n, title)\r
- char *filename;\r
- int n;\r
- char *title;\r
-{\r
- FILE *f;\r
- char buf[MSG_SIZ];\r
-\r
- if (strcmp(filename, "-") == 0) {\r
- return LoadPosition(stdin, n, "stdin");\r
- } else {\r
- f = fopen(filename, "rb");\r
- if (f == NULL) {\r
- snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);\r
- DisplayError(buf, errno);\r
- return FALSE;\r
- } else {\r
- return LoadPosition(f, n, title);\r
- }\r
- }\r
-}\r
-\r
-/* Load the nth position from the given open file, and close it */\r
-int\r
-LoadPosition(f, positionNumber, title)\r
- FILE *f;\r
- int positionNumber;\r
- char *title;\r
-{\r
- char *p, line[MSG_SIZ];\r
- Board initial_position;\r
- int i, j, fenMode, pn;\r
- \r
- if (gameMode == Training )\r
- SetTrainingModeOff();\r
-\r
- if (gameMode != BeginningOfGame) {\r
- Reset(FALSE, TRUE);\r
- }\r
- if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {\r
- fclose(lastLoadPositionFP);\r
- }\r
- if (positionNumber == 0) positionNumber = 1;\r
- lastLoadPositionFP = f;\r
- lastLoadPositionNumber = positionNumber;\r
- strcpy(lastLoadPositionTitle, title);\r
- if (first.pr == NoProc) {\r
- StartChessProgram(&first);\r
- InitChessProgram(&first, FALSE);\r
- } \r
- pn = positionNumber;\r
- if (positionNumber < 0) {\r
- /* Negative position number means to seek to that byte offset */\r
- if (fseek(f, -positionNumber, 0) == -1) {\r
- DisplayError(_("Can't seek on position file"), 0);\r
- return FALSE;\r
- };\r
- pn = 1;\r
- } else {\r
- if (fseek(f, 0, 0) == -1) {\r
- if (f == lastLoadPositionFP ?\r
- positionNumber == lastLoadPositionNumber + 1 :\r
- positionNumber == 1) {\r
- pn = 1;\r
- } else {\r
- DisplayError(_("Can't seek on position file"), 0);\r
- return FALSE;\r
- }\r
- }\r
- }\r
- /* See if this file is FEN or old-style xboard */\r
- if (fgets(line, MSG_SIZ, f) == NULL) {\r
- DisplayError(_("Position not found in file"), 0);\r
- return FALSE;\r
- }\r
-#if 0\r
- switch (line[0]) {\r
- case '#': case 'x':\r
- default:\r
- fenMode = FALSE;\r
- break;\r
- case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':\r
- case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':\r
- case '1': case '2': case '3': case '4': case '5': case '6':\r
- case '7': case '8': case '9':\r
- case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':\r
- case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':\r
- case 'C': case 'W': case 'c': case 'w': \r
- fenMode = TRUE;\r
- break;\r
- }\r
-#else\r
- // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces\r
- fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;\r
-#endif\r
-\r
- if (pn >= 2) {\r
- if (fenMode || line[0] == '#') pn--;\r
- while (pn > 0) {\r
- /* skip positions before number pn */\r
- if (fgets(line, MSG_SIZ, f) == NULL) {\r
- Reset(TRUE, TRUE);\r
- DisplayError(_("Position not found in file"), 0);\r
- return FALSE;\r
- }\r
- if (fenMode || line[0] == '#') pn--;\r
- }\r
- }\r
-\r
- if (fenMode) {\r
- if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {\r
- DisplayError(_("Bad FEN position in file"), 0);\r
- return FALSE;\r
- }\r
- } else {\r
- (void) fgets(line, MSG_SIZ, f);\r
- (void) fgets(line, MSG_SIZ, f);\r
- \r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
- (void) fgets(line, MSG_SIZ, f);\r
- for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {\r
- if (*p == ' ')\r
- continue;\r
- initial_position[i][j++] = CharToPiece(*p);\r
- }\r
- }\r
- \r
- blackPlaysFirst = FALSE;\r
- if (!feof(f)) {\r
- (void) fgets(line, MSG_SIZ, f);\r
- if (strncmp(line, "black", strlen("black"))==0)\r
- blackPlaysFirst = TRUE;\r
- }\r
- }\r
- startedFromSetupPosition = TRUE;\r
- \r
- SendToProgram("force\n", &first);\r
- CopyBoard(boards[0], initial_position);\r
- if (blackPlaysFirst) {\r
- currentMove = forwardMostMove = backwardMostMove = 1;\r
- strcpy(moveList[0], "");\r
- strcpy(parseList[0], "");\r
- CopyBoard(boards[1], initial_position);\r
- DisplayMessage("", _("Black to play"));\r
- } else {\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- DisplayMessage("", _("White to play"));\r
- }\r
- /* [HGM] copy FEN attributes as well */\r
- { int i;\r
- initialRulePlies = FENrulePlies;\r
- epStatus[forwardMostMove] = FENepStatus;\r
- for( i=0; i< nrCastlingRights; i++ )\r
- castlingRights[forwardMostMove][i] = FENcastlingRights[i];\r
- }\r
- SendBoard(&first, forwardMostMove);\r
- if (appData.debugMode) {\r
-int i, j;\r
- for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}\r
- for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");\r
- fprintf(debugFP, "Load Position\n");\r
- }\r
-\r
- if (positionNumber > 1) {\r
- sprintf(line, "%s %d", title, positionNumber);\r
- DisplayTitle(line);\r
- } else {\r
- DisplayTitle(title);\r
- }\r
- gameMode = EditGame;\r
- ModeHighlight();\r
- ResetClocks();\r
- timeRemaining[0][1] = whiteTimeRemaining;\r
- timeRemaining[1][1] = blackTimeRemaining;\r
- DrawPosition(FALSE, boards[currentMove]);\r
- \r
- return TRUE;\r
-}\r
-\r
-\r
-void\r
-CopyPlayerNameIntoFileName(dest, src)\r
- char **dest, *src;\r
-{\r
- while (*src != NULLCHAR && *src != ',') {\r
- if (*src == ' ') {\r
- *(*dest)++ = '_';\r
- src++;\r
- } else {\r
- *(*dest)++ = *src++;\r
- }\r
- }\r
-}\r
-\r
-char *DefaultFileName(ext)\r
- char *ext;\r
-{\r
- static char def[MSG_SIZ];\r
- char *p;\r
-\r
- if (gameInfo.white != NULL && gameInfo.white[0] != '-') {\r
- p = def;\r
- CopyPlayerNameIntoFileName(&p, gameInfo.white);\r
- *p++ = '-';\r
- CopyPlayerNameIntoFileName(&p, gameInfo.black);\r
- *p++ = '.';\r
- strcpy(p, ext);\r
- } else {\r
- def[0] = NULLCHAR;\r
- }\r
- return def;\r
-}\r
-\r
-/* Save the current game to the given file */\r
-int\r
-SaveGameToFile(filename, append)\r
- char *filename;\r
- int append;\r
-{\r
- FILE *f;\r
- char buf[MSG_SIZ];\r
-\r
- if (strcmp(filename, "-") == 0) {\r
- return SaveGame(stdout, 0, NULL);\r
- } else {\r
- f = fopen(filename, append ? "a" : "w");\r
- if (f == NULL) {\r
- snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);\r
- DisplayError(buf, errno);\r
- return FALSE;\r
- } else {\r
- return SaveGame(f, 0, NULL);\r
- }\r
- }\r
-}\r
-\r
-char *\r
-SavePart(str)\r
- char *str;\r
-{\r
- static char buf[MSG_SIZ];\r
- char *p;\r
- \r
- p = strchr(str, ' ');\r
- if (p == NULL) return str;\r
- strncpy(buf, str, p - str);\r
- buf[p - str] = NULLCHAR;\r
- return buf;\r
-}\r
-\r
-#define PGN_MAX_LINE 75\r
-\r
-#define PGN_SIDE_WHITE 0\r
-#define PGN_SIDE_BLACK 1\r
-\r
-/* [AS] */\r
-static int FindFirstMoveOutOfBook( int side )\r
-{\r
- int result = -1;\r
-\r
- if( backwardMostMove == 0 && ! startedFromSetupPosition) {\r
- int index = backwardMostMove;\r
- int has_book_hit = 0;\r
-\r
- if( (index % 2) != side ) {\r
- index++;\r
- }\r
-\r
- while( index < forwardMostMove ) {\r
- /* Check to see if engine is in book */\r
- int depth = pvInfoList[index].depth;\r
- int score = pvInfoList[index].score;\r
- int in_book = 0;\r
-\r
- if( depth <= 2 ) {\r
- in_book = 1;\r
- }\r
- else if( score == 0 && depth == 63 ) {\r
- in_book = 1; /* Zappa */\r
- }\r
- else if( score == 2 && depth == 99 ) {\r
- in_book = 1; /* Abrok */\r
- }\r
-\r
- has_book_hit += in_book;\r
-\r
- if( ! in_book ) {\r
- result = index;\r
-\r
- break;\r
- }\r
-\r
- index += 2;\r
- }\r
- }\r
-\r
- return result;\r
-}\r
-\r
-/* [AS] */\r
-void GetOutOfBookInfo( char * buf )\r
-{\r
- int oob[2];\r
- int i;\r
- int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
-\r
- oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );\r
- oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );\r
-\r
- *buf = '\0';\r
-\r
- if( oob[0] >= 0 || oob[1] >= 0 ) {\r
- for( i=0; i<2; i++ ) {\r
- int idx = oob[i];\r
-\r
- if( idx >= 0 ) {\r
- if( i > 0 && oob[0] >= 0 ) {\r
- strcat( buf, " " );\r
- }\r
-\r
- sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );\r
- sprintf( buf+strlen(buf), "%s%.2f", \r
- pvInfoList[idx].score >= 0 ? "+" : "",\r
- pvInfoList[idx].score / 100.0 );\r
- }\r
- }\r
- }\r
-}\r
-\r
-/* Save game in PGN style and close the file */\r
-int\r
-SaveGamePGN(f)\r
- FILE *f;\r
-{\r
- int i, offset, linelen, newblock;\r
- time_t tm;\r
-// char *movetext;\r
- char numtext[32];\r
- int movelen, numlen, blank;\r
- char move_buffer[100]; /* [AS] Buffer for move+PV info */\r
-\r
- offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
- \r
- tm = time((time_t *) NULL);\r
- \r
- PrintPGNTags(f, &gameInfo);\r
- \r
- if (backwardMostMove > 0 || startedFromSetupPosition) {\r
- char *fen = PositionToFEN(backwardMostMove, NULL);\r
- fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);\r
- fprintf(f, "\n{--------------\n");\r
- PrintPosition(f, backwardMostMove);\r
- fprintf(f, "--------------}\n");\r
- free(fen);\r
- }\r
- else {\r
- /* [AS] Out of book annotation */\r
- if( appData.saveOutOfBookInfo ) {\r
- char buf[64];\r
-\r
- GetOutOfBookInfo( buf );\r
-\r
- if( buf[0] != '\0' ) {\r
- fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf ); \r
- }\r
- }\r
-\r
- fprintf(f, "\n");\r
- }\r
-\r
- i = backwardMostMove;\r
- linelen = 0;\r
- newblock = TRUE;\r
-\r
- while (i < forwardMostMove) {\r
- /* Print comments preceding this move */\r
- if (commentList[i] != NULL) {\r
- if (linelen > 0) fprintf(f, "\n");\r
- fprintf(f, "{\n%s}\n", commentList[i]);\r
- linelen = 0;\r
- newblock = TRUE;\r
- }\r
-\r
- /* Format move number */\r
- if ((i % 2) == 0) {\r
- sprintf(numtext, "%d.", (i - offset)/2 + 1);\r
- } else {\r
- if (newblock) {\r
- sprintf(numtext, "%d...", (i - offset)/2 + 1);\r
- } else {\r
- numtext[0] = NULLCHAR;\r
- }\r
- }\r
- numlen = strlen(numtext);\r
- newblock = FALSE;\r
-\r
- /* Print move number */\r
- blank = linelen > 0 && numlen > 0;\r
- if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {\r
- fprintf(f, "\n");\r
- linelen = 0;\r
- blank = 0;\r
- }\r
- if (blank) {\r
- fprintf(f, " ");\r
- linelen++;\r
- }\r
- fprintf(f, numtext);\r
- linelen += numlen;\r
-\r
- /* Get move */\r
- strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited\r
- movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */\r
- if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
- int p = movelen - 1;\r
- if(move_buffer[p] == ' ') p--;\r
- if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info\r
- while(p && move_buffer[--p] != '(');\r
- if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;\r
- }\r
- }\r
-\r
- /* Print move */\r
- blank = linelen > 0 && movelen > 0;\r
- if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
- fprintf(f, "\n");\r
- linelen = 0;\r
- blank = 0;\r
- }\r
- if (blank) {\r
- fprintf(f, " ");\r
- linelen++;\r
- }\r
- fprintf(f, move_buffer);\r
- linelen += movelen;\r
-\r
- /* [AS] Add PV info if present */\r
- if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {\r
- /* [HGM] add time */\r
- char buf[MSG_SIZ]; int seconds = 0;\r
-\r
-#if 1\r
- if(i >= backwardMostMove) {\r
- if(WhiteOnMove(i))\r
- seconds = timeRemaining[0][i] - timeRemaining[0][i+1]\r
- + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);\r
- else\r
- seconds = timeRemaining[1][i] - timeRemaining[1][i+1]\r
- + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);\r
- }\r
- seconds = (seconds+50)/100; // deci-seconds, rounded to nearest\r
-#else\r
- seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time\r
-#endif\r
-\r
- if( seconds <= 0) buf[0] = 0; else\r
- if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {\r
- seconds = (seconds + 4)/10; // round to full seconds\r
- if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else\r
- sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);\r
- }\r
-\r
- sprintf( move_buffer, "{%s%.2f/%d%s}", \r
- pvInfoList[i].score >= 0 ? "+" : "",\r
- pvInfoList[i].score / 100.0,\r
- pvInfoList[i].depth,\r
- buf );\r
-\r
- movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */\r
-\r
- /* Print score/depth */\r
- blank = linelen > 0 && movelen > 0;\r
- if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {\r
- fprintf(f, "\n");\r
- linelen = 0;\r
- blank = 0;\r
- }\r
- if (blank) {\r
- fprintf(f, " ");\r
- linelen++;\r
- }\r
- fprintf(f, move_buffer);\r
- linelen += movelen;\r
- }\r
-\r
- i++;\r
- }\r
- \r
- /* Start a new line */\r
- if (linelen > 0) fprintf(f, "\n");\r
-\r
- /* Print comments after last move */\r
- if (commentList[i] != NULL) {\r
- fprintf(f, "{\n%s}\n", commentList[i]);\r
- }\r
-\r
- /* Print result */\r
- if (gameInfo.resultDetails != NULL &&\r
- gameInfo.resultDetails[0] != NULLCHAR) {\r
- fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,\r
- PGNResult(gameInfo.result));\r
- } else {\r
- fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
- }\r
-\r
- fclose(f);\r
- return TRUE;\r
-}\r
-\r
-/* Save game in old style and close the file */\r
-int\r
-SaveGameOldStyle(f)\r
- FILE *f;\r
-{\r
- int i, offset;\r
- time_t tm;\r
- \r
- tm = time((time_t *) NULL);\r
- \r
- fprintf(f, "# %s game file -- %s", programName, ctime(&tm));\r
- PrintOpponents(f);\r
- \r
- if (backwardMostMove > 0 || startedFromSetupPosition) {\r
- fprintf(f, "\n[--------------\n");\r
- PrintPosition(f, backwardMostMove);\r
- fprintf(f, "--------------]\n");\r
- } else {\r
- fprintf(f, "\n");\r
- }\r
-\r
- i = backwardMostMove;\r
- offset = backwardMostMove & (~1L); /* output move numbers start at 1 */\r
-\r
- while (i < forwardMostMove) {\r
- if (commentList[i] != NULL) {\r
- fprintf(f, "[%s]\n", commentList[i]);\r
- }\r
-\r
- if ((i % 2) == 1) {\r
- fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);\r
- i++;\r
- } else {\r
- fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);\r
- i++;\r
- if (commentList[i] != NULL) {\r
- fprintf(f, "\n");\r
- continue;\r
- }\r
- if (i >= forwardMostMove) {\r
- fprintf(f, "\n");\r
- break;\r
- }\r
- fprintf(f, "%s\n", parseList[i]);\r
- i++;\r
- }\r
- }\r
- \r
- if (commentList[i] != NULL) {\r
- fprintf(f, "[%s]\n", commentList[i]);\r
- }\r
-\r
- /* This isn't really the old style, but it's close enough */\r
- if (gameInfo.resultDetails != NULL &&\r
- gameInfo.resultDetails[0] != NULLCHAR) {\r
- fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),\r
- gameInfo.resultDetails);\r
- } else {\r
- fprintf(f, "%s\n\n", PGNResult(gameInfo.result));\r
- }\r
-\r
- fclose(f);\r
- return TRUE;\r
-}\r
-\r
-/* Save the current game to open file f and close the file */\r
-int\r
-SaveGame(f, dummy, dummy2)\r
- FILE *f;\r
- int dummy;\r
- char *dummy2;\r
-{\r
- if (gameMode == EditPosition) EditPositionDone();\r
- if (appData.oldSaveStyle)\r
- return SaveGameOldStyle(f);\r
- else\r
- return SaveGamePGN(f);\r
-}\r
-\r
-/* Save the current position to the given file */\r
-int\r
-SavePositionToFile(filename)\r
- char *filename;\r
-{\r
- FILE *f;\r
- char buf[MSG_SIZ];\r
-\r
- if (strcmp(filename, "-") == 0) {\r
- return SavePosition(stdout, 0, NULL);\r
- } else {\r
- f = fopen(filename, "a");\r
- if (f == NULL) {\r
- snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);\r
- DisplayError(buf, errno);\r
- return FALSE;\r
- } else {\r
- SavePosition(f, 0, NULL);\r
- return TRUE;\r
- }\r
- }\r
-}\r
-\r
-/* Save the current position to the given open file and close the file */\r
-int\r
-SavePosition(f, dummy, dummy2)\r
- FILE *f;\r
- int dummy;\r
- char *dummy2;\r
-{\r
- time_t tm;\r
- char *fen;\r
- \r
- if (appData.oldSaveStyle) {\r
- tm = time((time_t *) NULL);\r
- \r
- fprintf(f, "# %s position file -- %s", programName, ctime(&tm));\r
- PrintOpponents(f);\r
- fprintf(f, "[--------------\n");\r
- PrintPosition(f, currentMove);\r
- fprintf(f, "--------------]\n");\r
- } else {\r
- fen = PositionToFEN(currentMove, NULL);\r
- fprintf(f, "%s\n", fen);\r
- free(fen);\r
- }\r
- fclose(f);\r
- return TRUE;\r
-}\r
-\r
-void\r
-ReloadCmailMsgEvent(unregister)\r
- int unregister;\r
-{\r
-#if !WIN32\r
- static char *inFilename = NULL;\r
- static char *outFilename;\r
- int i;\r
- struct stat inbuf, outbuf;\r
- int status;\r
- \r
- /* Any registered moves are unregistered if unregister is set, */\r
- /* i.e. invoked by the signal handler */\r
- if (unregister) {\r
- for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
- cmailMoveRegistered[i] = FALSE;\r
- if (cmailCommentList[i] != NULL) {\r
- free(cmailCommentList[i]);\r
- cmailCommentList[i] = NULL;\r
- }\r
- }\r
- nCmailMovesRegistered = 0;\r
- }\r
-\r
- for (i = 0; i < CMAIL_MAX_GAMES; i ++) {\r
- cmailResult[i] = CMAIL_NOT_RESULT;\r
- }\r
- nCmailResults = 0;\r
-\r
- if (inFilename == NULL) {\r
- /* Because the filenames are static they only get malloced once */\r
- /* and they never get freed */\r
- inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);\r
- sprintf(inFilename, "%s.game.in", appData.cmailGameName);\r
-\r
- outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);\r
- sprintf(outFilename, "%s.out", appData.cmailGameName);\r
- }\r
- \r
- status = stat(outFilename, &outbuf);\r
- if (status < 0) {\r
- cmailMailedMove = FALSE;\r
- } else {\r
- status = stat(inFilename, &inbuf);\r
- cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);\r
- }\r
- \r
- /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE\r
- counts the games, notes how each one terminated, etc.\r
- \r
- It would be nice to remove this kludge and instead gather all\r
- the information while building the game list. (And to keep it\r
- in the game list nodes instead of having a bunch of fixed-size\r
- parallel arrays.) Note this will require getting each game's\r
- termination from the PGN tags, as the game list builder does\r
- not process the game moves. --mann\r
- */\r
- cmailMsgLoaded = TRUE;\r
- LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);\r
- \r
- /* Load first game in the file or popup game menu */\r
- LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);\r
-\r
-#endif /* !WIN32 */\r
- return;\r
-}\r
-\r
-int\r
-RegisterMove()\r
-{\r
- FILE *f;\r
- char string[MSG_SIZ];\r
-\r
- if ( cmailMailedMove\r
- || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {\r
- return TRUE; /* Allow free viewing */\r
- }\r
-\r
- /* Unregister move to ensure that we don't leave RegisterMove */\r
- /* with the move registered when the conditions for registering no */\r
- /* longer hold */\r
- if (cmailMoveRegistered[lastLoadGameNumber - 1]) {\r
- cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;\r
- nCmailMovesRegistered --;\r
-\r
- if (cmailCommentList[lastLoadGameNumber - 1] != NULL) \r
- {\r
- free(cmailCommentList[lastLoadGameNumber - 1]);\r
- cmailCommentList[lastLoadGameNumber - 1] = NULL;\r
- }\r
- }\r
-\r
- if (cmailOldMove == -1) {\r
- DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);\r
- return FALSE;\r
- }\r
-\r
- if (currentMove > cmailOldMove + 1) {\r
- DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);\r
- return FALSE;\r
- }\r
-\r
- if (currentMove < cmailOldMove) {\r
- DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);\r
- return FALSE;\r
- }\r
-\r
- if (forwardMostMove > currentMove) {\r
- /* Silently truncate extra moves */\r
- TruncateGame();\r
- }\r
-\r
- if ( (currentMove == cmailOldMove + 1)\r
- || ( (currentMove == cmailOldMove)\r
- && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)\r
- || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {\r
- if (gameInfo.result != GameUnfinished) {\r
- cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;\r
- }\r
-\r
- if (commentList[currentMove] != NULL) {\r
- cmailCommentList[lastLoadGameNumber - 1]\r
- = StrSave(commentList[currentMove]);\r
- }\r
- strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Saving %s for game %d\n",\r
- cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);\r
-\r
- sprintf(string,\r
- "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);\r
- \r
- f = fopen(string, "w");\r
- if (appData.oldSaveStyle) {\r
- SaveGameOldStyle(f); /* also closes the file */\r
- \r
- sprintf(string, "%s.pos.out", appData.cmailGameName);\r
- f = fopen(string, "w");\r
- SavePosition(f, 0, NULL); /* also closes the file */\r
- } else {\r
- fprintf(f, "{--------------\n");\r
- PrintPosition(f, currentMove);\r
- fprintf(f, "--------------}\n\n");\r
- \r
- SaveGame(f, 0, NULL); /* also closes the file*/\r
- }\r
- \r
- cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;\r
- nCmailMovesRegistered ++;\r
- } else if (nCmailGames == 1) {\r
- DisplayError(_("You have not made a move yet"), 0);\r
- return FALSE;\r
- }\r
-\r
- return TRUE;\r
-}\r
-\r
-void\r
-MailMoveEvent()\r
-{\r
-#if !WIN32\r
- static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";\r
- FILE *commandOutput;\r
- char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];\r
- int nBytes = 0; /* Suppress warnings on uninitialized variables */\r
- int nBuffers;\r
- int i;\r
- int archived;\r
- char *arcDir;\r
-\r
- if (! cmailMsgLoaded) {\r
- DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);\r
- return;\r
- }\r
-\r
- if (nCmailGames == nCmailResults) {\r
- DisplayError(_("No unfinished games"), 0);\r
- return;\r
- }\r
-\r
-#if CMAIL_PROHIBIT_REMAIL\r
- if (cmailMailedMove) {\r
- sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);\r
- DisplayError(msg, 0);\r
- return;\r
- }\r
-#endif\r
-\r
- if (! (cmailMailedMove || RegisterMove())) return;\r
- \r
- if ( cmailMailedMove\r
- || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {\r
- sprintf(string, partCommandString,\r
- appData.debugMode ? " -v" : "", appData.cmailGameName);\r
- commandOutput = popen(string, "r");\r
-\r
- if (commandOutput == NULL) {\r
- DisplayError(_("Failed to invoke cmail"), 0);\r
- } else {\r
- for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {\r
- nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);\r
- }\r
- if (nBuffers > 1) {\r
- (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);\r
- (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);\r
- nBytes = MSG_SIZ - 1;\r
- } else {\r
- (void) memcpy(msg, buffer, nBytes);\r
- }\r
- *(msg + nBytes) = '\0'; /* \0 for end-of-string*/\r
-\r
- if(StrStr(msg, "Mailed cmail message to ") != NULL) {\r
- cmailMailedMove = TRUE; /* Prevent >1 moves */\r
-\r
- archived = TRUE;\r
- for (i = 0; i < nCmailGames; i ++) {\r
- if (cmailResult[i] == CMAIL_NOT_RESULT) {\r
- archived = FALSE;\r
- }\r
- }\r
- if ( archived\r
- && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))\r
- != NULL)) {\r
- sprintf(buffer, "%s/%s.%s.archive",\r
- arcDir,\r
- appData.cmailGameName,\r
- gameInfo.date);\r
- LoadGameFromFile(buffer, 1, buffer, FALSE);\r
- cmailMsgLoaded = FALSE;\r
- }\r
- }\r
-\r
- DisplayInformation(msg);\r
- pclose(commandOutput);\r
- }\r
- } else {\r
- if ((*cmailMsg) != '\0') {\r
- DisplayInformation(cmailMsg);\r
- }\r
- }\r
-\r
- return;\r
-#endif /* !WIN32 */\r
-}\r
-\r
-char *\r
-CmailMsg()\r
-{\r
-#if WIN32\r
- return NULL;\r
-#else\r
- int prependComma = 0;\r
- char number[5];\r
- char string[MSG_SIZ]; /* Space for game-list */\r
- int i;\r
- \r
- if (!cmailMsgLoaded) return "";\r
-\r
- if (cmailMailedMove) {\r
- sprintf(cmailMsg, _("Waiting for reply from opponent\n"));\r
- } else {\r
- /* Create a list of games left */\r
- sprintf(string, "[");\r
- for (i = 0; i < nCmailGames; i ++) {\r
- if (! ( cmailMoveRegistered[i]\r
- || (cmailResult[i] == CMAIL_OLD_RESULT))) {\r
- if (prependComma) {\r
- sprintf(number, ",%d", i + 1);\r
- } else {\r
- sprintf(number, "%d", i + 1);\r
- prependComma = 1;\r
- }\r
- \r
- strcat(string, number);\r
- }\r
- }\r
- strcat(string, "]");\r
-\r
- if (nCmailMovesRegistered + nCmailResults == 0) {\r
- switch (nCmailGames) {\r
- case 1:\r
- sprintf(cmailMsg,\r
- _("Still need to make move for game\n"));\r
- break;\r
- \r
- case 2:\r
- sprintf(cmailMsg,\r
- _("Still need to make moves for both games\n"));\r
- break;\r
- \r
- default:\r
- sprintf(cmailMsg,\r
- _("Still need to make moves for all %d games\n"),\r
- nCmailGames);\r
- break;\r
- }\r
- } else {\r
- switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {\r
- case 1:\r
- sprintf(cmailMsg,\r
- _("Still need to make a move for game %s\n"),\r
- string);\r
- break;\r
- \r
- case 0:\r
- if (nCmailResults == nCmailGames) {\r
- sprintf(cmailMsg, _("No unfinished games\n"));\r
- } else {\r
- sprintf(cmailMsg, _("Ready to send mail\n"));\r
- }\r
- break;\r
- \r
- default:\r
- sprintf(cmailMsg,\r
- _("Still need to make moves for games %s\n"),\r
- string);\r
- }\r
- }\r
- }\r
- return cmailMsg;\r
-#endif /* WIN32 */\r
-}\r
-\r
-void\r
-ResetGameEvent()\r
-{\r
- if (gameMode == Training)\r
- SetTrainingModeOff();\r
-\r
- Reset(TRUE, TRUE);\r
- cmailMsgLoaded = FALSE;\r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("refresh\n");\r
- }\r
-}\r
-\r
-void\r
-ExitEvent(status)\r
- int status;\r
-{\r
- exiting++;\r
- if (exiting > 2) {\r
- /* Give up on clean exit */\r
- exit(status);\r
- }\r
- if (exiting > 1) {\r
- /* Keep trying for clean exit */\r
- return;\r
- }\r
-\r
- if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);\r
-\r
- if (telnetISR != NULL) {\r
- RemoveInputSource(telnetISR);\r
- }\r
- if (icsPR != NoProc) {\r
- DestroyChildProcess(icsPR, TRUE);\r
- }\r
-#if 0\r
- /* Save game if resource set and not already saved by GameEnds() */\r
- if ((gameInfo.resultDetails == NULL || errorExitFlag )\r
- && forwardMostMove > 0) {\r
- if (*appData.saveGameFile != NULLCHAR) {\r
- SaveGameToFile(appData.saveGameFile, TRUE);\r
- } else if (appData.autoSaveGames) {\r
- AutoSaveGame();\r
- }\r
- if (*appData.savePositionFile != NULLCHAR) {\r
- SavePositionToFile(appData.savePositionFile);\r
- }\r
- }\r
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
-#else\r
- /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */\r
- GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);\r
-#endif\r
- /* [HGM] crash: the above GameEnds() is a dud if another one was running */\r
- /* make sure this other one finishes before killing it! */\r
- if(endingGame) { int count = 0;\r
- if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");\r
- while(endingGame && count++ < 10) DoSleep(1);\r
- if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");\r
- }\r
-\r
- /* Kill off chess programs */\r
- if (first.pr != NoProc) {\r
- ExitAnalyzeMode();\r
- \r
- DoSleep( appData.delayBeforeQuit );\r
- SendToProgram("quit\n", &first);\r
- DoSleep( appData.delayAfterQuit );\r
- DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );\r
- }\r
- if (second.pr != NoProc) {\r
- DoSleep( appData.delayBeforeQuit );\r
- SendToProgram("quit\n", &second);\r
- DoSleep( appData.delayAfterQuit );\r
- DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );\r
- }\r
- if (first.isr != NULL) {\r
- RemoveInputSource(first.isr);\r
- }\r
- if (second.isr != NULL) {\r
- RemoveInputSource(second.isr);\r
- }\r
-\r
- ShutDownFrontEnd();\r
- exit(status);\r
-}\r
-\r
-void\r
-PauseEvent()\r
-{\r
- if (appData.debugMode)\r
- fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);\r
- if (pausing) {\r
- pausing = FALSE;\r
- ModeHighlight();\r
- if (gameMode == MachinePlaysWhite ||\r
- gameMode == MachinePlaysBlack) {\r
- StartClocks();\r
- } else {\r
- DisplayBothClocks();\r
- }\r
- if (gameMode == PlayFromGameFile) {\r
- if (appData.timeDelay >= 0) \r
- AutoPlayGameLoop();\r
- } else if (gameMode == IcsExamining && pauseExamInvalid) {\r
- Reset(FALSE, TRUE);\r
- SendToICS(ics_prefix);\r
- SendToICS("refresh\n");\r
- } else if (currentMove < forwardMostMove) {\r
- ForwardInner(forwardMostMove);\r
- }\r
- pauseExamInvalid = FALSE;\r
- } else {\r
- switch (gameMode) {\r
- default:\r
- return;\r
- case IcsExamining:\r
- pauseExamForwardMostMove = forwardMostMove;\r
- pauseExamInvalid = FALSE;\r
- /* fall through */\r
- case IcsObserving:\r
- case IcsPlayingWhite:\r
- case IcsPlayingBlack:\r
- pausing = TRUE;\r
- ModeHighlight();\r
- return;\r
- case PlayFromGameFile:\r
- (void) StopLoadGameTimer();\r
- pausing = TRUE;\r
- ModeHighlight();\r
- break;\r
- case BeginningOfGame:\r
- if (appData.icsActive) return;\r
- /* else fall through */\r
- case MachinePlaysWhite:\r
- case MachinePlaysBlack:\r
- case TwoMachinesPlay:\r
- if (forwardMostMove == 0)\r
- return; /* don't pause if no one has moved */\r
- if ((gameMode == MachinePlaysWhite &&\r
- !WhiteOnMove(forwardMostMove)) ||\r
- (gameMode == MachinePlaysBlack &&\r
- WhiteOnMove(forwardMostMove))) {\r
- StopClocks();\r
- }\r
- pausing = TRUE;\r
- ModeHighlight();\r
- break;\r
- }\r
- }\r
-}\r
-\r
-void\r
-EditCommentEvent()\r
-{\r
- char title[MSG_SIZ];\r
-\r
- if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {\r
- strcpy(title, _("Edit comment"));\r
- } else {\r
- sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,\r
- WhiteOnMove(currentMove - 1) ? " " : ".. ",\r
- parseList[currentMove - 1]);\r
- }\r
-\r
- EditCommentPopUp(currentMove, title, commentList[currentMove]);\r
-}\r
-\r
-\r
-void\r
-EditTagsEvent()\r
-{\r
- char *tags = PGNTags(&gameInfo);\r
- EditTagsPopUp(tags);\r
- free(tags);\r
-}\r
-\r
-void\r
-AnalyzeModeEvent()\r
-{\r
- if (appData.noChessProgram || gameMode == AnalyzeMode)\r
- return;\r
-\r
- if (gameMode != AnalyzeFile) {\r
- if (!appData.icsEngineAnalyze) {\r
- EditGameEvent();\r
- if (gameMode != EditGame) return;\r
- }\r
- ResurrectChessProgram();\r
- SendToProgram("analyze\n", &first);\r
- first.analyzing = TRUE;\r
- /*first.maybeThinking = TRUE;*/\r
- first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
- AnalysisPopUp(_("Analysis"),\r
- _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
- }\r
- if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;\r
- pausing = FALSE;\r
- ModeHighlight();\r
- SetGameInfo();\r
-\r
- StartAnalysisClock();\r
- GetTimeMark(&lastNodeCountTime);\r
- lastNodeCount = 0;\r
-}\r
-\r
-void\r
-AnalyzeFileEvent()\r
-{\r
- if (appData.noChessProgram || gameMode == AnalyzeFile)\r
- return;\r
-\r
- if (gameMode != AnalyzeMode) {\r
- EditGameEvent();\r
- if (gameMode != EditGame) return;\r
- ResurrectChessProgram();\r
- SendToProgram("analyze\n", &first);\r
- first.analyzing = TRUE;\r
- /*first.maybeThinking = TRUE;*/\r
- first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
- AnalysisPopUp(_("Analysis"),\r
- _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));\r
- }\r
- gameMode = AnalyzeFile;\r
- pausing = FALSE;\r
- ModeHighlight();\r
- SetGameInfo();\r
-\r
- StartAnalysisClock();\r
- GetTimeMark(&lastNodeCountTime);\r
- lastNodeCount = 0;\r
-}\r
-\r
-void\r
-MachineWhiteEvent()\r
-{\r
- char buf[MSG_SIZ];\r
- char *bookHit = NULL;\r
-\r
- if (appData.noChessProgram || (gameMode == MachinePlaysWhite))\r
- return;\r
-\r
-\r
- if (gameMode == PlayFromGameFile || \r
- gameMode == TwoMachinesPlay || \r
- gameMode == Training || \r
- gameMode == AnalyzeMode || \r
- gameMode == EndOfGame)\r
- EditGameEvent();\r
-\r
- if (gameMode == EditPosition) \r
- EditPositionDone();\r
-\r
- if (!WhiteOnMove(currentMove)) {\r
- DisplayError(_("It is not White's turn"), 0);\r
- return;\r
- }\r
- \r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
- ExitAnalyzeMode();\r
-\r
- if (gameMode == EditGame || gameMode == AnalyzeMode || \r
- gameMode == AnalyzeFile)\r
- TruncateGame();\r
-\r
- ResurrectChessProgram(); /* in case it isn't running */\r
- if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */\r
- gameMode = MachinePlaysWhite;\r
- ResetClocks();\r
- } else\r
- gameMode = MachinePlaysWhite;\r
- pausing = FALSE;\r
- ModeHighlight();\r
- SetGameInfo();\r
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
- DisplayTitle(buf);\r
- if (first.sendName) {\r
- sprintf(buf, "name %s\n", gameInfo.black);\r
- SendToProgram(buf, &first);\r
- }\r
- if (first.sendTime) {\r
- if (first.useColors) {\r
- SendToProgram("black\n", &first); /*gnu kludge*/\r
- }\r
- SendTimeRemaining(&first, TRUE);\r
- }\r
- if (first.useColors) {\r
- SendToProgram("white\n", &first); // [HGM] book: send 'go' separately\r
- }\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
- SetMachineThinkingEnables();\r
- first.maybeThinking = TRUE;\r
- StartClocks();\r
-\r
- if (appData.autoFlipView && !flipView) {\r
- flipView = !flipView;\r
- DrawPosition(FALSE, NULL);\r
- DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;\r
- }\r
-\r
- if(bookHit) { // [HGM] book: simulate book reply\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- HandleMachineMove(bookMove, &first);\r
- }\r
-}\r
-\r
-void\r
-MachineBlackEvent()\r
-{\r
- char buf[MSG_SIZ];\r
- char *bookHit = NULL;\r
-\r
- if (appData.noChessProgram || (gameMode == MachinePlaysBlack))\r
- return;\r
-\r
-\r
- if (gameMode == PlayFromGameFile || \r
- gameMode == TwoMachinesPlay || \r
- gameMode == Training || \r
- gameMode == AnalyzeMode || \r
- gameMode == EndOfGame)\r
- EditGameEvent();\r
-\r
- if (gameMode == EditPosition) \r
- EditPositionDone();\r
-\r
- if (WhiteOnMove(currentMove)) {\r
- DisplayError(_("It is not Black's turn"), 0);\r
- return;\r
- }\r
- \r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)\r
- ExitAnalyzeMode();\r
-\r
- if (gameMode == EditGame || gameMode == AnalyzeMode || \r
- gameMode == AnalyzeFile)\r
- TruncateGame();\r
-\r
- ResurrectChessProgram(); /* in case it isn't running */\r
- gameMode = MachinePlaysBlack;\r
- pausing = FALSE;\r
- ModeHighlight();\r
- SetGameInfo();\r
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
- DisplayTitle(buf);\r
- if (first.sendName) {\r
- sprintf(buf, "name %s\n", gameInfo.white);\r
- SendToProgram(buf, &first);\r
- }\r
- if (first.sendTime) {\r
- if (first.useColors) {\r
- SendToProgram("white\n", &first); /*gnu kludge*/\r
- }\r
- SendTimeRemaining(&first, FALSE);\r
- }\r
- if (first.useColors) {\r
- SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately\r
- }\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move\r
- SetMachineThinkingEnables();\r
- first.maybeThinking = TRUE;\r
- StartClocks();\r
-\r
- if (appData.autoFlipView && flipView) {\r
- flipView = !flipView;\r
- DrawPosition(FALSE, NULL);\r
- DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;\r
- }\r
- if(bookHit) { // [HGM] book: simulate book reply\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- HandleMachineMove(bookMove, &first);\r
- }\r
-}\r
-\r
-\r
-void\r
-DisplayTwoMachinesTitle()\r
-{\r
- char buf[MSG_SIZ];\r
- if (appData.matchGames > 0) {\r
- if (first.twoMachinesColor[0] == 'w') {\r
- sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
- gameInfo.white, gameInfo.black,\r
- first.matchWins, second.matchWins,\r
- matchGame - 1 - (first.matchWins + second.matchWins));\r
- } else {\r
- sprintf(buf, "%s vs. %s (%d-%d-%d)",\r
- gameInfo.white, gameInfo.black,\r
- second.matchWins, first.matchWins,\r
- matchGame - 1 - (first.matchWins + second.matchWins));\r
- }\r
- } else {\r
- sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);\r
- }\r
- DisplayTitle(buf);\r
-}\r
-\r
-void\r
-TwoMachinesEvent P((void))\r
-{\r
- int i;\r
- char buf[MSG_SIZ];\r
- ChessProgramState *onmove;\r
- char *bookHit = NULL;\r
- \r
- if (appData.noChessProgram) return;\r
-\r
- switch (gameMode) {\r
- case TwoMachinesPlay:\r
- return;\r
- case MachinePlaysWhite:\r
- case MachinePlaysBlack:\r
- if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
- DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
- return;\r
- }\r
- /* fall through */\r
- case BeginningOfGame:\r
- case PlayFromGameFile:\r
- case EndOfGame:\r
- EditGameEvent();\r
- if (gameMode != EditGame) return;\r
- break;\r
- case EditPosition:\r
- EditPositionDone();\r
- break;\r
- case AnalyzeMode:\r
- case AnalyzeFile:\r
- ExitAnalyzeMode();\r
- break;\r
- case EditGame:\r
- default:\r
- break;\r
- }\r
-\r
- forwardMostMove = currentMove;\r
- ResurrectChessProgram(); /* in case first program isn't running */\r
-\r
- if (second.pr == NULL) {\r
- StartChessProgram(&second);\r
- if (second.protocolVersion == 1) {\r
- TwoMachinesEventIfReady();\r
- } else {\r
- /* kludge: allow timeout for initial "feature" command */\r
- FreezeUI();\r
- DisplayMessage("", _("Starting second chess program"));\r
- ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);\r
- }\r
- return;\r
- }\r
- DisplayMessage("", "");\r
- InitChessProgram(&second, FALSE);\r
- SendToProgram("force\n", &second);\r
- if (startedFromSetupPosition) {\r
- SendBoard(&second, backwardMostMove);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "Two Machines\n");\r
- }\r
- }\r
- for (i = backwardMostMove; i < forwardMostMove; i++) {\r
- SendMoveToProgram(i, &second);\r
- }\r
-\r
- gameMode = TwoMachinesPlay;\r
- pausing = FALSE;\r
- ModeHighlight();\r
- SetGameInfo();\r
- DisplayTwoMachinesTitle();\r
- firstMove = TRUE;\r
- if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {\r
- onmove = &first;\r
- } else {\r
- onmove = &second;\r
- }\r
-\r
- SendToProgram(first.computerString, &first);\r
- if (first.sendName) {\r
- sprintf(buf, "name %s\n", second.tidy);\r
- SendToProgram(buf, &first);\r
- }\r
- SendToProgram(second.computerString, &second);\r
- if (second.sendName) {\r
- sprintf(buf, "name %s\n", first.tidy);\r
- SendToProgram(buf, &second);\r
- }\r
-\r
- ResetClocks();\r
- if (!first.sendTime || !second.sendTime) {\r
- timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
- timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
- }\r
- if (onmove->sendTime) {\r
- if (onmove->useColors) {\r
- SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/\r
- }\r
- SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));\r
- }\r
- if (onmove->useColors) {\r
- SendToProgram(onmove->twoMachinesColor, onmove);\r
- }\r
- bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move\r
-// SendToProgram("go\n", onmove);\r
- onmove->maybeThinking = TRUE;\r
- SetMachineThinkingEnables();\r
-\r
- StartClocks();\r
-\r
- if(bookHit) { // [HGM] book: simulate book reply\r
- static char bookMove[MSG_SIZ]; // a bit generous?\r
-\r
- programStats.nodes = programStats.depth = programStats.time = \r
- programStats.score = programStats.got_only_move = 0;\r
- sprintf(programStats.movelist, "%s (xbook)", bookHit);\r
-\r
- strcpy(bookMove, "move ");\r
- strcat(bookMove, bookHit);\r
- HandleMachineMove(bookMove, &first);\r
- }\r
-}\r
-\r
-void\r
-TrainingEvent()\r
-{\r
- if (gameMode == Training) {\r
- SetTrainingModeOff();\r
- gameMode = PlayFromGameFile;\r
- DisplayMessage("", _("Training mode off"));\r
- } else {\r
- gameMode = Training;\r
- animateTraining = appData.animate;\r
-\r
- /* make sure we are not already at the end of the game */\r
- if (currentMove < forwardMostMove) {\r
- SetTrainingModeOn();\r
- DisplayMessage("", _("Training mode on"));\r
- } else {\r
- gameMode = PlayFromGameFile;\r
- DisplayError(_("Already at end of game"), 0);\r
- }\r
- }\r
- ModeHighlight();\r
-}\r
-\r
-void\r
-IcsClientEvent()\r
-{\r
- if (!appData.icsActive) return;\r
- switch (gameMode) {\r
- case IcsPlayingWhite:\r
- case IcsPlayingBlack:\r
- case IcsObserving:\r
- case IcsIdle:\r
- case BeginningOfGame:\r
- case IcsExamining:\r
- return;\r
-\r
- case EditGame:\r
- break;\r
-\r
- case EditPosition:\r
- EditPositionDone();\r
- break;\r
-\r
- case AnalyzeMode:\r
- case AnalyzeFile:\r
- ExitAnalyzeMode();\r
- break;\r
- \r
- default:\r
- EditGameEvent();\r
- break;\r
- }\r
-\r
- gameMode = IcsIdle;\r
- ModeHighlight();\r
- return;\r
-}\r
-\r
-\r
-void\r
-EditGameEvent()\r
-{\r
- int i;\r
-\r
- switch (gameMode) {\r
- case Training:\r
- SetTrainingModeOff();\r
- break;\r
- case MachinePlaysWhite:\r
- case MachinePlaysBlack:\r
- case BeginningOfGame:\r
- SendToProgram("force\n", &first);\r
- SetUserThinkingEnables();\r
- break;\r
- case PlayFromGameFile:\r
- (void) StopLoadGameTimer();\r
- if (gameFileFP != NULL) {\r
- gameFileFP = NULL;\r
- }\r
- break;\r
- case EditPosition:\r
- EditPositionDone();\r
- break;\r
- case AnalyzeMode:\r
- case AnalyzeFile:\r
- ExitAnalyzeMode();\r
- SendToProgram("force\n", &first);\r
- break;\r
- case TwoMachinesPlay:\r
- GameEnds((ChessMove) 0, NULL, GE_PLAYER);\r
- ResurrectChessProgram();\r
- SetUserThinkingEnables();\r
- break;\r
- case EndOfGame:\r
- ResurrectChessProgram();\r
- break;\r
- case IcsPlayingBlack:\r
- case IcsPlayingWhite:\r
- DisplayError(_("Warning: You are still playing a game"), 0);\r
- break;\r
- case IcsObserving:\r
- DisplayError(_("Warning: You are still observing a game"), 0);\r
- break;\r
- case IcsExamining:\r
- DisplayError(_("Warning: You are still examining a game"), 0);\r
- break;\r
- case IcsIdle:\r
- break;\r
- case EditGame:\r
- default:\r
- return;\r
- }\r
- \r
- pausing = FALSE;\r
- StopClocks();\r
- first.offeredDraw = second.offeredDraw = 0;\r
-\r
- if (gameMode == PlayFromGameFile) {\r
- whiteTimeRemaining = timeRemaining[0][currentMove];\r
- blackTimeRemaining = timeRemaining[1][currentMove];\r
- DisplayTitle("");\r
- }\r
-\r
- if (gameMode == MachinePlaysWhite ||\r
- gameMode == MachinePlaysBlack ||\r
- gameMode == TwoMachinesPlay ||\r
- gameMode == EndOfGame) {\r
- i = forwardMostMove;\r
- while (i > currentMove) {\r
- SendToProgram("undo\n", &first);\r
- i--;\r
- }\r
- whiteTimeRemaining = timeRemaining[0][currentMove];\r
- blackTimeRemaining = timeRemaining[1][currentMove];\r
- DisplayBothClocks();\r
- if (whiteFlag || blackFlag) {\r
- whiteFlag = blackFlag = 0;\r
- }\r
- DisplayTitle("");\r
- } \r
- \r
- gameMode = EditGame;\r
- ModeHighlight();\r
- SetGameInfo();\r
-}\r
-\r
-\r
-void\r
-EditPositionEvent()\r
-{\r
- if (gameMode == EditPosition) {\r
- EditGameEvent();\r
- return;\r
- }\r
- \r
- EditGameEvent();\r
- if (gameMode != EditGame) return;\r
- \r
- gameMode = EditPosition;\r
- ModeHighlight();\r
- SetGameInfo();\r
- if (currentMove > 0)\r
- CopyBoard(boards[0], boards[currentMove]);\r
- \r
- blackPlaysFirst = !WhiteOnMove(currentMove);\r
- ResetClocks();\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
- DisplayMove(-1);\r
-}\r
-\r
-void\r
-ExitAnalyzeMode()\r
-{\r
- /* [DM] icsEngineAnalyze - possible call from other functions */\r
- if (appData.icsEngineAnalyze) {\r
- appData.icsEngineAnalyze = FALSE;\r
-\r
- DisplayMessage("",_("Close ICS engine analyze..."));\r
- }\r
- if (first.analysisSupport && first.analyzing) {\r
- SendToProgram("exit\n", &first);\r
- first.analyzing = FALSE;\r
- }\r
- AnalysisPopDown();\r
- thinkOutput[0] = NULLCHAR;\r
-}\r
-\r
-void\r
-EditPositionDone()\r
-{\r
- startedFromSetupPosition = TRUE;\r
- InitChessProgram(&first, FALSE);\r
- SendToProgram("force\n", &first);\r
- if (blackPlaysFirst) {\r
- strcpy(moveList[0], "");\r
- strcpy(parseList[0], "");\r
- currentMove = forwardMostMove = backwardMostMove = 1;\r
- CopyBoard(boards[1], boards[0]);\r
- /* [HGM] copy rights as well, as this code is also used after pasting a FEN */\r
- { int i;\r
- epStatus[1] = epStatus[0];\r
- for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];\r
- }\r
- } else {\r
- currentMove = forwardMostMove = backwardMostMove = 0;\r
- }\r
- SendBoard(&first, forwardMostMove);\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "EditPosDone\n");\r
- }\r
- DisplayTitle("");\r
- timeRemaining[0][forwardMostMove] = whiteTimeRemaining;\r
- timeRemaining[1][forwardMostMove] = blackTimeRemaining;\r
- gameMode = EditGame;\r
- ModeHighlight();\r
- HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);\r
- ClearHighlights(); /* [AS] */\r
-}\r
-\r
-/* Pause for `ms' milliseconds */\r
-/* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
-void\r
-TimeDelay(ms)\r
- long ms;\r
-{\r
- TimeMark m1, m2;\r
-\r
- GetTimeMark(&m1);\r
- do {\r
- GetTimeMark(&m2);\r
- } while (SubtractTimeMarks(&m2, &m1) < ms);\r
-}\r
-\r
-/* !! Ugh, this is a kludge. Fix it sometime. --tpm */\r
-void\r
-SendMultiLineToICS(buf)\r
- char *buf;\r
-{\r
- char temp[MSG_SIZ+1], *p;\r
- int len;\r
-\r
- len = strlen(buf);\r
- if (len > MSG_SIZ)\r
- len = MSG_SIZ;\r
- \r
- strncpy(temp, buf, len);\r
- temp[len] = 0;\r
-\r
- p = temp;\r
- while (*p) {\r
- if (*p == '\n' || *p == '\r')\r
- *p = ' ';\r
- ++p;\r
- }\r
-\r
- strcat(temp, "\n");\r
- SendToICS(temp);\r
- SendToPlayer(temp, strlen(temp));\r
-}\r
-\r
-void\r
-SetWhiteToPlayEvent()\r
-{\r
- if (gameMode == EditPosition) {\r
- blackPlaysFirst = FALSE;\r
- DisplayBothClocks(); /* works because currentMove is 0 */\r
- } else if (gameMode == IcsExamining) {\r
- SendToICS(ics_prefix);\r
- SendToICS("tomove white\n");\r
- }\r
-}\r
-\r
-void\r
-SetBlackToPlayEvent()\r
-{\r
- if (gameMode == EditPosition) {\r
- blackPlaysFirst = TRUE;\r
- currentMove = 1; /* kludge */\r
- DisplayBothClocks();\r
- currentMove = 0;\r
- } else if (gameMode == IcsExamining) {\r
- SendToICS(ics_prefix);\r
- SendToICS("tomove black\n");\r
- }\r
-}\r
-\r
-void\r
-EditPositionMenuEvent(selection, x, y)\r
- ChessSquare selection;\r
- int x, y;\r
-{\r
- char buf[MSG_SIZ];\r
- ChessSquare piece = boards[0][y][x];\r
-\r
- if (gameMode != EditPosition && gameMode != IcsExamining) return;\r
-\r
- switch (selection) {\r
- case ClearBoard:\r
- if (gameMode == IcsExamining && ics_type == ICS_FICS) {\r
- SendToICS(ics_prefix);\r
- SendToICS("bsetup clear\n");\r
- } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {\r
- SendToICS(ics_prefix);\r
- SendToICS("clearboard\n");\r
- } else {\r
- for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;\r
- if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */\r
- for (y = 0; y < BOARD_HEIGHT; y++) {\r
- if (gameMode == IcsExamining) {\r
- if (boards[currentMove][y][x] != EmptySquare) {\r
- sprintf(buf, "%sx@%c%c\n", ics_prefix,\r
- AAA + x, ONE + y);\r
- SendToICS(buf);\r
- }\r
- } else {\r
- boards[0][y][x] = p;\r
- }\r
- }\r
- }\r
- }\r
- if (gameMode == EditPosition) {\r
- DrawPosition(FALSE, boards[0]);\r
- }\r
- break;\r
-\r
- case WhitePlay:\r
- SetWhiteToPlayEvent();\r
- break;\r
-\r
- case BlackPlay:\r
- SetBlackToPlayEvent();\r
- break;\r
-\r
- case EmptySquare:\r
- if (gameMode == IcsExamining) {\r
- sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);\r
- SendToICS(buf);\r
- } else {\r
- boards[0][y][x] = EmptySquare;\r
- DrawPosition(FALSE, boards[0]);\r
- }\r
- break;\r
-\r
- case PromotePiece:\r
- if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||\r
- piece >= (int)BlackPawn && piece < (int)BlackMan ) {\r
- selection = (ChessSquare) (PROMOTED piece);\r
- } else if(piece == EmptySquare) selection = WhiteSilver;\r
- else selection = (ChessSquare)((int)piece - 1);\r
- goto defaultlabel;\r
-\r
- case DemotePiece:\r
- if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||\r
- piece > (int)BlackMan && piece <= (int)BlackKing ) {\r
- selection = (ChessSquare) (DEMOTED piece);\r
- } else if(piece == EmptySquare) selection = BlackSilver;\r
- else selection = (ChessSquare)((int)piece + 1); \r
- goto defaultlabel;\r
-\r
- case WhiteQueen:\r
- case BlackQueen:\r
- if(gameInfo.variant == VariantShatranj ||\r
- gameInfo.variant == VariantXiangqi ||\r
- gameInfo.variant == VariantCourier )\r
- selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);\r
- goto defaultlabel;\r
-\r
- case WhiteKing:\r
- case BlackKing:\r
- if(gameInfo.variant == VariantXiangqi)\r
- selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);\r
- if(gameInfo.variant == VariantKnightmate)\r
- selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);\r
- default:\r
- defaultlabel:\r
- if (gameMode == IcsExamining) {\r
- sprintf(buf, "%s%c@%c%c\n", ics_prefix,\r
- PieceToChar(selection), AAA + x, ONE + y);\r
- SendToICS(buf);\r
- } else {\r
- boards[0][y][x] = selection;\r
- DrawPosition(FALSE, boards[0]);\r
- }\r
- break;\r
- }\r
-}\r
-\r
-\r
-void\r
-DropMenuEvent(selection, x, y)\r
- ChessSquare selection;\r
- int x, y;\r
-{\r
- ChessMove moveType;\r
-\r
- switch (gameMode) {\r
- case IcsPlayingWhite:\r
- case MachinePlaysBlack:\r
- if (!WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is Black's turn"));\r
- return;\r
- }\r
- moveType = WhiteDrop;\r
- break;\r
- case IcsPlayingBlack:\r
- case MachinePlaysWhite:\r
- if (WhiteOnMove(currentMove)) {\r
- DisplayMoveError(_("It is White's turn"));\r
- return;\r
- }\r
- moveType = BlackDrop;\r
- break;\r
- case EditGame:\r
- moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;\r
- break;\r
- default:\r
- return;\r
- }\r
-\r
- if (moveType == BlackDrop && selection < BlackPawn) {\r
- selection = (ChessSquare) ((int) selection\r
- + (int) BlackPawn - (int) WhitePawn);\r
- }\r
- if (boards[currentMove][y][x] != EmptySquare) {\r
- DisplayMoveError(_("That square is occupied"));\r
- return;\r
- }\r
-\r
- FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);\r
-}\r
-\r
-void\r
-AcceptEvent()\r
-{\r
- /* Accept a pending offer of any kind from opponent */\r
- \r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("accept\n");\r
- } else if (cmailMsgLoaded) {\r
- if (currentMove == cmailOldMove &&\r
- commentList[cmailOldMove] != NULL &&\r
- StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
- "Black offers a draw" : "White offers a draw")) {\r
- TruncateGame();\r
- GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
- } else {\r
- DisplayError(_("There is no pending offer on this move"), 0);\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
- }\r
- } else {\r
- /* Not used for offers from chess program */\r
- }\r
-}\r
-\r
-void\r
-DeclineEvent()\r
-{\r
- /* Decline a pending offer of any kind from opponent */\r
- \r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("decline\n");\r
- } else if (cmailMsgLoaded) {\r
- if (currentMove == cmailOldMove &&\r
- commentList[cmailOldMove] != NULL &&\r
- StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
- "Black offers a draw" : "White offers a draw")) {\r
-#ifdef NOTDEF\r
- AppendComment(cmailOldMove, "Draw declined");\r
- DisplayComment(cmailOldMove - 1, "Draw declined");\r
-#endif /*NOTDEF*/\r
- } else {\r
- DisplayError(_("There is no pending offer on this move"), 0);\r
- }\r
- } else {\r
- /* Not used for offers from chess program */\r
- }\r
-}\r
-\r
-void\r
-RematchEvent()\r
-{\r
- /* Issue ICS rematch command */\r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("rematch\n");\r
- }\r
-}\r
-\r
-void\r
-CallFlagEvent()\r
-{\r
- /* Call your opponent's flag (claim a win on time) */\r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("flag\n");\r
- } else {\r
- switch (gameMode) {\r
- default:\r
- return;\r
- case MachinePlaysWhite:\r
- if (whiteFlag) {\r
- if (blackFlag)\r
- GameEnds(GameIsDrawn, "Both players ran out of time",\r
- GE_PLAYER);\r
- else\r
- GameEnds(BlackWins, "Black wins on time", GE_PLAYER);\r
- } else {\r
- DisplayError(_("Your opponent is not out of time"), 0);\r
- }\r
- break;\r
- case MachinePlaysBlack:\r
- if (blackFlag) {\r
- if (whiteFlag)\r
- GameEnds(GameIsDrawn, "Both players ran out of time",\r
- GE_PLAYER);\r
- else\r
- GameEnds(WhiteWins, "White wins on time", GE_PLAYER);\r
- } else {\r
- DisplayError(_("Your opponent is not out of time"), 0);\r
- }\r
- break;\r
- }\r
- }\r
-}\r
-\r
-void\r
-DrawEvent()\r
-{\r
- /* Offer draw or accept pending draw offer from opponent */\r
- \r
- if (appData.icsActive) {\r
- /* Note: tournament rules require draw offers to be\r
- made after you make your move but before you punch\r
- your clock. Currently ICS doesn't let you do that;\r
- instead, you immediately punch your clock after making\r
- a move, but you can offer a draw at any time. */\r
- \r
- SendToICS(ics_prefix);\r
- SendToICS("draw\n");\r
- } else if (cmailMsgLoaded) {\r
- if (currentMove == cmailOldMove &&\r
- commentList[cmailOldMove] != NULL &&\r
- StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?\r
- "Black offers a draw" : "White offers a draw")) {\r
- GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;\r
- } else if (currentMove == cmailOldMove + 1) {\r
- char *offer = WhiteOnMove(cmailOldMove) ?\r
- "White offers a draw" : "Black offers a draw";\r
- AppendComment(currentMove, offer);\r
- DisplayComment(currentMove - 1, offer);\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;\r
- } else {\r
- DisplayError(_("You must make your move before offering a draw"), 0);\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;\r
- }\r
- } else if (first.offeredDraw) {\r
- GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);\r
- } else {\r
- if (first.sendDrawOffers) {\r
- SendToProgram("draw\n", &first);\r
- userOfferedDraw = TRUE;\r
- }\r
- }\r
-}\r
-\r
-void\r
-AdjournEvent()\r
-{\r
- /* Offer Adjourn or accept pending Adjourn offer from opponent */\r
- \r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("adjourn\n");\r
- } else {\r
- /* Currently GNU Chess doesn't offer or accept Adjourns */\r
- }\r
-}\r
-\r
-\r
-void\r
-AbortEvent()\r
-{\r
- /* Offer Abort or accept pending Abort offer from opponent */\r
- \r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("abort\n");\r
- } else {\r
- GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);\r
- }\r
-}\r
-\r
-void\r
-ResignEvent()\r
-{\r
- /* Resign. You can do this even if it's not your turn. */\r
- \r
- if (appData.icsActive) {\r
- SendToICS(ics_prefix);\r
- SendToICS("resign\n");\r
- } else {\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
- break;\r
- case MachinePlaysBlack:\r
- GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
- break;\r
- case EditGame:\r
- if (cmailMsgLoaded) {\r
- TruncateGame();\r
- if (WhiteOnMove(cmailOldMove)) {\r
- GameEnds(BlackWins, "White resigns", GE_PLAYER);\r
- } else {\r
- GameEnds(WhiteWins, "Black resigns", GE_PLAYER);\r
- }\r
- cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;\r
- }\r
- break;\r
- default:\r
- break;\r
- }\r
- }\r
-}\r
-\r
-\r
-void\r
-StopObservingEvent()\r
-{\r
- /* Stop observing current games */\r
- SendToICS(ics_prefix);\r
- SendToICS("unobserve\n");\r
-}\r
-\r
-void\r
-StopExaminingEvent()\r
-{\r
- /* Stop observing current game */\r
- SendToICS(ics_prefix);\r
- SendToICS("unexamine\n");\r
-}\r
-\r
-void\r
-ForwardInner(target)\r
- int target;\r
-{\r
- int limit;\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",\r
- target, currentMove, forwardMostMove);\r
-\r
- if (gameMode == EditPosition)\r
- return;\r
-\r
- if (gameMode == PlayFromGameFile && !pausing)\r
- PauseEvent();\r
- \r
- if (gameMode == IcsExamining && pausing)\r
- limit = pauseExamForwardMostMove;\r
- else\r
- limit = forwardMostMove;\r
- \r
- if (target > limit) target = limit;\r
-\r
- if (target > 0 && moveList[target - 1][0]) {\r
- int fromX, fromY, toX, toY;\r
- toX = moveList[target - 1][2] - AAA;\r
- toY = moveList[target - 1][3] - ONE;\r
- if (moveList[target - 1][1] == '@') {\r
- if (appData.highlightLastMove) {\r
- SetHighlights(-1, -1, toX, toY);\r
- }\r
- } else {\r
- fromX = moveList[target - 1][0] - AAA;\r
- fromY = moveList[target - 1][1] - ONE;\r
- if (target == currentMove + 1) {\r
- AnimateMove(boards[currentMove], fromX, fromY, toX, toY);\r
- }\r
- if (appData.highlightLastMove) {\r
- SetHighlights(fromX, fromY, toX, toY);\r
- }\r
- }\r
- }\r
- if (gameMode == EditGame || gameMode == AnalyzeMode || \r
- gameMode == Training || gameMode == PlayFromGameFile || \r
- gameMode == AnalyzeFile) {\r
- while (currentMove < target) {\r
- SendMoveToProgram(currentMove++, &first);\r
- }\r
- } else {\r
- currentMove = target;\r
- }\r
- \r
- if (gameMode == EditGame || gameMode == EndOfGame) {\r
- whiteTimeRemaining = timeRemaining[0][currentMove];\r
- blackTimeRemaining = timeRemaining[1][currentMove];\r
- }\r
- DisplayBothClocks();\r
- DisplayMove(currentMove - 1);\r
- DrawPosition(FALSE, boards[currentMove]);\r
- HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
- if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty\r
- DisplayComment(currentMove - 1, commentList[currentMove]);\r
- }\r
-}\r
-\r
-\r
-void\r
-ForwardEvent()\r
-{\r
- if (gameMode == IcsExamining && !pausing) {\r
- SendToICS(ics_prefix);\r
- SendToICS("forward\n");\r
- } else {\r
- ForwardInner(currentMove + 1);\r
- }\r
-}\r
-\r
-void\r
-ToEndEvent()\r
-{\r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
- /* to optimze, we temporarily turn off analysis mode while we feed\r
- * the remaining moves to the engine. Otherwise we get analysis output\r
- * after each move.\r
- */ \r
- if (first.analysisSupport) {\r
- SendToProgram("exit\nforce\n", &first);\r
- first.analyzing = FALSE;\r
- }\r
- }\r
- \r
- if (gameMode == IcsExamining && !pausing) {\r
- SendToICS(ics_prefix);\r
- SendToICS("forward 999999\n");\r
- } else {\r
- ForwardInner(forwardMostMove);\r
- }\r
-\r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
- /* we have fed all the moves, so reactivate analysis mode */\r
- SendToProgram("analyze\n", &first);\r
- first.analyzing = TRUE;\r
- /*first.maybeThinking = TRUE;*/\r
- first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
- }\r
-}\r
-\r
-void\r
-BackwardInner(target)\r
- int target;\r
-{\r
- int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */\r
-\r
- if (appData.debugMode)\r
- fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",\r
- target, currentMove, forwardMostMove);\r
-\r
- if (gameMode == EditPosition) return;\r
- if (currentMove <= backwardMostMove) {\r
- ClearHighlights();\r
- DrawPosition(full_redraw, boards[currentMove]);\r
- return;\r
- }\r
- if (gameMode == PlayFromGameFile && !pausing)\r
- PauseEvent();\r
- \r
- if (moveList[target][0]) {\r
- int fromX, fromY, toX, toY;\r
- toX = moveList[target][2] - AAA;\r
- toY = moveList[target][3] - ONE;\r
- if (moveList[target][1] == '@') {\r
- if (appData.highlightLastMove) {\r
- SetHighlights(-1, -1, toX, toY);\r
- }\r
- } else {\r
- fromX = moveList[target][0] - AAA;\r
- fromY = moveList[target][1] - ONE;\r
- if (target == currentMove - 1) {\r
- AnimateMove(boards[currentMove], toX, toY, fromX, fromY);\r
- }\r
- if (appData.highlightLastMove) {\r
- SetHighlights(fromX, fromY, toX, toY);\r
- }\r
- }\r
- }\r
- if (gameMode == EditGame || gameMode==AnalyzeMode ||\r
- gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {\r
- while (currentMove > target) {\r
- SendToProgram("undo\n", &first);\r
- currentMove--;\r
- }\r
- } else {\r
- currentMove = target;\r
- }\r
- \r
- if (gameMode == EditGame || gameMode == EndOfGame) {\r
- whiteTimeRemaining = timeRemaining[0][currentMove];\r
- blackTimeRemaining = timeRemaining[1][currentMove];\r
- }\r
- DisplayBothClocks();\r
- DisplayMove(currentMove - 1);\r
- DrawPosition(full_redraw, boards[currentMove]);\r
- HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);\r
- // [HGM] PV info: routine tests if comment empty\r
- DisplayComment(currentMove - 1, commentList[currentMove]);\r
-}\r
-\r
-void\r
-BackwardEvent()\r
-{\r
- if (gameMode == IcsExamining && !pausing) {\r
- SendToICS(ics_prefix);\r
- SendToICS("backward\n");\r
- } else {\r
- BackwardInner(currentMove - 1);\r
- }\r
-}\r
-\r
-void\r
-ToStartEvent()\r
-{\r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
- /* to optimze, we temporarily turn off analysis mode while we undo\r
- * all the moves. Otherwise we get analysis output after each undo.\r
- */ \r
- if (first.analysisSupport) {\r
- SendToProgram("exit\nforce\n", &first);\r
- first.analyzing = FALSE;\r
- }\r
- }\r
-\r
- if (gameMode == IcsExamining && !pausing) {\r
- SendToICS(ics_prefix);\r
- SendToICS("backward 999999\n");\r
- } else {\r
- BackwardInner(backwardMostMove);\r
- }\r
-\r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
- /* we have fed all the moves, so reactivate analysis mode */\r
- SendToProgram("analyze\n", &first);\r
- first.analyzing = TRUE;\r
- /*first.maybeThinking = TRUE;*/\r
- first.maybeThinking = FALSE; /* avoid killing GNU Chess */\r
- }\r
-}\r
-\r
-void\r
-ToNrEvent(int to)\r
-{\r
- if (gameMode == PlayFromGameFile && !pausing) PauseEvent();\r
- if (to >= forwardMostMove) to = forwardMostMove;\r
- if (to <= backwardMostMove) to = backwardMostMove;\r
- if (to < currentMove) {\r
- BackwardInner(to);\r
- } else {\r
- ForwardInner(to);\r
- }\r
-}\r
-\r
-void\r
-RevertEvent()\r
-{\r
- if (gameMode != IcsExamining) {\r
- DisplayError(_("You are not examining a game"), 0);\r
- return;\r
- }\r
- if (pausing) {\r
- DisplayError(_("You can't revert while pausing"), 0);\r
- return;\r
- }\r
- SendToICS(ics_prefix);\r
- SendToICS("revert\n");\r
-}\r
-\r
-void\r
-RetractMoveEvent()\r
-{\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- case MachinePlaysBlack:\r
- if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {\r
- DisplayError(_("Wait until your turn,\nor select Move Now"), 0);\r
- return;\r
- }\r
- if (forwardMostMove < 2) return;\r
- currentMove = forwardMostMove = forwardMostMove - 2;\r
- whiteTimeRemaining = timeRemaining[0][currentMove];\r
- blackTimeRemaining = timeRemaining[1][currentMove];\r
- DisplayBothClocks();\r
- DisplayMove(currentMove - 1);\r
- ClearHighlights();/*!! could figure this out*/\r
- DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */\r
- SendToProgram("remove\n", &first);\r
- /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */\r
- break;\r
-\r
- case BeginningOfGame:\r
- default:\r
- break;\r
-\r
- case IcsPlayingWhite:\r
- case IcsPlayingBlack:\r
- if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {\r
- SendToICS(ics_prefix);\r
- SendToICS("takeback 2\n");\r
- } else {\r
- SendToICS(ics_prefix);\r
- SendToICS("takeback 1\n");\r
- }\r
- break;\r
- }\r
-}\r
-\r
-void\r
-MoveNowEvent()\r
-{\r
- ChessProgramState *cps;\r
-\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- if (!WhiteOnMove(forwardMostMove)) {\r
- DisplayError(_("It is your turn"), 0);\r
- return;\r
- }\r
- cps = &first;\r
- break;\r
- case MachinePlaysBlack:\r
- if (WhiteOnMove(forwardMostMove)) {\r
- DisplayError(_("It is your turn"), 0);\r
- return;\r
- }\r
- cps = &first;\r
- break;\r
- case TwoMachinesPlay:\r
- if (WhiteOnMove(forwardMostMove) ==\r
- (first.twoMachinesColor[0] == 'w')) {\r
- cps = &first;\r
- } else {\r
- cps = &second;\r
- }\r
- break;\r
- case BeginningOfGame:\r
- default:\r
- return;\r
- }\r
- SendToProgram("?\n", cps);\r
-}\r
-\r
-void\r
-TruncateGameEvent()\r
-{\r
- EditGameEvent();\r
- if (gameMode != EditGame) return;\r
- TruncateGame();\r
-}\r
-\r
-void\r
-TruncateGame()\r
-{\r
- if (forwardMostMove > currentMove) {\r
- if (gameInfo.resultDetails != NULL) {\r
- free(gameInfo.resultDetails);\r
- gameInfo.resultDetails = NULL;\r
- gameInfo.result = GameUnfinished;\r
- }\r
- forwardMostMove = currentMove;\r
- HistorySet(parseList, backwardMostMove, forwardMostMove,\r
- currentMove-1);\r
- }\r
-}\r
-\r
-void\r
-HintEvent()\r
-{\r
- if (appData.noChessProgram) return;\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- if (WhiteOnMove(forwardMostMove)) {\r
- DisplayError(_("Wait until your turn"), 0);\r
- return;\r
- }\r
- break;\r
- case BeginningOfGame:\r
- case MachinePlaysBlack:\r
- if (!WhiteOnMove(forwardMostMove)) {\r
- DisplayError(_("Wait until your turn"), 0);\r
- return;\r
- }\r
- break;\r
- default:\r
- DisplayError(_("No hint available"), 0);\r
- return;\r
- }\r
- SendToProgram("hint\n", &first);\r
- hintRequested = TRUE;\r
-}\r
-\r
-void\r
-BookEvent()\r
-{\r
- if (appData.noChessProgram) return;\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- if (WhiteOnMove(forwardMostMove)) {\r
- DisplayError(_("Wait until your turn"), 0);\r
- return;\r
- }\r
- break;\r
- case BeginningOfGame:\r
- case MachinePlaysBlack:\r
- if (!WhiteOnMove(forwardMostMove)) {\r
- DisplayError(_("Wait until your turn"), 0);\r
- return;\r
- }\r
- break;\r
- case EditPosition:\r
- EditPositionDone();\r
- break;\r
- case TwoMachinesPlay:\r
- return;\r
- default:\r
- break;\r
- }\r
- SendToProgram("bk\n", &first);\r
- bookOutput[0] = NULLCHAR;\r
- bookRequested = TRUE;\r
-}\r
-\r
-void\r
-AboutGameEvent()\r
-{\r
- char *tags = PGNTags(&gameInfo);\r
- TagsPopUp(tags, CmailMsg());\r
- free(tags);\r
-}\r
-\r
-/* end button procedures */\r
-\r
-void\r
-PrintPosition(fp, move)\r
- FILE *fp;\r
- int move;\r
-{\r
- int i, j;\r
- \r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
- for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
- char c = PieceToChar(boards[move][i][j]);\r
- fputc(c == 'x' ? '.' : c, fp);\r
- fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);\r
- }\r
- }\r
- if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))\r
- fprintf(fp, "white to play\n");\r
- else\r
- fprintf(fp, "black to play\n");\r
-}\r
-\r
-void\r
-PrintOpponents(fp)\r
- FILE *fp;\r
-{\r
- if (gameInfo.white != NULL) {\r
- fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);\r
- } else {\r
- fprintf(fp, "\n");\r
- }\r
-}\r
-\r
-/* Find last component of program's own name, using some heuristics */\r
-void\r
-TidyProgramName(prog, host, buf)\r
- char *prog, *host, buf[MSG_SIZ];\r
-{\r
- char *p, *q;\r
- int local = (strcmp(host, "localhost") == 0);\r
- while (!local && (p = strchr(prog, ';')) != NULL) {\r
- p++;\r
- while (*p == ' ') p++;\r
- prog = p;\r
- }\r
- if (*prog == '"' || *prog == '\'') {\r
- q = strchr(prog + 1, *prog);\r
- } else {\r
- q = strchr(prog, ' ');\r
- }\r
- if (q == NULL) q = prog + strlen(prog);\r
- p = q;\r
- while (p >= prog && *p != '/' && *p != '\\') p--;\r
- p++;\r
- if(p == prog && *p == '"') p++;\r
- if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;\r
- memcpy(buf, p, q - p);\r
- buf[q - p] = NULLCHAR;\r
- if (!local) {\r
- strcat(buf, "@");\r
- strcat(buf, host);\r
- }\r
-}\r
-\r
-char *\r
-TimeControlTagValue()\r
-{\r
- char buf[MSG_SIZ];\r
- if (!appData.clockMode) {\r
- strcpy(buf, "-");\r
- } else if (movesPerSession > 0) {\r
- sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);\r
- } else if (timeIncrement == 0) {\r
- sprintf(buf, "%ld", timeControl/1000);\r
- } else {\r
- sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);\r
- }\r
- return StrSave(buf);\r
-}\r
-\r
-void\r
-SetGameInfo()\r
-{\r
- /* This routine is used only for certain modes */\r
- VariantClass v = gameInfo.variant;\r
- ClearGameInfo(&gameInfo);\r
- gameInfo.variant = v;\r
-\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- gameInfo.event = StrSave( appData.pgnEventHeader );\r
- gameInfo.site = StrSave(HostName());\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
- gameInfo.white = StrSave(first.tidy);\r
- gameInfo.black = StrSave(UserName());\r
- gameInfo.timeControl = TimeControlTagValue();\r
- break;\r
-\r
- case MachinePlaysBlack:\r
- gameInfo.event = StrSave( appData.pgnEventHeader );\r
- gameInfo.site = StrSave(HostName());\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
- gameInfo.white = StrSave(UserName());\r
- gameInfo.black = StrSave(first.tidy);\r
- gameInfo.timeControl = TimeControlTagValue();\r
- break;\r
-\r
- case TwoMachinesPlay:\r
- gameInfo.event = StrSave( appData.pgnEventHeader );\r
- gameInfo.site = StrSave(HostName());\r
- gameInfo.date = PGNDate();\r
- if (matchGame > 0) {\r
- char buf[MSG_SIZ];\r
- sprintf(buf, "%d", matchGame);\r
- gameInfo.round = StrSave(buf);\r
- } else {\r
- gameInfo.round = StrSave("-");\r
- }\r
- if (first.twoMachinesColor[0] == 'w') {\r
- gameInfo.white = StrSave(first.tidy);\r
- gameInfo.black = StrSave(second.tidy);\r
- } else {\r
- gameInfo.white = StrSave(second.tidy);\r
- gameInfo.black = StrSave(first.tidy);\r
- }\r
- gameInfo.timeControl = TimeControlTagValue();\r
- break;\r
-\r
- case EditGame:\r
- gameInfo.event = StrSave("Edited game");\r
- gameInfo.site = StrSave(HostName());\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
- gameInfo.white = StrSave("-");\r
- gameInfo.black = StrSave("-");\r
- break;\r
-\r
- case EditPosition:\r
- gameInfo.event = StrSave("Edited position");\r
- gameInfo.site = StrSave(HostName());\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
- gameInfo.white = StrSave("-");\r
- gameInfo.black = StrSave("-");\r
- break;\r
-\r
- case IcsPlayingWhite:\r
- case IcsPlayingBlack:\r
- case IcsObserving:\r
- case IcsExamining:\r
- break;\r
-\r
- case PlayFromGameFile:\r
- gameInfo.event = StrSave("Game from non-PGN file");\r
- gameInfo.site = StrSave(HostName());\r
- gameInfo.date = PGNDate();\r
- gameInfo.round = StrSave("-");\r
- gameInfo.white = StrSave("?");\r
- gameInfo.black = StrSave("?");\r
- break;\r
-\r
- default:\r
- break;\r
- }\r
-}\r
-\r
-void\r
-ReplaceComment(index, text)\r
- int index;\r
- char *text;\r
-{\r
- int len;\r
-\r
- while (*text == '\n') text++;\r
- len = strlen(text);\r
- while (len > 0 && text[len - 1] == '\n') len--;\r
-\r
- if (commentList[index] != NULL)\r
- free(commentList[index]);\r
-\r
- if (len == 0) {\r
- commentList[index] = NULL;\r
- return;\r
- }\r
- commentList[index] = (char *) malloc(len + 2);\r
- strncpy(commentList[index], text, len);\r
- commentList[index][len] = '\n';\r
- commentList[index][len + 1] = NULLCHAR;\r
-}\r
-\r
-void\r
-CrushCRs(text)\r
- char *text;\r
-{\r
- char *p = text;\r
- char *q = text;\r
- char ch;\r
-\r
- do {\r
- ch = *p++;\r
- if (ch == '\r') continue;\r
- *q++ = ch;\r
- } while (ch != '\0');\r
-}\r
-\r
-void\r
-AppendComment(index, text)\r
- int index;\r
- char *text;\r
-{\r
- int oldlen, len;\r
- char *old;\r
-\r
- text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */\r
-\r
- CrushCRs(text);\r
- while (*text == '\n') text++;\r
- len = strlen(text);\r
- while (len > 0 && text[len - 1] == '\n') len--;\r
-\r
- if (len == 0) return;\r
-\r
- if (commentList[index] != NULL) {\r
- old = commentList[index];\r
- oldlen = strlen(old);\r
- commentList[index] = (char *) malloc(oldlen + len + 2);\r
- strcpy(commentList[index], old);\r
- free(old);\r
- strncpy(&commentList[index][oldlen], text, len);\r
- commentList[index][oldlen + len] = '\n';\r
- commentList[index][oldlen + len + 1] = NULLCHAR;\r
- } else {\r
- commentList[index] = (char *) malloc(len + 2);\r
- strncpy(commentList[index], text, len);\r
- commentList[index][len] = '\n';\r
- commentList[index][len + 1] = NULLCHAR;\r
- }\r
-}\r
-\r
-static char * FindStr( char * text, char * sub_text )\r
-{\r
- char * result = strstr( text, sub_text );\r
-\r
- if( result != NULL ) {\r
- result += strlen( sub_text );\r
- }\r
-\r
- return result;\r
-}\r
-\r
-/* [AS] Try to extract PV info from PGN comment */\r
-/* [HGM] PV time: and then remove it, to prevent it appearing twice */\r
-char *GetInfoFromComment( int index, char * text )\r
-{\r
- char * sep = text;\r
-\r
- if( text != NULL && index > 0 ) {\r
- int score = 0;\r
- int depth = 0;\r
- int time = -1, sec = 0, deci;\r
- char * s_eval = FindStr( text, "[%eval " );\r
- char * s_emt = FindStr( text, "[%emt " );\r
-\r
- if( s_eval != NULL || s_emt != NULL ) {\r
- /* New style */\r
- char delim;\r
-\r
- if( s_eval != NULL ) {\r
- if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {\r
- return text;\r
- }\r
-\r
- if( delim != ']' ) {\r
- return text;\r
- }\r
- }\r
-\r
- if( s_emt != NULL ) {\r
- }\r
- }\r
- else {\r
- /* We expect something like: [+|-]nnn.nn/dd */\r
- int score_lo = 0;\r
-\r
- sep = strchr( text, '/' );\r
- if( sep == NULL || sep < (text+4) ) {\r
- return text;\r
- }\r
-\r
- time = -1; sec = -1; deci = -1;\r
- if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&\r
- sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&\r
- sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&\r
- sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {\r
- return text;\r
- }\r
-\r
- if( score_lo < 0 || score_lo >= 100 ) {\r
- return text;\r
- }\r
-\r
- if(sec >= 0) time = 600*time + 10*sec; else\r
- if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec\r
-\r
- score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;\r
-\r
- /* [HGM] PV time: now locate end of PV info */\r
- while( *++sep >= '0' && *sep <= '9'); // strip depth\r
- if(time >= 0)\r
- while( *++sep >= '0' && *sep <= '9'); // strip time\r
- if(sec >= 0)\r
- while( *++sep >= '0' && *sep <= '9'); // strip seconds\r
- if(deci >= 0)\r
- while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds\r
- while(*sep == ' ') sep++;\r
- }\r
-\r
- if( depth <= 0 ) {\r
- return text;\r
- }\r
-\r
- if( time < 0 ) {\r
- time = -1;\r
- }\r
-\r
- pvInfoList[index-1].depth = depth;\r
- pvInfoList[index-1].score = score;\r
- pvInfoList[index-1].time = 10*time; // centi-sec\r
- }\r
- return sep;\r
-}\r
-\r
-void\r
-SendToProgram(message, cps)\r
- char *message;\r
- ChessProgramState *cps;\r
-{\r
- int count, outCount, error;\r
- char buf[MSG_SIZ];\r
-\r
- if (cps->pr == NULL) return;\r
- Attention(cps);\r
- \r
- if (appData.debugMode) {\r
- TimeMark now;\r
- GetTimeMark(&now);\r
- fprintf(debugFP, "%ld >%-6s: %s", \r
- SubtractTimeMarks(&now, &programStartTime),\r
- cps->which, message);\r
- }\r
- \r
- count = strlen(message);\r
- outCount = OutputToProcess(cps->pr, message, count, &error);\r
- if (outCount < count && !exiting \r
- && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */\r
- sprintf(buf, _("Error writing to %s chess program"), cps->which);\r
- if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
- if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
- gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
- sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);\r
- } else {\r
- gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
- }\r
- gameInfo.resultDetails = buf;\r
- }\r
- DisplayFatalError(buf, error, 1);\r
- }\r
-}\r
-\r
-void\r
-ReceiveFromProgram(isr, closure, message, count, error)\r
- InputSourceRef isr;\r
- VOIDSTAR closure;\r
- char *message;\r
- int count;\r
- int error;\r
-{\r
- char *end_str;\r
- char buf[MSG_SIZ];\r
- ChessProgramState *cps = (ChessProgramState *)closure;\r
-\r
- if (isr != cps->isr) return; /* Killed intentionally */\r
- if (count <= 0) {\r
- if (count == 0) {\r
- sprintf(buf,\r
- _("Error: %s chess program (%s) exited unexpectedly"),\r
- cps->which, cps->program);\r
- if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */\r
- if(epStatus[forwardMostMove] <= EP_DRAWS) {\r
- gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */\r
- sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);\r
- } else {\r
- gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;\r
- }\r
- gameInfo.resultDetails = buf;\r
- }\r
- RemoveInputSource(cps->isr);\r
- DisplayFatalError(buf, 0, 1);\r
- } else {\r
- sprintf(buf,\r
- _("Error reading from %s chess program (%s)"),\r
- cps->which, cps->program);\r
- RemoveInputSource(cps->isr);\r
-\r
- /* [AS] Program is misbehaving badly... kill it */\r
- if( count == -2 ) {\r
- DestroyChildProcess( cps->pr, 9 );\r
- cps->pr = NoProc;\r
- }\r
-\r
- DisplayFatalError(buf, error, 1);\r
- }\r
- return;\r
- }\r
- \r
- if ((end_str = strchr(message, '\r')) != NULL)\r
- *end_str = NULLCHAR;\r
- if ((end_str = strchr(message, '\n')) != NULL)\r
- *end_str = NULLCHAR;\r
- \r
- if (appData.debugMode) {\r
- TimeMark now; int print = 1;\r
- char *quote = ""; char c; int i;\r
-\r
- if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */\r
- char start = message[0];\r
- if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing\r
- if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 && \r
- sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&\r
- sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&\r
- sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&\r
- sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&\r
- sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')\r
- { quote = "# "; print = (appData.engineComments == 2); }\r
- message[0] = start; // restore original message\r
- }\r
- if(print) {\r
- GetTimeMark(&now);\r
- fprintf(debugFP, "%ld <%-6s: %s%s\n", \r
- SubtractTimeMarks(&now, &programStartTime), cps->which, \r
- quote,\r
- message);\r
- }\r
- }\r
-\r
- /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */\r
- if (appData.icsEngineAnalyze) {\r
- if (strstr(message, "whisper") != NULL ||\r
- strstr(message, "kibitz") != NULL || \r
- strstr(message, "tellics") != NULL) return;\r
- }\r
-\r
- HandleMachineMove(message, cps);\r
-}\r
-\r
-\r
-void\r
-SendTimeControl(cps, mps, tc, inc, sd, st)\r
- ChessProgramState *cps;\r
- int mps, inc, sd, st;\r
- long tc;\r
-{\r
- char buf[MSG_SIZ];\r
- int seconds;\r
-\r
- if( timeControl_2 > 0 ) {\r
- if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {\r
- tc = timeControl_2;\r
- }\r
- }\r
- tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */\r
- inc /= cps->timeOdds;\r
- st /= cps->timeOdds;\r
-\r
- seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */\r
-\r
- if (st > 0) {\r
- /* Set exact time per move, normally using st command */\r
- if (cps->stKludge) {\r
- /* GNU Chess 4 has no st command; uses level in a nonstandard way */\r
- seconds = st % 60;\r
- if (seconds == 0) {\r
- sprintf(buf, "level 1 %d\n", st/60);\r
- } else {\r
- sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);\r
- }\r
- } else {\r
- sprintf(buf, "st %d\n", st);\r
- }\r
- } else {\r
- /* Set conventional or incremental time control, using level command */\r
- if (seconds == 0) {\r
- /* Note old gnuchess bug -- minutes:seconds used to not work.\r
- Fixed in later versions, but still avoid :seconds\r
- when seconds is 0. */\r
- sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);\r
- } else {\r
- sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,\r
- seconds, inc/1000);\r
- }\r
- }\r
- SendToProgram(buf, cps);\r
-\r
- /* Orthoganally (except for GNU Chess 4), limit time to st seconds */\r
- /* Orthogonally, limit search to given depth */\r
- if (sd > 0) {\r
- if (cps->sdKludge) {\r
- sprintf(buf, "depth\n%d\n", sd);\r
- } else {\r
- sprintf(buf, "sd %d\n", sd);\r
- }\r
- SendToProgram(buf, cps);\r
- }\r
-\r
- if(cps->nps > 0) { /* [HGM] nps */\r
- if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!\r
- else {\r
- sprintf(buf, "nps %d\n", cps->nps);\r
- SendToProgram(buf, cps);\r
- }\r
- }\r
-}\r
-\r
-ChessProgramState *WhitePlayer()\r
-/* [HGM] return pointer to 'first' or 'second', depending on who plays white */\r
-{\r
- if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' || \r
- gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)\r
- return &second;\r
- return &first;\r
-}\r
-\r
-void\r
-SendTimeRemaining(cps, machineWhite)\r
- ChessProgramState *cps;\r
- int /*boolean*/ machineWhite;\r
-{\r
- char message[MSG_SIZ];\r
- long time, otime;\r
-\r
- /* Note: this routine must be called when the clocks are stopped\r
- or when they have *just* been set or switched; otherwise\r
- it will be off by the time since the current tick started.\r
- */\r
- if (machineWhite) {\r
- time = whiteTimeRemaining / 10;\r
- otime = blackTimeRemaining / 10;\r
- } else {\r
- time = blackTimeRemaining / 10;\r
- otime = whiteTimeRemaining / 10;\r
- }\r
- /* [HGM] translate opponent's time by time-odds factor */\r
- otime = (otime * cps->other->timeOdds) / cps->timeOdds;\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);\r
- }\r
-\r
- if (time <= 0) time = 1;\r
- if (otime <= 0) otime = 1;\r
- \r
- sprintf(message, "time %ld\n", time);\r
- SendToProgram(message, cps);\r
-\r
- sprintf(message, "otim %ld\n", otime);\r
- SendToProgram(message, cps);\r
-}\r
-\r
-int\r
-BoolFeature(p, name, loc, cps)\r
- char **p;\r
- char *name;\r
- int *loc;\r
- ChessProgramState *cps;\r
-{\r
- char buf[MSG_SIZ];\r
- int len = strlen(name);\r
- int val;\r
- if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
- (*p) += len + 1;\r
- sscanf(*p, "%d", &val);\r
- *loc = (val != 0);\r
- while (**p && **p != ' ') (*p)++;\r
- sprintf(buf, "accepted %s\n", name);\r
- SendToProgram(buf, cps);\r
- return TRUE;\r
- }\r
- return FALSE;\r
-}\r
-\r
-int\r
-IntFeature(p, name, loc, cps)\r
- char **p;\r
- char *name;\r
- int *loc;\r
- ChessProgramState *cps;\r
-{\r
- char buf[MSG_SIZ];\r
- int len = strlen(name);\r
- if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {\r
- (*p) += len + 1;\r
- sscanf(*p, "%d", loc);\r
- while (**p && **p != ' ') (*p)++;\r
- sprintf(buf, "accepted %s\n", name);\r
- SendToProgram(buf, cps);\r
- return TRUE;\r
- }\r
- return FALSE;\r
-}\r
-\r
-int\r
-StringFeature(p, name, loc, cps)\r
- char **p;\r
- char *name;\r
- char loc[];\r
- ChessProgramState *cps;\r
-{\r
- char buf[MSG_SIZ];\r
- int len = strlen(name);\r
- if (strncmp((*p), name, len) == 0\r
- && (*p)[len] == '=' && (*p)[len+1] == '\"') {\r
- (*p) += len + 2;\r
- sscanf(*p, "%[^\"]", loc);\r
- while (**p && **p != '\"') (*p)++;\r
- if (**p == '\"') (*p)++;\r
- sprintf(buf, "accepted %s\n", name);\r
- SendToProgram(buf, cps);\r
- return TRUE;\r
- }\r
- return FALSE;\r
-}\r
-\r
-int \r
-ParseOption(Option *opt, ChessProgramState *cps)\r
-// [HGM] options: process the string that defines an engine option, and determine\r
-// name, type, default value, and allowed value range\r
-{\r
- char *p, *q, buf[MSG_SIZ];\r
- int n, min = (-1)<<31, max = 1<<31, def;\r
-\r
- if(p = strstr(opt->name, " -spin ")) {\r
- if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;\r
- if(max < min) max = min; // enforce consistency\r
- if(def < min) def = min;\r
- if(def > max) def = max;\r
- opt->value = def;\r
- opt->min = min;\r
- opt->max = max;\r
- opt->type = Spin;\r
- } else if(p = strstr(opt->name, " -string ")) {\r
- opt->textValue = p+9;\r
- opt->type = TextBox;\r
- } else if(p = strstr(opt->name, " -check ")) {\r
- if(sscanf(p, " -check %d", &def) < 1) return FALSE;\r
- opt->value = (def != 0);\r
- opt->type = CheckBox;\r
- } else if(p = strstr(opt->name, " -combo ")) {\r
- opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type\r
- cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices\r
- opt->value = n = 0;\r
- while(q = StrStr(q, " /// ")) {\r
- n++; *q = 0; // count choices, and null-terminate each of them\r
- q += 5;\r
- if(*q == '*') { // remember default, which is marked with * prefix\r
- q++;\r
- opt->value = n;\r
- }\r
- cps->comboList[cps->comboCnt++] = q;\r
- }\r
- cps->comboList[cps->comboCnt++] = NULL;\r
- opt->max = n + 1;\r
- opt->type = ComboBox;\r
- } else if(p = strstr(opt->name, " -button")) {\r
- opt->type = Button;\r
- } else if(p = strstr(opt->name, " -save")) {\r
- opt->type = SaveButton;\r
- } else return FALSE;\r
- *p = 0; // terminate option name\r
- // now look if the command-line options define a setting for this engine option.\r
- if(cps->optionSettings && cps->optionSettings[0])\r
- p = strstr(cps->optionSettings, opt->name); else p = NULL;\r
- if(p && (p == cps->optionSettings || p[-1] == ',')) {\r
- sprintf(buf, "option %s", p);\r
- if(p = strstr(buf, ",")) *p = 0;\r
- strcat(buf, "\n");\r
- SendToProgram(buf, cps);\r
- }\r
- return TRUE;\r
-}\r
-\r
-void\r
-FeatureDone(cps, val)\r
- ChessProgramState* cps;\r
- int val;\r
-{\r
- DelayedEventCallback cb = GetDelayedEvent();\r
- if ((cb == InitBackEnd3 && cps == &first) ||\r
- (cb == TwoMachinesEventIfReady && cps == &second)) {\r
- CancelDelayedEvent();\r
- ScheduleDelayedEvent(cb, val ? 1 : 3600000);\r
- }\r
- cps->initDone = val;\r
-}\r
-\r
-/* Parse feature command from engine */\r
-void\r
-ParseFeatures(args, cps)\r
- char* args;\r
- ChessProgramState *cps; \r
-{\r
- char *p = args;\r
- char *q;\r
- int val;\r
- char buf[MSG_SIZ];\r
-\r
- for (;;) {\r
- while (*p == ' ') p++;\r
- if (*p == NULLCHAR) return;\r
-\r
- if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;\r
- if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue; \r
- if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue; \r
- if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue; \r
- if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue; \r
- if (BoolFeature(&p, "reuse", &val, cps)) {\r
- /* Engine can disable reuse, but can't enable it if user said no */\r
- if (!val) cps->reuse = FALSE;\r
- continue;\r
- }\r
- if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;\r
- if (StringFeature(&p, "myname", &cps->tidy, cps)) {\r
- if (gameMode == TwoMachinesPlay) {\r
- DisplayTwoMachinesTitle();\r
- } else {\r
- DisplayTitle("");\r
- }\r
- continue;\r
- }\r
- if (StringFeature(&p, "variants", &cps->variants, cps)) continue;\r
- if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;\r
- if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;\r
- if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;\r
- if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;\r
- if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;\r
- if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;\r
- if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;\r
- if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */\r
- if (IntFeature(&p, "done", &val, cps)) {\r
- FeatureDone(cps, val);\r
- continue;\r
- }\r
- /* Added by Tord: */\r
- if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;\r
- if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;\r
- /* End of additions by Tord */\r
-\r
- /* [HGM] added features: */\r
- if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;\r
- if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;\r
- if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;\r
- if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;\r
- if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
- if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;\r
- if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {\r
- ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature\r
- if(cps->nrOptions >= MAX_OPTIONS) {\r
- cps->nrOptions--;\r
- sprintf(buf, "%s engine has too many options\n", cps->which);\r
- DisplayError(buf, 0);\r
- }\r
- continue;\r
- }\r
- if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;\r
- /* End of additions by HGM */\r
-\r
- /* unknown feature: complain and skip */\r
- q = p;\r
- while (*q && *q != '=') q++;\r
- sprintf(buf, "rejected %.*s\n", q-p, p);\r
- SendToProgram(buf, cps);\r
- p = q;\r
- if (*p == '=') {\r
- p++;\r
- if (*p == '\"') {\r
- p++;\r
- while (*p && *p != '\"') p++;\r
- if (*p == '\"') p++;\r
- } else {\r
- while (*p && *p != ' ') p++;\r
- }\r
- }\r
- }\r
-\r
-}\r
-\r
-void\r
-PeriodicUpdatesEvent(newState)\r
- int newState;\r
-{\r
- if (newState == appData.periodicUpdates)\r
- return;\r
-\r
- appData.periodicUpdates=newState;\r
-\r
- /* Display type changes, so update it now */\r
- DisplayAnalysis();\r
-\r
- /* Get the ball rolling again... */\r
- if (newState) {\r
- AnalysisPeriodicEvent(1);\r
- StartAnalysisClock();\r
- }\r
-}\r
-\r
-void\r
-PonderNextMoveEvent(newState)\r
- int newState;\r
-{\r
- if (newState == appData.ponderNextMove) return;\r
- if (gameMode == EditPosition) EditPositionDone();\r
- if (newState) {\r
- SendToProgram("hard\n", &first);\r
- if (gameMode == TwoMachinesPlay) {\r
- SendToProgram("hard\n", &second);\r
- }\r
- } else {\r
- SendToProgram("easy\n", &first);\r
- thinkOutput[0] = NULLCHAR;\r
- if (gameMode == TwoMachinesPlay) {\r
- SendToProgram("easy\n", &second);\r
- }\r
- }\r
- appData.ponderNextMove = newState;\r
-}\r
-\r
-void\r
-NewSettingEvent(option, command, value)\r
- char *command;\r
- int option, value;\r
-{\r
- char buf[MSG_SIZ];\r
-\r
- if (gameMode == EditPosition) EditPositionDone();\r
- sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);\r
- SendToProgram(buf, &first);\r
- if (gameMode == TwoMachinesPlay) {\r
- SendToProgram(buf, &second);\r
- }\r
-}\r
-\r
-void\r
-ShowThinkingEvent()\r
-// [HGM] thinking: this routine is now also called from "Options -> Engine..." popup\r
-{\r
- static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated\r
- int newState = appData.showThinking\r
- // [HGM] thinking: other features now need thinking output as well\r
- || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();\r
- \r
- if (oldState == newState) return;\r
- oldState = newState;\r
- if (gameMode == EditPosition) EditPositionDone();\r
- if (oldState) {\r
- SendToProgram("post\n", &first);\r
- if (gameMode == TwoMachinesPlay) {\r
- SendToProgram("post\n", &second);\r
- }\r
- } else {\r
- SendToProgram("nopost\n", &first);\r
- thinkOutput[0] = NULLCHAR;\r
- if (gameMode == TwoMachinesPlay) {\r
- SendToProgram("nopost\n", &second);\r
- }\r
- }\r
-// appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!\r
-}\r
-\r
-void\r
-AskQuestionEvent(title, question, replyPrefix, which)\r
- char *title; char *question; char *replyPrefix; char *which;\r
-{\r
- ProcRef pr = (which[0] == '1') ? first.pr : second.pr;\r
- if (pr == NoProc) return;\r
- AskQuestion(title, question, replyPrefix, pr);\r
-}\r
-\r
-void\r
-DisplayMove(moveNumber)\r
- int moveNumber;\r
-{\r
- char message[MSG_SIZ];\r
- char res[MSG_SIZ];\r
- char cpThinkOutput[MSG_SIZ];\r
-\r
- if(appData.noGUI) return; // [HGM] fast: suppress display of moves\r
- \r
- if (moveNumber == forwardMostMove - 1 || \r
- gameMode == AnalyzeMode || gameMode == AnalyzeFile) {\r
-\r
- safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));\r
-\r
- if (strchr(cpThinkOutput, '\n')) {\r
- *strchr(cpThinkOutput, '\n') = NULLCHAR;\r
- }\r
- } else {\r
- *cpThinkOutput = NULLCHAR;\r
- }\r
-\r
- /* [AS] Hide thinking from human user */\r
- if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {\r
- *cpThinkOutput = NULLCHAR;\r
- if( thinkOutput[0] != NULLCHAR ) {\r
- int i;\r
-\r
- for( i=0; i<=hiddenThinkOutputState; i++ ) {\r
- cpThinkOutput[i] = '.';\r
- }\r
- cpThinkOutput[i] = NULLCHAR;\r
- hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;\r
- }\r
- }\r
-\r
- if (moveNumber == forwardMostMove - 1 &&\r
- gameInfo.resultDetails != NULL) {\r
- if (gameInfo.resultDetails[0] == NULLCHAR) {\r
- sprintf(res, " %s", PGNResult(gameInfo.result));\r
- } else {\r
- sprintf(res, " {%s} %s",\r
- gameInfo.resultDetails, PGNResult(gameInfo.result));\r
- }\r
- } else {\r
- res[0] = NULLCHAR;\r
- }\r
-\r
- if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
- DisplayMessage(res, cpThinkOutput);\r
- } else {\r
- sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,\r
- WhiteOnMove(moveNumber) ? " " : ".. ",\r
- parseList[moveNumber], res);\r
- DisplayMessage(message, cpThinkOutput);\r
- }\r
-}\r
-\r
-void\r
-DisplayAnalysisText(text)\r
- char *text;\r
-{\r
- char buf[MSG_SIZ];\r
-\r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile \r
- || appData.icsEngineAnalyze) {\r
- sprintf(buf, "Analysis (%s)", first.tidy);\r
- AnalysisPopUp(buf, text);\r
- }\r
-}\r
-\r
-static int\r
-only_one_move(str)\r
- char *str;\r
-{\r
- while (*str && isspace(*str)) ++str;\r
- while (*str && !isspace(*str)) ++str;\r
- if (!*str) return 1;\r
- while (*str && isspace(*str)) ++str;\r
- if (!*str) return 1;\r
- return 0;\r
-}\r
-\r
-void\r
-DisplayAnalysis()\r
-{\r
- char buf[MSG_SIZ];\r
- char lst[MSG_SIZ / 2];\r
- double nps;\r
- static char *xtra[] = { "", " (--)", " (++)" };\r
- int h, m, s, cs;\r
- \r
- if (programStats.time == 0) {\r
- programStats.time = 1;\r
- }\r
- \r
- if (programStats.got_only_move) {\r
- safeStrCpy(buf, programStats.movelist, sizeof(buf));\r
- } else {\r
- safeStrCpy( lst, programStats.movelist, sizeof(lst));\r
-\r
- nps = (u64ToDouble(programStats.nodes) /\r
- ((double)programStats.time /100.0));\r
-\r
- cs = programStats.time % 100;\r
- s = programStats.time / 100;\r
- h = (s / (60*60));\r
- s = s - h*60*60;\r
- m = (s/60);\r
- s = s - m*60;\r
-\r
- if (programStats.moves_left > 0 && appData.periodicUpdates) {\r
- if (programStats.move_name[0] != NULLCHAR) {\r
- sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
- programStats.depth,\r
- programStats.nr_moves-programStats.moves_left,\r
- programStats.nr_moves, programStats.move_name,\r
- ((float)programStats.score)/100.0, lst,\r
- only_one_move(lst)?\r
- xtra[programStats.got_fail] : "",\r
- (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
- } else {\r
- sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
- programStats.depth,\r
- programStats.nr_moves-programStats.moves_left,\r
- programStats.nr_moves, ((float)programStats.score)/100.0,\r
- lst,\r
- only_one_move(lst)?\r
- xtra[programStats.got_fail] : "",\r
- (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
- }\r
- } else {\r
- sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",\r
- programStats.depth,\r
- ((float)programStats.score)/100.0,\r
- lst,\r
- only_one_move(lst)?\r
- xtra[programStats.got_fail] : "",\r
- (u64)programStats.nodes, (int)nps, h, m, s, cs);\r
- }\r
- }\r
- DisplayAnalysisText(buf);\r
-}\r
-\r
-void\r
-DisplayComment(moveNumber, text)\r
- int moveNumber;\r
- char *text;\r
-{\r
- char title[MSG_SIZ];\r
- char buf[8000]; // comment can be long!\r
- int score, depth;\r
-\r
- if( appData.autoDisplayComment ) {\r
- if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {\r
- strcpy(title, "Comment");\r
- } else {\r
- sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,\r
- WhiteOnMove(moveNumber) ? " " : ".. ",\r
- parseList[moveNumber]);\r
- }\r
- // [HGM] PV info: display PV info together with (or as) comment\r
- if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {\r
- if(text == NULL) text = ""; \r
- score = pvInfoList[moveNumber].score;\r
- sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,\r
- depth, (pvInfoList[moveNumber].time+50)/100, text);\r
- text = buf;\r
- }\r
- } else title[0] = 0;\r
-\r
- if (text != NULL)\r
- CommentPopUp(title, text);\r
-}\r
-\r
-/* This routine sends a ^C interrupt to gnuchess, to awaken it if it\r
- * might be busy thinking or pondering. It can be omitted if your\r
- * gnuchess is configured to stop thinking immediately on any user\r
- * input. However, that gnuchess feature depends on the FIONREAD\r
- * ioctl, which does not work properly on some flavors of Unix.\r
- */\r
-void\r
-Attention(cps)\r
- ChessProgramState *cps;\r
-{\r
-#if ATTENTION\r
- if (!cps->useSigint) return;\r
- if (appData.noChessProgram || (cps->pr == NoProc)) return;\r
- switch (gameMode) {\r
- case MachinePlaysWhite:\r
- case MachinePlaysBlack:\r
- case TwoMachinesPlay:\r
- case IcsPlayingWhite:\r
- case IcsPlayingBlack:\r
- case AnalyzeMode:\r
- case AnalyzeFile:\r
- /* Skip if we know it isn't thinking */\r
- if (!cps->maybeThinking) return;\r
- if (appData.debugMode)\r
- fprintf(debugFP, "Interrupting %s\n", cps->which);\r
- InterruptChildProcess(cps->pr);\r
- cps->maybeThinking = FALSE;\r
- break;\r
- default:\r
- break;\r
- }\r
-#endif /*ATTENTION*/\r
-}\r
-\r
-int\r
-CheckFlags()\r
-{\r
- if (whiteTimeRemaining <= 0) {\r
- if (!whiteFlag) {\r
- whiteFlag = TRUE;\r
- if (appData.icsActive) {\r
- if (appData.autoCallFlag &&\r
- gameMode == IcsPlayingBlack && !blackFlag) {\r
- SendToICS(ics_prefix);\r
- SendToICS("flag\n");\r
- }\r
- } else {\r
- if (blackFlag) {\r
- if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
- } else {\r
- if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));\r
- if (appData.autoCallFlag) {\r
- GameEnds(BlackWins, "Black wins on time", GE_XBOARD);\r
- return TRUE;\r
- }\r
- }\r
- }\r
- }\r
- }\r
- if (blackTimeRemaining <= 0) {\r
- if (!blackFlag) {\r
- blackFlag = TRUE;\r
- if (appData.icsActive) {\r
- if (appData.autoCallFlag &&\r
- gameMode == IcsPlayingWhite && !whiteFlag) {\r
- SendToICS(ics_prefix);\r
- SendToICS("flag\n");\r
- }\r
- } else {\r
- if (whiteFlag) {\r
- if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));\r
- } else {\r
- if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));\r
- if (appData.autoCallFlag) {\r
- GameEnds(WhiteWins, "White wins on time", GE_XBOARD);\r
- return TRUE;\r
- }\r
- }\r
- }\r
- }\r
- }\r
- return FALSE;\r
-}\r
-\r
-void\r
-CheckTimeControl()\r
-{\r
- if (!appData.clockMode || appData.icsActive ||\r
- gameMode == PlayFromGameFile || forwardMostMove == 0) return;\r
-\r
- /*\r
- * add time to clocks when time control is achieved ([HGM] now also used for increment)\r
- */\r
- if ( !WhiteOnMove(forwardMostMove) )\r
- /* White made time control */\r
- whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
- /* [HGM] time odds: correct new time quota for time odds! */\r
- / WhitePlayer()->timeOdds;\r
- else\r
- /* Black made time control */\r
- blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)\r
- / WhitePlayer()->other->timeOdds;\r
-}\r
-\r
-void\r
-DisplayBothClocks()\r
-{\r
- int wom = gameMode == EditPosition ?\r
- !blackPlaysFirst : WhiteOnMove(currentMove);\r
- DisplayWhiteClock(whiteTimeRemaining, wom);\r
- DisplayBlackClock(blackTimeRemaining, !wom);\r
-}\r
-\r
-\r
-/* Timekeeping seems to be a portability nightmare. I think everyone\r
- has ftime(), but I'm really not sure, so I'm including some ifdefs\r
- to use other calls if you don't. Clocks will be less accurate if\r
- you have neither ftime nor gettimeofday.\r
-*/\r
-\r
-/* VS 2008 requires the #include outside of the function */\r
-#if !HAVE_GETTIMEOFDAY && HAVE_FTIME\r
-#include <sys/timeb.h>\r
-#endif\r
-\r
-/* Get the current time as a TimeMark */\r
-void\r
-GetTimeMark(tm)\r
- TimeMark *tm;\r
-{\r
-#if HAVE_GETTIMEOFDAY\r
-\r
- struct timeval timeVal;\r
- struct timezone timeZone;\r
-\r
- gettimeofday(&timeVal, &timeZone);\r
- tm->sec = (long) timeVal.tv_sec; \r
- tm->ms = (int) (timeVal.tv_usec / 1000L);\r
-\r
-#else /*!HAVE_GETTIMEOFDAY*/\r
-#if HAVE_FTIME\r
-\r
-// include <sys/timeb.h> / moved to just above start of function\r
- struct timeb timeB;\r
-\r
- ftime(&timeB);\r
- tm->sec = (long) timeB.time;\r
- tm->ms = (int) timeB.millitm;\r
-\r
-#else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/\r
- tm->sec = (long) time(NULL);\r
- tm->ms = 0;\r
-#endif\r
-#endif\r
-}\r
-\r
-/* Return the difference in milliseconds between two\r
- time marks. We assume the difference will fit in a long!\r
-*/\r
-long\r
-SubtractTimeMarks(tm2, tm1)\r
- TimeMark *tm2, *tm1;\r
-{\r
- return 1000L*(tm2->sec - tm1->sec) +\r
- (long) (tm2->ms - tm1->ms);\r
-}\r
-\r
-\r
-/*\r
- * Code to manage the game clocks.\r
- *\r
- * In tournament play, black starts the clock and then white makes a move.\r
- * We give the human user a slight advantage if he is playing white---the\r
- * clocks don't run until he makes his first move, so it takes zero time.\r
- * Also, we don't account for network lag, so we could get out of sync\r
- * with GNU Chess's clock -- but then, referees are always right. \r
- */\r
-\r
-static TimeMark tickStartTM;\r
-static long intendedTickLength;\r
-\r
-long\r
-NextTickLength(timeRemaining)\r
- long timeRemaining;\r
-{\r
- long nominalTickLength, nextTickLength;\r
-\r
- if (timeRemaining > 0L && timeRemaining <= 10000L)\r
- nominalTickLength = 100L;\r
- else\r
- nominalTickLength = 1000L;\r
- nextTickLength = timeRemaining % nominalTickLength;\r
- if (nextTickLength <= 0) nextTickLength += nominalTickLength;\r
-\r
- return nextTickLength;\r
-}\r
-\r
-/* Adjust clock one minute up or down */\r
-void\r
-AdjustClock(Boolean which, int dir)\r
-{\r
- if(which) blackTimeRemaining += 60000*dir;\r
- else whiteTimeRemaining += 60000*dir;\r
- DisplayBothClocks();\r
-}\r
-\r
-/* Stop clocks and reset to a fresh time control */\r
-void\r
-ResetClocks() \r
-{\r
- (void) StopClockTimer();\r
- if (appData.icsActive) {\r
- whiteTimeRemaining = blackTimeRemaining = 0;\r
- } else { /* [HGM] correct new time quote for time odds */\r
- whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;\r
- blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;\r
- }\r
- if (whiteFlag || blackFlag) {\r
- DisplayTitle("");\r
- whiteFlag = blackFlag = FALSE;\r
- }\r
- DisplayBothClocks();\r
-}\r
-\r
-#define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */\r
-\r
-/* Decrement running clock by amount of time that has passed */\r
-void\r
-DecrementClocks()\r
-{\r
- long timeRemaining;\r
- long lastTickLength, fudge;\r
- TimeMark now;\r
-\r
- if (!appData.clockMode) return;\r
- if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;\r
- \r
- GetTimeMark(&now);\r
-\r
- lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
-\r
- /* Fudge if we woke up a little too soon */\r
- fudge = intendedTickLength - lastTickLength;\r
- if (fudge < 0 || fudge > FUDGE) fudge = 0;\r
-\r
- if (WhiteOnMove(forwardMostMove)) {\r
- if(whiteNPS >= 0) lastTickLength = 0;\r
- timeRemaining = whiteTimeRemaining -= lastTickLength;\r
- DisplayWhiteClock(whiteTimeRemaining - fudge,\r
- WhiteOnMove(currentMove));\r
- } else {\r
- if(blackNPS >= 0) lastTickLength = 0;\r
- timeRemaining = blackTimeRemaining -= lastTickLength;\r
- DisplayBlackClock(blackTimeRemaining - fudge,\r
- !WhiteOnMove(currentMove));\r
- }\r
-\r
- if (CheckFlags()) return;\r
- \r
- tickStartTM = now;\r
- intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;\r
- StartClockTimer(intendedTickLength);\r
-\r
- /* if the time remaining has fallen below the alarm threshold, sound the\r
- * alarm. if the alarm has sounded and (due to a takeback or time control\r
- * with increment) the time remaining has increased to a level above the\r
- * threshold, reset the alarm so it can sound again. \r
- */\r
- \r
- if (appData.icsActive && appData.icsAlarm) {\r
-\r
- /* make sure we are dealing with the user's clock */\r
- if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||\r
- ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))\r
- )) return;\r
-\r
- if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {\r
- alarmSounded = FALSE;\r
- } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) { \r
- PlayAlarmSound();\r
- alarmSounded = TRUE;\r
- }\r
- }\r
-}\r
-\r
-\r
-/* A player has just moved, so stop the previously running\r
- clock and (if in clock mode) start the other one.\r
- We redisplay both clocks in case we're in ICS mode, because\r
- ICS gives us an update to both clocks after every move.\r
- Note that this routine is called *after* forwardMostMove\r
- is updated, so the last fractional tick must be subtracted\r
- from the color that is *not* on move now.\r
-*/\r
-void\r
-SwitchClocks()\r
-{\r
- long lastTickLength;\r
- TimeMark now;\r
- int flagged = FALSE;\r
-\r
- GetTimeMark(&now);\r
-\r
- if (StopClockTimer() && appData.clockMode) {\r
- lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
- if (WhiteOnMove(forwardMostMove)) {\r
- if(blackNPS >= 0) lastTickLength = 0;\r
- blackTimeRemaining -= lastTickLength;\r
- /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
-// if(pvInfoList[forwardMostMove-1].time == -1)\r
- pvInfoList[forwardMostMove-1].time = // use GUI time\r
- (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;\r
- } else {\r
- if(whiteNPS >= 0) lastTickLength = 0;\r
- whiteTimeRemaining -= lastTickLength;\r
- /* [HGM] PGNtime: save time for PGN file if engine did not give it */\r
-// if(pvInfoList[forwardMostMove-1].time == -1)\r
- pvInfoList[forwardMostMove-1].time = \r
- (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;\r
- }\r
- flagged = CheckFlags();\r
- }\r
- CheckTimeControl();\r
-\r
- if (flagged || !appData.clockMode) return;\r
-\r
- switch (gameMode) {\r
- case MachinePlaysBlack:\r
- case MachinePlaysWhite:\r
- case BeginningOfGame:\r
- if (pausing) return;\r
- break;\r
-\r
- case EditGame:\r
- case PlayFromGameFile:\r
- case IcsExamining:\r
- return;\r
-\r
- default:\r
- break;\r
- }\r
-\r
- tickStartTM = now;\r
- intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
- whiteTimeRemaining : blackTimeRemaining);\r
- StartClockTimer(intendedTickLength);\r
-}\r
- \r
-\r
-/* Stop both clocks */\r
-void\r
-StopClocks()\r
-{ \r
- long lastTickLength;\r
- TimeMark now;\r
-\r
- if (!StopClockTimer()) return;\r
- if (!appData.clockMode) return;\r
-\r
- GetTimeMark(&now);\r
-\r
- lastTickLength = SubtractTimeMarks(&now, &tickStartTM);\r
- if (WhiteOnMove(forwardMostMove)) {\r
- if(whiteNPS >= 0) lastTickLength = 0;\r
- whiteTimeRemaining -= lastTickLength;\r
- DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));\r
- } else {\r
- if(blackNPS >= 0) lastTickLength = 0;\r
- blackTimeRemaining -= lastTickLength;\r
- DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));\r
- }\r
- CheckFlags();\r
-}\r
- \r
-/* Start clock of player on move. Time may have been reset, so\r
- if clock is already running, stop and restart it. */\r
-void\r
-StartClocks()\r
-{\r
- (void) StopClockTimer(); /* in case it was running already */\r
- DisplayBothClocks();\r
- if (CheckFlags()) return;\r
-\r
- if (!appData.clockMode) return;\r
- if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;\r
-\r
- GetTimeMark(&tickStartTM);\r
- intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?\r
- whiteTimeRemaining : blackTimeRemaining);\r
-\r
- /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */\r
- whiteNPS = blackNPS = -1; \r
- if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'\r
- || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white\r
- whiteNPS = first.nps;\r
- if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'\r
- || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black\r
- blackNPS = first.nps;\r
- if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode\r
- whiteNPS = second.nps;\r
- if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')\r
- blackNPS = second.nps;\r
- if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);\r
-\r
- StartClockTimer(intendedTickLength);\r
-}\r
-\r
-char *\r
-TimeString(ms)\r
- long ms;\r
-{\r
- long second, minute, hour, day;\r
- char *sign = "";\r
- static char buf[32];\r
- \r
- if (ms > 0 && ms <= 9900) {\r
- /* convert milliseconds to tenths, rounding up */\r
- double tenths = floor( ((double)(ms + 99L)) / 100.00 );\r
-\r
- sprintf(buf, " %03.1f ", tenths/10.0);\r
- return buf;\r
- }\r
-\r
- /* convert milliseconds to seconds, rounding up */\r
- /* use floating point to avoid strangeness of integer division\r
- with negative dividends on many machines */\r
- second = (long) floor(((double) (ms + 999L)) / 1000.0);\r
-\r
- if (second < 0) {\r
- sign = "-";\r
- second = -second;\r
- }\r
- \r
- day = second / (60 * 60 * 24);\r
- second = second % (60 * 60 * 24);\r
- hour = second / (60 * 60);\r
- second = second % (60 * 60);\r
- minute = second / 60;\r
- second = second % 60;\r
- \r
- if (day > 0)\r
- sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",\r
- sign, day, hour, minute, second);\r
- else if (hour > 0)\r
- sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);\r
- else\r
- sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);\r
- \r
- return buf;\r
-}\r
-\r
-\r
-/*\r
- * This is necessary because some C libraries aren't ANSI C compliant yet.\r
- */\r
-char *\r
-StrStr(string, match)\r
- char *string, *match;\r
-{\r
- int i, length;\r
- \r
- length = strlen(match);\r
- \r
- for (i = strlen(string) - length; i >= 0; i--, string++)\r
- if (!strncmp(match, string, length))\r
- return string;\r
- \r
- return NULL;\r
-}\r
-\r
-char *\r
-StrCaseStr(string, match)\r
- char *string, *match;\r
-{\r
- int i, j, length;\r
- \r
- length = strlen(match);\r
- \r
- for (i = strlen(string) - length; i >= 0; i--, string++) {\r
- for (j = 0; j < length; j++) {\r
- if (ToLower(match[j]) != ToLower(string[j]))\r
- break;\r
- }\r
- if (j == length) return string;\r
- }\r
-\r
- return NULL;\r
-}\r
-\r
-#ifndef _amigados\r
-int\r
-StrCaseCmp(s1, s2)\r
- char *s1, *s2;\r
-{\r
- char c1, c2;\r
- \r
- for (;;) {\r
- c1 = ToLower(*s1++);\r
- c2 = ToLower(*s2++);\r
- if (c1 > c2) return 1;\r
- if (c1 < c2) return -1;\r
- if (c1 == NULLCHAR) return 0;\r
- }\r
-}\r
-\r
-\r
-int\r
-ToLower(c)\r
- int c;\r
-{\r
- return isupper(c) ? tolower(c) : c;\r
-}\r
-\r
-\r
-int\r
-ToUpper(c)\r
- int c;\r
-{\r
- return islower(c) ? toupper(c) : c;\r
-}\r
-#endif /* !_amigados */\r
-\r
-char *\r
-StrSave(s)\r
- char *s;\r
-{\r
- char *ret;\r
-\r
- if ((ret = (char *) malloc(strlen(s) + 1))) {\r
- strcpy(ret, s);\r
- }\r
- return ret;\r
-}\r
-\r
-char *\r
-StrSavePtr(s, savePtr)\r
- char *s, **savePtr;\r
-{\r
- if (*savePtr) {\r
- free(*savePtr);\r
- }\r
- if ((*savePtr = (char *) malloc(strlen(s) + 1))) {\r
- strcpy(*savePtr, s);\r
- }\r
- return(*savePtr);\r
-}\r
-\r
-char *\r
-PGNDate()\r
-{\r
- time_t clock;\r
- struct tm *tm;\r
- char buf[MSG_SIZ];\r
-\r
- clock = time((time_t *)NULL);\r
- tm = localtime(&clock);\r
- sprintf(buf, "%04d.%02d.%02d",\r
- tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);\r
- return StrSave(buf);\r
-}\r
-\r
-\r
-char *\r
-PositionToFEN(move, overrideCastling)\r
- int move;\r
- char *overrideCastling;\r
-{\r
- int i, j, fromX, fromY, toX, toY;\r
- int whiteToPlay;\r
- char buf[128];\r
- char *p, *q;\r
- int emptycount;\r
- ChessSquare piece;\r
-\r
- whiteToPlay = (gameMode == EditPosition) ?\r
- !blackPlaysFirst : (move % 2 == 0);\r
- p = buf;\r
-\r
- /* Piece placement data */\r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
- emptycount = 0;\r
- for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {\r
- if (boards[move][i][j] == EmptySquare) {\r
- emptycount++;\r
- } else { ChessSquare piece = boards[move][i][j];\r
- if (emptycount > 0) {\r
- if(emptycount<10) /* [HGM] can be >= 10 */\r
- *p++ = '0' + emptycount;\r
- else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
- emptycount = 0;\r
- }\r
- if(PieceToChar(piece) == '+') {\r
- /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */\r
- *p++ = '+';\r
- piece = (ChessSquare)(DEMOTED piece);\r
- } \r
- *p++ = PieceToChar(piece);\r
- if(p[-1] == '~') {\r
- /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */\r
- p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));\r
- *p++ = '~';\r
- }\r
- }\r
- }\r
- if (emptycount > 0) {\r
- if(emptycount<10) /* [HGM] can be >= 10 */\r
- *p++ = '0' + emptycount;\r
- else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }\r
- emptycount = 0;\r
- }\r
- *p++ = '/';\r
- }\r
- *(p - 1) = ' ';\r
-\r
- /* [HGM] print Crazyhouse or Shogi holdings */\r
- if( gameInfo.holdingsWidth ) {\r
- *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */\r
- q = p;\r
- for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */\r
- piece = boards[move][i][BOARD_WIDTH-1];\r
- if( piece != EmptySquare )\r
- for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)\r
- *p++ = PieceToChar(piece);\r
- }\r
- for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */\r
- piece = boards[move][BOARD_HEIGHT-i-1][0];\r
- if( piece != EmptySquare )\r
- for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)\r
- *p++ = PieceToChar(piece);\r
- }\r
-\r
- if( q == p ) *p++ = '-';\r
- *p++ = ']';\r
- *p++ = ' ';\r
- }\r
-\r
- /* Active color */\r
- *p++ = whiteToPlay ? 'w' : 'b';\r
- *p++ = ' ';\r
-\r
- if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines\r
- while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';\r
- } else {\r
- if(nrCastlingRights) {\r
- q = p;\r
- if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {\r
- /* [HGM] write directly from rights */\r
- if(castlingRights[move][2] >= 0 &&\r
- castlingRights[move][0] >= 0 )\r
- *p++ = castlingRights[move][0] + AAA + 'A' - 'a';\r
- if(castlingRights[move][2] >= 0 &&\r
- castlingRights[move][1] >= 0 )\r
- *p++ = castlingRights[move][1] + AAA + 'A' - 'a';\r
- if(castlingRights[move][5] >= 0 &&\r
- castlingRights[move][3] >= 0 )\r
- *p++ = castlingRights[move][3] + AAA;\r
- if(castlingRights[move][5] >= 0 &&\r
- castlingRights[move][4] >= 0 )\r
- *p++ = castlingRights[move][4] + AAA;\r
- } else {\r
-\r
- /* [HGM] write true castling rights */\r
- if( nrCastlingRights == 6 ) {\r
- if(castlingRights[move][0] == BOARD_RGHT-1 &&\r
- castlingRights[move][2] >= 0 ) *p++ = 'K';\r
- if(castlingRights[move][1] == BOARD_LEFT &&\r
- castlingRights[move][2] >= 0 ) *p++ = 'Q';\r
- if(castlingRights[move][3] == BOARD_RGHT-1 &&\r
- castlingRights[move][5] >= 0 ) *p++ = 'k';\r
- if(castlingRights[move][4] == BOARD_LEFT &&\r
- castlingRights[move][5] >= 0 ) *p++ = 'q';\r
- }\r
- }\r
- if (q == p) *p++ = '-'; /* No castling rights */\r
- *p++ = ' ';\r
- }\r
-\r
- if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&\r
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
- /* En passant target square */\r
- if (move > backwardMostMove) {\r
- fromX = moveList[move - 1][0] - AAA;\r
- fromY = moveList[move - 1][1] - ONE;\r
- toX = moveList[move - 1][2] - AAA;\r
- toY = moveList[move - 1][3] - ONE;\r
- if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&\r
- toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&\r
- boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&\r
- fromX == toX) {\r
- /* 2-square pawn move just happened */\r
- *p++ = toX + AAA;\r
- *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';\r
- } else {\r
- *p++ = '-';\r
- }\r
- } else {\r
- *p++ = '-';\r
- }\r
- *p++ = ' ';\r
- }\r
- }\r
-\r
- /* [HGM] find reversible plies */\r
- { int i = 0, j=move;\r
-\r
- if (appData.debugMode) { int k;\r
- fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);\r
- for(k=backwardMostMove; k<=forwardMostMove; k++)\r
- fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);\r
-\r
- }\r
-\r
- while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;\r
- if( j == backwardMostMove ) i += initialRulePlies;\r
- sprintf(p, "%d ", i);\r
- p += i>=100 ? 4 : i >= 10 ? 3 : 2;\r
- }\r
- /* Fullmove number */\r
- sprintf(p, "%d", (move / 2) + 1);\r
- \r
- return StrSave(buf);\r
-}\r
-\r
-Boolean\r
-ParseFEN(board, blackPlaysFirst, fen)\r
- Board board;\r
- int *blackPlaysFirst;\r
- char *fen;\r
-{\r
- int i, j;\r
- char *p;\r
- int emptycount;\r
- ChessSquare piece;\r
-\r
- p = fen;\r
-\r
- /* [HGM] by default clear Crazyhouse holdings, if present */\r
- if(gameInfo.holdingsWidth) {\r
- for(i=0; i<BOARD_HEIGHT; i++) {\r
- board[i][0] = EmptySquare; /* black holdings */\r
- board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */\r
- board[i][1] = (ChessSquare) 0; /* black counts */\r
- board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */\r
- }\r
- }\r
-\r
- /* Piece placement data */\r
- for (i = BOARD_HEIGHT - 1; i >= 0; i--) {\r
- j = 0;\r
- for (;;) {\r
- if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {\r
- if (*p == '/') p++;\r
- emptycount = gameInfo.boardWidth - j;\r
- while (emptycount--)\r
- board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
- break;\r
-#if(BOARD_SIZE >= 10)\r
- } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */\r
- p++; emptycount=10;\r
- if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
- while (emptycount--)\r
- board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
-#endif\r
- } else if (isdigit(*p)) {\r
- emptycount = *p++ - '0';\r
- while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */\r
- if (j + emptycount > gameInfo.boardWidth) return FALSE;\r
- while (emptycount--)\r
- board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;\r
- } else if (*p == '+' || isalpha(*p)) {\r
- if (j >= gameInfo.boardWidth) return FALSE;\r
- if(*p=='+') {\r
- piece = CharToPiece(*++p);\r
- if(piece == EmptySquare) return FALSE; /* unknown piece */\r
- piece = (ChessSquare) (PROMOTED piece ); p++;\r
- if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */\r
- } else piece = CharToPiece(*p++);\r
-\r
- if(piece==EmptySquare) return FALSE; /* unknown piece */\r
- if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */\r
- piece = (ChessSquare) (PROMOTED piece);\r
- if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */\r
- p++;\r
- }\r
- board[i][(j++)+gameInfo.holdingsWidth] = piece;\r
- } else {\r
- return FALSE;\r
- }\r
- }\r
- }\r
- while (*p == '/' || *p == ' ') p++;\r
-\r
- /* [HGM] look for Crazyhouse holdings here */\r
- while(*p==' ') p++;\r
- if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {\r
- if(*p == '[') p++;\r
- if(*p == '-' ) *p++; /* empty holdings */ else {\r
- if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */\r
- /* if we would allow FEN reading to set board size, we would */\r
- /* have to add holdings and shift the board read so far here */\r
- while( (piece = CharToPiece(*p) ) != EmptySquare ) {\r
- *p++;\r
- if((int) piece >= (int) BlackPawn ) {\r
- i = (int)piece - (int)BlackPawn;\r
- i = PieceToNumber((ChessSquare)i);\r
- if( i >= gameInfo.holdingsSize ) return FALSE;\r
- board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */\r
- board[BOARD_HEIGHT-1-i][1]++; /* black counts */\r
- } else {\r
- i = (int)piece - (int)WhitePawn;\r
- i = PieceToNumber((ChessSquare)i);\r
- if( i >= gameInfo.holdingsSize ) return FALSE;\r
- board[i][BOARD_WIDTH-1] = piece; /* white holdings */\r
- board[i][BOARD_WIDTH-2]++; /* black holdings */\r
- }\r
- }\r
- }\r
- if(*p == ']') *p++;\r
- }\r
-\r
- while(*p == ' ') p++;\r
-\r
- /* Active color */\r
- switch (*p++) {\r
- case 'w':\r
- *blackPlaysFirst = FALSE;\r
- break;\r
- case 'b': \r
- *blackPlaysFirst = TRUE;\r
- break;\r
- default:\r
- return FALSE;\r
- }\r
-\r
- /* [HGM] We NO LONGER ignore the rest of the FEN notation */\r
- /* return the extra info in global variiables */\r
-\r
- /* set defaults in case FEN is incomplete */\r
- FENepStatus = EP_UNKNOWN;\r
- for(i=0; i<nrCastlingRights; i++ ) {\r
- FENcastlingRights[i] =\r
- gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];\r
- } /* assume possible unless obviously impossible */\r
- if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;\r
- if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;\r
- if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;\r
- if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;\r
- if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;\r
- if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;\r
- FENrulePlies = 0;\r
-\r
- while(*p==' ') p++;\r
- if(nrCastlingRights) {\r
- if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {\r
- /* castling indicator present, so default becomes no castlings */\r
- for(i=0; i<nrCastlingRights; i++ ) {\r
- FENcastlingRights[i] = -1;\r
- }\r
- }\r
- while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||\r
- (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&\r
- ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||\r
- ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {\r
- char c = *p++; int whiteKingFile=-1, blackKingFile=-1;\r
-\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {\r
- if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;\r
- if(board[0 ][i] == WhiteKing) whiteKingFile = i;\r
- }\r
- switch(c) {\r
- case'K':\r
- for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);\r
- FENcastlingRights[0] = i != whiteKingFile ? i : -1;\r
- FENcastlingRights[2] = whiteKingFile;\r
- break;\r
- case'Q':\r
- for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);\r
- FENcastlingRights[1] = i != whiteKingFile ? i : -1;\r
- FENcastlingRights[2] = whiteKingFile;\r
- break;\r
- case'k':\r
- for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);\r
- FENcastlingRights[3] = i != blackKingFile ? i : -1;\r
- FENcastlingRights[5] = blackKingFile;\r
- break;\r
- case'q':\r
- for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);\r
- FENcastlingRights[4] = i != blackKingFile ? i : -1;\r
- FENcastlingRights[5] = blackKingFile;\r
- case '-':\r
- break;\r
- default: /* FRC castlings */\r
- if(c >= 'a') { /* black rights */\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
- if(board[BOARD_HEIGHT-1][i] == BlackKing) break;\r
- if(i == BOARD_RGHT) break;\r
- FENcastlingRights[5] = i;\r
- c -= AAA;\r
- if(board[BOARD_HEIGHT-1][c] < BlackPawn ||\r
- board[BOARD_HEIGHT-1][c] >= BlackKing ) break;\r
- if(c > i)\r
- FENcastlingRights[3] = c;\r
- else\r
- FENcastlingRights[4] = c;\r
- } else { /* white rights */\r
- for(i=BOARD_LEFT; i<BOARD_RGHT; i++)\r
- if(board[0][i] == WhiteKing) break;\r
- if(i == BOARD_RGHT) break;\r
- FENcastlingRights[2] = i;\r
- c -= AAA - 'a' + 'A';\r
- if(board[0][c] >= WhiteKing) break;\r
- if(c > i)\r
- FENcastlingRights[0] = c;\r
- else\r
- FENcastlingRights[1] = c;\r
- }\r
- }\r
- }\r
- if (appData.debugMode) {\r
- fprintf(debugFP, "FEN castling rights:");\r
- for(i=0; i<nrCastlingRights; i++)\r
- fprintf(debugFP, " %d", FENcastlingRights[i]);\r
- fprintf(debugFP, "\n");\r
- }\r
-\r
- while(*p==' ') p++;\r
- }\r
-\r
- /* read e.p. field in games that know e.p. capture */\r
- if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&\r
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) { \r
- if(*p=='-') {\r
- p++; FENepStatus = EP_NONE;\r
- } else {\r
- char c = *p++ - AAA;\r
-\r
- if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;\r
- if(*p >= '0' && *p <='9') *p++;\r
- FENepStatus = c;\r
- }\r
- }\r
-\r
-\r
- if(sscanf(p, "%d", &i) == 1) {\r
- FENrulePlies = i; /* 50-move ply counter */\r
- /* (The move number is still ignored) */\r
- }\r
-\r
- return TRUE;\r
-}\r
- \r
-void\r
-EditPositionPasteFEN(char *fen)\r
-{\r
- if (fen != NULL) {\r
- Board initial_position;\r
-\r
- if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {\r
- DisplayError(_("Bad FEN position in clipboard"), 0);\r
- return ;\r
- } else {\r
- int savedBlackPlaysFirst = blackPlaysFirst;\r
- EditPositionEvent();\r
- blackPlaysFirst = savedBlackPlaysFirst;\r
- CopyBoard(boards[0], initial_position);\r
- /* [HGM] copy FEN attributes as well */\r
- { int i;\r
- initialRulePlies = FENrulePlies;\r
- epStatus[0] = FENepStatus;\r
- for( i=0; i<nrCastlingRights; i++ )\r
- castlingRights[0][i] = FENcastlingRights[i];\r
- }\r
- EditPositionDone();\r
- DisplayBothClocks();\r
- DrawPosition(FALSE, boards[currentMove]);\r
- }\r
- }\r
-}\r
+/*
+ * backend.c -- Common back end for X and Windows NT versions of
+ * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
+ *
+ * Copyright 1991 by Digital Equipment Corporation, Maynard,
+ * Massachusetts. Enhancements Copyright
+ * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
+ * Foundation, Inc.
+ *
+ * The following terms apply to Digital Equipment Corporation's copyright
+ * interest in XBoard:
+ * ------------------------------------------------------------------------
+ * All Rights Reserved
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation, and that the name of Digital not be
+ * used in advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ *
+ * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
+ * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
+ * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ * ------------------------------------------------------------------------
+ *
+ * The following terms apply to the enhanced version of XBoard
+ * distributed by the Free Software Foundation:
+ * ------------------------------------------------------------------------
+ *
+ * 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 3 of the License, or (at
+ * your option) any later version.
+ *
+ * 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, see http://www.gnu.org/licenses/. *
+ *
+ *------------------------------------------------------------------------
+ ** See the file ChangeLog for a revision history. */
+
+/* [AS] Also useful here for debugging */
+#ifdef WIN32
+#include <windows.h>
+
+#define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
+
+#else
+
+#define DoSleep( n ) if( (n) >= 0) sleep(n)
+
+#endif
+
+#include "config.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <ctype.h>
+
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <string.h>
+#else /* not STDC_HEADERS */
+# if HAVE_STRING_H
+# include <string.h>
+# else /* not HAVE_STRING_H */
+# include <strings.h>
+# endif /* not HAVE_STRING_H */
+#endif /* not STDC_HEADERS */
+
+#if HAVE_SYS_FCNTL_H
+# include <sys/fcntl.h>
+#else /* not HAVE_SYS_FCNTL_H */
+# if HAVE_FCNTL_H
+# include <fcntl.h>
+# endif /* HAVE_FCNTL_H */
+#endif /* not HAVE_SYS_FCNTL_H */
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#if defined(_amigados) && !defined(__GNUC__)
+struct timezone {
+ int tz_minuteswest;
+ int tz_dsttime;
+};
+extern int gettimeofday(struct timeval *, struct timezone *);
+#endif
+
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include "common.h"
+#include "frontend.h"
+#include "backend.h"
+#include "parser.h"
+#include "moves.h"
+#if ZIPPY
+# include "zippy.h"
+#endif
+#include "backendz.h"
+#include "gettext.h"
+
+#ifdef ENABLE_NLS
+# define _(s) gettext (s)
+# define N_(s) gettext_noop (s)
+#else
+# define _(s) (s)
+# define N_(s) s
+#endif
+
+
+/* A point in time */
+typedef struct {
+ long sec; /* Assuming this is >= 32 bits */
+ int ms; /* Assuming this is >= 16 bits */
+} TimeMark;
+
+int establish P((void));
+void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
+ char *buf, int count, int error));
+void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
+ char *buf, int count, int error));
+void 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));
+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, 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));
+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));
+void GameEnds P((ChessMove result, char *resultDetails, int whosays));
+void EditPositionDone P((void));
+void PrintOpponents P((FILE *fp));
+void PrintPosition P((FILE *fp, int move));
+void StartChessProgram P((ChessProgramState *cps));
+void SendToProgram P((char *message, ChessProgramState *cps));
+void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
+void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
+ char *buf, int count, int error));
+void SendTimeControl P((ChessProgramState *cps,
+ int mps, long tc, int inc, int sd, int st));
+char *TimeControlTagValue P((void));
+void Attention P((ChessProgramState *cps));
+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));
+void StartClocks P((void));
+void SwitchClocks P((void));
+void StopClocks P((void));
+void ResetClocks P((void));
+char *PGNDate P((void));
+void SetGameInfo P((void));
+Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
+int RegisterMove P((void));
+void MakeRegisteredMove P((void));
+void TruncateGame P((void));
+int looking_at P((char *, int *, char *));
+void CopyPlayerNameIntoFileName P((char **, char *));
+char *SavePart P((char *));
+int SaveGameOldStyle P((FILE *));
+int SaveGamePGN P((FILE *));
+void GetTimeMark P((TimeMark *));
+long SubtractTimeMarks P((TimeMark *, TimeMark *));
+int CheckFlags P((void));
+long NextTickLength P((long));
+void CheckTimeControl P((void));
+void show_bytes P((FILE *, char *, int));
+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, 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();
+#endif
+
+ChessProgramState *WhitePlayer();
+void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
+int VerifyDisplayMode P(());
+
+char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
+void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
+char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
+char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
+extern char installDir[MSG_SIZ];
+
+extern int tinyLayout, smallLayout;
+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;
+
+/* States for ics_getting_history */
+#define H_FALSE 0
+#define H_REQUESTED 1
+#define H_GOT_REQ_HEADER 2
+#define H_GOT_UNREQ_HEADER 3
+#define H_GETTING_MOVES 4
+#define H_GOT_UNWANTED_HEADER 5
+
+/* whosays values for GameEnds */
+#define GE_ICS 0
+#define GE_ENGINE 1
+#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
+
+/* Different types of move when calling RegisterMove */
+#define CMAIL_MOVE 0
+#define CMAIL_RESIGN 1
+#define CMAIL_DRAW 2
+#define CMAIL_ACCEPT 3
+
+/* Different types of result to remember for each game */
+#define CMAIL_NOT_RESULT 0
+#define CMAIL_OLD_RESULT 1
+#define CMAIL_NEW_RESULT 2
+
+/* Telnet protocol constants */
+#define TN_WILL 0373
+#define TN_WONT 0374
+#define TN_DO 0375
+#define TN_DONT 0376
+#define TN_IAC 0377
+#define TN_ECHO 0001
+#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;
+}
+
+#if 0
+//[HGM] for future use? Conditioned out for now to suppress warning.
+static char * safeStrCat( char * dst, const char * src, size_t count )
+{
+ size_t dst_len;
+
+ assert( dst != NULL );
+ assert( src != NULL );
+ assert( count > 0 );
+
+ dst_len = strlen(dst);
+
+ assert( count > dst_len ); /* Buffer size must be greater than current length */
+
+ safeStrCpy( dst + dst_len, src, count - dst_len );
+
+ return dst;
+}
+#endif
+
+/* Some compiler can't cast u64 to double
+ * This function do the job for us:
+
+ * We use the highest bit for cast, this only
+ * works if the highest bit is not
+ * in use (This should not happen)
+ *
+ * We used this for all compiler
+ */
+double
+u64ToDouble(u64 value)
+{
+ double r;
+ u64 tmp = value & u64Const(0x7fffffffffffffff);
+ r = (double)(s64)tmp;
+ if (value & u64Const(0x8000000000000000))
+ r += 9.2233720368547758080e18; /* 2^63 */
+ return r;
+}
+
+/* Fake up flags for now, as we aren't keeping track of castling
+ availability yet. [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)
+{
+ int flags = F_ALL_CASTLE_OK;
+ if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
+ switch (gameInfo.variant) {
+ case VariantSuicide:
+ 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;
+ break;
+ 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:
+ break;
+ }
+ return flags;
+}
+
+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];
+
+ChessProgramState first, second;
+
+/* premove variables */
+int premoveToX = 0;
+int premoveToY = 0;
+int premoveFromX = 0;
+int premoveFromY = 0;
+int premovePromoChar = 0;
+int gotPremove = 0;
+Boolean alarmSounded;
+/* end premove variables */
+
+char *ics_prefix = "$";
+int ics_type = ICS_GENERIC;
+
+int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
+int pauseExamForwardMostMove = 0;
+int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
+int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
+int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
+int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
+int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
+int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
+int whiteFlag = FALSE, blackFlag = FALSE;
+int userOfferedDraw = FALSE;
+int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
+int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
+int cmailMoveType[CMAIL_MAX_GAMES];
+long ics_clock_paused = 0;
+ProcRef icsPR = NoProc, cmailPR = NoProc;
+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;
+char ics_handle[MSG_SIZ];
+int have_set_title = 0;
+
+/* animateTraining preserves the state of appData.animate
+ * when Training mode is activated. This allows the
+ * response to be animated when appData.animate == TRUE and
+ * appData.animateDragging == TRUE.
+ */
+Boolean animateTraining;
+
+GameInfo gameInfo;
+
+AppData appData;
+
+Board boards[MAX_MOVES];
+/* [HGM] Following 7 needed for accurate legality tests: */
+char epStatus[MAX_MOVES];
+char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
+char castlingRank[BOARD_SIZE]; // and corresponding ranks
+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;
+
+ChessSquare FIDEArray[2][BOARD_SIZE] = {
+ { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
+ WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackBishop, BlackQueen,
+ BlackKing, BlackBishop, BlackKnight, BlackRook }
+};
+
+ChessSquare twoKingsArray[2][BOARD_SIZE] = {
+ { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
+ WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackBishop, BlackQueen,
+ 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)
+ char *str;
+{
+ while(*str && !isdigit(*str)) ++str;
+ if (!*str)
+ return 0; /* One of the special "no rating" cases */
+ else
+ return atoi(str);
+}
+
+void
+ClearProgramStats()
+{
+ /* Init programStats */
+ programStats.movelist[0] = 0;
+ programStats.depth = 0;
+ programStats.nr_moves = 0;
+ programStats.moves_left = 0;
+ programStats.nodes = 0;
+ programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
+ programStats.score = 0;
+ programStats.got_only_move = 0;
+ programStats.got_fail = 0;
+ programStats.line_is_book = 0;
+}
+
+void
+InitBackEnd1()
+{
+ int matched, min, sec;
+
+ ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
+
+ GetTimeMark(&programStartTime);
+ srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
+
+ ClearProgramStats();
+ programStats.ok_to_send = 1;
+ programStats.seen_stat = 0;
+
+ /*
+ * Initialize game list
+ */
+ ListNew(&gameList);
+
+
+ /*
+ * Internet chess server status
+ */
+ if (appData.icsActive) {
+ appData.matchMode = FALSE;
+ appData.matchGames = 0;
+#if ZIPPY
+ appData.noChessProgram = !appData.zippyPlay;
+#else
+ appData.zippyPlay = FALSE;
+ appData.zippyTalk = FALSE;
+ appData.noChessProgram = TRUE;
+#endif
+ if (*appData.icsHelper != NULLCHAR) {
+ appData.useTelnet = TRUE;
+ appData.telnetProgram = appData.icsHelper;
+ }
+ } else {
+ appData.zippyTalk = appData.zippyPlay = FALSE;
+ }
+
+ /* [AS] Initialize pv info list [HGM] and game state */
+ {
+ int i, j;
+
+ for( i=0; i<MAX_MOVES; i++ ) {
+ pvInfoList[i].depth = -1;
+ epStatus[i]=EP_NONE;
+ for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
+ }
+ }
+
+ /*
+ * Parse timeControl resource
+ */
+ if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
+ appData.movesPerSession)) {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
+ DisplayFatalError(buf, 0, 2);
+ }
+
+ /*
+ * Parse searchTime resource
+ */
+ if (*appData.searchTime != NULLCHAR) {
+ matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
+ if (matched == 1) {
+ searchTime = min * 60;
+ } else if (matched == 2) {
+ searchTime = min * 60 + sec;
+ } else {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
+ DisplayFatalError(buf, 0, 2);
+ }
+ }
+
+ /* [AS] Adjudication threshold */
+ adjudicateLossThreshold = appData.adjudicateLossThreshold;
+
+ first.which = "first";
+ second.which = "second";
+ first.maybeThinking = second.maybeThinking = FALSE;
+ first.pr = second.pr = NoProc;
+ first.isr = second.isr = NULL;
+ first.sendTime = second.sendTime = 2;
+ first.sendDrawOffers = 1;
+ if (appData.firstPlaysBlack) {
+ first.twoMachinesColor = "black\n";
+ second.twoMachinesColor = "white\n";
+ } else {
+ first.twoMachinesColor = "white\n";
+ second.twoMachinesColor = "black\n";
+ }
+ first.program = appData.firstChessProgram;
+ second.program = appData.secondChessProgram;
+ first.host = appData.firstHost;
+ second.host = appData.secondHost;
+ first.dir = appData.firstDirectory;
+ second.dir = appData.secondDirectory;
+ first.other = &second;
+ second.other = &first;
+ first.initString = appData.initString;
+ second.initString = appData.secondInitString;
+ first.computerString = appData.firstComputerString;
+ second.computerString = appData.secondComputerString;
+ first.useSigint = second.useSigint = TRUE;
+ first.useSigterm = second.useSigterm = TRUE;
+ first.reuse = appData.reuseFirst;
+ second.reuse = appData.reuseSecond;
+ first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
+ second.nps = appData.secondNPS;
+ first.useSetboard = second.useSetboard = FALSE;
+ first.useSAN = second.useSAN = FALSE;
+ first.usePing = second.usePing = FALSE;
+ first.lastPing = second.lastPing = 0;
+ first.lastPong = second.lastPong = 0;
+ first.usePlayother = second.usePlayother = FALSE;
+ first.useColors = second.useColors = TRUE;
+ first.useUsermove = second.useUsermove = FALSE;
+ first.sendICS = second.sendICS = FALSE;
+ first.sendName = second.sendName = appData.icsActive;
+ first.sdKludge = second.sdKludge = FALSE;
+ first.stKludge = second.stKludge = FALSE;
+ TidyProgramName(first.program, first.host, first.tidy);
+ TidyProgramName(second.program, second.host, second.tidy);
+ first.matchWins = second.matchWins = 0;
+ strcpy(first.variants, appData.variant);
+ strcpy(second.variants, appData.variant);
+ first.analysisSupport = second.analysisSupport = 2; /* detect */
+ first.analyzing = second.analyzing = FALSE;
+ first.initDone = second.initDone = FALSE;
+
+ /* New features added by Tord: */
+ first.useFEN960 = FALSE; second.useFEN960 = FALSE;
+ first.useOOCastle = TRUE; second.useOOCastle = TRUE;
+ /* End of new features added by Tord. */
+ first.fenOverride = appData.fenOverride1;
+ second.fenOverride = appData.fenOverride2;
+
+ /* [HGM] time odds: set factor for each machine */
+ first.timeOdds = appData.firstTimeOdds;
+ second.timeOdds = appData.secondTimeOdds;
+ { int norm = 1;
+ if(appData.timeOddsMode) {
+ norm = first.timeOdds;
+ if(norm > 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];
+ sprintf(buf, _("protocol version %d not supported"),
+ appData.firstProtocolVersion);
+ DisplayFatalError(buf, 0, 2);
+ } else {
+ first.protocolVersion = appData.firstProtocolVersion;
+ }
+
+ if (appData.secondProtocolVersion > PROTOVER ||
+ appData.secondProtocolVersion < 1) {
+ char buf[MSG_SIZ];
+ sprintf(buf, _("protocol version %d not supported"),
+ appData.secondProtocolVersion);
+ DisplayFatalError(buf, 0, 2);
+ } else {
+ second.protocolVersion = appData.secondProtocolVersion;
+ }
+
+ if (appData.icsActive) {
+ appData.clockMode = TRUE; /* changes dynamically in ICS mode */
+ } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
+ 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
+ vars just set defaults, at least in xboard. Ugh.
+ */
+ if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
+ ZippyInit();
+ }
+#endif
+
+ if (appData.noChessProgram) {
+ programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
+ + strlen(PATCHLEVEL));
+ sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
+ } else {
+#if 0
+ char *p, *q;
+ q = first.program;
+ while (*q != ' ' && *q != NULLCHAR) q++;
+ p = q;
+ while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
+ programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
+ + strlen(PATCHLEVEL) + (q - p));
+ sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
+ strncat(programVersion, p, q - p);
+#else
+ /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
+ programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
+ + strlen(PATCHLEVEL) + strlen(first.tidy));
+ sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
+#endif
+ }
+
+ if (!appData.icsActive) {
+ char buf[MSG_SIZ];
+ /* Check for variants that are supported only in ICS mode,
+ or not at all. Some that are accepted here nevertheless
+ have bugs; see comments below.
+ */
+ VariantClass variant = StringToVariant(appData.variant);
+ switch (variant) {
+ case VariantBughouse: /* need four players and two boards */
+ case VariantKriegspiel: /* need to hide pieces and move details */
+ /* case VariantFischeRandom: (Fabien: moved below) */
+ sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
+ DisplayFatalError(buf, 0, 2);
+ return;
+
+ case VariantUnknown:
+ case VariantLoadable:
+ case Variant29:
+ case Variant30:
+ case Variant31:
+ case Variant32:
+ case Variant33:
+ case Variant34:
+ case Variant35:
+ case Variant36:
+ default:
+ sprintf(buf, _("Unknown variant name %s"), appData.variant);
+ DisplayFatalError(buf, 0, 2);
+ return;
+
+ case VariantXiangqi: /* [HGM] repetition rules not implemented */
+ case VariantFairy: /* [HGM] TestLegality definitely off! */
+ case VariantGothic: /* [HGM] should work */
+ case VariantCapablanca: /* [HGM] should work */
+ case VariantCourier: /* [HGM] initial forced moves not implemented */
+ case VariantShogi: /* [HGM] drops not tested for legality */
+ case VariantKnightmate: /* [HGM] should work */
+ case VariantCylinder: /* [HGM] untested */
+ case VariantFalcon: /* [HGM] untested */
+ case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
+ offboard interposition not understood */
+ case VariantNormal: /* definitely works! */
+ case VariantWildCastle: /* pieces not automatically shuffled */
+ case VariantNoCastle: /* pieces not automatically shuffled */
+ case VariantFischeRandom: /* [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,
+ and doesn't know captures are mandatory */
+ case VariantGiveaway: /* should work except for win condition,
+ and doesn't know captures are mandatory */
+ case VariantTwoKings: /* should work */
+ case VariantAtomic: /* should work except for win condition */
+ case Variant3Check: /* should work except for win condition */
+ case VariantShatranj: /* should work except for all win conditions */
+ case 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
+ParseTimeControl(tc, ti, mps)
+ char *tc;
+ int ti;
+ int mps;
+{
+#if 0
+ 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;
+ }
+#else
+ long tc1;
+ long tc2;
+ char buf[MSG_SIZ];
+
+ if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
+ if(ti > 0) {
+ if(mps)
+ sprintf(buf, "+%d/%s+%d", mps, tc, ti);
+ else sprintf(buf, "+%s+%d", tc, ti);
+ } else {
+ if(mps)
+ sprintf(buf, "+%d/%s", mps, tc);
+ else sprintf(buf, "+%s", tc);
+ }
+ fullTimeControlString = StrSave(buf);
+
+ if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
+ return FALSE;
+ }
+
+ if( *tc == '/' ) {
+ /* Parse second time control */
+ tc++;
+
+ if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
+ return FALSE;
+ }
+
+ if( tc2 == 0 ) {
+ return FALSE;
+ }
+
+ timeControl_2 = tc2 * 1000;
+ }
+ else {
+ timeControl_2 = 0;
+ }
+
+ if( tc1 == 0 ) {
+ return FALSE;
+ }
+
+ timeControl = tc1 * 1000;
+#endif
+
+ if (ti >= 0) {
+ timeIncrement = ti * 1000; /* convert to ms */
+ movesPerSession = 0;
+ } else {
+ timeIncrement = 0;
+ movesPerSession = mps;
+ }
+ return TRUE;
+}
+
+void
+InitBackEnd2()
+{
+ if (appData.debugMode) {
+ fprintf(debugFP, "%s\n", programVersion);
+ }
+
+ 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"));
+ ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
+ }
+}
+
+void
+InitBackEnd3 P((void))
+{
+ GameMode initialMode;
+ char buf[MSG_SIZ];
+ int err;
+
+ InitChessProgram(&first, startedFromSetupPosition);
+
+
+ if (appData.icsActive) {
+#ifdef WIN32
+ /* [DM] Make a console window if needed [HGM] merged ifs */
+ ConsoleCreate();
+#endif
+ err = establish();
+ if (err != 0) {
+ if (*appData.icsCommPort != NULLCHAR) {
+ sprintf(buf, _("Could not open comm port %s"),
+ appData.icsCommPort);
+ } else {
+ snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
+ appData.icsHost, appData.icsPort);
+ }
+ DisplayFatalError(buf, err, 1);
+ return;
+ }
+ SetICSMode();
+ telnetISR =
+ AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
+ fromUserISR =
+ AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
+ } else if (appData.noChessProgram) {
+ SetNCPMode();
+ } else {
+ SetGNUMode();
+ }
+
+ if (*appData.cmailGameName != NULLCHAR) {
+ SetCmailMode();
+ OpenLoopback(&cmailPR);
+ cmailISR =
+ AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
+ }
+
+ ThawUI();
+ DisplayMessage("", "");
+ if (StrCaseCmp(appData.initialMode, "") == 0) {
+ initialMode = BeginningOfGame;
+ } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
+ initialMode = TwoMachinesPlay;
+ } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
+ initialMode = AnalyzeFile;
+ } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
+ initialMode = AnalyzeMode;
+ } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
+ initialMode = MachinePlaysWhite;
+ } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
+ initialMode = MachinePlaysBlack;
+ } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
+ initialMode = EditGame;
+ } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
+ initialMode = EditPosition;
+ } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
+ initialMode = Training;
+ } else {
+ sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
+ DisplayFatalError(buf, 0, 2);
+ return;
+ }
+
+ if (appData.matchMode) {
+ /* Set up machine vs. machine match */
+ if (appData.noChessProgram) {
+ DisplayFatalError(_("Can't have a match with no chess programs"),
+ 0, 2);
+ return;
+ }
+ matchMode = TRUE;
+ matchGame = 1;
+ if (*appData.loadGameFile != NULLCHAR) {
+ int index = appData.loadGameIndex; // [HGM] autoinc
+ if(index<0) lastIndex = index = 1;
+ if (!LoadGameFromFile(appData.loadGameFile,
+ index,
+ appData.loadGameFile, FALSE)) {
+ DisplayFatalError(_("Bad game file"), 0, 1);
+ return;
+ }
+ } else if (*appData.loadPositionFile != NULLCHAR) {
+ int index = appData.loadPositionIndex; // [HGM] autoinc
+ if(index<0) lastIndex = index = 1;
+ if (!LoadPositionFromFile(appData.loadPositionFile,
+ index,
+ appData.loadPositionFile)) {
+ DisplayFatalError(_("Bad position file"), 0, 1);
+ return;
+ }
+ }
+ TwoMachinesEvent();
+ } else if (*appData.cmailGameName != NULLCHAR) {
+ /* Set up cmail mode */
+ ReloadCmailMsgEvent(TRUE);
+ } else {
+ /* Set up other modes */
+ if (initialMode == AnalyzeFile) {
+ if (*appData.loadGameFile == NULLCHAR) {
+ DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
+ return;
+ }
+ }
+ if (*appData.loadGameFile != NULLCHAR) {
+ (void) LoadGameFromFile(appData.loadGameFile,
+ appData.loadGameIndex,
+ appData.loadGameFile, TRUE);
+ } else if (*appData.loadPositionFile != NULLCHAR) {
+ (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<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
+ }
+ }
+ if (initialMode == AnalyzeMode) {
+ if (appData.noChessProgram) {
+ DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
+ return;
+ }
+ if (appData.icsActive) {
+ DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
+ return;
+ }
+ AnalyzeModeEvent();
+ } else if (initialMode == AnalyzeFile) {
+ appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
+ ShowThinkingEvent();
+ AnalyzeFileEvent();
+ AnalysisPeriodicEvent(1);
+ } else if (initialMode == MachinePlaysWhite) {
+ if (appData.noChessProgram) {
+ DisplayFatalError(_("MachineWhite mode requires a chess engine"),
+ 0, 2);
+ return;
+ }
+ if (appData.icsActive) {
+ DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
+ 0, 2);
+ return;
+ }
+ MachineWhiteEvent();
+ } else if (initialMode == MachinePlaysBlack) {
+ if (appData.noChessProgram) {
+ DisplayFatalError(_("MachineBlack mode requires a chess engine"),
+ 0, 2);
+ return;
+ }
+ if (appData.icsActive) {
+ DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
+ 0, 2);
+ return;
+ }
+ MachineBlackEvent();
+ } else if (initialMode == TwoMachinesPlay) {
+ if (appData.noChessProgram) {
+ DisplayFatalError(_("TwoMachines mode requires a chess engine"),
+ 0, 2);
+ return;
+ }
+ if (appData.icsActive) {
+ DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
+ 0, 2);
+ return;
+ }
+ TwoMachinesEvent();
+ } else if (initialMode == EditGame) {
+ EditGameEvent();
+ } else if (initialMode == EditPosition) {
+ EditPositionEvent();
+ } else if (initialMode == Training) {
+ if (*appData.loadGameFile == NULLCHAR) {
+ DisplayFatalError(_("Training mode requires a game file"), 0, 2);
+ return;
+ }
+ TrainingEvent();
+ }
+ }
+}
+
+/*
+ * Establish will establish a contact to a remote host.port.
+ * Sets icsPR to a ProcRef for a process (or pseudo-process)
+ * used to talk to the host.
+ * Returns 0 if okay, error code if not.
+ */
+int
+establish()
+{
+ char buf[MSG_SIZ];
+
+ if (*appData.icsCommPort != NULLCHAR) {
+ /* Talk to the host through a serial comm port */
+ return OpenCommPort(appData.icsCommPort, &icsPR);
+
+ } else if (*appData.gateway != NULLCHAR) {
+ if (*appData.remoteShell == NULLCHAR) {
+ /* Use the rcmd protocol to run telnet program on a gateway host */
+ snprintf(buf, sizeof(buf), "%s %s %s",
+ appData.telnetProgram, appData.icsHost, appData.icsPort);
+ return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
+
+ } else {
+ /* Use the rsh program to run telnet program on a gateway host */
+ if (*appData.remoteUser == NULLCHAR) {
+ snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
+ appData.gateway, appData.telnetProgram,
+ appData.icsHost, appData.icsPort);
+ } else {
+ snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
+ appData.remoteShell, appData.gateway,
+ appData.remoteUser, appData.telnetProgram,
+ appData.icsHost, appData.icsPort);
+ }
+ return StartChildProcess(buf, "", &icsPR);
+
+ }
+ } else if (appData.useTelnet) {
+ return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
+
+ } else {
+ /* TCP socket interface differs somewhat between
+ Unix and NT; handle details in the front end.
+ */
+ return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
+ }
+}
+
+void
+show_bytes(fp, buf, count)
+ FILE *fp;
+ char *buf;
+ int count;
+{
+ while (count--) {
+ if (*buf < 040 || *(unsigned char *) buf > 0177) {
+ fprintf(fp, "\\%03o", *buf & 0xff);
+ } else {
+ putc(*buf, fp);
+ }
+ buf++;
+ }
+ fflush(fp);
+}
+
+/* Returns an errno value */
+int
+OutputMaybeTelnet(pr, message, count, outError)
+ ProcRef pr;
+ char *message;
+ int count;
+ int *outError;
+{
+ char buf[8192], *p, *q, *buflim;
+ int left, newcount, outcount;
+
+ if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
+ *appData.gateway != NULLCHAR) {
+ if (appData.debugMode) {
+ fprintf(debugFP, ">ICS: ");
+ show_bytes(debugFP, message, count);
+ fprintf(debugFP, "\n");
+ }
+ return OutputToProcess(pr, message, count, outError);
+ }
+
+ buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
+ p = message;
+ q = buf;
+ left = count;
+ newcount = 0;
+ while (left) {
+ if (q >= buflim) {
+ if (appData.debugMode) {
+ fprintf(debugFP, ">ICS: ");
+ show_bytes(debugFP, buf, newcount);
+ fprintf(debugFP, "\n");
+ }
+ outcount = OutputToProcess(pr, buf, newcount, outError);
+ if (outcount < newcount) return -1; /* to be sure */
+ q = buf;
+ newcount = 0;
+ }
+ if (*p == '\n') {
+ *q++ = '\r';
+ newcount++;
+ } else if (((unsigned char) *p) == TN_IAC) {
+ *q++ = (char) TN_IAC;
+ newcount ++;
+ }
+ *q++ = *p++;
+ newcount++;
+ left--;
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, ">ICS: ");
+ show_bytes(debugFP, buf, newcount);
+ fprintf(debugFP, "\n");
+ }
+ outcount = OutputToProcess(pr, buf, newcount, outError);
+ if (outcount < newcount) return -1; /* to be sure */
+ return count;
+}
+
+void
+read_from_player(isr, closure, message, count, error)
+ InputSourceRef isr;
+ VOIDSTAR closure;
+ char *message;
+ int count;
+ int error;
+{
+ int outError, outCount;
+ static int gotEof = 0;
+
+ /* Pass data read from player on to ICS */
+ if (count > 0) {
+ gotEof = 0;
+ outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
+ if (outCount < count) {
+ DisplayFatalError(_("Error writing to ICS"), outError, 1);
+ }
+ } else if (count < 0) {
+ RemoveInputSource(isr);
+ DisplayFatalError(_("Error reading from keyboard"), error, 1);
+ } else if (gotEof++ > 0) {
+ RemoveInputSource(isr);
+ DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
+ }
+}
+
+void
+SendToICS(s)
+ char *s;
+{
+ int count, outCount, outError;
+
+ if (icsPR == NULL) return;
+
+ count = strlen(s);
+ outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
+ if (outCount < count) {
+ DisplayFatalError(_("Error writing to ICS"), outError, 1);
+ }
+}
+
+/* This is used for sending logon scripts to the ICS. Sending
+ without a delay causes problems when using timestamp on ICC
+ (at least on my machine). */
+void
+SendToICSDelayed(s,msdelay)
+ char *s;
+ long msdelay;
+{
+ int count, outCount, outError;
+
+ if (icsPR == NULL) return;
+
+ count = strlen(s);
+ if (appData.debugMode) {
+ fprintf(debugFP, ">ICS: ");
+ show_bytes(debugFP, s, count);
+ fprintf(debugFP, "\n");
+ }
+ outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
+ msdelay);
+ if (outCount < count) {
+ DisplayFatalError(_("Error writing to ICS"), outError, 1);
+ }
+}
+
+
+/* Remove all highlighting escape sequences in s
+ Also deletes any suffix starting with '('
+ */
+char *
+StripHighlightAndTitle(s)
+ char *s;
+{
+ static char retbuf[MSG_SIZ];
+ char *p = retbuf;
+
+ while (*s != NULLCHAR) {
+ while (*s == '\033') {
+ while (*s != NULLCHAR && !isalpha(*s)) s++;
+ if (*s != NULLCHAR) s++;
+ }
+ while (*s != NULLCHAR && *s != '\033') {
+ if (*s == '(' || *s == '[') {
+ *p = NULLCHAR;
+ return retbuf;
+ }
+ *p++ = *s++;
+ }
+ }
+ *p = NULLCHAR;
+ return retbuf;
+}
+
+/* Remove all highlighting escape sequences in s */
+char *
+StripHighlight(s)
+ char *s;
+{
+ static char retbuf[MSG_SIZ];
+ char *p = retbuf;
+
+ while (*s != NULLCHAR) {
+ while (*s == '\033') {
+ while (*s != NULLCHAR && !isalpha(*s)) s++;
+ if (*s != NULLCHAR) s++;
+ }
+ while (*s != NULLCHAR && *s != '\033') {
+ *p++ = *s++;
+ }
+ }
+ *p = NULLCHAR;
+ return retbuf;
+}
+
+char *variantNames[] = VARIANT_NAMES;
+char *
+VariantName(v)
+ VariantClass v;
+{
+ return variantNames[v];
+}
+
+
+/* Identify a variant from the strings the chess servers use or the
+ PGN Variant tag names we use. */
+VariantClass
+StringToVariant(e)
+ char *e;
+{
+ char *p;
+ int wnum = -1;
+ VariantClass v = VariantNormal;
+ int i, found = FALSE;
+ char buf[MSG_SIZ];
+
+ if (!e) return v;
+
+ /* [HGM] skip over optional board-size prefixes */
+ if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
+ sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
+ while( *e++ != '_');
+ }
+
+ for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
+ if (StrCaseStr(e, variantNames[i])) {
+ v = (VariantClass) i;
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
+ || StrCaseStr(e, "wild/fr")
+ || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
+ v = VariantFischeRandom;
+ } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
+ (i = 1, p = StrCaseStr(e, "w"))) {
+ p += i;
+ while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
+ if (isdigit(*p)) {
+ wnum = atoi(p);
+ } else {
+ wnum = -1;
+ }
+ switch (wnum) {
+ case 0: /* FICS only, actually */
+ case 1:
+ /* Castling legal even if K starts on d-file */
+ v = VariantWildCastle;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ /* Castling illegal even if K & R happen to start in
+ normal positions. */
+ v = VariantNoCastle;
+ break;
+ case 5:
+ case 7:
+ case 8:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 18:
+ case 19:
+ /* Castling legal iff K & R start in normal positions */
+ v = VariantNormal;
+ break;
+ case 6:
+ case 20:
+ case 21:
+ /* Special wilds for position setup; unclear what to do here */
+ v = VariantLoadable;
+ break;
+ case 9:
+ /* Bizarre ICC game */
+ v = VariantTwoKings;
+ break;
+ case 16:
+ v = VariantKriegspiel;
+ break;
+ case 17:
+ v = VariantLosers;
+ break;
+ case 22:
+ v = VariantFischeRandom;
+ break;
+ case 23:
+ v = VariantCrazyhouse;
+ break;
+ case 24:
+ v = VariantBughouse;
+ break;
+ case 25:
+ v = Variant3Check;
+ break;
+ case 26:
+ /* Not quite the same as FICS suicide! */
+ v = VariantGiveaway;
+ break;
+ case 27:
+ v = VariantAtomic;
+ break;
+ case 28:
+ v = VariantShatranj;
+ break;
+
+ /* Temporary names for future ICC types. The name *will* change in
+ the next xboard/WinBoard release after ICC defines it. */
+ case 29:
+ v = Variant29;
+ break;
+ case 30:
+ v = Variant30;
+ break;
+ case 31:
+ v = Variant31;
+ break;
+ case 32:
+ v = Variant32;
+ break;
+ case 33:
+ v = Variant33;
+ break;
+ case 34:
+ v = Variant34;
+ break;
+ case 35:
+ v = Variant35;
+ break;
+ case 36:
+ v = Variant36;
+ break;
+ case 37:
+ v = VariantShogi;
+ break;
+ case 38:
+ v = VariantXiangqi;
+ break;
+ case 39:
+ v = VariantCourier;
+ break;
+ case 40:
+ v = VariantGothic;
+ break;
+ case 41:
+ v = VariantCapablanca;
+ break;
+ case 42:
+ v = VariantKnightmate;
+ break;
+ case 43:
+ v = VariantFairy;
+ break;
+ case 44:
+ v = VariantCylinder;
+ break;
+ case 45:
+ v = VariantFalcon;
+ break;
+ case 46:
+ v = VariantCapaRandom;
+ break;
+ case 47:
+ v = VariantBerolina;
+ break;
+ case 48:
+ v = VariantJanus;
+ break;
+ case 49:
+ v = VariantSuper;
+ break;
+ case 50:
+ v = VariantGreat;
+ break;
+ case -1:
+ /* Found "wild" or "w" in the string but no number;
+ must assume it's normal chess. */
+ v = VariantNormal;
+ break;
+ default:
+ sprintf(buf, _("Unknown wild type %d"), wnum);
+ DisplayError(buf, 0);
+ v = VariantUnknown;
+ break;
+ }
+ }
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
+ e, wnum, VariantName(v));
+ }
+ return v;
+}
+
+static int leftover_start = 0, leftover_len = 0;
+char star_match[STAR_MATCH_N][MSG_SIZ];
+
+/* Test whether pattern is present at &buf[*index]; if so, return TRUE,
+ advance *index beyond it, and set leftover_start to the new value of
+ *index; else return FALSE. If pattern contains the character '*', it
+ matches any sequence of characters not containing '\r', '\n', or the
+ character following the '*' (if any), and the matched sequence(s) are
+ copied into star_match.
+ */
+int
+looking_at(buf, index, pattern)
+ char *buf;
+ int *index;
+ char *pattern;
+{
+ char *bufp = &buf[*index], *patternp = pattern;
+ int star_count = 0;
+ char *matchp = star_match[0];
+
+ for (;;) {
+ if (*patternp == NULLCHAR) {
+ *index = leftover_start = bufp - buf;
+ *matchp = NULLCHAR;
+ return TRUE;
+ }
+ if (*bufp == NULLCHAR) return FALSE;
+ if (*patternp == '*') {
+ if (*bufp == *(patternp + 1)) {
+ *matchp = NULLCHAR;
+ matchp = star_match[++star_count];
+ patternp += 2;
+ bufp++;
+ continue;
+ } else if (*bufp == '\n' || *bufp == '\r') {
+ patternp++;
+ if (*patternp == NULLCHAR)
+ continue;
+ else
+ return FALSE;
+ } else {
+ *matchp++ = *bufp++;
+ continue;
+ }
+ }
+ if (*patternp != *bufp) return FALSE;
+ patternp++;
+ bufp++;
+ }
+}
+
+void
+SendToPlayer(data, length)
+ char *data;
+ int length;
+{
+ int error, outCount;
+ outCount = OutputToProcess(NoProc, data, length, &error);
+ if (outCount < length) {
+ DisplayFatalError(_("Error writing to display"), error, 1);
+ }
+}
+
+void
+PackHolding(packed, holding)
+ char packed[];
+ char *holding;
+{
+ char *p = holding;
+ char *q = packed;
+ int runlength = 0;
+ int curr = 9999;
+ do {
+ if (*p == curr) {
+ runlength++;
+ } else {
+ switch (runlength) {
+ case 0:
+ break;
+ case 1:
+ *q++ = curr;
+ break;
+ case 2:
+ *q++ = curr;
+ *q++ = curr;
+ break;
+ default:
+ sprintf(q, "%d", runlength);
+ while (*q) q++;
+ *q++ = curr;
+ break;
+ }
+ runlength = 1;
+ curr = *p;
+ }
+ } while (*p++);
+ *q = NULLCHAR;
+}
+
+/* Telnet protocol requests from the front end */
+void
+TelnetRequest(ddww, option)
+ unsigned char ddww, option;
+{
+ unsigned char msg[3];
+ int outCount, outError;
+
+ if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
+
+ if (appData.debugMode) {
+ char buf1[8], buf2[8], *ddwwStr, *optionStr;
+ switch (ddww) {
+ case TN_DO:
+ ddwwStr = "DO";
+ break;
+ case TN_DONT:
+ ddwwStr = "DONT";
+ break;
+ case TN_WILL:
+ ddwwStr = "WILL";
+ break;
+ case TN_WONT:
+ ddwwStr = "WONT";
+ break;
+ default:
+ ddwwStr = buf1;
+ sprintf(buf1, "%d", ddww);
+ break;
+ }
+ switch (option) {
+ case TN_ECHO:
+ optionStr = "ECHO";
+ break;
+ default:
+ optionStr = buf2;
+ sprintf(buf2, "%d", option);
+ break;
+ }
+ fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
+ }
+ msg[0] = TN_IAC;
+ msg[1] = ddww;
+ msg[2] = option;
+ outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
+ if (outCount < 3) {
+ DisplayFatalError(_("Error writing to ICS"), outError, 1);
+ }
+}
+
+void
+DoEcho()
+{
+ if (!appData.icsActive) return;
+ TelnetRequest(TN_DO, TN_ECHO);
+}
+
+void
+DontEcho()
+{
+ if (!appData.icsActive) return;
+ TelnetRequest(TN_DONT, TN_ECHO);
+}
+
+void
+CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
+{
+ /* put the holdings sent to us by the server on the board holdings area */
+ int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
+ char p;
+ ChessSquare piece;
+
+ if(gameInfo.holdingsWidth < 2) return;
+
+ if( (int)lowestPiece >= 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<BOARD_HEIGHT; i++) { /* clear holdings */
+ board[i][holdingsColumn] = EmptySquare;
+ board[i][countsColumn] = (ChessSquare) 0;
+ }
+ while( (p=*holdings++) != NULLCHAR ) {
+ piece = CharToPiece( ToUpper(p) );
+ if(piece == EmptySquare) continue;
+ /*j = (int) piece - (int) WhitePawn;*/
+ j = PieceToNumber(piece);
+ if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
+ if(j < 0) continue; /* should not happen */
+ piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
+ board[holdingsStartRow+j*direction][holdingsColumn] = piece;
+ board[holdingsStartRow+j*direction][countsColumn]++;
+ }
+
+}
+
+
+void
+VariantSwitch(Board board, VariantClass newVariant)
+{
+ int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
+ int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
+// Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
+
+ startedFromPositionFile = FALSE;
+ if(gameInfo.variant == newVariant) return;
+
+ /* [HGM] This routine is called each time an assignment is made to
+ * gameInfo.variant during a game, to make sure the board sizes
+ * are set to match the new variant. If that means adding or deleting
+ * holdings, we shift the playing board accordingly
+ * This kludge is needed because in ICS observe mode, we get boards
+ * of an ongoing game without knowing the variant, and learn about the
+ * latter only later. This can be because of the move list we requested,
+ * in which case the game history is refilled from the beginning anyway,
+ * but also when receiving holdings of a crazyhouse game. In the latter
+ * case we want to add those holdings to the already received position.
+ */
+
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "Switch board from %s to %s\n",
+ VariantName(gameInfo.variant), VariantName(newVariant));
+ setbuf(debugFP, NULL);
+ }
+ shuffleOpenings = 0; /* [HGM] shuffle */
+ gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
+ switch(newVariant) {
+ case VariantShogi:
+ newWidth = 9; newHeight = 9;
+ gameInfo.holdingsSize = 7;
+ case VariantBughouse:
+ case VariantCrazyhouse:
+ newHoldingsWidth = 2; break;
+ default:
+ newHoldingsWidth = gameInfo.holdingsSize = 0;
+ }
+
+ if(newWidth != gameInfo.boardWidth ||
+ newHeight != gameInfo.boardHeight ||
+ newHoldingsWidth != gameInfo.holdingsWidth ) {
+
+ /* shift position to new playing area, if needed */
+ if(newHoldingsWidth > gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++)
+ for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
+ board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+ board[i][j];
+ for(i=0; i<newHeight; i++) {
+ board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
+ board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
+ }
+ } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
+ for(i=0; i<BOARD_HEIGHT; i++)
+ for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+ board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
+ board[i][j];
+ }
+
+ gameInfo.boardWidth = newWidth;
+ gameInfo.boardHeight = newHeight;
+ gameInfo.holdingsWidth = newHoldingsWidth;
+ gameInfo.variant = newVariant;
+ InitDrawingSizes(-2, 0);
+
+ /* [HGM] The following should definitely be solved in a better way */
+#if 0
+ CopyBoard(board, tempBoard); /* save position in case it is board[0] */
+ for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
+ saveEP = epStatus[0];
+#endif
+ InitPosition(FALSE); /* this sets up board[0], but also other stuff */
+#if 0
+ epStatus[0] = saveEP;
+ for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
+ CopyBoard(tempBoard, board); /* restore position received from ICS */
+#endif
+ } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
+
+ forwardMostMove = oldForwardMostMove;
+ backwardMostMove = oldBackwardMostMove;
+ currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
+}
+
+static int loggedOn = FALSE;
+
+/*-- Game start info cache: --*/
+int gs_gamenum;
+char gs_kind[MSG_SIZ];
+static char player1Name[128] = "";
+static char player2Name[128] = "";
+static int player1Rating = -1;
+static int player2Rating = -1;
+/*----------------------------*/
+
+ColorClass curColor = ColorNormal;
+int suppressKibitz = 0;
+
+void
+read_from_ics(isr, closure, data, count, error)
+ InputSourceRef isr;
+ VOIDSTAR closure;
+ char *data;
+ int count;
+ int error;
+{
+#define BUF_SIZE 8192
+#define STARTED_NONE 0
+#define STARTED_MOVES 1
+#define STARTED_BOARD 2
+#define STARTED_OBSERVE 3
+#define STARTED_HOLDINGS 4
+#define STARTED_CHATTER 5
+#define STARTED_COMMENT 6
+#define STARTED_MOVES_NOHIDE 7
+
+ static int started = STARTED_NONE;
+ static char parse[20000];
+ static int parse_pos = 0;
+ static char buf[BUF_SIZE + 1];
+ static int firstTime = TRUE, intfSet = FALSE;
+ static ColorClass prevColor = ColorNormal;
+ static int savingComment = FALSE;
+ char str[500];
+ int i, oldi;
+ int buf_len;
+ int next_out;
+ int tkind;
+ int backup; /* [DM] For zippy color lines */
+ char *p;
+
+ if (appData.debugMode) {
+ if (!error) {
+ fprintf(debugFP, "<ICS: ");
+ show_bytes(debugFP, data, count);
+ fprintf(debugFP, "\n");
+ }
+ }
+
+ if (appData.debugMode) { int f = forwardMostMove;
+ fprintf(debugFP, "ics input %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 (count > 0) {
+ /* If last read ended with a partial line that we couldn't parse,
+ prepend it to the new read and try again. */
+ if (leftover_len > 0) {
+ for (i=0; i<leftover_len; i++)
+ 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];
+ if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
+ buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
+ buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
+ }
+
+ buf[buf_len] = NULLCHAR;
+ next_out = leftover_len;
+ leftover_start = 0;
+
+ i = 0;
+ while (i < buf_len) {
+ /* Deal with part of the TELNET option negotiation
+ protocol. We refuse to do anything beyond the
+ defaults, except that we allow the WILL ECHO option,
+ which ICS uses to turn off password echoing when we are
+ directly connected to it. We reject this option
+ if localLineEditing mode is on (always on in xboard)
+ and we are talking to port 23, which might be a real
+ telnet server that will try to keep WILL ECHO on permanently.
+ */
+ if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
+ static int remoteEchoOption = FALSE; /* telnet ECHO option */
+ unsigned char option;
+ oldi = i;
+ switch ((unsigned char) buf[++i]) {
+ case TN_WILL:
+ if (appData.debugMode)
+ fprintf(debugFP, "\n<WILL ");
+ switch (option = (unsigned char) buf[++i]) {
+ case TN_ECHO:
+ if (appData.debugMode)
+ fprintf(debugFP, "ECHO ");
+ /* Reply only if this is a change, according
+ to the protocol rules. */
+ if (remoteEchoOption) break;
+ if (appData.localLineEditing &&
+ atoi(appData.icsPort) == TN_PORT) {
+ TelnetRequest(TN_DONT, TN_ECHO);
+ } else {
+ EchoOff();
+ TelnetRequest(TN_DO, TN_ECHO);
+ remoteEchoOption = TRUE;
+ }
+ break;
+ default:
+ if (appData.debugMode)
+ fprintf(debugFP, "%d ", option);
+ /* Whatever this is, we don't want it. */
+ TelnetRequest(TN_DONT, option);
+ break;
+ }
+ break;
+ case TN_WONT:
+ if (appData.debugMode)
+ fprintf(debugFP, "\n<WONT ");
+ switch (option = (unsigned char) buf[++i]) {
+ case TN_ECHO:
+ if (appData.debugMode)
+ fprintf(debugFP, "ECHO ");
+ /* Reply only if this is a change, according
+ to the protocol rules. */
+ if (!remoteEchoOption) break;
+ EchoOn();
+ TelnetRequest(TN_DONT, TN_ECHO);
+ remoteEchoOption = FALSE;
+ break;
+ default:
+ if (appData.debugMode)
+ fprintf(debugFP, "%d ", (unsigned char) option);
+ /* Whatever this is, it must already be turned
+ off, because we never agree to turn on
+ anything non-default, so according to the
+ protocol rules, we don't reply. */
+ break;
+ }
+ break;
+ case TN_DO:
+ if (appData.debugMode)
+ fprintf(debugFP, "\n<DO ");
+ switch (option = (unsigned char) buf[++i]) {
+ default:
+ /* Whatever this is, we refuse to do it. */
+ if (appData.debugMode)
+ fprintf(debugFP, "%d ", option);
+ TelnetRequest(TN_WONT, option);
+ break;
+ }
+ break;
+ case TN_DONT:
+ if (appData.debugMode)
+ fprintf(debugFP, "\n<DONT ");
+ switch (option = (unsigned char) buf[++i]) {
+ default:
+ if (appData.debugMode)
+ fprintf(debugFP, "%d ", option);
+ /* Whatever this is, we are already not doing
+ it, because we never agree to do anything
+ non-default, so according to the protocol
+ rules, we don't reply. */
+ break;
+ }
+ break;
+ case TN_IAC:
+ if (appData.debugMode)
+ fprintf(debugFP, "\n<IAC ");
+ /* Doubled IAC; pass it through */
+ i--;
+ break;
+ default:
+ if (appData.debugMode)
+ fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
+ /* Drop all other telnet commands on the floor */
+ break;
+ }
+ if (oldi > next_out)
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ if (++i > next_out)
+ next_out = i;
+ continue;
+ }
+
+ /* OK, this at least will *usually* work */
+ if (!loggedOn && looking_at(buf, &i, "ics%")) {
+ loggedOn = TRUE;
+ }
+
+ if (loggedOn && !intfSet) {
+ if (ics_type == ICS_ICC) {
+ sprintf(str,
+ "/set-quietly interface %s\n/set-quietly style 12\n",
+ programVersion);
+
+ } else if (ics_type == ICS_CHESSNET) {
+ sprintf(str, "/style 12\n");
+ } else {
+ strcpy(str, "alias $ @\n$set interface ");
+ strcat(str, programVersion);
+ strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
+#ifdef WIN32
+ strcat(str, "$iset nohighlight 1\n");
+#endif
+ strcat(str, "$iset lock 1\n$style 12\n");
+ }
+ SendToICS(str);
+ intfSet = TRUE;
+ }
+
+ if (started == STARTED_COMMENT) {
+ /* Accumulate characters in comment */
+ parse[parse_pos++] = buf[i];
+ if (buf[i] == '\n') {
+ parse[parse_pos] = NULLCHAR;
+ 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<parse_pos; i++) { // count letters and digits
+ nrDigit += (parse[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 */
+ i++;
+ continue;
+ }
+ }
+ if (started == STARTED_CHATTER) {
+ if (buf[i] != '\n') {
+ /* Don't match patterns against characters in chatter */
+ i++;
+ continue;
+ }
+ started = STARTED_NONE;
+ }
+
+ /* Kludge to deal with rcmd protocol */
+ if (firstTime && looking_at(buf, &i, "\001*")) {
+ DisplayFatalError(&buf[1], 0, 1);
+ continue;
+ } else {
+ firstTime = FALSE;
+ }
+
+ if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
+ ics_type = ICS_ICC;
+ ics_prefix = "/";
+ if (appData.debugMode)
+ fprintf(debugFP, "ics_type %d\n", ics_type);
+ continue;
+ }
+ if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
+ ics_type = ICS_FICS;
+ ics_prefix = "$";
+ if (appData.debugMode)
+ fprintf(debugFP, "ics_type %d\n", ics_type);
+ continue;
+ }
+ if (!loggedOn && looking_at(buf, &i, "chess.net")) {
+ ics_type = ICS_CHESSNET;
+ ics_prefix = "/";
+ if (appData.debugMode)
+ fprintf(debugFP, "ics_type %d\n", ics_type);
+ continue;
+ }
+
+ if (!loggedOn &&
+ (looking_at(buf, &i, "\"*\" is *a registered name") ||
+ looking_at(buf, &i, "Logging you in as \"*\"") ||
+ looking_at(buf, &i, "will be \"*\""))) {
+ strcpy(ics_handle, star_match[0]);
+ continue;
+ }
+
+ if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
+ char buf[MSG_SIZ];
+ snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
+ DisplayIcsInteractionTitle(buf);
+ have_set_title = TRUE;
+ }
+
+ /* skip finger notes */
+ if (started == STARTED_NONE &&
+ ((buf[i] == ' ' && isdigit(buf[i+1])) ||
+ (buf[i] == '1' && buf[i+1] == '0')) &&
+ buf[i+2] == ':' && buf[i+3] == ' ') {
+ started = STARTED_CHATTER;
+ i += 3;
+ continue;
+ }
+
+ /* skip formula vars */
+ if (started == STARTED_NONE &&
+ buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
+ started = STARTED_CHATTER;
+ i += 3;
+ continue;
+ }
+
+ oldi = i;
+ // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
+ if (appData.autoKibitz && started == STARTED_NONE &&
+ !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
+ (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
+ if(looking_at(buf, &i, "* kibitzes: ") &&
+ (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
+ StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
+ suppressKibitz = TRUE;
+ if((StrStr(star_match[0], gameInfo.white) == star_match[0]
+ && (gameMode == IcsPlayingWhite)) ||
+ (StrStr(star_match[0], gameInfo.black) == star_match[0]
+ && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
+ started = STARTED_CHATTER; // own kibitz we simply discard
+ else {
+ started = STARTED_COMMENT; // make sure it will be collected in parse[]
+ parse_pos = 0; parse[0] = NULLCHAR;
+ savingComment = TRUE;
+ suppressKibitz = gameMode != IcsObserving ? 2 :
+ (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
+ }
+ continue;
+ } else
+ if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
+ started = STARTED_CHATTER;
+ suppressKibitz = TRUE;
+ }
+ } // [HGM] kibitz: end of patch
+
+ if (appData.zippyTalk || appData.zippyPlay) {
+ /* [DM] Backup address for color zippy lines */
+ backup = i;
+#if ZIPPY
+ #ifdef WIN32
+ if (loggedOn == TRUE)
+ if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
+ (appData.zippyPlay && ZippyMatch(buf, &backup)));
+ #else
+ if (ZippyControl(buf, &i) ||
+ ZippyConverse(buf, &i) ||
+ (appData.zippyPlay && ZippyMatch(buf, &i))) {
+ loggedOn = TRUE;
+ if (!appData.colorize) continue;
+ }
+ #endif
+#endif
+ } // [DM] 'else { ' deleted
+ if (/* Don't color "message" or "messages" output */
+ (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
+ looking_at(buf, &i, "*. * at *:*: ") ||
+ looking_at(buf, &i, "--* (*:*): ") ||
+ /* Regular tells and says */
+ (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
+ looking_at(buf, &i, "* (your partner) tells you: ") ||
+ looking_at(buf, &i, "* says: ") ||
+ /* Message notifications (same color as tells) */
+ looking_at(buf, &i, "* has left a message ") ||
+ looking_at(buf, &i, "* just sent you a message:\n") ||
+ /* Whispers and kibitzes */
+ (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
+ looking_at(buf, &i, "* kibitzes: ") ||
+ /* Channel tells */
+ (tkind = 3, looking_at(buf, &i, "*(*: "))) {
+
+ if (tkind == 1 && strchr(star_match[0], ':')) {
+ /* Avoid "tells you:" spoofs in channels */
+ tkind = 3;
+ }
+ if (star_match[0][0] == NULLCHAR ||
+ strchr(star_match[0], ' ') ||
+ (tkind == 3 && strchr(star_match[1], ' '))) {
+ /* Reject bogus matches */
+ i = oldi;
+ } else {
+ if (appData.colorize) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ switch (tkind) {
+ case 1:
+ Colorize(ColorTell, FALSE);
+ curColor = ColorTell;
+ break;
+ case 2:
+ Colorize(ColorKibitz, FALSE);
+ curColor = ColorKibitz;
+ break;
+ case 3:
+ p = strrchr(star_match[1], '(');
+ if (p == NULL) {
+ p = star_match[1];
+ } else {
+ p++;
+ }
+ if (atoi(p) == 1) {
+ Colorize(ColorChannel1, FALSE);
+ curColor = ColorChannel1;
+ } else {
+ Colorize(ColorChannel, FALSE);
+ curColor = ColorChannel;
+ }
+ break;
+ case 5:
+ curColor = ColorNormal;
+ break;
+ }
+ }
+ if (started == STARTED_NONE && appData.autoComment &&
+ (gameMode == IcsObserving ||
+ gameMode == IcsPlayingWhite ||
+ gameMode == IcsPlayingBlack)) {
+ parse_pos = i - oldi;
+ memcpy(parse, &buf[oldi], parse_pos);
+ parse[parse_pos] = NULLCHAR;
+ started = STARTED_COMMENT;
+ savingComment = TRUE;
+ } else {
+ started = STARTED_CHATTER;
+ savingComment = FALSE;
+ }
+ loggedOn = TRUE;
+ continue;
+ }
+ }
+
+ if (looking_at(buf, &i, "* s-shouts: ") ||
+ looking_at(buf, &i, "* c-shouts: ")) {
+ if (appData.colorize) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(ColorSShout, FALSE);
+ curColor = ColorSShout;
+ }
+ loggedOn = TRUE;
+ started = STARTED_CHATTER;
+ continue;
+ }
+
+ if (looking_at(buf, &i, "--->")) {
+ loggedOn = TRUE;
+ continue;
+ }
+
+ if (looking_at(buf, &i, "* shouts: ") ||
+ looking_at(buf, &i, "--> ")) {
+ if (appData.colorize) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(ColorShout, FALSE);
+ curColor = ColorShout;
+ }
+ loggedOn = TRUE;
+ started = STARTED_CHATTER;
+ continue;
+ }
+
+ if (looking_at( buf, &i, "Challenge:")) {
+ if (appData.colorize) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(ColorChallenge, FALSE);
+ curColor = ColorChallenge;
+ }
+ loggedOn = TRUE;
+ continue;
+ }
+
+ if (looking_at(buf, &i, "* offers you") ||
+ looking_at(buf, &i, "* offers to be") ||
+ looking_at(buf, &i, "* would like to") ||
+ looking_at(buf, &i, "* requests to") ||
+ looking_at(buf, &i, "Your opponent offers") ||
+ looking_at(buf, &i, "Your opponent requests")) {
+
+ if (appData.colorize) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(ColorRequest, FALSE);
+ curColor = ColorRequest;
+ }
+ continue;
+ }
+
+ if (looking_at(buf, &i, "* (*) seeking")) {
+ if (appData.colorize) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(ColorSeek, FALSE);
+ curColor = ColorSeek;
+ }
+ continue;
+ }
+
+ if (looking_at(buf, &i, "\\ ")) {
+ if (prevColor != ColorNormal) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(prevColor, TRUE);
+ curColor = prevColor;
+ }
+ if (savingComment) {
+ parse_pos = i - oldi;
+ memcpy(parse, &buf[oldi], parse_pos);
+ parse[parse_pos] = NULLCHAR;
+ started = STARTED_COMMENT;
+ } else {
+ started = STARTED_CHATTER;
+ }
+ continue;
+ }
+
+ if (looking_at(buf, &i, "Black Strength :") ||
+ looking_at(buf, &i, "<<< style 10 board >>>") ||
+ looking_at(buf, &i, "<10>") ||
+ looking_at(buf, &i, "#@#")) {
+ /* Wrong board style */
+ loggedOn = TRUE;
+ SendToICS(ics_prefix);
+ SendToICS("set style 12\n");
+ SendToICS(ics_prefix);
+ SendToICS("refresh\n");
+ continue;
+ }
+
+ if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
+ ICSInitScript();
+ have_sent_ICS_logon = 1;
+ continue;
+ }
+
+ if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
+ (looking_at(buf, &i, "\n<12> ") ||
+ looking_at(buf, &i, "<12> "))) {
+ loggedOn = TRUE;
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ }
+ next_out = i;
+ started = STARTED_BOARD;
+ parse_pos = 0;
+ continue;
+ }
+
+ if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
+ looking_at(buf, &i, "<b1> ")) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ }
+ next_out = i;
+ started = STARTED_HOLDINGS;
+ parse_pos = 0;
+ continue;
+ }
+
+ if (looking_at(buf, &i, "* *vs. * *--- *")) {
+ loggedOn = TRUE;
+ /* Header for a move list -- first line */
+
+ switch (ics_getting_history) {
+ case H_FALSE:
+ switch (gameMode) {
+ case IcsIdle:
+ case BeginningOfGame:
+ /* User typed "moves" or "oldmoves" while we
+ were idle. Pretend we asked for these
+ moves and soak them up so user can step
+ through them and/or save them.
+ */
+ Reset(FALSE, TRUE);
+ gameMode = IcsObserving;
+ ModeHighlight();
+ ics_gamenum = -1;
+ ics_getting_history = H_GOT_UNREQ_HEADER;
+ break;
+ case EditGame: /*?*/
+ case EditPosition: /*?*/
+ /* Should above feature work in these modes too? */
+ /* For now it doesn't */
+ ics_getting_history = H_GOT_UNWANTED_HEADER;
+ break;
+ default:
+ ics_getting_history = H_GOT_UNWANTED_HEADER;
+ break;
+ }
+ break;
+ case H_REQUESTED:
+ /* Is this the right one? */
+ if (gameInfo.white && gameInfo.black &&
+ strcmp(gameInfo.white, star_match[0]) == 0 &&
+ strcmp(gameInfo.black, star_match[2]) == 0) {
+ /* All is well */
+ ics_getting_history = H_GOT_REQ_HEADER;
+ }
+ break;
+ case H_GOT_REQ_HEADER:
+ case H_GOT_UNREQ_HEADER:
+ case H_GOT_UNWANTED_HEADER:
+ case H_GETTING_MOVES:
+ /* Should not happen */
+ DisplayError(_("Error gathering move list: two headers"), 0);
+ ics_getting_history = H_FALSE;
+ break;
+ }
+
+ /* Save player ratings into gameInfo if needed */
+ if ((ics_getting_history == H_GOT_REQ_HEADER ||
+ ics_getting_history == H_GOT_UNREQ_HEADER) &&
+ (gameInfo.whiteRating == -1 ||
+ gameInfo.blackRating == -1)) {
+
+ 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"),
+ gameInfo.whiteRating, gameInfo.blackRating);
+ }
+ continue;
+ }
+
+ if (looking_at(buf, &i,
+ "* * 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);
+ /* [HGM] we switched variant. Translate boards if needed. */
+ VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
+ continue;
+ }
+
+ if (looking_at(buf, &i, "Move ")) {
+ /* Beginning of a move list */
+ switch (ics_getting_history) {
+ case H_FALSE:
+ /* Normally should not happen */
+ /* Maybe user hit reset while we were parsing */
+ break;
+ case H_REQUESTED:
+ /* Happens if we are ignoring a move list that is not
+ * the one we just requested. Common if the user
+ * tries to observe two games without turning off
+ * getMoveList */
+ break;
+ case H_GETTING_MOVES:
+ /* Should not happen */
+ DisplayError(_("Error gathering move list: nested"), 0);
+ ics_getting_history = H_FALSE;
+ break;
+ case H_GOT_REQ_HEADER:
+ ics_getting_history = H_GETTING_MOVES;
+ started = STARTED_MOVES;
+ parse_pos = 0;
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ }
+ break;
+ case H_GOT_UNREQ_HEADER:
+ ics_getting_history = H_GETTING_MOVES;
+ started = STARTED_MOVES_NOHIDE;
+ parse_pos = 0;
+ break;
+ case H_GOT_UNWANTED_HEADER:
+ ics_getting_history = H_FALSE;
+ break;
+ }
+ continue;
+ }
+
+ if (looking_at(buf, &i, "% ") ||
+ ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
+ && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
+ savingComment = FALSE;
+ switch (started) {
+ case STARTED_MOVES:
+ case STARTED_MOVES_NOHIDE:
+ memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
+ parse[parse_pos + i - oldi] = NULLCHAR;
+ ParseGameHistory(parse);
+#if ZIPPY
+ if (appData.zippyPlay && first.initDone) {
+ FeedMovesToProgram(&first, forwardMostMove);
+ if (gameMode == IcsPlayingWhite) {
+ if (WhiteOnMove(forwardMostMove)) {
+ if (first.sendTime) {
+ if (first.useColors) {
+ SendToProgram("black\n", &first);
+ }
+ SendTimeRemaining(&first, TRUE);
+ }
+#if 0
+ if (first.useColors) {
+ SendToProgram("white\ngo\n", &first);
+ } else {
+ SendToProgram("go\n", &first);
+ }
+#else
+ if (first.useColors) {
+ SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
+ }
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
+#endif
+ first.maybeThinking = TRUE;
+ } else {
+ if (first.usePlayother) {
+ if (first.sendTime) {
+ SendTimeRemaining(&first, TRUE);
+ }
+ SendToProgram("playother\n", &first);
+ firstMove = FALSE;
+ } else {
+ firstMove = TRUE;
+ }
+ }
+ } else if (gameMode == IcsPlayingBlack) {
+ if (!WhiteOnMove(forwardMostMove)) {
+ if (first.sendTime) {
+ if (first.useColors) {
+ SendToProgram("white\n", &first);
+ }
+ SendTimeRemaining(&first, FALSE);
+ }
+#if 0
+ if (first.useColors) {
+ SendToProgram("black\ngo\n", &first);
+ } else {
+ SendToProgram("go\n", &first);
+ }
+#else
+ if (first.useColors) {
+ SendToProgram("black\n", &first);
+ }
+ bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
+#endif
+ first.maybeThinking = TRUE;
+ } else {
+ if (first.usePlayother) {
+ if (first.sendTime) {
+ SendTimeRemaining(&first, FALSE);
+ }
+ SendToProgram("playother\n", &first);
+ firstMove = FALSE;
+ } else {
+ firstMove = TRUE;
+ }
+ }
+ }
+ }
+#endif
+ if (gameMode == IcsObserving && ics_gamenum == -1) {
+ /* Moves came from oldmoves or moves command
+ while we weren't doing anything else.
+ */
+ currentMove = forwardMostMove;
+ ClearHighlights();/*!!could figure this out*/
+ flipView = appData.flipView;
+ DrawPosition(FALSE, boards[currentMove]);
+ DisplayBothClocks();
+ sprintf(str, "%s vs. %s",
+ gameInfo.white, gameInfo.black);
+ DisplayTitle(str);
+ gameMode = IcsIdle;
+ } else {
+ /* Moves were history of an active game */
+ if (gameInfo.resultDetails != NULL) {
+ free(gameInfo.resultDetails);
+ gameInfo.resultDetails = NULL;
+ }
+ }
+ HistorySet(parseList, backwardMostMove,
+ forwardMostMove, currentMove-1);
+ DisplayMove(currentMove - 1);
+ if (started == STARTED_MOVES) next_out = i;
+ started = STARTED_NONE;
+ ics_getting_history = H_FALSE;
+ break;
+
+ case STARTED_OBSERVE:
+ started = STARTED_NONE;
+ SendToICS(ics_prefix);
+ SendToICS("refresh\n");
+ break;
+
+ 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
+ to observe in order to get the current board right away. */
+ if (looking_at(buf, &i, "Adding game * to observation list")) {
+ started = STARTED_OBSERVE;
+ continue;
+ }
+
+ /* Handle auto-observe */
+ if (appData.autoObserve &&
+ (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
+ looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
+ char *player;
+ /* Choose the player that was highlighted, if any. */
+ if (star_match[0][0] == '\033' ||
+ star_match[1][0] != '\033') {
+ player = star_match[0];
+ } else {
+ player = star_match[2];
+ }
+ sprintf(str, "%sobserve %s\n",
+ ics_prefix, StripHighlightAndTitle(player));
+ SendToICS(str);
+
+ /* Save ratings from notify string */
+ strcpy(player1Name, star_match[0]);
+ player1Rating = string_to_rating(star_match[1]);
+ strcpy(player2Name, star_match[2]);
+ player2Rating = string_to_rating(star_match[3]);
+
+ if (appData.debugMode)
+ fprintf(debugFP,
+ "Ratings from 'Game notification:' %s %d, %s %d\n",
+ player1Name, player1Rating,
+ player2Name, player2Rating);
+
+ continue;
+ }
+
+ /* Deal with automatic examine mode after a game,
+ and with IcsObserving -> IcsExamining transition */
+ if (looking_at(buf, &i, "Entering examine mode for game *") ||
+ looking_at(buf, &i, "has made you an examiner of game *")) {
+
+ int gamenum = atoi(star_match[0]);
+ if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
+ gamenum == ics_gamenum) {
+ /* We were already playing or observing this game;
+ no need to refetch history */
+ gameMode = IcsExamining;
+ if (pausing) {
+ pauseExamForwardMostMove = forwardMostMove;
+ } else if (currentMove < forwardMostMove) {
+ ForwardInner(forwardMostMove);
+ }
+ } else {
+ /* I don't think this case really can happen */
+ SendToICS(ics_prefix);
+ SendToICS("refresh\n");
+ }
+ continue;
+ }
+
+ /* Error messages */
+ if (ics_user_moved) {
+ 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) {
+ currentMove = --forwardMostMove;
+ DisplayMove(currentMove - 1); /* before DMError */
+ DisplayMoveError(_("Illegal move (rejected by ICS)"));
+ DrawPosition(FALSE, boards[currentMove]);
+ SwitchClocks();
+ DisplayBothClocks();
+ }
+ continue;
+ }
+ }
+
+ if (looking_at(buf, &i, "still have time") ||
+ looking_at(buf, &i, "not out of time") ||
+ looking_at(buf, &i, "either player is out of time") ||
+ looking_at(buf, &i, "has timeseal; checking")) {
+ /* We must have called his flag a little too soon */
+ whiteFlag = blackFlag = FALSE;
+ continue;
+ }
+
+ if (looking_at(buf, &i, "added * seconds to") ||
+ looking_at(buf, &i, "seconds were added to")) {
+ /* Update the clocks */
+ SendToICS(ics_prefix);
+ SendToICS("refresh\n");
+ continue;
+ }
+
+ if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
+ ics_clock_paused = TRUE;
+ StopClocks();
+ continue;
+ }
+
+ if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
+ ics_clock_paused = FALSE;
+ StartClocks();
+ continue;
+ }
+
+ /* Grab player ratings from the Creating: message.
+ Note we have to check for the special case when
+ the ICS inserts things like [white] or [black]. */
+ if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
+ looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
+ /* star_matches:
+ 0 player 1 name (not necessarily white)
+ 1 player 1 rating
+ 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).
+ */
+ strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
+ player1Rating = string_to_rating(star_match[1]);
+ strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
+ player2Rating = string_to_rating(star_match[4]);
+
+ if (appData.debugMode)
+ 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. *(*)) *}*"))){
+ /* If tkind == 0: */
+ /* star_match[0] is the game number */
+ /* [1] is the white player's name */
+ /* [2] is the black player's name */
+ /* For end-of-game: */
+ /* [3] is the reason for the game end */
+ /* [4] is a PGN end game-token, preceded by " " */
+ /* For start-of-game: */
+ /* [3] begins with "Creating" or "Continuing" */
+ /* [4] is " *" or empty (don't care). */
+ int gamenum = atoi(star_match[0]);
+ char *whitename, *blackname, *why, *endtoken;
+ ChessMove endtype = (ChessMove) 0;
+
+ if (tkind == 0) {
+ whitename = star_match[1];
+ blackname = star_match[2];
+ why = star_match[3];
+ endtoken = star_match[4];
+ } else {
+ whitename = star_match[1];
+ blackname = star_match[3];
+ why = star_match[5];
+ endtoken = star_match[6];
+ }
+
+ /* Game start messages */
+ if (strncmp(why, "Creating ", 9) == 0 ||
+ strncmp(why, "Continuing ", 11) == 0) {
+ gs_gamenum = gamenum;
+ strcpy(gs_kind, strchr(why, ' ') + 1);
+#if ZIPPY
+ if (appData.zippyPlay) {
+ ZippyGameStart(whitename, blackname);
+ }
+#endif /*ZIPPY*/
+ continue;
+ }
+
+ /* Game end messages */
+ if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
+ ics_gamenum != gamenum) {
+ continue;
+ }
+ while (endtoken[0] == ' ') endtoken++;
+ switch (endtoken[0]) {
+ case '*':
+ default:
+ endtype = GameUnfinished;
+ break;
+ case '0':
+ endtype = BlackWins;
+ break;
+ case '1':
+ if (endtoken[1] == '/')
+ endtype = GameIsDrawn;
+ else
+ endtype = WhiteWins;
+ break;
+ }
+ GameEnds(endtype, why, GE_ICS);
+#if ZIPPY
+ if (appData.zippyPlay && first.initDone) {
+ ZippyGameEnd(endtype, why);
+ if (first.pr == NULL) {
+ /* Start the next process early so that we'll
+ be ready for the next challenge */
+ StartChessProgram(&first);
+ }
+ /* 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*/
+ continue;
+ }
+
+ if (looking_at(buf, &i, "Removing game * from observation") ||
+ looking_at(buf, &i, "no longer observing game *") ||
+ looking_at(buf, &i, "Game * (*) has no examiners")) {
+ if (gameMode == IcsObserving &&
+ atoi(star_match[0]) == ics_gamenum)
+ {
+ /* icsEngineAnalyze */
+ if (appData.icsEngineAnalyze) {
+ ExitAnalyzeMode();
+ ModeHighlight();
+ }
+ StopClocks();
+ gameMode = IcsIdle;
+ ics_gamenum = -1;
+ ics_user_moved = FALSE;
+ }
+ continue;
+ }
+
+ if (looking_at(buf, &i, "no longer examining game *")) {
+ if (gameMode == IcsExamining &&
+ atoi(star_match[0]) == ics_gamenum)
+ {
+ gameMode = IcsIdle;
+ ics_gamenum = -1;
+ ics_user_moved = FALSE;
+ }
+ continue;
+ }
+
+ /* Advance leftover_start past any newlines we find,
+ so only partial lines can get reparsed */
+ if (looking_at(buf, &i, "\n")) {
+ prevColor = curColor;
+ if (curColor != ColorNormal) {
+ if (oldi > next_out) {
+ SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = oldi;
+ }
+ Colorize(ColorNormal, FALSE);
+ curColor = ColorNormal;
+ }
+ if (started == STARTED_BOARD) {
+ started = STARTED_NONE;
+ parse[parse_pos] = NULLCHAR;
+ ParseBoard12(parse);
+ ics_user_moved = 0;
+
+ /* Send premove here */
+ if (appData.premove) {
+ char str[MSG_SIZ];
+ if (currentMove == 0 &&
+ gameMode == IcsPlayingWhite &&
+ appData.premoveWhite) {
+ sprintf(str, "%s%s\n", ics_prefix,
+ appData.premoveWhiteText);
+ if (appData.debugMode)
+ fprintf(debugFP, "Sending premove:\n");
+ SendToICS(str);
+ } else if (currentMove == 1 &&
+ gameMode == IcsPlayingBlack &&
+ appData.premoveBlack) {
+ sprintf(str, "%s%s\n", ics_prefix,
+ appData.premoveBlackText);
+ if (appData.debugMode)
+ fprintf(debugFP, "Sending premove:\n");
+ SendToICS(str);
+ } else if (gotPremove) {
+ gotPremove = 0;
+ ClearPremoveHighlights();
+ if (appData.debugMode)
+ fprintf(debugFP, "Sending premove:\n");
+ UserMoveEvent(premoveFromX, premoveFromY,
+ premoveToX, premoveToY,
+ premovePromoChar);
+ }
+ }
+
+ /* Usually suppress following prompt */
+ if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
+ if (looking_at(buf, &i, "*% ")) {
+ savingComment = FALSE;
+ }
+ }
+ next_out = i;
+ } else if (started == STARTED_HOLDINGS) {
+ int gamenum;
+ char new_piece[MSG_SIZ];
+ started = STARTED_NONE;
+ parse[parse_pos] = NULLCHAR;
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
+ parse, currentMove);
+ if (sscanf(parse, " game %d", &gamenum) == 1 &&
+ gamenum == ics_gamenum) {
+ if (gameInfo.variant == VariantNormal) {
+ /* [HGM] We seem to switch variant during a game!
+ * Presumably no holdings were displayed, so we have
+ * to move the position two files to the right to
+ * create room for them!
+ */
+ VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
+ /* Get a move list just to see the header, which
+ will tell us whether this is really bug or zh */
+ if (ics_getting_history == H_FALSE) {
+ ics_getting_history = H_REQUESTED;
+ sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ SendToICS(str);
+ }
+ }
+ new_piece[0] = NULLCHAR;
+ sscanf(parse, "game %d white [%s black [%s <- %s",
+ &gamenum, white_holding, black_holding,
+ 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,
+ new_piece);
+ }
+#endif /*ZIPPY*/
+ if (tinyLayout || smallLayout) {
+ char wh[16], bh[16];
+ PackHolding(wh, white_holding);
+ PackHolding(bh, black_holding);
+ sprintf(str, "[%s-%s] %s-%s", wh, bh,
+ gameInfo.white, gameInfo.black);
+ } else {
+ sprintf(str, "%s [%s] vs. %s [%s]",
+ gameInfo.white, white_holding,
+ gameInfo.black, black_holding);
+ }
+
+ DrawPosition(FALSE, boards[currentMove]);
+ DisplayTitle(str);
+ }
+ /* Suppress following prompt */
+ if (looking_at(buf, &i, "*% ")) {
+ savingComment = FALSE;
+ }
+ next_out = i;
+ }
+ continue;
+ }
+
+ i++; /* skip unparsed character and loop back */
+ }
+
+ 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);
+ } else {
+ DisplayFatalError(_("Error reading from ICS"), error, 1);
+ }
+}
+
+
+/* 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.
+ */
+
+#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 */
+#define RELATION_PLAYING_MYMOVE 1
+#define RELATION_PLAYING_NOTMYMOVE -1
+#define RELATION_EXAMINING 2
+#define RELATION_ISOLATED_BOARD -3
+#define RELATION_STARTING_POSITION -4 /* FICS only */
+
+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[200];
+ char move_str[500], str[500], elapsed_time[500];
+ char black[32], white[32];
+ Board board;
+ int prevMove = currentMove;
+ int ticking = 2;
+ ChessMove moveType;
+ int fromX, fromY, toX, toY;
+ char promoChar;
+ int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
+ char *bookHit = NULL; // [HGM] book
+
+ fromX = fromY = toX = toY = -1;
+
+ newGame = FALSE;
+
+ if (appData.debugMode)
+ fprintf(debugFP, _("Parsing board: %s\n"), string);
+
+ move_str[0] = NULLCHAR;
+ elapsed_time[0] = NULLCHAR;
+ { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
+ int i = 0, j;
+ while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
+ if(string[i] == ' ') { ranks++; files = 0; }
+ else files++;
+ i++;
+ }
+ for(j = 0; j <i; j++) board_chars[j] = string[j];
+ board_chars[i] = '\0';
+ string += i + 1;
+ }
+ n = sscanf(string, PATTERN, &to_play, &double_push,
+ &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
+ &gamenum, white, black, &relation, &basetime, &increment,
+ &white_stren, &black_stren, &white_time, &black_time,
+ &moveNum, str, elapsed_time, move_str, &ics_flip,
+ &ticking);
+
+ if (n < 21) {
+ snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
+ DisplayError(str, 0);
+ return;
+ }
+
+ /* Convert the move number to internal form */
+ moveNum = (moveNum - 1) * 2;
+ if (to_play == 'B') moveNum++;
+ if (moveNum >= MAX_MOVES) {
+ DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
+ 0, 1);
+ return;
+ }
+
+ switch (relation) {
+ case RELATION_OBSERVING_PLAYED:
+ case RELATION_OBSERVING_STATIC:
+ if (gamenum == -1) {
+ /* Old ICC buglet */
+ relation = RELATION_OBSERVING_STATIC;
+ }
+ newGameMode = IcsObserving;
+ break;
+ case RELATION_PLAYING_MYMOVE:
+ case RELATION_PLAYING_NOTMYMOVE:
+ newGameMode =
+ ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
+ IcsPlayingWhite : IcsPlayingBlack;
+ break;
+ case RELATION_EXAMINING:
+ newGameMode = IcsExamining;
+ break;
+ case RELATION_ISOLATED_BOARD:
+ default:
+ /* Just display this board. If user was doing something else,
+ we will forget about it until the next board comes. */
+ newGameMode = IcsIdle;
+ break;
+ case RELATION_STARTING_POSITION:
+ newGameMode = gameMode;
+ break;
+ }
+
+ /* Modify behavior for initial board display on move listing
+ of wild games.
+ */
+ switch (ics_getting_history) {
+ case H_FALSE:
+ case H_REQUESTED:
+ break;
+ case H_GOT_REQ_HEADER:
+ case H_GOT_UNREQ_HEADER:
+ /* This is the initial position of the current game */
+ gamenum = ics_gamenum;
+ moveNum = 0; /* old ICS bug workaround */
+ if (to_play == 'B') {
+ startedFromSetupPosition = TRUE;
+ blackPlaysFirst = TRUE;
+ moveNum = 1;
+ if (forwardMostMove == 0) forwardMostMove = 1;
+ if (backwardMostMove == 0) backwardMostMove = 1;
+ if (currentMove == 0) currentMove = 1;
+ }
+ newGameMode = gameMode;
+ relation = RELATION_STARTING_POSITION; /* ICC needs this */
+ break;
+ case H_GOT_UNWANTED_HEADER:
+ /* This is an initial board that we don't want */
+ return;
+ case H_GETTING_MOVES:
+ /* Should not happen */
+ DisplayError(_("Error gathering move list: extra board"), 0);
+ ics_getting_history = H_FALSE;
+ return;
+ }
+
+ /* Take action if this is the first board of a new game, or of a
+ different game than is currently being displayed. */
+ if (gamenum != ics_gamenum || newGameMode != gameMode ||
+ relation == RELATION_ISOLATED_BOARD) {
+
+ /* Forget the old game and get the history (if any) of the new one */
+ if (gameMode != BeginningOfGame) {
+ Reset(FALSE, TRUE);
+ }
+ newGame = TRUE;
+ if (appData.autoRaiseBoard) BoardToTop();
+ prevMove = -3;
+ if (gamenum == -1) {
+ newGameMode = IcsIdle;
+ } else if (moveNum > 0 && newGameMode != IcsIdle &&
+ appData.getMoveList) {
+ /* Need to get game history */
+ ics_getting_history = H_REQUESTED;
+ sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ SendToICS(str);
+ }
+
+ /* Initially flip the board to have black on the bottom if playing
+ black or if the ICS flip flag is set, but let the user change
+ it with the Flip View button. */
+ flipView = appData.autoFlipView ?
+ (newGameMode == IcsPlayingBlack) || ics_flip :
+ appData.flipView;
+
+ /* Done with values from previous mode; copy in new ones */
+ gameMode = newGameMode;
+ ModeHighlight();
+ ics_gamenum = gamenum;
+ if (gamenum == gs_gamenum) {
+ int klen = strlen(gs_kind);
+ if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
+ sprintf(str, "ICS %s", gs_kind);
+ gameInfo.event = StrSave(str);
+ } else {
+ gameInfo.event = StrSave("ICS game");
+ }
+ gameInfo.site = StrSave(appData.icsHost);
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+ gameInfo.white = StrSave(white);
+ gameInfo.black = StrSave(black);
+ timeControl = basetime * 60 * 1000;
+ timeControl_2 = 0;
+ timeIncrement = increment * 1000;
+ movesPerSession = 0;
+ gameInfo.timeControl = TimeControlTagValue();
+ VariantSwitch(board, StringToVariant(gameInfo.event) );
+ if (appData.debugMode) {
+ fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
+ fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
+ setbuf(debugFP, NULL);
+ }
+
+ gameInfo.outOfBook = NULL;
+
+ /* Do we have the ratings? */
+ if (strcmp(player1Name, white) == 0 &&
+ strcmp(player2Name, black) == 0) {
+ if (appData.debugMode)
+ fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
+ player1Rating, player2Rating);
+ gameInfo.whiteRating = player1Rating;
+ gameInfo.blackRating = player2Rating;
+ } else if (strcmp(player2Name, white) == 0 &&
+ strcmp(player1Name, black) == 0) {
+ if (appData.debugMode)
+ fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
+ player2Rating, player1Rating);
+ gameInfo.whiteRating = player2Rating;
+ gameInfo.blackRating = player1Rating;
+ }
+ player1Name[0] = player2Name[0] = NULLCHAR;
+
+ /* Silence shouts if requested */
+ if (appData.quietPlay &&
+ (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
+ SendToICS(ics_prefix);
+ SendToICS("set shout 0\n");
+ }
+ }
+
+ /* Deal with midgame name changes */
+ if (!newGame) {
+ if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
+ if (gameInfo.white) free(gameInfo.white);
+ gameInfo.white = StrSave(white);
+ }
+ if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
+ if (gameInfo.black) free(gameInfo.black);
+ gameInfo.black = StrSave(black);
+ }
+ }
+
+ /* Throw away game result if anything actually changes in examine mode */
+ if (gameMode == IcsExamining && !newGame) {
+ gameInfo.result = GameUnfinished;
+ if (gameInfo.resultDetails != NULL) {
+ free(gameInfo.resultDetails);
+ gameInfo.resultDetails = NULL;
+ }
+ }
+
+ /* In pausing && IcsExamining mode, we ignore boards coming
+ in if they are in a different variation than we are. */
+ if (pauseExamInvalid) return;
+ if (pausing && gameMode == IcsExamining) {
+ if (moveNum <= pauseExamForwardMostMove) {
+ pauseExamInvalid = TRUE;
+ forwardMostMove = pauseExamForwardMostMove;
+ return;
+ }
+ }
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "load %dx%d board\n", files, ranks);
+ }
+ /* Parse the board */
+ for (k = 0; k < ranks; k++) {
+ for (j = 0; j < files; j++)
+ board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+ if(gameInfo.holdingsWidth > 1) {
+ board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+ board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+ }
+ }
+ CopyBoard(boards[moveNum], board);
+ if (moveNum == 0) {
+ startedFromSetupPosition =
+ !CompareBoards(board, initialPosition);
+ if(startedFromSetupPosition)
+ initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
+ }
+
+ /* [HGM] Set castling rights. Take the outermost Rooks,
+ to make it also work for FRC opening positions. Note that board12
+ is really defective for later FRC positions, as it has no way to
+ indicate which Rook can castle if they are on the same side of King.
+ For the initial position we grant rights to the outermost Rooks,
+ and remember thos rights, and we then copy them on positions
+ later in an FRC game. This means WB might not recognize castlings with
+ Rooks that have moved back to their original position as illegal,
+ but in ICS mode that is not its job anyway.
+ */
+ if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
+ { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
+
+ for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+ if(board[0][i] == WhiteRook) j = i;
+ initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+ for(i=BOARD_RGHT-1, 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_RGHT; i++)
+ if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
+ initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
+ for(i=BOARD_RGHT-1, 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<BOARD_RGHT; k++)
+ if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
+ for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
+ if(board[BOARD_HEIGHT-1][k] == bKing)
+ initialRights[5] = castlingRights[moveNum][5] = k;
+ } else { int r;
+ r = castlingRights[moveNum][0] = initialRights[0];
+ if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
+ r = castlingRights[moveNum][1] = initialRights[1];
+ if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
+ r = castlingRights[moveNum][3] = initialRights[3];
+ if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
+ r = castlingRights[moveNum][4] = initialRights[4];
+ if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
+ /* wildcastle kludge: always assume King has rights */
+ r = castlingRights[moveNum][2] = initialRights[2];
+ r = castlingRights[moveNum][5] = initialRights[5];
+ }
+ /* [HGM] e.p. rights. Assume that ICS sends file number here? */
+ epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
+
+
+ if (ics_getting_history == H_GOT_REQ_HEADER ||
+ ics_getting_history == H_GOT_UNREQ_HEADER) {
+ /* This was an initial position from a move list, not
+ the current position */
+ return;
+ }
+
+ /* Update currentMove and known move number limits */
+ newMove = newGame || moveNum > forwardMostMove;
+
+ /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
+ if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
+ takeback = forwardMostMove - moveNum;
+ for (i = 0; i < takeback; i++) {
+ if (appData.debugMode) fprintf(debugFP, "take back move\n");
+ SendToProgram("undo\n", &first);
+ }
+ }
+
+ if (newGame) {
+ forwardMostMove = backwardMostMove = currentMove = moveNum;
+ if (gameMode == IcsExamining && moveNum == 0) {
+ /* Workaround for ICS limitation: we are not told the wild
+ type when starting to examine a game. But if we ask for
+ the move list, the move list header will tell us */
+ ics_getting_history = H_REQUESTED;
+ sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ SendToICS(str);
+ }
+ } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
+ || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
+ forwardMostMove = moveNum;
+ if (!pausing || currentMove > forwardMostMove)
+ currentMove = forwardMostMove;
+ } else {
+ /* New part of history that is not contiguous with old part */
+ if (pausing && gameMode == IcsExamining) {
+ pauseExamInvalid = TRUE;
+ forwardMostMove = pauseExamForwardMostMove;
+ return;
+ }
+ forwardMostMove = backwardMostMove = currentMove = moveNum;
+ if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
+ ics_getting_history = H_REQUESTED;
+ sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
+ SendToICS(str);
+ }
+ }
+
+ /* Update the clocks */
+ if (strchr(elapsed_time, '.')) {
+ /* Time is in ms */
+ timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
+ timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
+ } else {
+ /* Time is in seconds */
+ timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
+ timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
+ }
+
+
+#if ZIPPY
+ if (appData.zippyPlay && newGame &&
+ gameMode != IcsObserving && gameMode != IcsIdle &&
+ 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. */
+ strcpy(parseList[moveNum - 1], move_str);
+ strcat(parseList[moveNum - 1], " ");
+ strcat(parseList[moveNum - 1], elapsed_time);
+ moveList[moveNum - 1][0] = NULLCHAR;
+ } 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,
+ castlingRights[moveNum]) ) {
+ case MT_NONE:
+ case MT_STALEMATE:
+ default:
+ break;
+ case MT_CHECK:
+ 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;
+ }
+ strcat(parseList[moveNum - 1], " ");
+ strcat(parseList[moveNum - 1], elapsed_time);
+ /* currentMoveString is set as a side-effect of ParseOneMove */
+ strcpy(moveList[moveNum - 1], currentMoveString);
+ strcat(moveList[moveNum - 1], "\n");
+ } else {
+ /* Move from ICS was illegal!? Punt. */
+ if (appData.debugMode) {
+ fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
+ fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
+ }
+#if 0
+ if (appData.testLegality && appData.debugMode) {
+ sprintf(str, "Illegal move \"%s\" from ICS", move_str);
+ DisplayError(str, 0);
+ }
+#endif
+ 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 &&
+ (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
+
+ if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
+ (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
+ if (moveList[moveNum - 1][0] == NULLCHAR) {
+ sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
+ move_str);
+ DisplayError(str, 0);
+ } else {
+ if (first.sendTime) {
+ SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
+ }
+ bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
+ if (firstMove && !bookHit) {
+ firstMove = FALSE;
+ if (first.useColors) {
+ SendToProgram(gameMode == IcsPlayingWhite ?
+ "white\ngo\n" :
+ "black\ngo\n", &first);
+ } else {
+ SendToProgram("go\n", &first);
+ }
+ first.maybeThinking = TRUE;
+ }
+ }
+ } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
+ if (moveList[moveNum - 1][0] == NULLCHAR) {
+ 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);
+ }
+ }
+ }
+#endif
+ }
+
+ if (moveNum > 0 && !gotPremove) {
+ /* 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) {
+ AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
+ }
+ if (!pausing && appData.highlightLastMove) {
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
+
+ /* Start the clocks */
+ whiteFlag = blackFlag = FALSE;
+ appData.clockMode = !(basetime == 0 && increment == 0);
+ if (ticking == 0) {
+ ics_clock_paused = TRUE;
+ StopClocks();
+ } else if (ticking == 1) {
+ ics_clock_paused = FALSE;
+ }
+ if (gameMode == IcsIdle ||
+ relation == RELATION_OBSERVING_STATIC ||
+ relation == RELATION_EXAMINING ||
+ ics_clock_paused)
+ DisplayBothClocks();
+ else
+ StartClocks();
+
+ /* Display opponents and material strengths */
+ if (gameInfo.variant != VariantBughouse &&
+ gameInfo.variant != VariantCrazyhouse) {
+ if (tinyLayout || smallLayout) {
+ 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 {
+ 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 (appData.premove)
+ 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();
+ }
+
+ 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
+GetMoveListEvent()
+{
+ char buf[MSG_SIZ];
+ if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
+ ics_getting_history = H_REQUESTED;
+ sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
+ SendToICS(buf);
+ }
+}
+
+void
+AnalysisPeriodicEvent(force)
+ int force;
+{
+ if (((programStats.ok_to_send == 0 || programStats.line_is_book)
+ && !force) || !appData.periodicUpdates)
+ return;
+
+ /* Send . command to Crafty to collect stats */
+ SendToProgram(".\n", &first);
+
+ /* Don't send another until we get a response (this makes
+ us stop sending to old Crafty's which don't understand
+ the "." command (sending illegal cmds resets node count & time,
+ which looks bad)) */
+ programStats.ok_to_send = 0;
+}
+
+void
+SendMoveToProgram(moveNum, cps)
+ int moveNum;
+ ChessProgramState *cps;
+{
+ char buf[MSG_SIZ];
+
+ if (cps->useUsermove) {
+ SendToProgram("usermove ", cps);
+ }
+ if (cps->useSAN) {
+ char *space;
+ if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
+ int len = space - parseList[moveNum];
+ memcpy(buf, parseList[moveNum], len);
+ buf[len++] = '\n';
+ buf[len] = NULLCHAR;
+ } else {
+ sprintf(buf, "%s\n", parseList[moveNum]);
+ }
+ SendToProgram(buf, cps);
+ } else {
+ if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
+ AlphaRank(moveList[moveNum], 4);
+ SendToProgram(moveList[moveNum], cps);
+ AlphaRank(moveList[moveNum], 4); // and back
+ } else
+ /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
+ * the engine. It would be nice to have a better way to identify castle
+ * moves here. */
+ if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
+ && cps->useOOCastle) {
+ 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
+SendMoveToICS(moveType, fromX, fromY, toX, toY)
+ ChessMove moveType;
+ int fromX, fromY, toX, toY;
+{
+ char user_move[MSG_SIZ];
+
+ switch (moveType) {
+ default:
+ 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;
+ case WhiteKingSideCastle:
+ 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:
+ case BlackPromotionQueen:
+ case WhitePromotionRook:
+ case BlackPromotionRook:
+ case WhitePromotionBishop:
+ case BlackPromotionBishop:
+ case WhitePromotionKnight:
+ case BlackPromotionKnight:
+ case WhitePromotionKing:
+ case BlackPromotionKing:
+ 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)),
+ 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",
+ AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+ break;
+ }
+ SendToICS(user_move);
+}
+
+void
+CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
+ int rf, ff, rt, ft;
+ char promoChar;
+ char move[7];
+{
+ if (rf == DROP_RANK) {
+ sprintf(move, "%c@%c%c\n",
+ ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
+ } else {
+ if (promoChar == 'x' || promoChar == NULLCHAR) {
+ sprintf(move, "%c%c%c%c\n",
+ AAA + ff, ONE + rf, AAA + ft, ONE + rt);
+ } else {
+ sprintf(move, "%c%c%c%c%c\n",
+ AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
+ }
+ }
+}
+
+void
+ProcessICSInitScript(f)
+ FILE *f;
+{
+ char buf[MSG_SIZ];
+
+ while (fgets(buf, MSG_SIZ, f)) {
+ SendToICSDelayed(buf,(long)appData.msLoginDelay);
+ }
+
+ fclose(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)
+ char *move;
+ int moveNum;
+ 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:
+ case BlackPromotionRook:
+ case WhitePromotionBishop:
+ case BlackPromotionBishop:
+ case WhitePromotionKnight:
+ case BlackPromotionKnight:
+ case WhitePromotionKing:
+ case BlackPromotionKing:
+ case NormalMove:
+ case WhiteCapturesEnPassant:
+ case BlackCapturesEnPassant:
+ case WhiteKingSideCastle:
+ case WhiteQueenSideCastle:
+ case BlackKingSideCastle:
+ case BlackQueenSideCastle:
+ case WhiteKingSideCastleWild:
+ 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] - AAA;
+ *fromY = currentMoveString[1] - ONE;
+ *toX = currentMoveString[2] - AAA;
+ *toY = currentMoveString[3] - ONE;
+ *promoChar = currentMoveString[4];
+ 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;
+ }
+ if (appData.testLegality) {
+ return (*moveType != IllegalMove);
+ } else {
+ return !(fromX == fromY && toX == toY);
+ }
+
+ case WhiteDrop:
+ case BlackDrop:
+ *fromX = *moveType == WhiteDrop ?
+ (int) CharToPiece(ToUpper(currentMoveString[0])) :
+ (int) CharToPiece(ToLower(currentMoveString[0]));
+ *fromY = DROP_RANK;
+ *toX = currentMoveString[2] - AAA;
+ *toY = currentMoveString[3] - ONE;
+ *promoChar = NULLCHAR;
+ return TRUE;
+
+ case AmbiguousMove:
+ case ImpossibleMove:
+ case (ChessMove) 0: /* end of file */
+ case ElapsedTime:
+ case Comment:
+ case PGNTag:
+ case NAG:
+ case WhiteWins:
+ 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;
+ return FALSE;
+ }
+}
+
+// [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<BOARD_RGHT; i++) {
+ if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
+ board[rank][i] = (ChessSquare) pieceType;
+ squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
+ squaresLeft[ANY]--;
+ piecesLeft[pieceType]--;
+ return i;
+ }
+ }
+ return -1;
+}
+
+
+void AddOnePiece(Board board, int pieceType, int rank, int shade)
+// calculate where the next piece goes, (any empty square), and put it there
+{
+ int i;
+
+ i = seed % squaresLeft[shade];
+ nrOfShuffles *= squaresLeft[shade];
+ seed /= squaresLeft[shade];
+ put(board, pieceType, rank, i, shade);
+}
+
+void AddTwoPieces(Board board, int pieceType, int rank)
+// calculate where the next 2 identical pieces go, (any empty square), and put it there
+{
+ int i, n=squaresLeft[ANY], j=n-1, k;
+
+ k = n*(n-1)/2; // nr of possibilities, not counting permutations
+ i = seed % k; // pick one
+ nrOfShuffles *= k;
+ seed /= k;
+ while(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<BOARD_RGHT; i++) { // count pieces and clear board
+ p = (int) board[0][i];
+ if(p < (int) BlackPawn) piecesLeft[p] ++;
+ board[0][i] = EmptySquare;
+ }
+
+ if(PosFlags(0) & F_ALL_CASTLE_OK) {
+ // shuffles restricted to allow normal castling put KRR first
+ if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
+ put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
+ else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
+ put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
+ if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
+ put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
+ if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
+ put(board, WhiteRook, 0, 0, ANY);
+ // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
+ }
+
+ if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
+ // only for even boards make effort to put pairs of colorbound pieces on opposite colors
+ for(p = (int) WhiteKing; p > (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<BOARD_RGHT; i++) { // copy black from white
+ board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
+ }
+
+ if(number >= 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<NrPieces/2-1; i++ ) {
+ table[i] = map[i];
+ table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
+ }
+ table[(int) WhiteKing] = map[NrPieces/2-1];
+ table[(int) BlackKing] = map[NrPieces-1];
+
+ result = TRUE;
+ }
+
+ return result;
+}
+
+void Prelude(Board board)
+{ // [HGM] superchess: random selection of exo-pieces
+ int i, j, k; ChessSquare p;
+ static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
+
+ GetPositionNumber(); // use FRC position number
+
+ if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
+ SetCharTable(pieceToChar, appData.pieceToCharTable);
+ for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
+ if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
+ }
+
+ j = seed%4; seed /= 4;
+ p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%3 + (seed%3 >= j); seed /= 3;
+ p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%3; seed /= 3;
+ p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%2 + (seed%2 >= j); seed /= 2;
+ p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
+ board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
+ board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
+ j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
+ j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
+ j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
+ put(board, exoPieces[0], 0, 0, ANY);
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
+}
+
+void
+InitPosition(redraw)
+ int redraw;
+{
+ ChessSquare (* pieces)[BOARD_SIZE];
+ int i, j, pawnRow, overrule,
+ oldx = gameInfo.boardWidth,
+ oldy = gameInfo.boardHeight,
+ oldh = gameInfo.holdingsWidth,
+ oldv = gameInfo.variant;
+
+ currentMove = forwardMostMove = backwardMostMove = 0;
+ if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
+
+ /* [AS] Initialize pv info list [HGM] and game status */
+ {
+ for( i=0; i<MAX_MOVES; i++ ) {
+ pvInfoList[i].depth = 0;
+ epStatus[i]=EP_NONE;
+ for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
+ }
+
+ initialRulePlies = 0; /* 50-move counter start */
+
+ castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
+ castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
+ }
+
+
+ /* [HGM] logic here is completely changed. In stead of full positions */
+ /* the initialized data only consist of the two backranks. The switch */
+ /* selects which one we will use, which is than copied to the Board */
+ /* initialPosition, which for the rest is initialized by Pawns and */
+ /* empty squares. This initial position is then copied to boards[0], */
+ /* possibly after shuffling, so that it remains available. */
+
+ gameInfo.holdingsWidth = 0; /* default board sizes */
+ gameInfo.boardWidth = 8;
+ gameInfo.boardHeight = 8;
+ gameInfo.holdingsSize = 0;
+ nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
+ for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
+ SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+
+ switch (gameInfo.variant) {
+ case VariantFischeRandom:
+ shuffleOpenings = TRUE;
+ default:
+ pieces = FIDEArray;
+ break;
+ case VariantShatranj:
+ pieces = ShatranjArray;
+ nrCastlingRights = 0;
+ SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
+ break;
+ case VariantTwoKings:
+ pieces = twoKingsArray;
+ break;
+ case VariantCapaRandom:
+ shuffleOpenings = TRUE;
+ case VariantCapablanca:
+ pieces = CapablancaArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ break;
+ case VariantGothic:
+ pieces = GothicArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
+ break;
+ case VariantJanus:
+ pieces = JanusArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
+ 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)>>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<BOARD_SIZE; i++) initialRights[i] = -1;
+ break;
+ case VariantKnightmate:
+ pieces = KnightmateArray;
+ SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
+ break;
+ case VariantFairy:
+ pieces = fairyArray;
+ SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
+ break;
+ case VariantGreat:
+ pieces = GreatArray;
+ gameInfo.boardWidth = 10;
+ SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
+ gameInfo.holdingsSize = 8;
+ break;
+ case VariantSuper:
+ pieces = FIDEArray;
+ SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
+ gameInfo.holdingsSize = 8;
+ startedFromSetupPosition = TRUE;
+ break;
+ case VariantCrazyhouse:
+ case VariantBughouse:
+ pieces = FIDEArray;
+ SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
+ gameInfo.holdingsSize = 5;
+ break;
+ case VariantWildCastle:
+ pieces = FIDEArray;
+ /* !!?shuffle with kings guaranteed to be on d or e file */
+ shuffleOpenings = 1;
+ break;
+ case VariantNoCastle:
+ pieces = FIDEArray;
+ nrCastlingRights = 0;
+ for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
+ /* !!?unconstrained back-rank shuffle */
+ shuffleOpenings = 1;
+ break;
+ }
+
+ overrule = 0;
+ if(appData.NrFiles >= 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_WIDTH; j++ ) { ChessSquare s = EmptySquare;
+
+ if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
+ s = (ChessSquare) 0; /* account holding counts in guard band */
+ for( i=0; i<BOARD_HEIGHT; i++ )
+ initialPosition[i][j] = s;
+
+ if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
+ initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
+ initialPosition[pawnRow][j] = WhitePawn;
+ initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
+ if(gameInfo.variant == VariantXiangqi) {
+ if(j&1) {
+ initialPosition[pawnRow][j] =
+ initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
+ if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
+ initialPosition[2][j] = WhiteCannon;
+ initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
+ }
+ }
+ }
+ initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
+ }
+ if( (gameInfo.variant == VariantShogi) && !overrule ) {
+
+ j=BOARD_LEFT+1;
+ initialPosition[1][j] = WhiteBishop;
+ initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
+ j=BOARD_RGHT-2;
+ initialPosition[1][j] = WhiteRook;
+ initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
+ }
+
+ if( nrCastlingRights == -1) {
+ /* [HGM] Build normal castling rights (must be done after board sizing!) */
+ /* This sets default castling rights from none to normal corners */
+ /* Variants with other castling rights must set them themselves above */
+ nrCastlingRights = 6;
+
+ castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
+ castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
+ castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
+ castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
+ castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
+ castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
+ }
+
+ if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
+ if(gameInfo.variant == VariantGreat) { // promotion commoners
+ initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
+ initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
+ initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
+ initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
+ }
+#if 0
+ if(gameInfo.variant == VariantFischeRandom) {
+ if( appData.defaultFrcPosition < 0 ) {
+ ShuffleFRC( initialPosition );
+ }
+ else {
+ SetupFRC( initialPosition, appData.defaultFrcPosition );
+ }
+ startedFromSetupPosition = TRUE;
+ } else
+#else
+ if (appData.debugMode) {
+ fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
+ }
+ if(shuffleOpenings) {
+ SetUpShuffle(initialPosition, appData.defaultFrcPosition);
+ startedFromSetupPosition = TRUE;
+ }
+#endif
+ if(startedFromPositionFile) {
+ /* [HGM] loadPos: use PositionFile for every new game */
+ CopyBoard(initialPosition, filePosition);
+ for(i=0; i<nrCastlingRights; i++)
+ castlingRights[0][i] = initialRights[i] = fileRights[i];
+ startedFromSetupPosition = TRUE;
+ }
+
+ CopyBoard(boards[0], initialPosition);
+
+ if(oldx != gameInfo.boardWidth ||
+ oldy != gameInfo.boardHeight ||
+ oldh != gameInfo.holdingsWidth
+#ifdef GOTHIC
+ || oldv == VariantGothic || // For licensing popups
+ gameInfo.variant == VariantGothic
+#endif
+#ifdef FALCON
+ || oldv == VariantFalcon ||
+ gameInfo.variant == VariantFalcon
+#endif
+ )
+ InitDrawingSizes(-2 ,0);
+
+ if (redraw)
+ DrawPosition(TRUE, boards[currentMove]);
+}
+
+void
+SendBoard(cps, moveNum)
+ ChessProgramState *cps;
+ int moveNum;
+{
+ char message[MSG_SIZ];
+
+ if (cps->useSetboard) {
+ char* fen = PositionToFEN(moveNum, cps->fenOverride);
+ sprintf(message, "setboard %s\n", fen);
+ SendToProgram(message, cps);
+ free(fen);
+
+ } else {
+ ChessSquare *bp;
+ int i, j;
+ /* Kludge to set black to move, avoiding the troublesome and now
+ * deprecated "black" command.
+ */
+ if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
+
+ SendToProgram("edit\n", cps);
+ SendToProgram("#\n", cps);
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ bp = &boards[moveNum][i][BOARD_LEFT];
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
+ if ((int) *bp < (int) BlackPawn) {
+ 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_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)),
+ 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;
+{
+ /* [HGM] add Shogi promotions */
+ int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
+ ChessSquare piece;
+
+ if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
+ !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
+ /* [HGM] Note to self: line above also weeds out drops */
+ piece = boards[currentMove][fromY][fromX];
+ if(gameInfo.variant == VariantShogi) {
+ promotionZoneSize = 3;
+ highestPromotingPiece = (int)WhiteKing;
+ /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
+ and if in normal chess we then allow promotion to King, why not
+ allow promotion of other piece in Shogi? */
+ }
+ if((int)piece >= BlackPawn) {
+ if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
+ return FALSE;
+ highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
+ } else {
+ if( toY < BOARD_HEIGHT - promotionZoneSize &&
+ fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
+ }
+ return ( (int)piece <= highestPromotingPiece );
+}
+
+int
+InPalace(row, column)
+ int row, column;
+{ /* [HGM] for Xiangqi */
+ if( (row < 3 || row > BOARD_HEIGHT-4) &&
+ column < (BOARD_WIDTH + 4)/2 &&
+ column > (BOARD_WIDTH - 5)/2 ) return TRUE;
+ return FALSE;
+}
+
+int
+PieceForSquare (x, y)
+ int x;
+ int y;
+{
+ if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
+ return -1;
+ else
+ return boards[currentMove][y][x];
+}
+
+int
+OKToStartUserMove(x, y)
+ int x, y;
+{
+ ChessSquare from_piece;
+ int white_piece;
+
+ if (matchMode) return FALSE;
+ if (gameMode == EditPosition) return TRUE;
+
+ if (x >= 0 && y >= 0)
+ from_piece = boards[currentMove][y][x];
+ else
+ from_piece = EmptySquare;
+
+ if (from_piece == EmptySquare) return FALSE;
+
+ white_piece = (int)from_piece >= (int)WhitePawn &&
+ (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
+
+ switch (gameMode) {
+ case PlayFromGameFile:
+ case AnalyzeFile:
+ case TwoMachinesPlay:
+ case EndOfGame:
+ return FALSE;
+
+ case IcsObserving:
+ case IcsIdle:
+ return FALSE;
+
+ case MachinePlaysWhite:
+ case IcsPlayingBlack:
+ if (appData.zippyPlay) return FALSE;
+ if (white_piece) {
+ DisplayMoveError(_("You are playing Black"));
+ return FALSE;
+ }
+ break;
+
+ case MachinePlaysBlack:
+ case IcsPlayingWhite:
+ if (appData.zippyPlay) return FALSE;
+ if (!white_piece) {
+ DisplayMoveError(_("You are playing White"));
+ return FALSE;
+ }
+ break;
+
+ case EditGame:
+ 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 */
+ cmailOldMove = -1;
+ }
+ if (currentMove < forwardMostMove) {
+ /* Discarding moves */
+ /* Could prompt for confirmation here,
+ but I don't think that's such a good idea */
+ forwardMostMove = currentMove;
+ }
+ break;
+
+ case BeginningOfGame:
+ if (appData.icsActive) return FALSE;
+ if (!appData.noChessProgram) {
+ if (!white_piece) {
+ DisplayMoveError(_("You are playing White"));
+ return FALSE;
+ }
+ }
+ 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:
+ case IcsExamining:
+ break;
+ }
+ if (currentMove != forwardMostMove && gameMode != AnalyzeMode
+ && gameMode != AnalyzeFile && gameMode != Training) {
+ DisplayMoveError(_("Displayed position is not current"));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
+int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
+int lastLoadGameUseList = FALSE;
+char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
+ChessMove lastLoadGameStart = (ChessMove) 0;
+
+
+ChessMove
+UserMoveTest(fromX, fromY, toX, toY, promoChar)
+ int fromX, fromY, toX, toY;
+ int promoChar;
+{
+ ChessMove moveType;
+ ChessSquare pdown, pup;
+
+ if (fromX < 0 || fromY < 0) return ImpossibleMove;
+ if ((fromX == toX) && (fromY == toY)) {
+ return ImpossibleMove;
+ }
+
+ /* [HGM] suppress all moves into holdings area and guard band */
+ if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
+ return ImpossibleMove;
+
+ /* [HGM] <sameColor> moved to here from winboard.c */
+ /* note: this code seems to exist for filtering out some obviously illegal premoves */
+ pdown = boards[currentMove][fromY][fromX];
+ pup = boards[currentMove][toY][toX];
+ if ( gameMode != EditPosition &&
+ (WhitePawn <= pdown && pdown < BlackPawn &&
+ WhitePawn <= pup && pup < BlackPawn ||
+ BlackPawn <= pdown && pdown < EmptySquare &&
+ BlackPawn <= pup && pup < EmptySquare
+ ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+ (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
+ pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
+ ) )
+ return ImpossibleMove;
+
+ /* Check if the user is playing in turn. This is complicated because we
+ let the user "pick up" a piece before it is his turn. So the piece he
+ tried to pick up may have been captured by the time he puts it down!
+ Therefore we use the color the user is supposed to be playing in this
+ test, not the color of the piece that is currently on the starting
+ square---except in EditGame mode, where the user is playing both
+ sides; fortunately there the capture race can't happen. (It can
+ now happen in IcsExamining mode, but that's just too bad. The user
+ will get a somewhat confusing message in that case.)
+ */
+
+ switch (gameMode) {
+ case PlayFromGameFile:
+ case AnalyzeFile:
+ case TwoMachinesPlay:
+ case EndOfGame:
+ case IcsObserving:
+ case IcsIdle:
+ /* We switched into a game mode where moves are not accepted,
+ perhaps while the mouse button was down. */
+ return ImpossibleMove;
+
+ case MachinePlaysWhite:
+ /* User is moving for Black */
+ if (WhiteOnMove(currentMove)) {
+ DisplayMoveError(_("It is White's turn"));
+ return ImpossibleMove;
+ }
+ break;
+
+ case MachinePlaysBlack:
+ /* User is moving for White */
+ if (!WhiteOnMove(currentMove)) {
+ DisplayMoveError(_("It is Black's turn"));
+ return ImpossibleMove;
+ }
+ break;
+
+ case EditGame:
+ case IcsExamining:
+ case BeginningOfGame:
+ case AnalyzeMode:
+ case Training:
+ if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
+ (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
+ /* User is moving for Black */
+ if (WhiteOnMove(currentMove)) {
+ DisplayMoveError(_("It is White's turn"));
+ return ImpossibleMove;
+ }
+ } else {
+ /* User is moving for White */
+ if (!WhiteOnMove(currentMove)) {
+ DisplayMoveError(_("It is Black's turn"));
+ return ImpossibleMove;
+ }
+ }
+ break;
+
+ case IcsPlayingBlack:
+ /* User is moving for Black */
+ if (WhiteOnMove(currentMove)) {
+ if (!appData.premove) {
+ DisplayMoveError(_("It is White's turn"));
+ } else if (toX >= 0 && toY >= 0) {
+ premoveToX = toX;
+ premoveToY = toY;
+ premoveFromX = fromX;
+ premoveFromY = fromY;
+ premovePromoChar = promoChar;
+ gotPremove = 1;
+ if (appData.debugMode)
+ fprintf(debugFP, "Got premove: fromX %d,"
+ "fromY %d, toX %d, toY %d\n",
+ fromX, fromY, toX, toY);
+ }
+ return ImpossibleMove;
+ }
+ break;
+
+ case IcsPlayingWhite:
+ /* User is moving for White */
+ if (!WhiteOnMove(currentMove)) {
+ if (!appData.premove) {
+ DisplayMoveError(_("It is Black's turn"));
+ } else if (toX >= 0 && toY >= 0) {
+ premoveToX = toX;
+ premoveToY = toY;
+ premoveFromX = fromX;
+ premoveFromY = fromY;
+ premovePromoChar = promoChar;
+ gotPremove = 1;
+ if (appData.debugMode)
+ fprintf(debugFP, "Got premove: fromX %d,"
+ "fromY %d, toX %d, toY %d\n",
+ fromX, fromY, toX, toY);
+ }
+ return ImpossibleMove;
+ }
+ break;
+
+ default:
+ 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;
+ return AmbiguousMove;
+ } else if (toX >= 0 && toY >= 0) {
+ boards[0][toY][toX] = boards[0][fromY][fromX];
+ boards[0][fromY][fromX] = EmptySquare;
+ return AmbiguousMove;
+ }
+ return ImpossibleMove;
+ }
+
+ /* [HGM] If move started in holdings, it means a drop */
+ if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+ if( pup != EmptySquare ) return ImpossibleMove;
+ if(appData.testLegality) {
+ /* it would be more logical if LegalityTest() also figured out
+ * which drops are legal. For now we forbid pawns on back rank.
+ * Shogi is on its own here...
+ */
+ if( (pdown == WhitePawn || pdown == BlackPawn) &&
+ (toY == 0 || toY == BOARD_HEIGHT -1 ) )
+ return(ImpossibleMove); /* no pawn drops on 1st/8th */
+ }
+ return WhiteDrop; /* Not needed to specify white or black yet */
+ }
+
+ 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) {
+ if (moveType == IllegalMove || moveType == ImpossibleMove) {
+ DisplayMoveError(_("Illegal move"));
+ return ImpossibleMove;
+ }
+ }
+if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
+ return moveType;
+ /* [HGM] <popupFix> 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] <popupFix> 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;
+ fromX = boards[currentMove][fromY][fromX];
+ fromY = DROP_RANK;
+ }
+
+ /* [HGM] <popupFix> The following if has been moved here from
+ UserMoveEvent(). Because it seemed to belon here (why not allow
+ piece drops in training games?), and because it can only be
+ performed after it is known to what we promote. */
+ if (gameMode == Training) {
+ /* compare the move played on the board to the next move in the
+ * game. If they match, display the move and the opponent's response.
+ * If they don't match, display an error message.
+ */
+ int saveAnimate;
+ Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
+ CopyBoard(testBoard, boards[currentMove]);
+ ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
+
+ if (CompareBoards(testBoard, boards[currentMove+1])) {
+ ForwardInner(currentMove+1);
+
+ /* Autoplay the opponent's response.
+ * if appData.animate was TRUE when Training mode was entered,
+ * the response will be animated.
+ */
+ saveAnimate = appData.animate;
+ appData.animate = animateTraining;
+ ForwardInner(currentMove+1);
+ appData.animate = saveAnimate;
+
+ /* check for the end of the game */
+ if (currentMove >= forwardMostMove) {
+ gameMode = PlayFromGameFile;
+ ModeHighlight();
+ SetTrainingModeOff();
+ DisplayInformation(_("End of game"));
+ }
+ } else {
+ DisplayError(_("Incorrect move"), 0);
+ }
+ return 1;
+ }
+
+ /* Ok, now we know that the move is good, so we can kill
+ the previous line in Analysis Mode */
+ if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
+ forwardMostMove = currentMove;
+ }
+
+ /* If we need the chess program but it's dead, restart it */
+ ResurrectChessProgram();
+
+ /* A user move restarts a paused game*/
+ if (pausing)
+ PauseEvent();
+
+ thinkOutput[0] = NULLCHAR;
+
+ MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
+
+ if (gameMode == BeginningOfGame) {
+ if (appData.noChessProgram) {
+ gameMode = EditGame;
+ SetGameInfo();
+ } else {
+ char buf[MSG_SIZ];
+ gameMode = MachinePlaysBlack;
+ StartClocks();
+ SetGameInfo();
+ sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ DisplayTitle(buf);
+ if (first.sendName) {
+ 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 ||
+ gameMode == IcsExamining) {
+ SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ ics_user_moved = 1;
+ }
+ } else {
+ if (first.sendTime && (gameMode == BeginningOfGame ||
+ gameMode == MachinePlaysWhite ||
+ gameMode == MachinePlaysBlack)) {
+ SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
+ }
+ if (gameMode != EditGame && gameMode != PlayFromGameFile) {
+ // [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;
+ }
+ }
+
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+ switch (gameMode) {
+ case EditGame:
+ switch (MateTest(boards[currentMove], PosFlags(currentMove),
+ 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 MachinePlaysBlack:
+ case MachinePlaysWhite:
+ /* disable certain menu options while machine is thinking */
+ SetMachineThinkingEnables();
+ break;
+
+ default:
+ break;
+ }
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
+ return 1;
+}
+
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
+ int fromX, fromY, toX, toY;
+ int promoChar;
+{
+ /* [HGM] This routine was added to allow calling of its two logical
+ parts from other modules in the old way. Before, UserMoveEvent()
+ automatically called FinishMove() if the move was OK, and returned
+ otherwise. I separated the two, in order to make it possible to
+ slip a promotion popup in between. But that it always needs two
+ calls, to the first part, (now called UserMoveTest() ), and to
+ FinishMove if the first part succeeded. Calls that do not need
+ to do anything in between, can call this routine the old way.
+ */
+ ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
+if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
+ if(moveType != ImpossibleMove)
+ FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
+}
+
+void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
+{
+// char * hint = lastHint;
+ FrontEndProgramStats stats;
+
+ stats.which = cps == &first ? 0 : 1;
+ stats.depth = cpstats->depth;
+ stats.nodes = cpstats->nodes;
+ stats.score = cpstats->score;
+ stats.time = cpstats->time;
+ stats.pv = cpstats->movelist;
+ stats.hint = lastHint;
+ stats.an_move_index = 0;
+ stats.an_move_count = 0;
+
+ if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
+ stats.hint = cpstats->move_name;
+ stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
+ stats.an_move_count = cpstats->nr_moves;
+ }
+
+ SetProgramStats( &stats );
+}
+
+char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
+{ // [HGM] book: this routine intercepts moves to simulate book replies
+ char *bookHit = NULL;
+
+ //first determine if the incoming move brings opponent into his book
+ if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
+ bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
+ if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
+ if(bookHit != NULL && !cps->bookSuspend) {
+ // make sure opponent is not going to reply after receiving move to book position
+ SendToProgram("force\n", cps);
+ cps->bookSuspend = TRUE; // flag indicating it has to be restarted
+ }
+ if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
+ // now arrange restart after book miss
+ if(bookHit) {
+ // after a book hit we never send 'go', and the code after the call to this routine
+ // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
+ char buf[MSG_SIZ];
+ if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
+ sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+ SendToProgram(buf, cps);
+ if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
+ } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
+ SendToProgram("go\n", cps);
+ cps->bookSuspend = FALSE; // after a 'go' we are never suspended
+ } else { // 'go' might be sent based on 'firstMove' after this routine returns
+ if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
+ SendToProgram("go\n", cps);
+ cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
+ }
+ return bookHit; // notify caller of hit, so it can take action to send move to opponent
+}
+
+char *savedMessage;
+ChessProgramState *savedState;
+void DeferredBookMove(void)
+{
+ if(savedState->lastPing != savedState->lastPong)
+ ScheduleDelayedEvent(DeferredBookMove, 10);
+ else
+ HandleMachineMove(savedMessage, savedState);
+}
+
+void
+HandleMachineMove(message, cps)
+ char *message;
+ ChessProgramState *cps;
+{
+ char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
+ char realname[MSG_SIZ];
+ int fromX, fromY, toX, toY;
+ ChessMove moveType;
+ char promoChar;
+ char *p;
+ 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) {
+ if (message[0] == '\t' || message[0] == ' ') {
+ /* Part of the book output is here; append it */
+ strcat(bookOutput, message);
+ strcat(bookOutput, " \n");
+ return;
+ } else if (bookOutput[0] != NULLCHAR) {
+ /* All of book output has arrived; display it */
+ char *p = bookOutput;
+ while (*p != NULLCHAR) {
+ if (*p == '\t') *p = ' ';
+ p++;
+ }
+ DisplayInformation(bookOutput);
+ bookRequested = FALSE;
+ /* Fall through to parse the current output */
+ }
+ }
+
+ /*
+ * 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))
+ {
+ /* This method is only useful on engines that support ping */
+ if (cps->lastPing != cps->lastPong) {
+ if (gameMode == BeginningOfGame) {
+ /* Extra move from before last new; ignore */
+ if (appData.debugMode) {
+ fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
+ }
+ } else {
+ if (appData.debugMode) {
+ fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
+ cps->which, gameMode);
+ }
+
+ SendToProgram("undo\n", cps);
+ }
+ return;
+ }
+
+ switch (gameMode) {
+ case BeginningOfGame:
+ /* Extra move from before last reset; ignore */
+ if (appData.debugMode) {
+ fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
+ }
+ return;
+
+ case EndOfGame:
+ case IcsIdle:
+ default:
+ /* Extra move after we tried to stop. The mode test is
+ not a reliable way of detecting this problem, but it's
+ the best we can do on engines that don't support ping.
+ */
+ if (appData.debugMode) {
+ fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
+ cps->which, gameMode);
+ }
+ SendToProgram("undo\n", cps);
+ return;
+
+ case MachinePlaysWhite:
+ case IcsPlayingWhite:
+ machineWhite = TRUE;
+ break;
+
+ case MachinePlaysBlack:
+ case IcsPlayingBlack:
+ machineWhite = FALSE;
+ break;
+
+ case TwoMachinesPlay:
+ machineWhite = (cps->twoMachinesColor[0] == 'w');
+ break;
+ }
+ if (WhiteOnMove(forwardMostMove) != machineWhite) {
+ if (appData.debugMode) {
+ fprintf(debugFP,
+ "Ignoring move out of turn by %s, gameMode %d"
+ ", forwardMost %d\n",
+ cps->which, gameMode, forwardMostMove);
+ }
+ return;
+ }
+
+ 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"),
+ 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,
+ 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;
+ /* Program may be pondering now */
+ cps->maybeThinking = TRUE;
+ if (cps->sendTime == 2) cps->sendTime = 1;
+ if (cps->offeredDraw) cps->offeredDraw--;
+
+#if ZIPPY
+ if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
+ 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, %.0f nodes, %1.0f knps) PV=%s\n",
+ programStats.score / 100.,
+ programStats.depth,
+ programStats.time / 100.,
+ u64ToDouble(programStats.nodes),
+ u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
+ programStats.movelist);
+ SendToICS(buf);
+ }
+ }
+#endif
+ /* currentMoveString is set as a side-effect of ParseOneMove */
+ strcpy(machineMove, currentMoveString);
+ strcat(machineMove, "\n");
+ strcpy(moveList[forwardMostMove], machineMove);
+
+ /* [AS] Save move info and clear stats for next move */
+ pvInfoList[ forwardMostMove ].score = programStats.score;
+ pvInfoList[ forwardMostMove ].depth = programStats.depth;
+ pvInfoList[ forwardMostMove ].time = 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; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
+ { ChessSquare p = boards[forwardMostMove][i][j];
+ int m=i;
+
+ switch((int) p)
+ { /* count B,N,R and other of each side */
+ case WhiteKing:
+ case BlackKing:
+ NrK++; break; // [HGM] atomic: count Kings
+ case WhiteKnight:
+ NrWN++; break;
+ case WhiteBishop:
+ case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
+ bishopsColor |= 1 << ((i^j)&1);
+ NrWB++; break;
+ case BlackKnight:
+ NrBN++; break;
+ case BlackBishop:
+ case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
+ bishopsColor |= 1 << ((i^j)&1);
+ NrBB++; break;
+ case WhiteRook:
+ NrWR++; break;
+ case BlackRook:
+ NrBR++; break;
+ case WhiteQueen:
+ NrWQ++; break;
+ case BlackQueen:
+ NrBQ++; break;
+ case EmptySquare:
+ break;
+ case BlackPawn:
+ m = 7-i;
+ case WhitePawn:
+ PawnAdvance += m; NrPawns++;
+ }
+ NrPieces += (p != EmptySquare);
+ NrW += ((int)p < (int)BlackPawn);
+ if(gameInfo.variant == VariantXiangqi &&
+ (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
+ NrPieces--; // [HGM] XQ: do not count purely defensive pieces
+ NrW -= ((int)p < (int)BlackPawn);
+ }
+ }
+
+ /* Some material-based adjudications that have to be made before stalemate test */
+ if(gameInfo.variant == VariantAtomic && NrK < 2) {
+ // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
+ epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
+ if(appData.checkMates) {
+ SendMoveToProgram(forwardMostMove-1, cps->other); // 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 1
+ if (appData.debugMode) { int i;
+ fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+ forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
+ appData.drawRepeats);
+ for( i=forwardMostMove; i>=backwardMostMove; i-- )
+ fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
+
+ }
+#endif
+ /* Check for rep-draws */
+ count = 0;
+ for(k = forwardMostMove-2;
+ k>=backwardMostMove && k>=forwardMostMove-100 &&
+ epStatus[k] < EP_UNKNOWN &&
+ epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
+ k-=2)
+ { int rights=0;
+#if 0
+ if (appData.debugMode) {
+ fprintf(debugFP, " loop\n");
+ }
+#endif
+ if(CompareBoards(boards[k], boards[forwardMostMove])) {
+#if 0
+ if (appData.debugMode) {
+ fprintf(debugFP, "match\n");
+ }
+#endif
+ /* compare castling rights */
+ if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
+ (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
+ rights++; /* King lost rights, while rook still had them */
+ if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
+ if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
+ castlingRights[forwardMostMove][1] != castlingRights[k][1] )
+ rights++; /* but at least one rook lost them */
+ }
+ if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
+ (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
+ rights++;
+ if( castlingRights[forwardMostMove][5] >= 0 ) {
+ if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
+ castlingRights[forwardMostMove][4] != castlingRights[k][4] )
+ rights++;
+ }
+#if 0
+ if (appData.debugMode) {
+ for(i=0; i<nrCastlingRights; i++)
+ fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
+ }
+
+ if (appData.debugMode) {
+ fprintf(debugFP, " %d %d\n", rights, k);
+ }
+#endif
+ 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');
+ }
+ bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
+ if (firstMove && !bookHit) {
+ firstMove = FALSE;
+ if (cps->other->useColors) {
+ SendToProgram(cps->other->twoMachinesColor, cps->other);
+ }
+ SendToProgram("go\n", cps->other);
+ }
+ cps->other->maybeThinking = TRUE;
+ }
+
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+
+ 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;
+ }
+
+ /* Set special modes for chess engines. Later something general
+ * could be added here; for now there is just one kludge feature,
+ * needed because Crafty 15.10 and earlier don't ignore SIGINT
+ * when "xboard" is given as an interactive command.
+ */
+ if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
+ cps->useSigint = FALSE;
+ cps->useSigterm = FALSE;
+ }
+
+ /* [HGM] Allow engine to set up a position. Don't ask me why one would
+ * want this, I was asked to put it in, and obliged.
+ */
+ if (!strncmp(message, "setboard ", 9)) {
+ Board initial_position; int i;
+
+ GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
+
+ if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
+ DisplayError(_("Bad FEN received from engine"), 0);
+ return ;
+ } else {
+ Reset(FALSE, FALSE);
+ CopyBoard(boards[0], initial_position);
+ initialRulePlies = FENrulePlies;
+ epStatus[0] = FENepStatus;
+ for( i=0; i<nrCastlingRights; i++ )
+ castlingRights[0][i] = FENcastlingRights[i];
+ if(blackPlaysFirst) gameMode = MachinePlaysWhite;
+ else gameMode = MachinePlaysBlack;
+ DrawPosition(FALSE, boards[currentMove]);
+ }
+ return;
+ }
+
+ /*
+ * Look for communication commands
+ */
+ if (!strncmp(message, "telluser ", 9)) {
+ DisplayNote(message + 9);
+ return;
+ }
+ if (!strncmp(message, "tellusererror ", 14)) {
+ DisplayError(message + 14, 0);
+ return;
+ }
+ if (!strncmp(message, "tellopponent ", 13)) {
+ if (appData.icsActive) {
+ if (loggedOn) {
+ snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
+ SendToICS(buf1);
+ }
+ } else {
+ DisplayNote(message + 13);
+ }
+ return;
+ }
+ if (!strncmp(message, "tellothers ", 11)) {
+ if (appData.icsActive) {
+ if (loggedOn) {
+ snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
+ SendToICS(buf1);
+ }
+ }
+ return;
+ }
+ if (!strncmp(message, "tellall ", 8)) {
+ if (appData.icsActive) {
+ if (loggedOn) {
+ snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
+ SendToICS(buf1);
+ }
+ } else {
+ DisplayNote(message + 8);
+ }
+ return;
+ }
+ if (strncmp(message, "warning", 7) == 0) {
+ /* Undocumented feature, use tellusererror in new code */
+ DisplayError(message, 0);
+ return;
+ }
+ if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
+ strcpy(realname, cps->tidy);
+ strcat(realname, " query");
+ AskQuestion(realname, buf2, buf1, cps->pr);
+ 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
+ * interfere with the login process.
+ */
+ if (loggedOn) {
+ if (!strncmp(message, "tellics ", 8)) {
+ SendToICS(message + 8);
+ SendToICS("\n");
+ return;
+ }
+ if (!strncmp(message, "tellicsnoalias ", 15)) {
+ SendToICS(ics_prefix);
+ SendToICS(message + 15);
+ SendToICS("\n");
+ return;
+ }
+ /* The following are for backward compatibility only */
+ if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
+ !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
+ SendToICS(ics_prefix);
+ SendToICS(message);
+ SendToICS("\n");
+ return;
+ }
+ }
+ if (strncmp(message, "feature ", 8) == 0) {
+ ParseFeatures(message+8, cps);
+ }
+ if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
+ return;
+ }
+ /*
+ * If the move is illegal, cancel it and redraw the board.
+ * Also deal with other error cases. Matching is rather loose
+ * here to accommodate engines written before the spec.
+ */
+ if (strncmp(message + 1, "llegal move", 11) == 0 ||
+ strncmp(message, "Error", 5) == 0) {
+ if (StrStr(message, "name") ||
+ StrStr(message, "rating") || StrStr(message, "?") ||
+ StrStr(message, "result") || StrStr(message, "board") ||
+ StrStr(message, "bk") || StrStr(message, "computer") ||
+ StrStr(message, "variant") || StrStr(message, "hint") ||
+ StrStr(message, "random") || StrStr(message, "depth") ||
+ StrStr(message, "accepted")) {
+ return;
+ }
+ if (StrStr(message, "protover")) {
+ /* Program is responding to input, so it's apparently done
+ initializing, and this error message indicates it is
+ protocol version 1. So we don't need to wait any longer
+ for it to initialize and send feature commands. */
+ FeatureDone(cps, 1);
+ cps->protocolVersion = 1;
+ return;
+ }
+ cps->maybeThinking = FALSE;
+
+ if (StrStr(message, "draw")) {
+ /* Program doesn't have "draw" command */
+ cps->sendDrawOffers = 0;
+ return;
+ }
+ if (cps->sendTime != 1 &&
+ (StrStr(message, "time") || StrStr(message, "otim"))) {
+ /* Program apparently doesn't have "time" or "otim" command */
+ cps->sendTime = 0;
+ return;
+ }
+ if (StrStr(message, "analyze")) {
+ cps->analysisSupport = FALSE;
+ cps->analyzing = FALSE;
+ Reset(FALSE, TRUE);
+ sprintf(buf2, _("%s does not support analysis"), cps->tidy);
+ DisplayError(buf2, 0);
+ return;
+ }
+ if (StrStr(message, "(no matching move)st")) {
+ /* Special kludge for GNU Chess 4 only */
+ cps->stKludge = TRUE;
+ SendTimeControl(cps, movesPerSession, timeControl,
+ timeIncrement, appData.searchDepth,
+ searchTime);
+ return;
+ }
+ if (StrStr(message, "(no matching move)sd")) {
+ /* Special kludge for GNU Chess 4 only */
+ cps->sdKludge = TRUE;
+ SendTimeControl(cps, movesPerSession, timeControl,
+ timeIncrement, appData.searchDepth,
+ searchTime);
+ 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 (gameMode == PlayFromGameFile) {
+ /* Stop reading this game file */
+ gameMode = EditGame;
+ ModeHighlight();
+ }
+ currentMove = --forwardMostMove;
+ DisplayMove(currentMove-1); /* before DisplayMoveError */
+ SwitchClocks();
+ DisplayBothClocks();
+ sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
+ parseList[currentMove], cps->which);
+ DisplayMoveError(buf1);
+ DrawPosition(FALSE, boards[currentMove]);
+
+ /* [HGM] illegal-move claim should forfeit game when Xboard */
+ /* only passes fully legal moves */
+ if( appData.testLegality && gameMode == TwoMachinesPlay ) {
+ GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
+ "False illegal-move claim", GE_XBOARD );
+ }
+ return;
+ }
+ if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
+ /* Program has a broken "time" command that
+ outputs a string not ending in newline.
+ Don't use it. */
+ cps->sendTime = 0;
+ }
+
+ /*
+ * If chess program startup fails, exit with an error message.
+ * Attempts to recover here are futile.
+ */
+ if ((StrStr(message, "unknown host") != NULL)
+ || (StrStr(message, "No remote directory") != NULL)
+ || (StrStr(message, "not found") != NULL)
+ || (StrStr(message, "No such file") != NULL)
+ || (StrStr(message, "can't alloc") != NULL)
+ || (StrStr(message, "Permission denied") != NULL)) {
+
+ cps->maybeThinking = FALSE;
+ 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) {
+ if (cps == &first && hintRequested) {
+ hintRequested = FALSE;
+ if (ParseOneMove(buf1, forwardMostMove, &moveType,
+ &fromX, &fromY, &toX, &toY, &promoChar)) {
+ (void) CoordsToAlgebraic(boards[forwardMostMove],
+ PosFlags(forwardMostMove), EP_UNKNOWN,
+ fromY, fromX, toY, toX, promoChar, buf1);
+ snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
+ DisplayInformation(buf2);
+ } else {
+ /* Hint move could not be parsed!? */
+ snprintf(buf2, sizeof(buf2),
+ _("Illegal hint move \"%s\"\nfrom %s chess program"),
+ buf1, cps->which);
+ DisplayError(buf2, 0);
+ }
+ } else {
+ strcpy(lastHint, buf1);
+ }
+ return;
+ }
+
+ /*
+ * Ignore other messages if game is not in progress
+ */
+ if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
+ gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
+
+ /*
+ * look for win, lose, draw, or draw offer
+ */
+ if (strncmp(message, "1-0", 3) == 0) {
+ char *p, *q, *r = "";
+ p = strchr(message, '{');
+ if (p) {
+ q = strchr(p, '}');
+ if (q) {
+ *q = NULLCHAR;
+ r = p + 1;
+ }
+ }
+ 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 = "";
+ p = strchr(message, '{');
+ if (p) {
+ q = strchr(p, '}');
+ if (q) {
+ *q = NULLCHAR;
+ r = p + 1;
+ }
+ }
+ /* Kludge for Arasan 4.1 bug */
+ if (strcmp(r, "Black resigns") == 0) {
+ GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
+ return;
+ }
+ GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
+ return;
+ } else if (strncmp(message, "1/2", 3) == 0) {
+ char *p, *q, *r = "";
+ p = strchr(message, '{');
+ if (p) {
+ q = strchr(p, '}');
+ if (q) {
+ *q = NULLCHAR;
+ r = p + 1;
+ }
+ }
+
+ GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
+ return;
+
+ } else if (strncmp(message, "White resign", 12) == 0) {
+ GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
+ return;
+ } else if (strncmp(message, "Black resign", 12) == 0) {
+ 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_ENGINE1 + (cps != &first));
+ return;
+ } else if (strncmp(message, "Black", 5) == 0 &&
+ message[5] != '(') {
+ 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);
+ break;
+ case MachinePlaysWhite:
+ case IcsPlayingWhite:
+ GameEnds(BlackWins, "White resigns", GE_ENGINE);
+ break;
+ case TwoMachinesPlay:
+ if (cps->twoMachinesColor[0] == 'w')
+ GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
+ else
+ GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
+ break;
+ default:
+ /* can't happen */
+ break;
+ }
+ return;
+ } else if (strncmp(message, "opponent mates", 14) == 0) {
+ switch (gameMode) {
+ case MachinePlaysBlack:
+ case IcsPlayingBlack:
+ GameEnds(WhiteWins, "White mates", GE_ENGINE);
+ break;
+ case MachinePlaysWhite:
+ case IcsPlayingWhite:
+ GameEnds(BlackWins, "Black mates", GE_ENGINE);
+ break;
+ case TwoMachinesPlay:
+ if (cps->twoMachinesColor[0] == 'w')
+ GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
+ else
+ GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
+ break;
+ default:
+ /* can't happen */
+ break;
+ }
+ return;
+ } else if (strncmp(message, "computer mates", 14) == 0) {
+ switch (gameMode) {
+ case MachinePlaysBlack:
+ case IcsPlayingBlack:
+ GameEnds(BlackWins, "Black mates", GE_ENGINE1);
+ break;
+ case MachinePlaysWhite:
+ case IcsPlayingWhite:
+ GameEnds(WhiteWins, "White mates", GE_ENGINE);
+ break;
+ case TwoMachinesPlay:
+ if (cps->twoMachinesColor[0] == 'w')
+ GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
+ else
+ GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
+ break;
+ default:
+ /* can't happen */
+ break;
+ }
+ return;
+ } else if (strncmp(message, "checkmate", 9) == 0) {
+ if (WhiteOnMove(forwardMostMove)) {
+ GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
+ } else {
+ 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_ENGINE1 + (cps != &first));
+ return;
+ } else if (strstr(message, "offer") != NULL &&
+ strstr(message, "draw") != NULL) {
+#if ZIPPY
+ if (appData.zippyPlay && first.initDone) {
+ /* Relay offer to ICS */
+ SendToICS(ics_prefix);
+ SendToICS("draw\n");
+ }
+#endif
+ cps->offeredDraw = 2; /* valid until this engine moves twice */
+ if (gameMode == TwoMachinesPlay) {
+ if (cps->other->offeredDraw) {
+ GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
+ /* [HGM] in two-machine mode we delay relaying draw offer */
+ /* until after we also have move, to see if it is really claim */
+ }
+#if 0
+ else {
+ if (cps->other->sendDrawOffers) {
+ SendToProgram("draw\n", cps->other);
+ }
+ }
+#endif
+ } else if (gameMode == MachinePlaysWhite ||
+ gameMode == MachinePlaysBlack) {
+ if (userOfferedDraw) {
+ DisplayInformation(_("Machine accepts your draw offer"));
+ GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
+ } else {
+ DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
+ }
+ }
+ }
+
+
+ /*
+ * Look for thinking output
+ */
+ 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; // [DM]
+ char plyext;
+ int ignore = FALSE;
+ int prefixHint = FALSE;
+ mvname[0] = NULLCHAR;
+
+ switch (gameMode) {
+ case MachinePlaysBlack:
+ case IcsPlayingBlack:
+ if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
+ break;
+ case MachinePlaysWhite:
+ case IcsPlayingWhite:
+ if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
+ break;
+ case AnalyzeMode:
+ case AnalyzeFile:
+ break;
+ case IcsObserving: /* [DM] icsEngineAnalyze */
+ if (!appData.icsEngineAnalyze) ignore = TRUE;
+ break;
+ case TwoMachinesPlay:
+ if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
+ ignore = TRUE;
+ }
+ break;
+ default:
+ ignore = TRUE;
+ break;
+ }
+
+ if (!ignore) {
+ buf1[0] = NULLCHAR;
+ 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)
+ && appData.debugMode) {
+ fprintf(debugFP,
+ "PV is too long; using the first %d bytes.\n",
+ sizeof(programStats.movelist) - 1);
+ }
+
+ safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
+ } else {
+ sprintf(programStats.movelist, " no PV\n");
+ }
+
+ if (programStats.seen_stat) {
+ programStats.ok_to_send = 1;
+ }
+
+ if (strchr(programStats.movelist, '(') != NULL) {
+ programStats.line_is_book = 1;
+ programStats.nr_moves = 0;
+ programStats.moves_left = 0;
+ } else {
+ programStats.line_is_book = 0;
+ }
+
+ 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 ? " " : "" );
+
+ 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) {
+ DisplayMove(currentMove - 1);
+ DisplayAnalysis();
+ }
+ return;
+
+ } else if ((p=StrStr(message, "(only move)")) != NULL) {
+ /* crafty (9.25+) says "(only move) <move>"
+ * if there is only 1 legal move
+ */
+ sscanf(p, "(only move) %s", buf1);
+ sprintf(thinkOutput, "%s (only move)", buf1);
+ sprintf(programStats.movelist, "%s (only move)", buf1);
+ programStats.depth = 1;
+ programStats.nr_moves = 1;
+ programStats.moves_left = 1;
+ programStats.nodes = 1;
+ programStats.time = 1;
+ programStats.got_only_move = 1;
+
+ /* Not really, but we also use this member to
+ mean "line isn't going to change" (Crafty
+ isn't searching, so stats won't change) */
+ programStats.line_is_book = 1;
+
+ 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",
+ &time, &nodes, &plylev, &mvleft,
+ &mvtot, mvname) >= 5) {
+ /* The stat01: line is from Crafty (9.29+) in response
+ to the "." command */
+ programStats.seen_stat = 1;
+ cps->maybeThinking = TRUE;
+
+ if (programStats.got_only_move || !appData.periodicUpdates)
+ return;
+
+ programStats.depth = plylev;
+ programStats.time = time;
+ programStats.nodes = nodes;
+ programStats.moves_left = mvleft;
+ programStats.nr_moves = mvtot;
+ strcpy(programStats.move_name, mvname);
+ programStats.ok_to_send = 1;
+ programStats.movelist[0] = '\0';
+
+ SendProgramStatsToFrontend( cps, &programStats );
+
+ DisplayAnalysis();
+ return;
+
+ } else if (strncmp(message,"++",2) == 0) {
+ /* Crafty 9.29+ outputs this */
+ programStats.got_fail = 2;
+ return;
+
+ } else if (strncmp(message,"--",2) == 0) {
+ /* Crafty 9.29+ outputs this */
+ programStats.got_fail = 1;
+ return;
+
+ } else if (thinkOutput[0] != NULLCHAR &&
+ strncmp(message, " ", 4) == 0) {
+ unsigned message_len;
+
+ p = message;
+ while (*p && *p == ' ') 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) {
+ 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.
+ The display is not updated in any way.
+ */
+void
+ParseGameHistory(game)
+ char *game;
+{
+ ChessMove moveType;
+ int fromX, fromY, toX, toY, boardIndex;
+ char promoChar;
+ char *p, *q;
+ char buf[MSG_SIZ];
+
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsing game history: %s\n", game);
+
+ if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
+ gameInfo.site = StrSave(appData.icsHost);
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+
+ /* Parse out names of players */
+ while (*game == ' ') game++;
+ p = buf;
+ while (*game != ' ') *p++ = *game++;
+ *p = NULLCHAR;
+ gameInfo.white = StrSave(buf);
+ while (*game == ' ') game++;
+ p = buf;
+ while (*game != ' ' && *game != '\n') *p++ = *game++;
+ *p = NULLCHAR;
+ gameInfo.black = StrSave(buf);
+
+ /* Parse moves */
+ boardIndex = blackPlaysFirst ? 1 : 0;
+ yynewstr(game);
+ for (;;) {
+ 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:
+ case BlackPromotionRook:
+ case WhitePromotionBishop:
+ case BlackPromotionBishop:
+ case WhitePromotionKnight:
+ case BlackPromotionKnight:
+ case WhitePromotionKing:
+ case BlackPromotionKing:
+ case NormalMove:
+ case WhiteCapturesEnPassant:
+ case BlackCapturesEnPassant:
+ case WhiteKingSideCastle:
+ case WhiteQueenSideCastle:
+ case BlackKingSideCastle:
+ case BlackQueenSideCastle:
+ case WhiteKingSideCastleWild:
+ case WhiteQueenSideCastleWild:
+ case BlackKingSideCastleWild:
+ case BlackQueenSideCastleWild:
+ /* 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:
+ case BlackDrop:
+ fromX = moveType == WhiteDrop ?
+ (int) CharToPiece(ToUpper(currentMoveString[0])) :
+ (int) CharToPiece(ToLower(currentMoveString[0]));
+ fromY = DROP_RANK;
+ 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 */
+ if (boardIndex < backwardMostMove) {
+ /* Oops, gap. How did that happen? */
+ DisplayError(_("Gap in move list"), 0);
+ return;
+ }
+ backwardMostMove = blackPlaysFirst ? 1 : 0;
+ if (boardIndex > forwardMostMove) {
+ forwardMostMove = boardIndex;
+ }
+ return;
+ case ElapsedTime:
+ if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
+ strcat(parseList[boardIndex-1], " ");
+ strcat(parseList[boardIndex-1], yy_text);
+ }
+ continue;
+ case Comment:
+ case PGNTag:
+ case NAG:
+ default:
+ /* ignore */
+ continue;
+ case WhiteWins:
+ case BlackWins:
+ case GameIsDrawn:
+ case GameUnfinished:
+ if (gameMode == IcsExamining) {
+ if (boardIndex < backwardMostMove) {
+ /* Oops, gap. How did that happen? */
+ return;
+ }
+ backwardMostMove = blackPlaysFirst ? 1 : 0;
+ return;
+ }
+ gameInfo.result = moveType;
+ p = strchr(yy_text, '{');
+ if (p == NULL) p = strchr(yy_text, '(');
+ if (p == NULL) {
+ p = yy_text;
+ if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
+ } else {
+ q = strchr(p, *p == '{' ? '}' : ')');
+ if (q != NULL) *q = NULLCHAR;
+ p++;
+ }
+ gameInfo.resultDetails = StrSave(p);
+ continue;
+ }
+ if (boardIndex >= forwardMostMove &&
+ !(gameMode == IcsObserving && ics_gamenum == -1)) {
+ backwardMostMove = blackPlaysFirst ? 1 : 0;
+ return;
+ }
+ (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
+ EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
+ parseList[boardIndex]);
+ CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
+ {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
+ /* currentMoveString is set as a side-effect of yylex */
+ strcpy(moveList[boardIndex], currentMoveString);
+ strcat(moveList[boardIndex], "\n");
+ boardIndex++;
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
+ castlingRights[boardIndex], &epStatus[boardIndex]);
+ switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
+ EP_UNKNOWN, castlingRights[boardIndex]) ) {
+ case MT_NONE:
+ case MT_STALEMATE:
+ default:
+ break;
+ case MT_CHECK:
+ if(gameInfo.variant != VariantShogi)
+ strcat(parseList[boardIndex - 1], "+");
+ break;
+ case MT_CHECKMATE:
+ case MT_STAINMATE:
+ strcat(parseList[boardIndex - 1], "#");
+ break;
+ }
+ }
+}
+
+
+/* Apply a move to the given board */
+void
+ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
+ int fromX, fromY, toX, toY;
+ int promoChar;
+ Board board;
+ char *castling;
+ char *ep;
+{
+ ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
+
+ /* [HGM] compute & store e.p. status and castling rights for new position */
+ /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
+ { int i;
+
+ if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
+ oldEP = *ep;
+ *ep = EP_NONE;
+
+ if( board[toY][toX] != EmptySquare )
+ *ep = EP_CAPTURE;
+
+ if( board[fromY][fromX] == WhitePawn ) {
+ 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] == BlackPawn &&
+ gameInfo.variant != VariantBerolina || toX < fromX)
+ *ep = toX | berolina;
+ if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
+ gameInfo.variant != VariantBerolina || 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<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
+ gameInfo.variant != VariantBerolina || toX > fromX)
+ *ep = toX;
+ }
+ }
+
+ for(i=0; i<nrCastlingRights; i++) {
+ if(castling[i] == fromX && castlingRank[i] == fromY ||
+ castling[i] == toX && castlingRank[i] == toY
+ ) castling[i] = -1; // revoke for moved or captured piece
+ }
+
+ }
+
+ /* [HGM] In Shatranj and Courier all promotions are to Ferz */
+ if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
+ && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
+
+ if (fromX == toX && fromY == toY) return;
+
+ if (fromY == DROP_RANK) {
+ /* must be first */
+ piece = board[toY][toX] = (ChessSquare) fromX;
+ } else {
+ piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
+ king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
+ if(gameInfo.variant == VariantKnightmate)
+ king += (int) WhiteUnicorn - (int) WhiteKing;
+
+ /* Code added by Tord: */
+ /* FRC castling assumed when king captures friendly rook. */
+ if (board[fromY][fromX] == WhiteKing &&
+ board[toY][toX] == WhiteRook) {
+ board[fromY][fromX] = EmptySquare;
+ board[toY][toX] = EmptySquare;
+ if(toX > 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] = 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 (board[fromY][fromX] == WhitePawn
+ && toY == BOARD_HEIGHT-1
+ && gameInfo.variant != VariantXiangqi
+ ) {
+ /* white pawn promotion */
+ board[toY][toX] = CharToPiece(ToUpper(promoChar));
+ if (board[toY][toX] == EmptySquare) {
+ board[toY][toX] = WhiteQueen;
+ }
+ if(gameInfo.variant==VariantBughouse ||
+ gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
+ board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
+ board[fromY][fromX] = EmptySquare;
+ } else if ((fromY == BOARD_HEIGHT-4)
+ && (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 == BOARD_HEIGHT-4)
+ && (toX == fromX)
+ && gameInfo.variant == VariantBerolina
+ && (board[fromY][fromX] == WhitePawn)
+ && (board[toY][toX] == EmptySquare)) {
+ board[fromY][fromX] = EmptySquare;
+ 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] = 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) {
+ board[fromY][fromX] = EmptySquare;
+ board[toY][toX] = BlackKing;
+ board[fromY][7] = EmptySquare;
+ board[toY][4] = BlackRook;
+ } else if (fromY == 7 && fromX == 3
+ && board[fromY][fromX] == BlackKing
+ && toY == 7 && toX == 1) {
+ board[fromY][fromX] = EmptySquare;
+ board[toY][toX] = BlackKing;
+ board[fromY][0] = EmptySquare;
+ board[toY][2] = BlackRook;
+ } else if (board[fromY][fromX] == BlackPawn
+ && 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;
+ }
+
+ /* [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, by decreasing count */
+ /* and erasing image if necessary */
+ p = (int) fromX;
+ if(p < (int) BlackPawn) { /* white drop */
+ p -= (int)WhitePawn;
+ if(p >= gameInfo.holdingsSize) p = 0;
+ if(--board[p][BOARD_WIDTH-2] == 0)
+ board[p][BOARD_WIDTH-1] = EmptySquare;
+ } else { /* black drop */
+ p -= (int)BlackPawn;
+ if(p >= gameInfo.holdingsSize) p = 0;
+ if(--board[BOARD_HEIGHT-1-p][1] == 0)
+ board[BOARD_HEIGHT-1-p][0] = EmptySquare;
+ }
+ }
+ if (captured != EmptySquare && 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 {
+ 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;
+ }
+ }
+
+ } 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 < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
+ board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
+ board[y][x] = EmptySquare;
+ }
+ }
+ }
+ 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 */
+void
+MakeMove(fromX, fromY, toX, toY, promoChar)
+ int fromX, fromY, toX, toY;
+ int promoChar;
+{
+// forwardMostMove++; // [HGM] bare: moved downstream
+
+ if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
+ int timeLeft; static int lastLoadFlag=0; int king, piece;
+ piece = boards[forwardMostMove][fromY][fromX];
+ king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
+ if(gameInfo.variant == VariantKnightmate)
+ king += (int) WhiteUnicorn - (int) WhiteKing;
+ if(forwardMostMove == 0) {
+ if(blackPlaysFirst)
+ fprintf(serverMoves, "%s;", second.tidy);
+ fprintf(serverMoves, "%s;", first.tidy);
+ if(!blackPlaysFirst)
+ fprintf(serverMoves, "%s;", second.tidy);
+ } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
+ lastLoadFlag = loadFlag;
+ // print base move
+ fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
+ // print castling suffix
+ if( toY == fromY && piece == king ) {
+ if(toX-fromX > 1)
+ fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
+ if(fromX-toX >1)
+ fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
+ }
+ // e.p. suffix
+ if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
+ boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
+ boards[forwardMostMove][toY][toX] == EmptySquare
+ && fromX != toX )
+ fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
+ // promotion suffix
+ if(promoChar != NULLCHAR)
+ fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
+ if(!loadFlag) {
+ fprintf(serverMoves, "/%d/%d",
+ pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
+ if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
+ else timeLeft = blackTimeRemaining/1000;
+ fprintf(serverMoves, "/%d", timeLeft);
+ }
+ fflush(serverMoves);
+ }
+
+ if (forwardMostMove+1 >= MAX_MOVES) {
+ DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
+ 0, 1);
+ return;
+ }
+ SwitchClocks();
+ timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
+ timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
+ if (commentList[forwardMostMove+1] != NULL) {
+ free(commentList[forwardMostMove+1]);
+ commentList[forwardMostMove+1] = NULL;
+ }
+ CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
+ {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
+ castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
+ forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+ gameInfo.result = GameUnfinished;
+ if (gameInfo.resultDetails != NULL) {
+ free(gameInfo.resultDetails);
+ gameInfo.resultDetails = NULL;
+ }
+ CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
+ moveList[forwardMostMove - 1]);
+ (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
+ PosFlags(forwardMostMove - 1), EP_UNKNOWN,
+ fromY, fromX, toY, toX, promoChar,
+ parseList[forwardMostMove - 1]);
+ switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
+ epStatus[forwardMostMove], /* [HGM] use true e.p. */
+ castlingRights[forwardMostMove]) ) {
+ case MT_NONE:
+ case MT_STALEMATE:
+ default:
+ break;
+ case MT_CHECK:
+ if(gameInfo.variant != VariantShogi)
+ strcat(parseList[forwardMostMove - 1], "+");
+ break;
+ case MT_CHECKMATE:
+ case MT_STAINMATE:
+ strcat(parseList[forwardMostMove - 1], "#");
+ break;
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
+ }
+
+}
+
+/* Updates currentMove if not pausing */
+void
+ShowMove(fromX, fromY, toX, toY)
+{
+ int instant = (gameMode == PlayFromGameFile) ?
+ (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
+ if(appData.noGUI) return;
+ if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+ if (!instant) {
+ if (forwardMostMove == currentMove + 1) {
+ AnimateMove(boards[forwardMostMove - 1],
+ fromX, fromY, toX, toY);
+ }
+ if (appData.highlightLastMove) {
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
+ currentMove = forwardMostMove;
+ }
+
+ if (instant) return;
+
+ DisplayMove(currentMove - 1);
+ DrawPosition(FALSE, boards[currentMove]);
+ DisplayBothClocks();
+ HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+}
+
+void SendEgtPath(ChessProgramState *cps)
+{ /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
+ char buf[MSG_SIZ], name[MSG_SIZ], *p;
+
+ if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
+
+ while(*p) {
+ char c, *q = name+1, *r, *s;
+
+ name[0] = ','; // extract next format name from feature and copy with prefixed ','
+ while(*p && *p != ',') *q++ = *p++;
+ *q++ = ':'; *q = 0;
+ if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
+ strcmp(name, ",nalimov:") == 0 ) {
+ // take nalimov path from the menu-changeable option first, if it is defined
+ sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
+ SendToProgram(buf,cps); // send egtbpath command for nalimov
+ } else
+ if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
+ (s = StrStr(appData.egtFormats, name)) != NULL) {
+ // format name occurs amongst user-supplied formats, at beginning or immediately after comma
+ s = r = StrStr(s, ":") + 1; // beginning of path info
+ while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
+ c = *r; *r = 0; // temporarily null-terminate path info
+ *--q = 0; // strip of trailig ':' from name
+ sprintf(buf, "egtbpath %s %s\n", name+1, s);
+ *r = c;
+ SendToProgram(buf,cps); // send egtbpath command for this format
+ }
+ if(*p == ',') p++; // read away comma to position for next format name
+ }
+}
+
+void
+InitChessProgram(cps, setup)
+ ChessProgramState *cps;
+ int setup; /* [HGM] needed to setup FRC opening position */
+{
+ 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
+ /* [HGM] also send variant if board size non-standard */
+ || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
+ ) {
+ char *v = VariantName(gameInfo.variant);
+ if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
+ /* [HGM] in protocol 1 we have to assume all variants valid */
+ sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
+ DisplayFatalError(buf, 0, 1);
+ return;
+ }
+
+ /* [HGM] make prefix for non-standard board size. Awkward testing... */
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantXiangqi )
+ overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
+ if( gameInfo.variant == VariantShogi )
+ overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
+ if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
+ overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
+ if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
+ gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
+ 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) {
+ snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
+ SendToProgram(buf, cps);
+ }
+ cps->maybeThinking = FALSE;
+ cps->offeredDraw = 0;
+ if (!appData.icsActive) {
+ SendTimeControl(cps, movesPerSession, timeControl,
+ timeIncrement, appData.searchDepth,
+ searchTime);
+ }
+ 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);
+ if (!appData.ponderNextMove) {
+ /* Warning: "easy" is a toggle in GNU Chess, so don't send
+ it without being sure what state we are in first. "hard"
+ is not a toggle, so that one is OK.
+ */
+ SendToProgram("easy\n", cps);
+ }
+ if (cps->usePing) {
+ sprintf(buf, "ping %d\n", ++cps->lastPing);
+ SendToProgram(buf, cps);
+ }
+ cps->initDone = TRUE;
+}
+
+
+void
+StartChessProgram(cps)
+ ChessProgramState *cps;
+{
+ char buf[MSG_SIZ];
+ int err;
+
+ if (appData.noChessProgram) return;
+ cps->initDone = FALSE;
+
+ if (strcmp(cps->host, "localhost") == 0) {
+ err = StartChildProcess(cps->program, cps->dir, &cps->pr);
+ } else if (*appData.remoteShell == NULLCHAR) {
+ err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
+ } else {
+ if (*appData.remoteUser == NULLCHAR) {
+ snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
+ cps->program);
+ } else {
+ 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);
+ 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);
+ }
+}
+
+
+void
+TwoMachinesEventIfReady P((void))
+{
+ if (first.lastPing != first.lastPong) {
+ 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, 10); // [HGM] fast: lowered from 1000
+ return;
+ }
+ ThawUI();
+ TwoMachinesEvent();
+}
+
+void
+NextMatchGame P((void))
+{
+ int index; /* [HGM] autoinc: step lod index during match */
+ Reset(FALSE, TRUE);
+ if (*appData.loadGameFile != NULLCHAR) {
+ index = appData.loadGameIndex;
+ if(index < 0) { // [HGM] autoinc
+ lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
+ if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
+ }
+ LoadGameFromFile(appData.loadGameFile,
+ 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,
+ 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 );
+ }
+}
+
+
+void
+GameEnds(result, resultDetails, whosays)
+ ChessMove result;
+ char *resultDetails;
+ int 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 || 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.
+ */
+#if ZIPPY
+ if (appData.zippyPlay && first.initDone) {
+ if (result == GameIsDrawn) {
+ /* In case draw still needs to be claimed */
+ SendToICS(ics_prefix);
+ SendToICS("draw\n");
+ } else if (StrCaseStr(resultDetails, "resign")) {
+ SendToICS(ics_prefix);
+ SendToICS("resign\n");
+ }
+ }
+#endif
+ endingGame = 0; /* [HGM] crash */
+ return;
+ }
+
+ /* If we're loading the game from a file, stop */
+ if (whosays == GE_FILE) {
+ (void) StopLoadGameTimer();
+ gameFileFP = NULL;
+ }
+
+ /* Cancel draw offers */
+ 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 ||
+ gameMode == IcsExamining);
+
+ if (!isIcsGame || whosays == GE_ICS) {
+ /* OK -- not an ICS game, or ICS said it was done */
+ StopClocks();
+ 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<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
+ int p = (int)boards[forwardMostMove][i][j] - color;
+ if(p >= 0 && p <= (int)WhiteKing) k++;
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
+ result, resultDetails ? resultDetails : "(null)", whosays, k, color);
+ }
+ if(k <= 1) {
+ result = GameIsDrawn;
+ sprintf(buf, "%s but bare king", resultDetails);
+ resultDetails = buf;
+ }
+ }
+ }
+
+
+ if(serverMoves != NULL && !loadFlag) { char c = '=';
+ if(result==WhiteWins) c = '+';
+ if(result==BlackWins) c = '-';
+ if(resultDetails != NULL)
+ fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
+ }
+ if (resultDetails != NULL) {
+ gameInfo.result = result;
+ gameInfo.resultDetails = StrSave(resultDetails);
+
+ /* display last move only if game was not loaded from file */
+ if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
+ DisplayMove(currentMove - 1);
+
+ if (forwardMostMove != 0) {
+ if (gameMode != PlayFromGameFile && gameMode != EditGame) {
+ 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 ||
+ gameMode == IcsPlayingWhite ||
+ gameMode == IcsPlayingBlack ||
+ gameMode == BeginningOfGame) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "result %s {%s}\n", PGNResult(result),
+ resultDetails);
+ if (first.pr != NoProc) {
+ SendToProgram(buf, &first);
+ }
+ if (second.pr != NoProc &&
+ gameMode == TwoMachinesPlay) {
+ SendToProgram(buf, &second);
+ }
+ }
+ }
+
+ if (appData.icsActive) {
+ if (appData.quietPlay &&
+ (gameMode == IcsPlayingWhite ||
+ gameMode == IcsPlayingBlack)) {
+ SendToICS(ics_prefix);
+ SendToICS("set shout 1\n");
+ }
+ nextGameMode = IcsIdle;
+ ics_user_moved = FALSE;
+ /* clean up premove. It's ugly when the game has ended and the
+ * premove highlights are still on the board.
+ */
+ if (gotPremove) {
+ gotPremove = FALSE;
+ ClearPremoveHighlights();
+ DrawPosition(FALSE, boards[currentMove]);
+ }
+ if (whosays == GE_ICS) {
+ switch (result) {
+ case WhiteWins:
+ if (gameMode == IcsPlayingWhite)
+ PlayIcsWinSound();
+ else if(gameMode == IcsPlayingBlack)
+ PlayIcsLossSound();
+ break;
+ case BlackWins:
+ if (gameMode == IcsPlayingBlack)
+ PlayIcsWinSound();
+ else if(gameMode == IcsPlayingWhite)
+ PlayIcsLossSound();
+ break;
+ case GameIsDrawn:
+ PlayIcsDrawSound();
+ break;
+ default:
+ PlayIcsUnfinishedSound();
+ }
+ }
+ } else if (gameMode == EditGame ||
+ gameMode == PlayFromGameFile ||
+ gameMode == AnalyzeMode ||
+ gameMode == AnalyzeFile) {
+ nextGameMode = gameMode;
+ } else {
+ nextGameMode = EndOfGame;
+ }
+ pausing = FALSE;
+ ModeHighlight();
+ } else {
+ nextGameMode = gameMode;
+ }
+
+ if (appData.noChessProgram) {
+ gameMode = nextGameMode;
+ ModeHighlight();
+ endingGame = 0; /* [HGM] crash */
+ return;
+ }
+
+ if (first.reuse) {
+ /* Put first chess program into idle state */
+ if (first.pr != NoProc &&
+ (gameMode == MachinePlaysWhite ||
+ gameMode == MachinePlaysBlack ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == IcsPlayingWhite ||
+ gameMode == IcsPlayingBlack ||
+ gameMode == BeginningOfGame)) {
+ SendToProgram("force\n", &first);
+ if (first.usePing) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "ping %d\n", ++first.lastPing);
+ SendToProgram(buf, &first);
+ }
+ }
+ } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
+ /* Kill off first chess program */
+ 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;
+ }
+ if (second.reuse) {
+ /* Put second chess program into idle state */
+ if (second.pr != NoProc &&
+ gameMode == TwoMachinesPlay) {
+ SendToProgram("force\n", &second);
+ if (second.usePing) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "ping %d\n", ++second.lastPing);
+ SendToProgram(buf, &second);
+ }
+ }
+ } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
+ /* Kill off second chess program */
+ 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;
+ }
+
+ if (matchMode && gameMode == TwoMachinesPlay) {
+ switch (result) {
+ case WhiteWins:
+ if (first.twoMachinesColor[0] == 'w') {
+ first.matchWins++;
+ } else {
+ second.matchWins++;
+ }
+ break;
+ case BlackWins:
+ if (first.twoMachinesColor[0] == 'b') {
+ first.matchWins++;
+ } else {
+ second.matchWins++;
+ }
+ break;
+ default:
+ break;
+ }
+ if (matchGame < appData.matchGames) {
+ char *tmp;
+ if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
+ tmp = first.twoMachinesColor;
+ first.twoMachinesColor = second.twoMachinesColor;
+ second.twoMachinesColor = tmp;
+ }
+ gameMode = nextGameMode;
+ matchGame++;
+ if(appData.matchPause>10000 || appData.matchPause<10)
+ appData.matchPause = 10000; /* [HGM] make pause adjustable */
+ ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
+ endingGame = 0; /* [HGM] crash */
+ return;
+ } else {
+ char buf[MSG_SIZ];
+ gameMode = nextGameMode;
+ sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
+ first.tidy, second.tidy,
+ first.matchWins, second.matchWins,
+ appData.matchGames - (first.matchWins + second.matchWins));
+ DisplayFatalError(buf, 0, 0);
+ }
+ }
+ if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
+ !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
+ ExitAnalyzeMode();
+ gameMode = nextGameMode;
+ ModeHighlight();
+ endingGame = 0; /* [HGM] crash */
+}
+
+/* Assumes program was just initialized (initString sent).
+ Leaves program in force mode. */
+void
+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);
+ }
+}
+
+
+void
+ResurrectChessProgram()
+{
+ /* The chess program may have exited.
+ If so, restart it and feed it all the moves made so far. */
+
+ if (appData.noChessProgram || first.pr != NoProc) return;
+
+ StartChessProgram(&first);
+ InitChessProgram(&first, FALSE);
+ FeedMovesToProgram(&first, currentMove);
+
+ if (!first.sendTime) {
+ /* can't tell gnuchess what its clock should read,
+ so we bow to its notion. */
+ ResetClocks();
+ timeRemaining[0][currentMove] = whiteTimeRemaining;
+ timeRemaining[1][currentMove] = blackTimeRemaining;
+ }
+
+ if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
+ appData.icsEngineAnalyze) && first.analysisSupport) {
+ SendToProgram("analyze\n", &first);
+ first.analyzing = TRUE;
+ }
+}
+
+/*
+ * Button procedures
+ */
+void
+Reset(redraw, init)
+ int redraw, init;
+{
+ int i;
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
+ redraw, init, gameMode);
+ }
+ pausing = pauseExamInvalid = FALSE;
+ startedFromSetupPosition = blackPlaysFirst = FALSE;
+ firstMove = TRUE;
+ whiteFlag = blackFlag = FALSE;
+ userOfferedDraw = FALSE;
+ 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);
+ gameInfo.variant = StringToVariant(appData.variant);
+ ics_user_moved = ics_clock_paused = FALSE;
+ ics_getting_history = H_FALSE;
+ 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;
+ ClearPremoveHighlights();
+ gotPremove = FALSE;
+ 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;
+ InitPosition(redraw);
+ for (i = 0; i < MAX_MOVES; i++) {
+ if (commentList[i] != NULL) {
+ free(commentList[i]);
+ commentList[i] = NULL;
+ }
+ }
+ ResetClocks();
+ timeRemaining[0][0] = whiteTimeRemaining;
+ timeRemaining[1][0] = blackTimeRemaining;
+ if (first.pr == NULL) {
+ StartChessProgram(&first);
+ }
+ if (init) {
+ InitChessProgram(&first, startedFromSetupPosition);
+ }
+ DisplayTitle("");
+ DisplayMessage("", "");
+ HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+}
+
+void
+AutoPlayGameLoop()
+{
+ for (;;) {
+ if (!AutoPlayOneMove())
+ return;
+ if (matchMode || appData.timeDelay == 0)
+ continue;
+ if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
+ return;
+ StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
+ break;
+ }
+}
+
+
+int
+AutoPlayOneMove()
+{
+ int fromX, fromY, toX, toY;
+
+ if (appData.debugMode) {
+ fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
+ }
+
+ if (gameMode != PlayFromGameFile)
+ return FALSE;
+
+ 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] - AAA;
+ toY = moveList[currentMove][3] - ONE;
+
+ if (moveList[currentMove][1] == '@') {
+ if (appData.highlightLastMove) {
+ SetHighlights(-1, -1, toX, toY);
+ }
+ } else {
+ 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) {
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
+ DisplayMove(currentMove);
+ SendMoveToProgram(currentMove++, &first);
+ DisplayBothClocks();
+ DrawPosition(FALSE, boards[currentMove]);
+ // [HGM] PV info: always display, routine tests if empty
+ DisplayComment(currentMove - 1, commentList[currentMove]);
+ return TRUE;
+}
+
+
+int
+LoadGameOneMove(readAhead)
+ ChessMove readAhead;
+{
+ int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
+ char promoChar = NULLCHAR;
+ ChessMove moveType;
+ char move[MSG_SIZ];
+ char *p, *q;
+
+ if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
+ gameMode != AnalyzeMode && gameMode != Training) {
+ gameFileFP = NULL;
+ return FALSE;
+ }
+
+ yyboardindex = forwardMostMove;
+ if (readAhead != (ChessMove)0) {
+ moveType = readAhead;
+ } else {
+ if (gameFileFP == NULL)
+ return FALSE;
+ moveType = (ChessMove) yylex();
+ }
+
+ done = FALSE;
+ switch (moveType) {
+ case Comment:
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
+ p = yy_text;
+ if (*p == '{' || *p == '[' || *p == '(') {
+ p[strlen(p) - 1] = NULLCHAR;
+ p++;
+ }
+
+ /* append the comment but don't display it */
+ while (*p == '\n') p++;
+ AppendComment(currentMove, p);
+ return TRUE;
+
+ case WhiteCapturesEnPassant:
+ case BlackCapturesEnPassant:
+ case WhitePromotionChancellor:
+ case BlackPromotionChancellor:
+ case WhitePromotionArchbishop:
+ case BlackPromotionArchbishop:
+ case WhitePromotionCentaur:
+ case BlackPromotionCentaur:
+ case WhitePromotionQueen:
+ case BlackPromotionQueen:
+ case WhitePromotionRook:
+ case BlackPromotionRook:
+ case WhitePromotionBishop:
+ case BlackPromotionBishop:
+ case WhitePromotionKnight:
+ case BlackPromotionKnight:
+ case WhitePromotionKing:
+ case BlackPromotionKing:
+ case NormalMove:
+ case WhiteKingSideCastle:
+ case WhiteQueenSideCastle:
+ case BlackKingSideCastle:
+ case BlackQueenSideCastle:
+ case WhiteKingSideCastleWild:
+ 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] - AAA;
+ fromY = currentMoveString[1] - ONE;
+ toX = currentMoveString[2] - AAA;
+ toY = currentMoveString[3] - ONE;
+ promoChar = currentMoveString[4];
+ break;
+
+ case WhiteDrop:
+ case BlackDrop:
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
+ fromX = moveType == WhiteDrop ?
+ (int) CharToPiece(ToUpper(currentMoveString[0])) :
+ (int) CharToPiece(ToLower(currentMoveString[0]));
+ fromY = DROP_RANK;
+ toX = currentMoveString[2] - AAA;
+ toY = currentMoveString[3] - ONE;
+ break;
+
+ case WhiteWins:
+ case BlackWins:
+ case GameIsDrawn:
+ case GameUnfinished:
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed game end: %s\n", yy_text);
+ p = strchr(yy_text, '{');
+ if (p == NULL) p = strchr(yy_text, '(');
+ if (p == NULL) {
+ p = yy_text;
+ if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
+ } else {
+ q = strchr(p, *p == '{' ? '}' : ')');
+ if (q != NULL) *q = NULLCHAR;
+ p++;
+ }
+ GameEnds(moveType, p, GE_FILE);
+ done = TRUE;
+ if (cmailMsgLoaded) {
+ ClearHighlights();
+ flipView = WhiteOnMove(currentMove);
+ if (moveType == GameUnfinished) flipView = !flipView;
+ if (appData.debugMode)
+ fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
+ }
+ break;
+
+ case (ChessMove) 0: /* end of file */
+ if (appData.debugMode)
+ fprintf(debugFP, "Parser hit end of file\n");
+ switch (MateTest(boards[currentMove], PosFlags(currentMove),
+ 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 {
+ GameEnds(WhiteWins, "White mates", GE_FILE);
+ }
+ break;
+ case MT_STALEMATE:
+ GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
+ break;
+ }
+ done = TRUE;
+ break;
+
+ case MoveNumberOne:
+ if (lastLoadGameStart == GNUChessGame) {
+ /* GNUChessGames have numbers, but they aren't move numbers */
+ if (appData.debugMode)
+ fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
+ yy_text, (int) moveType);
+ return LoadGameOneMove((ChessMove)0); /* tail recursion */
+ }
+ /* else fall thru */
+
+ case XBoardGame:
+ case GNUChessGame:
+ case PGNTag:
+ /* Reached start of next game in file */
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
+ switch (MateTest(boards[currentMove], PosFlags(currentMove),
+ 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 {
+ GameEnds(WhiteWins, "White mates", GE_FILE);
+ }
+ break;
+ case MT_STALEMATE:
+ GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
+ break;
+ }
+ done = TRUE;
+ break;
+
+ case PositionDiagram: /* should not happen; ignore */
+ case ElapsedTime: /* ignore */
+ case NAG: /* ignore */
+ if (appData.debugMode)
+ fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
+ yy_text, (int) moveType);
+ return LoadGameOneMove((ChessMove)0); /* tail recursion */
+
+ case IllegalMove:
+ if (appData.testLegality) {
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
+ sprintf(move, _("Illegal move: %d.%s%s"),
+ (forwardMostMove / 2) + 1,
+ WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
+ DisplayError(move, 0);
+ done = TRUE;
+ } else {
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
+ yy_text, currentMoveString);
+ fromX = currentMoveString[0] - AAA;
+ fromY = currentMoveString[1] - ONE;
+ toX = currentMoveString[2] - AAA;
+ toY = currentMoveString[3] - ONE;
+ promoChar = currentMoveString[4];
+ }
+ break;
+
+ case AmbiguousMove:
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
+ sprintf(move, _("Ambiguous move: %d.%s%s"),
+ (forwardMostMove / 2) + 1,
+ WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
+ DisplayError(move, 0);
+ done = TRUE;
+ break;
+
+ default:
+ case ImpossibleMove:
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
+ sprintf(move, _("Illegal move: %d.%s%s"),
+ (forwardMostMove / 2) + 1,
+ WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
+ DisplayError(move, 0);
+ done = TRUE;
+ break;
+ }
+
+ if (done) {
+ if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
+ DrawPosition(FALSE, boards[currentMove]);
+ DisplayBothClocks();
+ if (!appData.matchMode) // [HGM] PV info: routine tests if empty
+ DisplayComment(currentMove - 1, commentList[currentMove]);
+ }
+ (void) StopLoadGameTimer();
+ gameFileFP = NULL;
+ cmailOldMove = forwardMostMove;
+ return FALSE;
+ } else {
+ /* 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;
+ return TRUE;
+ }
+}
+
+/* Load the nth game from the given file */
+int
+LoadGameFromFile(filename, n, title, useList)
+ char *filename;
+ int n;
+ char *title;
+ /*Boolean*/ int useList;
+{
+ FILE *f;
+ char buf[MSG_SIZ];
+
+ if (strcmp(filename, "-") == 0) {
+ f = stdin;
+ title = "stdin";
+ } else {
+ f = fopen(filename, "rb");
+ if (f == NULL) {
+ snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
+ DisplayError(buf, errno);
+ return FALSE;
+ }
+ }
+ if (fseek(f, 0, 0) == -1) {
+ /* f is not seekable; probably a pipe */
+ useList = FALSE;
+ }
+ if (useList && n == 0) {
+ int error = GameListBuild(f);
+ if (error) {
+ DisplayError(_("Cannot build game list"), error);
+ } else if (!ListEmpty(&gameList) &&
+ ((ListGame *) gameList.tailPred)->number > 1) {
+ GameListPopUp(f, title);
+ return TRUE;
+ }
+ GameListDestroy();
+ n = 1;
+ }
+ if (n == 0) n = 1;
+ return LoadGame(f, n, title, FALSE);
+}
+
+
+void
+MakeRegisteredMove()
+{
+ int fromX, fromY, toX, toY;
+ char promoChar;
+ if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
+ switch (cmailMoveType[lastLoadGameNumber - 1]) {
+ case CMAIL_MOVE:
+ case CMAIL_DRAW:
+ 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] - 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, 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);
+ } else {
+ GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
+ }
+ break;
+
+ case CMAIL_ACCEPT:
+ GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return;
+}
+
+/* Wrapper around LoadGame for use when a Cmail message is loaded */
+int
+CmailLoadGame(f, gameNumber, title, useList)
+ FILE *f;
+ int gameNumber;
+ char *title;
+ int useList;
+{
+ int retVal;
+
+ if (gameNumber > nCmailGames) {
+ DisplayError(_("No more games in this message"), 0);
+ return FALSE;
+ }
+ if (f == lastLoadGameFP) {
+ int offset = gameNumber - lastLoadGameNumber;
+ if (offset == 0) {
+ cmailMsg[0] = NULLCHAR;
+ if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
+ cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
+ nCmailMovesRegistered--;
+ }
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
+ if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
+ cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
+ }
+ } else {
+ if (! RegisterMove()) return FALSE;
+ }
+ }
+
+ retVal = LoadGame(f, gameNumber, title, useList);
+
+ /* Make move registered during previous look at this game, if any */
+ MakeRegisteredMove();
+
+ if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
+ commentList[currentMove]
+ = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
+ DisplayComment(currentMove - 1, commentList[currentMove]);
+ }
+
+ return retVal;
+}
+
+/* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
+int
+ReloadGame(offset)
+ int offset;
+{
+ int gameNumber = lastLoadGameNumber + offset;
+ if (lastLoadGameFP == NULL) {
+ DisplayError(_("No game has been loaded yet"), 0);
+ return FALSE;
+ }
+ if (gameNumber <= 0) {
+ DisplayError(_("Can't back up any further"), 0);
+ return FALSE;
+ }
+ if (cmailMsgLoaded) {
+ return CmailLoadGame(lastLoadGameFP, gameNumber,
+ lastLoadGameTitle, lastLoadGameUseList);
+ } else {
+ return LoadGame(lastLoadGameFP, gameNumber,
+ lastLoadGameTitle, lastLoadGameUseList);
+ }
+}
+
+
+
+/* Load the nth game from open file f */
+int
+LoadGame(f, gameNumber, title, useList)
+ FILE *f;
+ int gameNumber;
+ char *title;
+ int useList;
+{
+ ChessMove cm;
+ char buf[MSG_SIZ];
+ int gn = gameNumber;
+ ListGame *lg = NULL;
+ int numPGNTags = 0;
+ int err;
+ GameMode oldGameMode;
+ VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
+
+ if (appData.debugMode)
+ fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
+
+ if (gameMode == Training )
+ SetTrainingModeOff();
+
+ oldGameMode = gameMode;
+ if (gameMode != BeginningOfGame) {
+ Reset(FALSE, TRUE);
+ }
+
+ gameFileFP = f;
+ if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
+ fclose(lastLoadGameFP);
+ }
+
+ if (useList) {
+ lg = (ListGame *) ListElem(&gameList, gameNumber-1);
+
+ if (lg) {
+ fseek(f, lg->offset, 0);
+ GameListHighlight(gameNumber);
+ gn = 1;
+ }
+ else {
+ DisplayError(_("Game number out of range"), 0);
+ return FALSE;
+ }
+ } else {
+ GameListDestroy();
+ if (fseek(f, 0, 0) == -1) {
+ if (f == lastLoadGameFP ?
+ gameNumber == lastLoadGameNumber + 1 :
+ gameNumber == 1) {
+ gn = 1;
+ } else {
+ DisplayError(_("Can't seek on game file"), 0);
+ return FALSE;
+ }
+ }
+ }
+ lastLoadGameFP = f;
+ lastLoadGameNumber = gameNumber;
+ strcpy(lastLoadGameTitle, title);
+ lastLoadGameUseList = useList;
+
+ yynewfile(f);
+
+ if (lg && lg->gameInfo.white && lg->gameInfo.black) {
+ snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
+ lg->gameInfo.black);
+ DisplayTitle(buf);
+ } else if (*title != NULLCHAR) {
+ if (gameNumber > 1) {
+ sprintf(buf, "%s %d", title, gameNumber);
+ DisplayTitle(buf);
+ } else {
+ DisplayTitle(title);
+ }
+ }
+
+ if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
+ gameMode = PlayFromGameFile;
+ ModeHighlight();
+ }
+
+ currentMove = forwardMostMove = backwardMostMove = 0;
+ CopyBoard(boards[0], initialPosition);
+ StopClocks();
+
+ /*
+ * 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
+ * recognized start of game markers are the move number "1",
+ * the pattern "gnuchess .* game", the pattern
+ * "^[#;%] [^ ]* 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.
+ * 5-4-02: Let's try being more lenient and allowing a game to
+ * start with an unnumbered move. Does that break anything?
+ */
+ cm = lastLoadGameStart = (ChessMove) 0;
+ while (gn > 0) {
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+ switch (cm) {
+ case (ChessMove) 0:
+ if (cmailMsgLoaded) {
+ nCmailGames = CMAIL_MAX_GAMES - gn;
+ } else {
+ Reset(TRUE, TRUE);
+ DisplayError(_("Game not found in file"), 0);
+ }
+ return FALSE;
+
+ case GNUChessGame:
+ case XBoardGame:
+ gn--;
+ lastLoadGameStart = cm;
+ break;
+
+ case MoveNumberOne:
+ switch (lastLoadGameStart) {
+ case GNUChessGame:
+ case XBoardGame:
+ case PGNTag:
+ break;
+ case MoveNumberOne:
+ case (ChessMove) 0:
+ gn--; /* count this game */
+ lastLoadGameStart = cm;
+ break;
+ default:
+ /* impossible */
+ break;
+ }
+ break;
+
+ case PGNTag:
+ switch (lastLoadGameStart) {
+ case GNUChessGame:
+ case PGNTag:
+ case MoveNumberOne:
+ case (ChessMove) 0:
+ gn--; /* count this game */
+ lastLoadGameStart = cm;
+ break;
+ case XBoardGame:
+ lastLoadGameStart = cm; /* game counted already */
+ break;
+ default:
+ /* impossible */
+ break;
+ }
+ if (gn > 0) {
+ do {
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+ } while (cm == PGNTag || cm == Comment);
+ }
+ break;
+
+ case WhiteWins:
+ case BlackWins:
+ case GameIsDrawn:
+ if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
+ if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
+ != CMAIL_OLD_RESULT) {
+ nCmailResults ++ ;
+ cmailResult[ CMAIL_MAX_GAMES
+ - gn - 1] = CMAIL_OLD_RESULT;
+ }
+ }
+ break;
+
+ case NormalMove:
+ /* Only a NormalMove can be at the start of a game
+ * without a position diagram. */
+ if (lastLoadGameStart == (ChessMove) 0) {
+ gn--;
+ lastLoadGameStart = MoveNumberOne;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
+
+ if (cm == XBoardGame) {
+ /* Skip any header junk before position diagram and/or move 1 */
+ for (;;) {
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+
+ if (cm == (ChessMove) 0 ||
+ cm == GNUChessGame || cm == XBoardGame) {
+ /* Empty game; pretend end-of-file and handle later */
+ cm = (ChessMove) 0;
+ break;
+ }
+
+ if (cm == MoveNumberOne || cm == PositionDiagram ||
+ cm == PGNTag || cm == Comment)
+ break;
+ }
+ } else if (cm == GNUChessGame) {
+ if (gameInfo.event != NULL) {
+ free(gameInfo.event);
+ }
+ gameInfo.event = StrSave(yy_text);
+ }
+
+ startedFromSetupPosition = FALSE;
+ while (cm == PGNTag) {
+ 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;
+ if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
+ Reset(TRUE, TRUE);
+ DisplayError(_("Bad FEN position in file"), 0);
+ return FALSE;
+ }
+ CopyBoard(boards[0], initial_position);
+ if (blackPlaysFirst) {
+ currentMove = forwardMostMove = backwardMostMove = 1;
+ CopyBoard(boards[1], initial_position);
+ strcpy(moveList[0], "");
+ strcpy(parseList[0], "");
+ timeRemaining[0][1] = whiteTimeRemaining;
+ timeRemaining[1][1] = blackTimeRemaining;
+ if (commentList[0] != NULL) {
+ commentList[1] = commentList[0];
+ commentList[0] = NULL;
+ }
+ } 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;
+ }
+
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+
+ /* Handle comments interspersed among the tags */
+ while (cm == Comment) {
+ char *p;
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
+ p = yy_text;
+ if (*p == '{' || *p == '[' || *p == '(') {
+ p[strlen(p) - 1] = NULLCHAR;
+ p++;
+ }
+ while (*p == '\n') p++;
+ AppendComment(currentMove, p);
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+ }
+ }
+
+ /* don't rely on existence of Event tag since if game was
+ * pasted from clipboard the Event tag may not exist
+ */
+ if (numPGNTags > 0){
+ char *tags;
+ if (gameInfo.variant == VariantNormal) {
+ gameInfo.variant = StringToVariant(gameInfo.event);
+ }
+ if (!matchMode) {
+ if( appData.autoDisplayTags ) {
+ tags = PGNTags(&gameInfo);
+ TagsPopUp(tags, CmailMsg());
+ free(tags);
+ }
+ }
+ } else {
+ /* Make something up, but don't display it now */
+ SetGameInfo();
+ TagsPopDown();
+ }
+
+ if (cm == PositionDiagram) {
+ int i, j;
+ char *p;
+ Board initial_position;
+
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
+
+ if (!startedFromSetupPosition) {
+ p = yy_text;
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--)
+ for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
+ switch (*p) {
+ case '[':
+ case '-':
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ break;
+ default:
+ initial_position[i][j++] = CharToPiece(*p);
+ break;
+ }
+ 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;
+ CopyBoard(boards[1], initial_position);
+ strcpy(moveList[0], "");
+ strcpy(parseList[0], "");
+ timeRemaining[0][1] = whiteTimeRemaining;
+ timeRemaining[1][1] = blackTimeRemaining;
+ if (commentList[0] != NULL) {
+ commentList[1] = commentList[0];
+ commentList[0] = NULL;
+ }
+ } else {
+ currentMove = forwardMostMove = backwardMostMove = 0;
+ }
+ }
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+ }
+
+ if (first.pr == NoProc) {
+ StartChessProgram(&first);
+ }
+ InitChessProgram(&first, FALSE);
+ SendToProgram("force\n", &first);
+ if (startedFromSetupPosition) {
+ SendBoard(&first, forwardMostMove);
+ if (appData.debugMode) {
+ fprintf(debugFP, "Load Game\n");
+ }
+ DisplayBothClocks();
+ }
+
+ /* [HGM] server: flag to write setup moves in broadcast file as one */
+ loadFlag = appData.suppressLoadMoves;
+
+ while (cm == Comment) {
+ char *p;
+ if (appData.debugMode)
+ fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
+ p = yy_text;
+ if (*p == '{' || *p == '[' || *p == '(') {
+ p[strlen(p) - 1] = NULLCHAR;
+ p++;
+ }
+ while (*p == '\n') p++;
+ AppendComment(currentMove, p);
+ yyboardindex = forwardMostMove;
+ cm = (ChessMove) yylex();
+ }
+
+ if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
+ cm == WhiteWins || cm == BlackWins ||
+ cm == GameIsDrawn || cm == GameUnfinished) {
+ DisplayMessage("", _("No moves in game"));
+ if (cmailMsgLoaded) {
+ if (appData.debugMode)
+ fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
+ ClearHighlights();
+ flipView = FALSE;
+ }
+ DrawPosition(FALSE, boards[currentMove]);
+ DisplayBothClocks();
+ gameMode = EditGame;
+ ModeHighlight();
+ gameFileFP = NULL;
+ cmailOldMove = 0;
+ return TRUE;
+ }
+
+ // [HGM] PV info: routine tests if comment empty
+ if (!matchMode && (pausing || appData.timeDelay != 0)) {
+ DisplayComment(currentMove - 1, commentList[currentMove]);
+ }
+ if (!matchMode && appData.timeDelay != 0)
+ DrawPosition(FALSE, boards[currentMove]);
+
+ if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
+ programStats.ok_to_send = 1;
+ }
+
+ /* if the first token after the PGN tags is a move
+ * and not move number 1, retrieve it from the parser
+ */
+ if (cm != MoveNumberOne)
+ LoadGameOneMove(cm);
+
+ /* load the remaining moves from the file */
+ while (LoadGameOneMove((ChessMove)0)) {
+ timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
+ timeRemaining[1][forwardMostMove] = blackTimeRemaining;
+ }
+
+ /* rewind to the start of the game */
+ currentMove = backwardMostMove;
+
+ HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+
+ if (oldGameMode == AnalyzeFile ||
+ oldGameMode == AnalyzeMode) {
+ AnalyzeFileEvent();
+ }
+
+ if (matchMode || appData.timeDelay == 0) {
+ ToEndEvent();
+ gameMode = EditGame;
+ ModeHighlight();
+ } else if (appData.timeDelay > 0) {
+ AutoPlayGameLoop();
+ }
+
+ if (appData.debugMode)
+ fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
+
+ loadFlag = 0; /* [HGM] true game starts */
+ return TRUE;
+}
+
+/* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
+int
+ReloadPosition(offset)
+ int offset;
+{
+ int positionNumber = lastLoadPositionNumber + offset;
+ if (lastLoadPositionFP == NULL) {
+ DisplayError(_("No position has been loaded yet"), 0);
+ return FALSE;
+ }
+ if (positionNumber <= 0) {
+ DisplayError(_("Can't back up any further"), 0);
+ return FALSE;
+ }
+ return LoadPosition(lastLoadPositionFP, positionNumber,
+ lastLoadPositionTitle);
+}
+
+/* Load the nth position from the given file */
+int
+LoadPositionFromFile(filename, n, title)
+ char *filename;
+ int n;
+ char *title;
+{
+ FILE *f;
+ char buf[MSG_SIZ];
+
+ if (strcmp(filename, "-") == 0) {
+ return LoadPosition(stdin, n, "stdin");
+ } else {
+ f = fopen(filename, "rb");
+ if (f == NULL) {
+ snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
+ DisplayError(buf, errno);
+ return FALSE;
+ } else {
+ return LoadPosition(f, n, title);
+ }
+ }
+}
+
+/* Load the nth position from the given open file, and close it */
+int
+LoadPosition(f, positionNumber, title)
+ FILE *f;
+ int positionNumber;
+ char *title;
+{
+ char *p, line[MSG_SIZ];
+ Board initial_position;
+ int i, j, fenMode, pn;
+
+ if (gameMode == Training )
+ SetTrainingModeOff();
+
+ if (gameMode != BeginningOfGame) {
+ Reset(FALSE, TRUE);
+ }
+ if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
+ fclose(lastLoadPositionFP);
+ }
+ if (positionNumber == 0) positionNumber = 1;
+ lastLoadPositionFP = f;
+ lastLoadPositionNumber = positionNumber;
+ strcpy(lastLoadPositionTitle, title);
+ if (first.pr == NoProc) {
+ StartChessProgram(&first);
+ InitChessProgram(&first, FALSE);
+ }
+ pn = positionNumber;
+ if (positionNumber < 0) {
+ /* Negative position number means to seek to that byte offset */
+ if (fseek(f, -positionNumber, 0) == -1) {
+ DisplayError(_("Can't seek on position file"), 0);
+ return FALSE;
+ };
+ pn = 1;
+ } else {
+ if (fseek(f, 0, 0) == -1) {
+ if (f == lastLoadPositionFP ?
+ positionNumber == lastLoadPositionNumber + 1 :
+ positionNumber == 1) {
+ pn = 1;
+ } else {
+ DisplayError(_("Can't seek on position file"), 0);
+ return FALSE;
+ }
+ }
+ }
+ /* See if this file is FEN or old-style xboard */
+ if (fgets(line, MSG_SIZ, f) == NULL) {
+ DisplayError(_("Position not found in file"), 0);
+ return FALSE;
+ }
+#if 0
+ 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': case '9':
+ case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
+ case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
+ case 'C': case 'W': case 'c': case 'w':
+ fenMode = TRUE;
+ break;
+ }
+#else
+ // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
+ fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
+#endif
+
+ if (pn >= 2) {
+ if (fenMode || line[0] == '#') pn--;
+ while (pn > 0) {
+ /* skip positions before number pn */
+ if (fgets(line, MSG_SIZ, f) == NULL) {
+ Reset(TRUE, TRUE);
+ DisplayError(_("Position not found in file"), 0);
+ return FALSE;
+ }
+ if (fenMode || line[0] == '#') pn--;
+ }
+ }
+
+ if (fenMode) {
+ if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
+ DisplayError(_("Bad FEN position in file"), 0);
+ return FALSE;
+ }
+ } else {
+ (void) fgets(line, MSG_SIZ, f);
+ (void) fgets(line, MSG_SIZ, f);
+
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ (void) fgets(line, MSG_SIZ, f);
+ 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);
+ if (strncmp(line, "black", strlen("black"))==0)
+ blackPlaysFirst = TRUE;
+ }
+ }
+ startedFromSetupPosition = TRUE;
+
+ SendToProgram("force\n", &first);
+ CopyBoard(boards[0], initial_position);
+ if (blackPlaysFirst) {
+ currentMove = forwardMostMove = backwardMostMove = 1;
+ strcpy(moveList[0], "");
+ strcpy(parseList[0], "");
+ CopyBoard(boards[1], initial_position);
+ DisplayMessage("", _("Black to play"));
+ } else {
+ 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);
+ DisplayTitle(line);
+ } else {
+ DisplayTitle(title);
+ }
+ gameMode = EditGame;
+ ModeHighlight();
+ ResetClocks();
+ timeRemaining[0][1] = whiteTimeRemaining;
+ timeRemaining[1][1] = blackTimeRemaining;
+ DrawPosition(FALSE, boards[currentMove]);
+
+ return TRUE;
+}
+
+
+void
+CopyPlayerNameIntoFileName(dest, src)
+ char **dest, *src;
+{
+ while (*src != NULLCHAR && *src != ',') {
+ if (*src == ' ') {
+ *(*dest)++ = '_';
+ src++;
+ } else {
+ *(*dest)++ = *src++;
+ }
+ }
+}
+
+char *DefaultFileName(ext)
+ char *ext;
+{
+ static char def[MSG_SIZ];
+ char *p;
+
+ if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
+ p = def;
+ CopyPlayerNameIntoFileName(&p, gameInfo.white);
+ *p++ = '-';
+ CopyPlayerNameIntoFileName(&p, gameInfo.black);
+ *p++ = '.';
+ strcpy(p, ext);
+ } else {
+ def[0] = NULLCHAR;
+ }
+ return def;
+}
+
+/* Save the current game to the given file */
+int
+SaveGameToFile(filename, append)
+ char *filename;
+ int append;
+{
+ FILE *f;
+ char buf[MSG_SIZ];
+
+ if (strcmp(filename, "-") == 0) {
+ return SaveGame(stdout, 0, NULL);
+ } else {
+ f = fopen(filename, append ? "a" : "w");
+ if (f == NULL) {
+ snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
+ DisplayError(buf, errno);
+ return FALSE;
+ } else {
+ return SaveGame(f, 0, NULL);
+ }
+ }
+}
+
+char *
+SavePart(str)
+ char *str;
+{
+ static char buf[MSG_SIZ];
+ char *p;
+
+ p = strchr(str, ' ');
+ if (p == NULL) return str;
+ strncpy(buf, str, p - str);
+ buf[p - str] = NULLCHAR;
+ return buf;
+}
+
+#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)
+ FILE *f;
+{
+ int i, offset, linelen, newblock;
+ time_t tm;
+// 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, NULL);
+ fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
+ fprintf(f, "\n{--------------\n");
+ PrintPosition(f, backwardMostMove);
+ fprintf(f, "--------------}\n");
+ free(fen);
+ }
+ 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;
+ linelen = 0;
+ newblock = TRUE;
+
+ while (i < forwardMostMove) {
+ /* Print comments preceding this move */
+ if (commentList[i] != NULL) {
+ if (linelen > 0) fprintf(f, "\n");
+ fprintf(f, "{\n%s}\n", commentList[i]);
+ linelen = 0;
+ newblock = TRUE;
+ }
+
+ /* Format move number */
+ if ((i % 2) == 0) {
+ sprintf(numtext, "%d.", (i - offset)/2 + 1);
+ } else {
+ if (newblock) {
+ sprintf(numtext, "%d...", (i - offset)/2 + 1);
+ } else {
+ numtext[0] = NULLCHAR;
+ }
+ }
+ numlen = strlen(numtext);
+ newblock = FALSE;
+
+ /* Print move number */
+ blank = linelen > 0 && numlen > 0;
+ if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
+ fprintf(f, "\n");
+ linelen = 0;
+ blank = 0;
+ }
+ if (blank) {
+ fprintf(f, " ");
+ linelen++;
+ }
+ fprintf(f, numtext);
+ linelen += numlen;
+
+ /* Get move */
+ strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
+ movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
+ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+ int p = movelen - 1;
+ if(move_buffer[p] == ' ') p--;
+ if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
+ while(p && move_buffer[--p] != '(');
+ if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
+ }
+ }
+
+ /* Print move */
+ blank = linelen > 0 && movelen > 0;
+ if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
+ fprintf(f, "\n");
+ linelen = 0;
+ blank = 0;
+ }
+ if (blank) {
+ fprintf(f, " ");
+ linelen++;
+ }
+ fprintf(f, move_buffer);
+ linelen += movelen;
+
+ /* [AS] Add PV info if present */
+ if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
+ /* [HGM] add time */
+ char buf[MSG_SIZ]; int seconds = 0;
+
+#if 1
+ if(i >= backwardMostMove) {
+ if(WhiteOnMove(i))
+ seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
+ + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
+ else
+ seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
+ + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
+ }
+ seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
+#else
+ seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
+#endif
+
+ if( seconds <= 0) buf[0] = 0; else
+ if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
+ seconds = (seconds + 4)/10; // round to full seconds
+ if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
+ sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
+ }
+
+ sprintf( move_buffer, "{%s%.2f/%d%s}",
+ pvInfoList[i].score >= 0 ? "+" : "",
+ pvInfoList[i].score / 100.0,
+ pvInfoList[i].depth,
+ buf );
+
+ movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
+
+ /* Print score/depth */
+ blank = linelen > 0 && movelen > 0;
+ if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
+ fprintf(f, "\n");
+ linelen = 0;
+ blank = 0;
+ }
+ if (blank) {
+ fprintf(f, " ");
+ linelen++;
+ }
+ fprintf(f, move_buffer);
+ linelen += movelen;
+ }
+
+ i++;
+ }
+
+ /* Start a new line */
+ if (linelen > 0) fprintf(f, "\n");
+
+ /* Print comments after last move */
+ if (commentList[i] != NULL) {
+ fprintf(f, "{\n%s}\n", commentList[i]);
+ }
+
+ /* Print result */
+ if (gameInfo.resultDetails != NULL &&
+ gameInfo.resultDetails[0] != NULLCHAR) {
+ fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
+ PGNResult(gameInfo.result));
+ } else {
+ fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
+ }
+
+ fclose(f);
+ return TRUE;
+}
+
+/* Save game in old style and close the file */
+int
+SaveGameOldStyle(f)
+ FILE *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);
+ fprintf(f, "--------------]\n");
+ } else {
+ fprintf(f, "\n");
+ }
+
+ i = backwardMostMove;
+ offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
+
+ while (i < forwardMostMove) {
+ if (commentList[i] != NULL) {
+ fprintf(f, "[%s]\n", commentList[i]);
+ }
+
+ if ((i % 2) == 1) {
+ fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
+ i++;
+ } else {
+ fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
+ i++;
+ if (commentList[i] != NULL) {
+ fprintf(f, "\n");
+ continue;
+ }
+ if (i >= forwardMostMove) {
+ fprintf(f, "\n");
+ break;
+ }
+ fprintf(f, "%s\n", parseList[i]);
+ i++;
+ }
+ }
+
+ if (commentList[i] != NULL) {
+ fprintf(f, "[%s]\n", commentList[i]);
+ }
+
+ /* This isn't really the old style, but it's close enough */
+ if (gameInfo.resultDetails != NULL &&
+ gameInfo.resultDetails[0] != NULLCHAR) {
+ fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
+ gameInfo.resultDetails);
+ } else {
+ fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
+ }
+
+ fclose(f);
+ return TRUE;
+}
+
+/* Save the current game to open file f and close the file */
+int
+SaveGame(f, dummy, dummy2)
+ FILE *f;
+ int dummy;
+ char *dummy2;
+{
+ if (gameMode == EditPosition) EditPositionDone();
+ if (appData.oldSaveStyle)
+ return SaveGameOldStyle(f);
+ else
+ return SaveGamePGN(f);
+}
+
+/* Save the current position to the given file */
+int
+SavePositionToFile(filename)
+ char *filename;
+{
+ FILE *f;
+ char buf[MSG_SIZ];
+
+ if (strcmp(filename, "-") == 0) {
+ return SavePosition(stdout, 0, NULL);
+ } else {
+ f = fopen(filename, "a");
+ if (f == NULL) {
+ snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
+ DisplayError(buf, errno);
+ return FALSE;
+ } else {
+ SavePosition(f, 0, NULL);
+ return TRUE;
+ }
+ }
+}
+
+/* Save the current position to the given open file and close the file */
+int
+SavePosition(f, dummy, dummy2)
+ FILE *f;
+ int dummy;
+ char *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, NULL);
+ fprintf(f, "%s\n", fen);
+ free(fen);
+ }
+ fclose(f);
+ return TRUE;
+}
+
+void
+ReloadCmailMsgEvent(unregister)
+ int unregister;
+{
+#if !WIN32
+ static char *inFilename = NULL;
+ static char *outFilename;
+ 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) {
+ for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
+ cmailMoveRegistered[i] = FALSE;
+ if (cmailCommentList[i] != NULL) {
+ free(cmailCommentList[i]);
+ cmailCommentList[i] = NULL;
+ }
+ }
+ nCmailMovesRegistered = 0;
+ }
+
+ for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
+ cmailResult[i] = CMAIL_NOT_RESULT;
+ }
+ nCmailResults = 0;
+
+ if (inFilename == NULL) {
+ /* Because the filenames are static they only get malloced once */
+ /* and they never get freed */
+ inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
+ sprintf(inFilename, "%s.game.in", appData.cmailGameName);
+
+ outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
+ sprintf(outFilename, "%s.out", appData.cmailGameName);
+ }
+
+ status = stat(outFilename, &outbuf);
+ if (status < 0) {
+ cmailMailedMove = FALSE;
+ } else {
+ 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
+ parallel arrays.) Note this will require getting each game's
+ termination from the PGN tags, as the game list builder does
+ not process the game moves. --mann
+ */
+ cmailMsgLoaded = TRUE;
+ LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
+
+ /* Load first game in the file or popup game menu */
+ LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
+
+#endif /* !WIN32 */
+ return;
+}
+
+int
+RegisterMove()
+{
+ FILE *f;
+ char string[MSG_SIZ];
+
+ if ( cmailMailedMove
+ || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
+ return TRUE; /* Allow free viewing */
+ }
+
+ /* Unregister move to ensure that we don't leave RegisterMove */
+ /* with the move registered when the conditions for registering no */
+ /* longer hold */
+ if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
+ cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
+ nCmailMovesRegistered --;
+
+ if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
+ {
+ free(cmailCommentList[lastLoadGameNumber - 1]);
+ cmailCommentList[lastLoadGameNumber - 1] = NULL;
+ }
+ }
+
+ if (cmailOldMove == -1) {
+ DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
+ return FALSE;
+ }
+
+ if (currentMove > cmailOldMove + 1) {
+ DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
+ return FALSE;
+ }
+
+ if (currentMove < cmailOldMove) {
+ DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
+ return FALSE;
+ }
+
+ if (forwardMostMove > currentMove) {
+ /* Silently truncate extra moves */
+ TruncateGame();
+ }
+
+ if ( (currentMove == cmailOldMove + 1)
+ || ( (currentMove == cmailOldMove)
+ && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
+ || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
+ if (gameInfo.result != GameUnfinished) {
+ cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
+ }
+
+ if (commentList[currentMove] != NULL) {
+ cmailCommentList[lastLoadGameNumber - 1]
+ = StrSave(commentList[currentMove]);
+ }
+ strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
+
+ if (appData.debugMode)
+ fprintf(debugFP, "Saving %s for game %d\n",
+ cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
+
+ 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 */
+ } else {
+ 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) {
+ DisplayError(_("You have not made a move yet"), 0);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+MailMoveEvent()
+{
+#if !WIN32
+ static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
+ FILE *commandOutput;
+ char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
+ int nBytes = 0; /* Suppress warnings on uninitialized variables */
+ int nBuffers;
+ int i;
+ int archived;
+ char *arcDir;
+
+ if (! cmailMsgLoaded) {
+ DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
+ return;
+ }
+
+ if (nCmailGames == nCmailResults) {
+ DisplayError(_("No unfinished games"), 0);
+ return;
+ }
+
+#if CMAIL_PROHIBIT_REMAIL
+ if (cmailMailedMove) {
+ sprintf(msg, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
+ DisplayError(msg, 0);
+ return;
+ }
+#endif
+
+ if (! (cmailMailedMove || RegisterMove())) return;
+
+ if ( cmailMailedMove
+ || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
+ sprintf(string, partCommandString,
+ appData.debugMode ? " -v" : "", appData.cmailGameName);
+ commandOutput = popen(string, "r");
+
+ if (commandOutput == NULL) {
+ DisplayError(_("Failed to invoke cmail"), 0);
+ } else {
+ for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
+ nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
+ }
+ if (nBuffers > 1) {
+ (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
+ (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
+ nBytes = MSG_SIZ - 1;
+ } else {
+ (void) memcpy(msg, buffer, nBytes);
+ }
+ *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
+
+ if(StrStr(msg, "Mailed cmail message to ") != NULL) {
+ cmailMailedMove = TRUE; /* Prevent >1 moves */
+
+ archived = TRUE;
+ for (i = 0; i < nCmailGames; i ++) {
+ if (cmailResult[i] == CMAIL_NOT_RESULT) {
+ archived = FALSE;
+ }
+ }
+ if ( archived
+ && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
+ != NULL)) {
+ sprintf(buffer, "%s/%s.%s.archive",
+ arcDir,
+ appData.cmailGameName,
+ gameInfo.date);
+ LoadGameFromFile(buffer, 1, buffer, FALSE);
+ cmailMsgLoaded = FALSE;
+ }
+ }
+
+ DisplayInformation(msg);
+ pclose(commandOutput);
+ }
+ } else {
+ if ((*cmailMsg) != '\0') {
+ DisplayInformation(cmailMsg);
+ }
+ }
+
+ return;
+#endif /* !WIN32 */
+}
+
+char *
+CmailMsg()
+{
+#if WIN32
+ return NULL;
+#else
+ int prependComma = 0;
+ char number[5];
+ char string[MSG_SIZ]; /* Space for game-list */
+ int i;
+
+ if (!cmailMsgLoaded) return "";
+
+ if (cmailMailedMove) {
+ sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
+ } else {
+ /* Create a list of games left */
+ sprintf(string, "[");
+ for (i = 0; i < nCmailGames; i ++) {
+ if (! ( cmailMoveRegistered[i]
+ || (cmailResult[i] == CMAIL_OLD_RESULT))) {
+ if (prependComma) {
+ sprintf(number, ",%d", i + 1);
+ } else {
+ sprintf(number, "%d", i + 1);
+ prependComma = 1;
+ }
+
+ strcat(string, number);
+ }
+ }
+ strcat(string, "]");
+
+ if (nCmailMovesRegistered + nCmailResults == 0) {
+ switch (nCmailGames) {
+ case 1:
+ 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"),
+ nCmailGames);
+ break;
+ }
+ } else {
+ switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
+ case 1:
+ sprintf(cmailMsg,
+ _("Still need to make a move for game %s\n"),
+ string);
+ break;
+
+ case 0:
+ if (nCmailResults == nCmailGames) {
+ sprintf(cmailMsg, _("No unfinished games\n"));
+ } else {
+ sprintf(cmailMsg, _("Ready to send mail\n"));
+ }
+ break;
+
+ default:
+ sprintf(cmailMsg,
+ _("Still need to make moves for games %s\n"),
+ string);
+ }
+ }
+ }
+ return cmailMsg;
+#endif /* WIN32 */
+}
+
+void
+ResetGameEvent()
+{
+ if (gameMode == Training)
+ SetTrainingModeOff();
+
+ Reset(TRUE, TRUE);
+ cmailMsgLoaded = FALSE;
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("refresh\n");
+ }
+}
+
+void
+ExitEvent(status)
+ int status;
+{
+ exiting++;
+ if (exiting > 2) {
+ /* Give up on clean exit */
+ exit(status);
+ }
+ if (exiting > 1) {
+ /* Keep trying for clean exit */
+ return;
+ }
+
+ if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
+
+ if (telnetISR != NULL) {
+ RemoveInputSource(telnetISR);
+ }
+ if (icsPR != NoProc) {
+ DestroyChildProcess(icsPR, TRUE);
+ }
+#if 0
+ /* Save game if resource set and not already saved by GameEnds() */
+ if ((gameInfo.resultDetails == NULL || errorExitFlag )
+ && forwardMostMove > 0) {
+ if (*appData.saveGameFile != NULLCHAR) {
+ SaveGameToFile(appData.saveGameFile, TRUE);
+ } else if (appData.autoSaveGames) {
+ AutoSaveGame();
+ }
+ if (*appData.savePositionFile != NULLCHAR) {
+ SavePositionToFile(appData.savePositionFile);
+ }
+ }
+ GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+#else
+ /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
+ GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
+#endif
+ /* [HGM] crash: the above GameEnds() is a dud if another one was running */
+ /* make sure this other one finishes before killing it! */
+ if(endingGame) { int count = 0;
+ if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
+ while(endingGame && count++ < 10) DoSleep(1);
+ if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
+ }
+
+ /* Kill off chess programs */
+ if (first.pr != NoProc) {
+ ExitAnalyzeMode();
+
+ DoSleep( appData.delayBeforeQuit );
+ SendToProgram("quit\n", &first);
+ DoSleep( appData.delayAfterQuit );
+ DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
+ }
+ if (second.pr != NoProc) {
+ DoSleep( appData.delayBeforeQuit );
+ SendToProgram("quit\n", &second);
+ DoSleep( appData.delayAfterQuit );
+ DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
+ }
+ if (first.isr != NULL) {
+ RemoveInputSource(first.isr);
+ }
+ if (second.isr != NULL) {
+ RemoveInputSource(second.isr);
+ }
+
+ ShutDownFrontEnd();
+ exit(status);
+}
+
+void
+PauseEvent()
+{
+ if (appData.debugMode)
+ fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
+ if (pausing) {
+ pausing = FALSE;
+ ModeHighlight();
+ if (gameMode == MachinePlaysWhite ||
+ gameMode == MachinePlaysBlack) {
+ StartClocks();
+ } else {
+ DisplayBothClocks();
+ }
+ if (gameMode == PlayFromGameFile) {
+ if (appData.timeDelay >= 0)
+ AutoPlayGameLoop();
+ } else if (gameMode == IcsExamining && pauseExamInvalid) {
+ Reset(FALSE, TRUE);
+ SendToICS(ics_prefix);
+ SendToICS("refresh\n");
+ } else if (currentMove < forwardMostMove) {
+ ForwardInner(forwardMostMove);
+ }
+ pauseExamInvalid = FALSE;
+ } else {
+ switch (gameMode) {
+ default:
+ return;
+ case IcsExamining:
+ pauseExamForwardMostMove = forwardMostMove;
+ pauseExamInvalid = FALSE;
+ /* fall through */
+ case IcsObserving:
+ case IcsPlayingWhite:
+ case IcsPlayingBlack:
+ pausing = TRUE;
+ ModeHighlight();
+ return;
+ case PlayFromGameFile:
+ (void) StopLoadGameTimer();
+ pausing = TRUE;
+ ModeHighlight();
+ break;
+ case BeginningOfGame:
+ if (appData.icsActive) return;
+ /* else fall through */
+ case MachinePlaysWhite:
+ case MachinePlaysBlack:
+ case TwoMachinesPlay:
+ if (forwardMostMove == 0)
+ return; /* don't pause if no one has moved */
+ if ((gameMode == MachinePlaysWhite &&
+ !WhiteOnMove(forwardMostMove)) ||
+ (gameMode == MachinePlaysBlack &&
+ WhiteOnMove(forwardMostMove))) {
+ StopClocks();
+ }
+ pausing = TRUE;
+ ModeHighlight();
+ break;
+ }
+ }
+}
+
+void
+EditCommentEvent()
+{
+ char title[MSG_SIZ];
+
+ if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
+ strcpy(title, _("Edit comment"));
+ } else {
+ sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
+ WhiteOnMove(currentMove - 1) ? " " : ".. ",
+ parseList[currentMove - 1]);
+ }
+
+ EditCommentPopUp(currentMove, title, commentList[currentMove]);
+}
+
+
+void
+EditTagsEvent()
+{
+ char *tags = PGNTags(&gameInfo);
+ EditTagsPopUp(tags);
+ free(tags);
+}
+
+void
+AnalyzeModeEvent()
+{
+ if (appData.noChessProgram || gameMode == AnalyzeMode)
+ return;
+
+ if (gameMode != AnalyzeFile) {
+ 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."));
+ }
+ if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
+ pausing = FALSE;
+ ModeHighlight();
+ SetGameInfo();
+
+ StartAnalysisClock();
+ GetTimeMark(&lastNodeCountTime);
+ lastNodeCount = 0;
+}
+
+void
+AnalyzeFileEvent()
+{
+ if (appData.noChessProgram || gameMode == AnalyzeFile)
+ return;
+
+ if (gameMode != AnalyzeMode) {
+ 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."));
+ }
+ gameMode = AnalyzeFile;
+ pausing = FALSE;
+ ModeHighlight();
+ SetGameInfo();
+
+ StartAnalysisClock();
+ GetTimeMark(&lastNodeCountTime);
+ lastNodeCount = 0;
+}
+
+void
+MachineWhiteEvent()
+{
+ char buf[MSG_SIZ];
+ char *bookHit = NULL;
+
+ if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
+ return;
+
+
+ if (gameMode == PlayFromGameFile ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == Training ||
+ gameMode == AnalyzeMode ||
+ gameMode == EndOfGame)
+ EditGameEvent();
+
+ 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 ||
+ 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();
+ SetGameInfo();
+ sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ DisplayTitle(buf);
+ if (first.sendName) {
+ sprintf(buf, "name %s\n", gameInfo.black);
+ SendToProgram(buf, &first);
+ }
+ if (first.sendTime) {
+ if (first.useColors) {
+ SendToProgram("black\n", &first); /*gnu kludge*/
+ }
+ SendTimeRemaining(&first, TRUE);
+ }
+ if (first.useColors) {
+ 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();
+
+ 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);
+ }
+}
+
+void
+MachineBlackEvent()
+{
+ char buf[MSG_SIZ];
+ char *bookHit = NULL;
+
+ if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
+ return;
+
+
+ if (gameMode == PlayFromGameFile ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == Training ||
+ gameMode == AnalyzeMode ||
+ gameMode == EndOfGame)
+ EditGameEvent();
+
+ 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 ||
+ gameMode == AnalyzeFile)
+ TruncateGame();
+
+ ResurrectChessProgram(); /* in case it isn't running */
+ gameMode = MachinePlaysBlack;
+ pausing = FALSE;
+ ModeHighlight();
+ SetGameInfo();
+ sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ DisplayTitle(buf);
+ if (first.sendName) {
+ sprintf(buf, "name %s\n", gameInfo.white);
+ SendToProgram(buf, &first);
+ }
+ if (first.sendTime) {
+ if (first.useColors) {
+ SendToProgram("white\n", &first); /*gnu kludge*/
+ }
+ SendTimeRemaining(&first, FALSE);
+ }
+ if (first.useColors) {
+ 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();
+
+ 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);
+ }
+}
+
+
+void
+DisplayTwoMachinesTitle()
+{
+ char buf[MSG_SIZ];
+ if (appData.matchGames > 0) {
+ if (first.twoMachinesColor[0] == 'w') {
+ sprintf(buf, "%s vs. %s (%d-%d-%d)",
+ gameInfo.white, gameInfo.black,
+ first.matchWins, second.matchWins,
+ matchGame - 1 - (first.matchWins + second.matchWins));
+ } else {
+ sprintf(buf, "%s vs. %s (%d-%d-%d)",
+ gameInfo.white, gameInfo.black,
+ second.matchWins, first.matchWins,
+ matchGame - 1 - (first.matchWins + second.matchWins));
+ }
+ } else {
+ sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
+ }
+ DisplayTitle(buf);
+}
+
+void
+TwoMachinesEvent P((void))
+{
+ int i;
+ char buf[MSG_SIZ];
+ ChessProgramState *onmove;
+ char *bookHit = NULL;
+
+ if (appData.noChessProgram) return;
+
+ switch (gameMode) {
+ case TwoMachinesPlay:
+ return;
+ case MachinePlaysWhite:
+ case MachinePlaysBlack:
+ if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
+ DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
+ return;
+ }
+ /* fall through */
+ case BeginningOfGame:
+ case PlayFromGameFile:
+ case EndOfGame:
+ EditGameEvent();
+ if (gameMode != EditGame) return;
+ break;
+ case EditPosition:
+ EditPositionDone();
+ break;
+ case AnalyzeMode:
+ case AnalyzeFile:
+ ExitAnalyzeMode();
+ break;
+ case EditGame:
+ default:
+ break;
+ }
+
+ forwardMostMove = currentMove;
+ ResurrectChessProgram(); /* in case first program isn't running */
+
+ if (second.pr == NULL) {
+ StartChessProgram(&second);
+ if (second.protocolVersion == 1) {
+ TwoMachinesEventIfReady();
+ } else {
+ /* kludge: allow timeout for initial "feature" command */
+ FreezeUI();
+ DisplayMessage("", _("Starting second chess program"));
+ ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
+ }
+ return;
+ }
+ DisplayMessage("", "");
+ 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);
+ }
+
+ gameMode = TwoMachinesPlay;
+ pausing = FALSE;
+ ModeHighlight();
+ SetGameInfo();
+ DisplayTwoMachinesTitle();
+ firstMove = TRUE;
+ if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
+ onmove = &first;
+ } else {
+ onmove = &second;
+ }
+
+ SendToProgram(first.computerString, &first);
+ if (first.sendName) {
+ sprintf(buf, "name %s\n", second.tidy);
+ SendToProgram(buf, &first);
+ }
+ SendToProgram(second.computerString, &second);
+ if (second.sendName) {
+ sprintf(buf, "name %s\n", first.tidy);
+ SendToProgram(buf, &second);
+ }
+
+ ResetClocks();
+ if (!first.sendTime || !second.sendTime) {
+ timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
+ timeRemaining[1][forwardMostMove] = blackTimeRemaining;
+ }
+ if (onmove->sendTime) {
+ if (onmove->useColors) {
+ SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
+ }
+ SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
+ }
+ if (onmove->useColors) {
+ SendToProgram(onmove->twoMachinesColor, onmove);
+ }
+ bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
+// SendToProgram("go\n", onmove);
+ onmove->maybeThinking = TRUE;
+ SetMachineThinkingEnables();
+
+ StartClocks();
+
+ if(bookHit) { // [HGM] book: simulate book reply
+ static char bookMove[MSG_SIZ]; // a bit generous?
+
+ programStats.nodes = programStats.depth = programStats.time =
+ programStats.score = programStats.got_only_move = 0;
+ sprintf(programStats.movelist, "%s (xbook)", bookHit);
+
+ strcpy(bookMove, "move ");
+ strcat(bookMove, bookHit);
+ HandleMachineMove(bookMove, &first);
+ }
+}
+
+void
+TrainingEvent()
+{
+ if (gameMode == Training) {
+ SetTrainingModeOff();
+ gameMode = PlayFromGameFile;
+ DisplayMessage("", _("Training mode off"));
+ } else {
+ gameMode = Training;
+ animateTraining = appData.animate;
+
+ /* make sure we are not already at the end of the game */
+ if (currentMove < forwardMostMove) {
+ SetTrainingModeOn();
+ DisplayMessage("", _("Training mode on"));
+ } else {
+ gameMode = PlayFromGameFile;
+ DisplayError(_("Already at end of game"), 0);
+ }
+ }
+ ModeHighlight();
+}
+
+void
+IcsClientEvent()
+{
+ if (!appData.icsActive) return;
+ switch (gameMode) {
+ case IcsPlayingWhite:
+ case IcsPlayingBlack:
+ case IcsObserving:
+ case IcsIdle:
+ case BeginningOfGame:
+ case IcsExamining:
+ return;
+
+ case EditGame:
+ break;
+
+ case EditPosition:
+ EditPositionDone();
+ break;
+
+ case AnalyzeMode:
+ case AnalyzeFile:
+ ExitAnalyzeMode();
+ break;
+
+ default:
+ EditGameEvent();
+ break;
+ }
+
+ gameMode = IcsIdle;
+ ModeHighlight();
+ return;
+}
+
+
+void
+EditGameEvent()
+{
+ int i;
+
+ switch (gameMode) {
+ case Training:
+ SetTrainingModeOff();
+ break;
+ case MachinePlaysWhite:
+ case MachinePlaysBlack:
+ case BeginningOfGame:
+ SendToProgram("force\n", &first);
+ SetUserThinkingEnables();
+ break;
+ case PlayFromGameFile:
+ (void) StopLoadGameTimer();
+ if (gameFileFP != NULL) {
+ gameFileFP = NULL;
+ }
+ break;
+ case EditPosition:
+ EditPositionDone();
+ break;
+ case AnalyzeMode:
+ case AnalyzeFile:
+ ExitAnalyzeMode();
+ SendToProgram("force\n", &first);
+ break;
+ case TwoMachinesPlay:
+ GameEnds((ChessMove) 0, NULL, GE_PLAYER);
+ ResurrectChessProgram();
+ SetUserThinkingEnables();
+ break;
+ case EndOfGame:
+ ResurrectChessProgram();
+ break;
+ case IcsPlayingBlack:
+ case IcsPlayingWhite:
+ DisplayError(_("Warning: You are still playing a game"), 0);
+ break;
+ case IcsObserving:
+ DisplayError(_("Warning: You are still observing a game"), 0);
+ break;
+ case IcsExamining:
+ DisplayError(_("Warning: You are still examining a game"), 0);
+ break;
+ case IcsIdle:
+ break;
+ case EditGame:
+ default:
+ return;
+ }
+
+ pausing = FALSE;
+ StopClocks();
+ first.offeredDraw = second.offeredDraw = 0;
+
+ if (gameMode == PlayFromGameFile) {
+ whiteTimeRemaining = timeRemaining[0][currentMove];
+ blackTimeRemaining = timeRemaining[1][currentMove];
+ DisplayTitle("");
+ }
+
+ if (gameMode == MachinePlaysWhite ||
+ gameMode == MachinePlaysBlack ||
+ gameMode == TwoMachinesPlay ||
+ gameMode == EndOfGame) {
+ i = forwardMostMove;
+ while (i > currentMove) {
+ SendToProgram("undo\n", &first);
+ i--;
+ }
+ whiteTimeRemaining = timeRemaining[0][currentMove];
+ blackTimeRemaining = timeRemaining[1][currentMove];
+ DisplayBothClocks();
+ if (whiteFlag || blackFlag) {
+ whiteFlag = blackFlag = 0;
+ }
+ DisplayTitle("");
+ }
+
+ gameMode = EditGame;
+ ModeHighlight();
+ SetGameInfo();
+}
+
+
+void
+EditPositionEvent()
+{
+ if (gameMode == EditPosition) {
+ 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;
+ HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+ DisplayMove(-1);
+}
+
+void
+ExitAnalyzeMode()
+{
+ /* [DM] icsEngineAnalyze - possible call from other functions */
+ if (appData.icsEngineAnalyze) {
+ appData.icsEngineAnalyze = FALSE;
+
+ DisplayMessage("",_("Close ICS engine analyze..."));
+ }
+ if (first.analysisSupport && first.analyzing) {
+ SendToProgram("exit\n", &first);
+ first.analyzing = FALSE;
+ }
+ AnalysisPopDown();
+ thinkOutput[0] = NULLCHAR;
+}
+
+void
+EditPositionDone()
+{
+ startedFromSetupPosition = TRUE;
+ InitChessProgram(&first, FALSE);
+ 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<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
+ }
+ } else {
+ currentMove = forwardMostMove = backwardMostMove = 0;
+ }
+ SendBoard(&first, forwardMostMove);
+ if (appData.debugMode) {
+ fprintf(debugFP, "EditPosDone\n");
+ }
+ DisplayTitle("");
+ timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
+ timeRemaining[1][forwardMostMove] = blackTimeRemaining;
+ gameMode = EditGame;
+ ModeHighlight();
+ HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
+ ClearHighlights(); /* [AS] */
+}
+
+/* Pause for `ms' milliseconds */
+/* !! Ugh, this is a kludge. Fix it sometime. --tpm */
+void
+TimeDelay(ms)
+ long ms;
+{
+ TimeMark m1, m2;
+
+ GetTimeMark(&m1);
+ do {
+ GetTimeMark(&m2);
+ } while (SubtractTimeMarks(&m2, &m1) < ms);
+}
+
+/* !! Ugh, this is a kludge. Fix it sometime. --tpm */
+void
+SendMultiLineToICS(buf)
+ char *buf;
+{
+ char temp[MSG_SIZ+1], *p;
+ int len;
+
+ len = strlen(buf);
+ if (len > MSG_SIZ)
+ len = MSG_SIZ;
+
+ strncpy(temp, buf, len);
+ temp[len] = 0;
+
+ p = temp;
+ while (*p) {
+ if (*p == '\n' || *p == '\r')
+ *p = ' ';
+ ++p;
+ }
+
+ strcat(temp, "\n");
+ SendToICS(temp);
+ SendToPlayer(temp, strlen(temp));
+}
+
+void
+SetWhiteToPlayEvent()
+{
+ if (gameMode == EditPosition) {
+ blackPlaysFirst = FALSE;
+ DisplayBothClocks(); /* works because currentMove is 0 */
+ } else if (gameMode == IcsExamining) {
+ SendToICS(ics_prefix);
+ SendToICS("tomove white\n");
+ }
+}
+
+void
+SetBlackToPlayEvent()
+{
+ if (gameMode == EditPosition) {
+ blackPlaysFirst = TRUE;
+ currentMove = 1; /* kludge */
+ DisplayBothClocks();
+ currentMove = 0;
+ } else if (gameMode == IcsExamining) {
+ SendToICS(ics_prefix);
+ SendToICS("tomove black\n");
+ }
+}
+
+void
+EditPositionMenuEvent(selection, x, y)
+ ChessSquare selection;
+ int x, y;
+{
+ char buf[MSG_SIZ];
+ ChessSquare piece = boards[0][y][x];
+
+ if (gameMode != EditPosition && gameMode != IcsExamining) return;
+
+ switch (selection) {
+ case ClearBoard:
+ if (gameMode == IcsExamining && ics_type == ICS_FICS) {
+ SendToICS(ics_prefix);
+ SendToICS("bsetup clear\n");
+ } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
+ SendToICS(ics_prefix);
+ SendToICS("clearboard\n");
+ } else {
+ 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,
+ AAA + x, ONE + y);
+ SendToICS(buf);
+ }
+ } else {
+ boards[0][y][x] = p;
+ }
+ }
+ }
+ }
+ if (gameMode == EditPosition) {
+ DrawPosition(FALSE, boards[0]);
+ }
+ break;
+
+ case WhitePlay:
+ SetWhiteToPlayEvent();
+ break;
+
+ case BlackPlay:
+ SetBlackToPlayEvent();
+ break;
+
+ case EmptySquare:
+ if (gameMode == IcsExamining) {
+ sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
+ SendToICS(buf);
+ } else {
+ boards[0][y][x] = EmptySquare;
+ DrawPosition(FALSE, boards[0]);
+ }
+ 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), AAA + x, ONE + y);
+ SendToICS(buf);
+ } else {
+ boards[0][y][x] = selection;
+ DrawPosition(FALSE, boards[0]);
+ }
+ break;
+ }
+}
+
+
+void
+DropMenuEvent(selection, x, y)
+ ChessSquare selection;
+ int x, y;
+{
+ ChessMove moveType;
+
+ switch (gameMode) {
+ case IcsPlayingWhite:
+ case MachinePlaysBlack:
+ if (!WhiteOnMove(currentMove)) {
+ DisplayMoveError(_("It is Black's turn"));
+ return;
+ }
+ moveType = WhiteDrop;
+ break;
+ case IcsPlayingBlack:
+ case MachinePlaysWhite:
+ if (WhiteOnMove(currentMove)) {
+ DisplayMoveError(_("It is White's turn"));
+ return;
+ }
+ moveType = BlackDrop;
+ break;
+ case EditGame:
+ moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
+ break;
+ default:
+ return;
+ }
+
+ if (moveType == BlackDrop && selection < BlackPawn) {
+ selection = (ChessSquare) ((int) selection
+ + (int) BlackPawn - (int) WhitePawn);
+ }
+ if (boards[currentMove][y][x] != EmptySquare) {
+ DisplayMoveError(_("That square is occupied"));
+ return;
+ }
+
+ FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
+}
+
+void
+AcceptEvent()
+{
+ /* Accept a pending offer of any kind from opponent */
+
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("accept\n");
+ } else if (cmailMsgLoaded) {
+ if (currentMove == cmailOldMove &&
+ commentList[cmailOldMove] != NULL &&
+ StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
+ "Black offers a draw" : "White offers a draw")) {
+ TruncateGame();
+ GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
+ } else {
+ DisplayError(_("There is no pending offer on this move"), 0);
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
+ }
+ } else {
+ /* Not used for offers from chess program */
+ }
+}
+
+void
+DeclineEvent()
+{
+ /* Decline a pending offer of any kind from opponent */
+
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("decline\n");
+ } else if (cmailMsgLoaded) {
+ if (currentMove == cmailOldMove &&
+ commentList[cmailOldMove] != NULL &&
+ StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
+ "Black offers a draw" : "White offers a draw")) {
+#ifdef NOTDEF
+ AppendComment(cmailOldMove, "Draw declined");
+ DisplayComment(cmailOldMove - 1, "Draw declined");
+#endif /*NOTDEF*/
+ } else {
+ DisplayError(_("There is no pending offer on this move"), 0);
+ }
+ } else {
+ /* Not used for offers from chess program */
+ }
+}
+
+void
+RematchEvent()
+{
+ /* Issue ICS rematch command */
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("rematch\n");
+ }
+}
+
+void
+CallFlagEvent()
+{
+ /* Call your opponent's flag (claim a win on time) */
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("flag\n");
+ } else {
+ switch (gameMode) {
+ default:
+ return;
+ case MachinePlaysWhite:
+ if (whiteFlag) {
+ if (blackFlag)
+ GameEnds(GameIsDrawn, "Both players ran out of time",
+ GE_PLAYER);
+ else
+ GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
+ } else {
+ DisplayError(_("Your opponent is not out of time"), 0);
+ }
+ break;
+ case MachinePlaysBlack:
+ if (blackFlag) {
+ if (whiteFlag)
+ GameEnds(GameIsDrawn, "Both players ran out of time",
+ GE_PLAYER);
+ else
+ GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
+ } else {
+ DisplayError(_("Your opponent is not out of time"), 0);
+ }
+ break;
+ }
+ }
+}
+
+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) {
+ if (currentMove == cmailOldMove &&
+ commentList[cmailOldMove] != NULL &&
+ StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
+ "Black offers a draw" : "White offers a draw")) {
+ GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
+ } else if (currentMove == cmailOldMove + 1) {
+ char *offer = WhiteOnMove(cmailOldMove) ?
+ "White offers a draw" : "Black offers a draw";
+ AppendComment(currentMove, offer);
+ DisplayComment(currentMove - 1, offer);
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
+ } else {
+ DisplayError(_("You must make your move before offering a draw"), 0);
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
+ }
+ } else if (first.offeredDraw) {
+ GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
+ } else {
+ if (first.sendDrawOffers) {
+ SendToProgram("draw\n", &first);
+ userOfferedDraw = TRUE;
+ }
+ }
+}
+
+void
+AdjournEvent()
+{
+ /* Offer Adjourn or accept pending Adjourn offer from opponent */
+
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("adjourn\n");
+ } else {
+ /* Currently GNU Chess doesn't offer or accept Adjourns */
+ }
+}
+
+
+void
+AbortEvent()
+{
+ /* Offer Abort or accept pending Abort offer from opponent */
+
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("abort\n");
+ } else {
+ GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
+ }
+}
+
+void
+ResignEvent()
+{
+ /* Resign. You can do this even if it's not your turn. */
+
+ if (appData.icsActive) {
+ SendToICS(ics_prefix);
+ SendToICS("resign\n");
+ } else {
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
+ break;
+ case MachinePlaysBlack:
+ GameEnds(BlackWins, "White resigns", GE_PLAYER);
+ break;
+ case EditGame:
+ if (cmailMsgLoaded) {
+ TruncateGame();
+ if (WhiteOnMove(cmailOldMove)) {
+ GameEnds(BlackWins, "White resigns", GE_PLAYER);
+ } else {
+ GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
+ }
+ cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+
+void
+StopObservingEvent()
+{
+ /* Stop observing current games */
+ SendToICS(ics_prefix);
+ SendToICS("unobserve\n");
+}
+
+void
+StopExaminingEvent()
+{
+ /* Stop observing current game */
+ SendToICS(ics_prefix);
+ SendToICS("unexamine\n");
+}
+
+void
+ForwardInner(target)
+ int target;
+{
+ int limit;
+
+ if (appData.debugMode)
+ fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
+ target, currentMove, forwardMostMove);
+
+ if (gameMode == EditPosition)
+ return;
+
+ 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] - 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] - AAA;
+ fromY = moveList[target - 1][1] - ONE;
+ if (target == currentMove + 1) {
+ AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
+ }
+ if (appData.highlightLastMove) {
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
+ }
+ if (gameMode == EditGame || gameMode == AnalyzeMode ||
+ gameMode == Training || gameMode == PlayFromGameFile ||
+ gameMode == AnalyzeFile) {
+ while (currentMove < target) {
+ SendMoveToProgram(currentMove++, &first);
+ }
+ } else {
+ currentMove = target;
+ }
+
+ if (gameMode == EditGame || gameMode == EndOfGame) {
+ whiteTimeRemaining = timeRemaining[0][currentMove];
+ blackTimeRemaining = timeRemaining[1][currentMove];
+ }
+ DisplayBothClocks();
+ DisplayMove(currentMove - 1);
+ DrawPosition(FALSE, boards[currentMove]);
+ HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+ if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
+ DisplayComment(currentMove - 1, commentList[currentMove]);
+ }
+}
+
+
+void
+ForwardEvent()
+{
+ if (gameMode == IcsExamining && !pausing) {
+ SendToICS(ics_prefix);
+ SendToICS("forward\n");
+ } else {
+ ForwardInner(currentMove + 1);
+ }
+}
+
+void
+ToEndEvent()
+{
+ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+ /* 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");
+ } else {
+ ForwardInner(forwardMostMove);
+ }
+
+ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+ /* we have fed all the moves, so reactivate analysis mode */
+ SendToProgram("analyze\n", &first);
+ first.analyzing = TRUE;
+ /*first.maybeThinking = TRUE;*/
+ first.maybeThinking = FALSE; /* avoid killing GNU Chess */
+ }
+}
+
+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);
+
+ if (gameMode == EditPosition) return;
+ if (currentMove <= backwardMostMove) {
+ ClearHighlights();
+ DrawPosition(full_redraw, boards[currentMove]);
+ return;
+ }
+ if (gameMode == PlayFromGameFile && !pausing)
+ PauseEvent();
+
+ if (moveList[target][0]) {
+ int fromX, fromY, toX, toY;
+ 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] - AAA;
+ fromY = moveList[target][1] - ONE;
+ if (target == currentMove - 1) {
+ AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
+ }
+ if (appData.highlightLastMove) {
+ SetHighlights(fromX, fromY, toX, toY);
+ }
+ }
+ }
+ if (gameMode == EditGame || gameMode==AnalyzeMode ||
+ gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
+ while (currentMove > target) {
+ SendToProgram("undo\n", &first);
+ currentMove--;
+ }
+ } else {
+ currentMove = target;
+ }
+
+ if (gameMode == EditGame || gameMode == EndOfGame) {
+ whiteTimeRemaining = timeRemaining[0][currentMove];
+ blackTimeRemaining = timeRemaining[1][currentMove];
+ }
+ DisplayBothClocks();
+ DisplayMove(currentMove - 1);
+ DrawPosition(full_redraw, boards[currentMove]);
+ HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
+ // [HGM] PV info: routine tests if comment empty
+ DisplayComment(currentMove - 1, commentList[currentMove]);
+}
+
+void
+BackwardEvent()
+{
+ if (gameMode == IcsExamining && !pausing) {
+ SendToICS(ics_prefix);
+ SendToICS("backward\n");
+ } else {
+ BackwardInner(currentMove - 1);
+ }
+}
+
+void
+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;
+ }
+ }
+
+ if (gameMode == IcsExamining && !pausing) {
+ SendToICS(ics_prefix);
+ SendToICS("backward 999999\n");
+ } else {
+ BackwardInner(backwardMostMove);
+ }
+
+ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+ /* we have fed all the moves, so reactivate analysis mode */
+ SendToProgram("analyze\n", &first);
+ first.analyzing = TRUE;
+ /*first.maybeThinking = TRUE;*/
+ first.maybeThinking = FALSE; /* avoid killing GNU Chess */
+ }
+}
+
+void
+ToNrEvent(int to)
+{
+ if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
+ if (to >= forwardMostMove) to = forwardMostMove;
+ if (to <= backwardMostMove) to = backwardMostMove;
+ if (to < currentMove) {
+ BackwardInner(to);
+ } else {
+ ForwardInner(to);
+ }
+}
+
+void
+RevertEvent()
+{
+ if (gameMode != IcsExamining) {
+ DisplayError(_("You are not examining a game"), 0);
+ return;
+ }
+ if (pausing) {
+ DisplayError(_("You can't revert while pausing"), 0);
+ return;
+ }
+ SendToICS(ics_prefix);
+ SendToICS("revert\n");
+}
+
+void
+RetractMoveEvent()
+{
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ case MachinePlaysBlack:
+ if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
+ DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
+ return;
+ }
+ if (forwardMostMove < 2) return;
+ currentMove = forwardMostMove = forwardMostMove - 2;
+ whiteTimeRemaining = timeRemaining[0][currentMove];
+ blackTimeRemaining = timeRemaining[1][currentMove];
+ DisplayBothClocks();
+ DisplayMove(currentMove - 1);
+ ClearHighlights();/*!! could figure this out*/
+ DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
+ SendToProgram("remove\n", &first);
+ /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
+ break;
+
+ case BeginningOfGame:
+ default:
+ break;
+
+ case IcsPlayingWhite:
+ case IcsPlayingBlack:
+ if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
+ SendToICS(ics_prefix);
+ SendToICS("takeback 2\n");
+ } else {
+ SendToICS(ics_prefix);
+ SendToICS("takeback 1\n");
+ }
+ break;
+ }
+}
+
+void
+MoveNowEvent()
+{
+ ChessProgramState *cps;
+
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ if (!WhiteOnMove(forwardMostMove)) {
+ DisplayError(_("It is your turn"), 0);
+ return;
+ }
+ cps = &first;
+ break;
+ case MachinePlaysBlack:
+ if (WhiteOnMove(forwardMostMove)) {
+ DisplayError(_("It is your turn"), 0);
+ return;
+ }
+ cps = &first;
+ break;
+ case TwoMachinesPlay:
+ if (WhiteOnMove(forwardMostMove) ==
+ (first.twoMachinesColor[0] == 'w')) {
+ cps = &first;
+ } else {
+ cps = &second;
+ }
+ break;
+ case BeginningOfGame:
+ default:
+ return;
+ }
+ SendToProgram("?\n", cps);
+}
+
+void
+TruncateGameEvent()
+{
+ EditGameEvent();
+ if (gameMode != EditGame) return;
+ TruncateGame();
+}
+
+void
+TruncateGame()
+{
+ if (forwardMostMove > currentMove) {
+ if (gameInfo.resultDetails != NULL) {
+ free(gameInfo.resultDetails);
+ gameInfo.resultDetails = NULL;
+ gameInfo.result = GameUnfinished;
+ }
+ forwardMostMove = currentMove;
+ HistorySet(parseList, backwardMostMove, forwardMostMove,
+ currentMove-1);
+ }
+}
+
+void
+HintEvent()
+{
+ if (appData.noChessProgram) return;
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ if (WhiteOnMove(forwardMostMove)) {
+ DisplayError(_("Wait until your turn"), 0);
+ return;
+ }
+ break;
+ case BeginningOfGame:
+ case MachinePlaysBlack:
+ if (!WhiteOnMove(forwardMostMove)) {
+ DisplayError(_("Wait until your turn"), 0);
+ return;
+ }
+ break;
+ default:
+ DisplayError(_("No hint available"), 0);
+ return;
+ }
+ SendToProgram("hint\n", &first);
+ hintRequested = TRUE;
+}
+
+void
+BookEvent()
+{
+ if (appData.noChessProgram) return;
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ if (WhiteOnMove(forwardMostMove)) {
+ DisplayError(_("Wait until your turn"), 0);
+ return;
+ }
+ break;
+ case BeginningOfGame:
+ case MachinePlaysBlack:
+ if (!WhiteOnMove(forwardMostMove)) {
+ DisplayError(_("Wait until your turn"), 0);
+ return;
+ }
+ break;
+ case EditPosition:
+ EditPositionDone();
+ break;
+ case TwoMachinesPlay:
+ return;
+ default:
+ break;
+ }
+ SendToProgram("bk\n", &first);
+ bookOutput[0] = NULLCHAR;
+ bookRequested = TRUE;
+}
+
+void
+AboutGameEvent()
+{
+ char *tags = PGNTags(&gameInfo);
+ TagsPopUp(tags, CmailMsg());
+ free(tags);
+}
+
+/* end button procedures */
+
+void
+PrintPosition(fp, move)
+ FILE *fp;
+ int move;
+{
+ int i, 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_RGHT - 1 ? '\n' : ' ', fp);
+ }
+ }
+ if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
+ fprintf(fp, "white to play\n");
+ else
+ fprintf(fp, "black to play\n");
+}
+
+void
+PrintOpponents(fp)
+ FILE *fp;
+{
+ if (gameInfo.white != NULL) {
+ fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
+ } else {
+ fprintf(fp, "\n");
+ }
+}
+
+/* Find last component of program's own name, using some heuristics */
+void
+TidyProgramName(prog, host, buf)
+ char *prog, *host, buf[MSG_SIZ];
+{
+ char *p, *q;
+ int local = (strcmp(host, "localhost") == 0);
+ while (!local && (p = strchr(prog, ';')) != NULL) {
+ p++;
+ while (*p == ' ') p++;
+ prog = p;
+ }
+ if (*prog == '"' || *prog == '\'') {
+ q = strchr(prog + 1, *prog);
+ } else {
+ q = strchr(prog, ' ');
+ }
+ if (q == NULL) q = prog + strlen(prog);
+ 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;
+ if (!local) {
+ strcat(buf, "@");
+ strcat(buf, host);
+ }
+}
+
+char *
+TimeControlTagValue()
+{
+ char buf[MSG_SIZ];
+ if (!appData.clockMode) {
+ strcpy(buf, "-");
+ } else if (movesPerSession > 0) {
+ sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
+ } else if (timeIncrement == 0) {
+ sprintf(buf, "%ld", timeControl/1000);
+ } else {
+ sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
+ }
+ return StrSave(buf);
+}
+
+void
+SetGameInfo()
+{
+ /* This routine is used only for certain modes */
+ VariantClass v = gameInfo.variant;
+ ClearGameInfo(&gameInfo);
+ gameInfo.variant = v;
+
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ gameInfo.event = StrSave( appData.pgnEventHeader );
+ gameInfo.site = StrSave(HostName());
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+ gameInfo.white = StrSave(first.tidy);
+ gameInfo.black = StrSave(UserName());
+ gameInfo.timeControl = TimeControlTagValue();
+ break;
+
+ case MachinePlaysBlack:
+ gameInfo.event = StrSave( appData.pgnEventHeader );
+ gameInfo.site = StrSave(HostName());
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+ gameInfo.white = StrSave(UserName());
+ gameInfo.black = StrSave(first.tidy);
+ gameInfo.timeControl = TimeControlTagValue();
+ break;
+
+ case TwoMachinesPlay:
+ gameInfo.event = StrSave( appData.pgnEventHeader );
+ gameInfo.site = StrSave(HostName());
+ gameInfo.date = PGNDate();
+ if (matchGame > 0) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "%d", matchGame);
+ gameInfo.round = StrSave(buf);
+ } else {
+ gameInfo.round = StrSave("-");
+ }
+ if (first.twoMachinesColor[0] == 'w') {
+ gameInfo.white = StrSave(first.tidy);
+ gameInfo.black = StrSave(second.tidy);
+ } else {
+ gameInfo.white = StrSave(second.tidy);
+ gameInfo.black = StrSave(first.tidy);
+ }
+ gameInfo.timeControl = TimeControlTagValue();
+ break;
+
+ case EditGame:
+ gameInfo.event = StrSave("Edited game");
+ gameInfo.site = StrSave(HostName());
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+ gameInfo.white = StrSave("-");
+ gameInfo.black = StrSave("-");
+ break;
+
+ case EditPosition:
+ gameInfo.event = StrSave("Edited position");
+ gameInfo.site = StrSave(HostName());
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+ gameInfo.white = StrSave("-");
+ gameInfo.black = StrSave("-");
+ break;
+
+ case IcsPlayingWhite:
+ case IcsPlayingBlack:
+ case IcsObserving:
+ case IcsExamining:
+ break;
+
+ case PlayFromGameFile:
+ gameInfo.event = StrSave("Game from non-PGN file");
+ gameInfo.site = StrSave(HostName());
+ gameInfo.date = PGNDate();
+ gameInfo.round = StrSave("-");
+ gameInfo.white = StrSave("?");
+ gameInfo.black = StrSave("?");
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+ReplaceComment(index, text)
+ int index;
+ char *text;
+{
+ int len;
+
+ while (*text == '\n') text++;
+ len = strlen(text);
+ while (len > 0 && text[len - 1] == '\n') len--;
+
+ if (commentList[index] != NULL)
+ free(commentList[index]);
+
+ if (len == 0) {
+ commentList[index] = NULL;
+ return;
+ }
+ commentList[index] = (char *) malloc(len + 2);
+ strncpy(commentList[index], text, len);
+ commentList[index][len] = '\n';
+ commentList[index][len + 1] = NULLCHAR;
+}
+
+void
+CrushCRs(text)
+ char *text;
+{
+ char *p = text;
+ char *q = text;
+ char ch;
+
+ do {
+ ch = *p++;
+ if (ch == '\r') continue;
+ *q++ = ch;
+ } while (ch != '\0');
+}
+
+void
+AppendComment(index, text)
+ int index;
+ char *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);
+ while (len > 0 && text[len - 1] == '\n') len--;
+
+ if (len == 0) return;
+
+ if (commentList[index] != NULL) {
+ old = commentList[index];
+ oldlen = strlen(old);
+ commentList[index] = (char *) malloc(oldlen + len + 2);
+ strcpy(commentList[index], old);
+ free(old);
+ strncpy(&commentList[index][oldlen], text, len);
+ commentList[index][oldlen + len] = '\n';
+ commentList[index][oldlen + len + 1] = NULLCHAR;
+ } else {
+ commentList[index] = (char *) malloc(len + 2);
+ strncpy(commentList[index], text, len);
+ commentList[index][len] = '\n';
+ commentList[index][len + 1] = NULLCHAR;
+ }
+}
+
+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;
+ ChessProgramState *cps;
+{
+ int count, outCount, error;
+ char buf[MSG_SIZ];
+
+ if (cps->pr == NULL) return;
+ Attention(cps);
+
+ if (appData.debugMode) {
+ TimeMark now;
+ GetTimeMark(&now);
+ 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
+ && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
+ sprintf(buf, _("Error writing to %s chess program"), cps->which);
+ if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
+ if(epStatus[forwardMostMove] <= EP_DRAWS) {
+ gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
+ sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
+ } else {
+ gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
+ }
+ gameInfo.resultDetails = buf;
+ }
+ DisplayFatalError(buf, error, 1);
+ }
+}
+
+void
+ReceiveFromProgram(isr, closure, message, count, error)
+ InputSourceRef isr;
+ VOIDSTAR closure;
+ char *message;
+ int count;
+ int error;
+{
+ char *end_str;
+ char buf[MSG_SIZ];
+ ChessProgramState *cps = (ChessProgramState *)closure;
+
+ if (isr != cps->isr) return; /* Killed intentionally */
+ if (count <= 0) {
+ if (count == 0) {
+ 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 {
+ sprintf(buf,
+ _("Error reading from %s chess program (%s)"),
+ cps->which, cps->program);
+ RemoveInputSource(cps->isr);
+
+ /* [AS] Program is misbehaving badly... kill it */
+ if( count == -2 ) {
+ DestroyChildProcess( cps->pr, 9 );
+ cps->pr = NoProc;
+ }
+
+ DisplayFatalError(buf, error, 1);
+ }
+ 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; 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 && 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);
+}
+
+
+void
+SendTimeControl(cps, mps, tc, inc, sd, st)
+ ChessProgramState *cps;
+ int mps, inc, sd, st;
+ long tc;
+{
+ char buf[MSG_SIZ];
+ 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 */
+ if (cps->stKludge) {
+ /* GNU Chess 4 has no st command; uses level in a nonstandard way */
+ seconds = st % 60;
+ if (seconds == 0) {
+ sprintf(buf, "level 1 %d\n", st/60);
+ } else {
+ sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
+ }
+ } else {
+ sprintf(buf, "st %d\n", st);
+ }
+ } else {
+ /* Set conventional or incremental time control, using level command */
+ if (seconds == 0) {
+ /* Note old gnuchess bug -- minutes:seconds used to not work.
+ Fixed in later versions, but still avoid :seconds
+ when seconds is 0. */
+ sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
+ } else {
+ sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
+ seconds, inc/1000);
+ }
+ }
+ SendToProgram(buf, cps);
+
+ /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
+ /* Orthogonally, limit search to given depth */
+ if (sd > 0) {
+ if (cps->sdKludge) {
+ sprintf(buf, "depth\n%d\n", sd);
+ } else {
+ sprintf(buf, "sd %d\n", sd);
+ }
+ 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
+SendTimeRemaining(cps, machineWhite)
+ ChessProgramState *cps;
+ int /*boolean*/ machineWhite;
+{
+ char message[MSG_SIZ];
+ long time, otime;
+
+ /* Note: this routine must be called when the clocks are stopped
+ or when they have *just* been set or switched; otherwise
+ it will be off by the time since the current tick started.
+ */
+ if (machineWhite) {
+ time = whiteTimeRemaining / 10;
+ otime = blackTimeRemaining / 10;
+ } else {
+ 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, "otim %ld\n", otime);
+ SendToProgram(message, cps);
+}
+
+int
+BoolFeature(p, name, loc, cps)
+ char **p;
+ char *name;
+ int *loc;
+ ChessProgramState *cps;
+{
+ char buf[MSG_SIZ];
+ int len = strlen(name);
+ int val;
+ if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
+ (*p) += len + 1;
+ sscanf(*p, "%d", &val);
+ *loc = (val != 0);
+ while (**p && **p != ' ') (*p)++;
+ sprintf(buf, "accepted %s\n", name);
+ SendToProgram(buf, cps);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+IntFeature(p, name, loc, cps)
+ char **p;
+ char *name;
+ int *loc;
+ ChessProgramState *cps;
+{
+ char buf[MSG_SIZ];
+ int len = strlen(name);
+ if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
+ (*p) += len + 1;
+ sscanf(*p, "%d", loc);
+ while (**p && **p != ' ') (*p)++;
+ sprintf(buf, "accepted %s\n", name);
+ SendToProgram(buf, cps);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+StringFeature(p, name, loc, cps)
+ char **p;
+ char *name;
+ char loc[];
+ ChessProgramState *cps;
+{
+ char buf[MSG_SIZ];
+ int len = strlen(name);
+ if (strncmp((*p), name, len) == 0
+ && (*p)[len] == '=' && (*p)[len+1] == '\"') {
+ (*p) += len + 2;
+ sscanf(*p, "%[^\"]", loc);
+ while (**p && **p != '\"') (*p)++;
+ if (**p == '\"') (*p)++;
+ sprintf(buf, "accepted %s\n", name);
+ SendToProgram(buf, cps);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+ParseOption(Option *opt, ChessProgramState *cps)
+// [HGM] options: process the string that defines an engine option, and determine
+// name, type, default value, and allowed value range
+{
+ char *p, *q, buf[MSG_SIZ];
+ int n, min = (-1)<<31, max = 1<<31, def;
+
+ if(p = strstr(opt->name, " -spin ")) {
+ if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
+ if(max < min) max = min; // enforce consistency
+ if(def < min) def = min;
+ if(def > max) def = max;
+ opt->value = def;
+ opt->min = min;
+ opt->max = max;
+ opt->type = Spin;
+ } else if(p = strstr(opt->name, " -string ")) {
+ opt->textValue = p+9;
+ opt->type = TextBox;
+ } else if(p = strstr(opt->name, " -check ")) {
+ if(sscanf(p, " -check %d", &def) < 1) return FALSE;
+ opt->value = (def != 0);
+ opt->type = CheckBox;
+ } else if(p = strstr(opt->name, " -combo ")) {
+ opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
+ cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
+ opt->value = n = 0;
+ while(q = StrStr(q, " /// ")) {
+ n++; *q = 0; // count choices, and null-terminate each of them
+ q += 5;
+ if(*q == '*') { // remember default, which is marked with * prefix
+ q++;
+ opt->value = n;
+ }
+ cps->comboList[cps->comboCnt++] = q;
+ }
+ cps->comboList[cps->comboCnt++] = NULL;
+ opt->max = n + 1;
+ opt->type = ComboBox;
+ } else if(p = strstr(opt->name, " -button")) {
+ opt->type = Button;
+ } else if(p = strstr(opt->name, " -save")) {
+ opt->type = SaveButton;
+ } else return FALSE;
+ *p = 0; // terminate option name
+ // 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;
+ int val;
+{
+ DelayedEventCallback cb = GetDelayedEvent();
+ if ((cb == InitBackEnd3 && cps == &first) ||
+ (cb == TwoMachinesEventIfReady && cps == &second)) {
+ CancelDelayedEvent();
+ ScheduleDelayedEvent(cb, val ? 1 : 3600000);
+ }
+ cps->initDone = val;
+}
+
+/* Parse feature command from engine */
+void
+ParseFeatures(args, cps)
+ char* args;
+ ChessProgramState *cps;
+{
+ char *p = args;
+ char *q;
+ int val;
+ char buf[MSG_SIZ];
+
+ for (;;) {
+ while (*p == ' ') p++;
+ 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, "reuse", &val, cps)) {
+ /* Engine can disable reuse, but can't enable it if user said no */
+ if (!val) cps->reuse = FALSE;
+ continue;
+ }
+ if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
+ if (StringFeature(&p, "myname", &cps->tidy, cps)) {
+ if (gameMode == TwoMachinesPlay) {
+ DisplayTwoMachinesTitle();
+ } else {
+ DisplayTitle("");
+ }
+ continue;
+ }
+ if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
+ if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
+ if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
+ if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
+ if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
+ if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
+ if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
+ if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
+ if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
+ if (IntFeature(&p, "done", &val, 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)) {
+ ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
+ if(cps->nrOptions >= MAX_OPTIONS) {
+ cps->nrOptions--;
+ sprintf(buf, "%s engine has too many options\n", cps->which);
+ DisplayError(buf, 0);
+ }
+ continue;
+ }
+ if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
+ /* End of additions by HGM */
+
+ /* unknown feature: complain and skip */
+ q = p;
+ while (*q && *q != '=') q++;
+ sprintf(buf, "rejected %.*s\n", q-p, p);
+ SendToProgram(buf, cps);
+ p = q;
+ if (*p == '=') {
+ p++;
+ if (*p == '\"') {
+ p++;
+ while (*p && *p != '\"') p++;
+ if (*p == '\"') p++;
+ } else {
+ while (*p && *p != ' ') p++;
+ }
+ }
+ }
+
+}
+
+void
+PeriodicUpdatesEvent(newState)
+ int newState;
+{
+ if (newState == appData.periodicUpdates)
+ return;
+
+ appData.periodicUpdates=newState;
+
+ /* Display type changes, so update it now */
+ DisplayAnalysis();
+
+ /* Get the ball rolling again... */
+ if (newState) {
+ AnalysisPeriodicEvent(1);
+ StartAnalysisClock();
+ }
+}
+
+void
+PonderNextMoveEvent(newState)
+ int newState;
+{
+ if (newState == appData.ponderNextMove) return;
+ if (gameMode == EditPosition) EditPositionDone();
+ if (newState) {
+ SendToProgram("hard\n", &first);
+ if (gameMode == TwoMachinesPlay) {
+ SendToProgram("hard\n", &second);
+ }
+ } else {
+ SendToProgram("easy\n", &first);
+ thinkOutput[0] = NULLCHAR;
+ if (gameMode == TwoMachinesPlay) {
+ SendToProgram("easy\n", &second);
+ }
+ }
+ appData.ponderNextMove = newState;
+}
+
+void
+NewSettingEvent(option, command, value)
+ char *command;
+ int option, value;
+{
+ char buf[MSG_SIZ];
+
+ if (gameMode == EditPosition) EditPositionDone();
+ 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);
+ }
+ } else {
+ SendToProgram("nopost\n", &first);
+ thinkOutput[0] = NULLCHAR;
+ if (gameMode == TwoMachinesPlay) {
+ SendToProgram("nopost\n", &second);
+ }
+ }
+// appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
+}
+
+void
+AskQuestionEvent(title, question, replyPrefix, which)
+ char *title; char *question; char *replyPrefix; char *which;
+{
+ ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
+ if (pr == NoProc) return;
+ AskQuestion(title, question, replyPrefix, pr);
+}
+
+void
+DisplayMove(moveNumber)
+ int moveNumber;
+{
+ char message[MSG_SIZ];
+ char res[MSG_SIZ];
+ char cpThinkOutput[MSG_SIZ];
+
+ if(appData.noGUI) return; // [HGM] fast: suppress display of moves
+
+ if (moveNumber == forwardMostMove - 1 ||
+ gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
+
+ safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
+
+ if (strchr(cpThinkOutput, '\n')) {
+ *strchr(cpThinkOutput, '\n') = NULLCHAR;
+ }
+ } 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) {
+ sprintf(res, " %s", PGNResult(gameInfo.result));
+ } else {
+ sprintf(res, " {%s} %s",
+ gameInfo.resultDetails, PGNResult(gameInfo.result));
+ }
+ } else {
+ res[0] = NULLCHAR;
+ }
+
+ if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
+ DisplayMessage(res, cpThinkOutput);
+ } else {
+ sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
+ WhiteOnMove(moveNumber) ? " " : ".. ",
+ parseList[moveNumber], res);
+ DisplayMessage(message, cpThinkOutput);
+ }
+}
+
+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];
+ char lst[MSG_SIZ / 2];
+ double nps;
+ static char *xtra[] = { "", " (--)", " (++)" };
+ int h, m, s, cs;
+
+ if (programStats.time == 0) {
+ programStats.time = 1;
+ }
+
+ if (programStats.got_only_move) {
+ safeStrCpy(buf, programStats.movelist, sizeof(buf));
+ } else {
+ safeStrCpy( lst, programStats.movelist, sizeof(lst));
+
+ 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, lst,
+ only_one_move(lst)?
+ 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,
+ lst,
+ only_one_move(lst)?
+ 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,
+ lst,
+ only_one_move(lst)?
+ 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 (text != NULL)
+ CommentPopUp(title, text);
+}
+
+/* This routine sends a ^C interrupt to gnuchess, to awaken it if it
+ * might be busy thinking or pondering. It can be omitted if your
+ * gnuchess is configured to stop thinking immediately on any user
+ * input. However, that gnuchess feature depends on the FIONREAD
+ * ioctl, which does not work properly on some flavors of Unix.
+ */
+void
+Attention(cps)
+ ChessProgramState *cps;
+{
+#if ATTENTION
+ if (!cps->useSigint) return;
+ if (appData.noChessProgram || (cps->pr == NoProc)) return;
+ switch (gameMode) {
+ case MachinePlaysWhite:
+ case MachinePlaysBlack:
+ case TwoMachinesPlay:
+ case IcsPlayingWhite:
+ case IcsPlayingBlack:
+ case AnalyzeMode:
+ case AnalyzeFile:
+ /* Skip if we know it isn't thinking */
+ if (!cps->maybeThinking) return;
+ if (appData.debugMode)
+ fprintf(debugFP, "Interrupting %s\n", cps->which);
+ InterruptChildProcess(cps->pr);
+ cps->maybeThinking = FALSE;
+ break;
+ default:
+ break;
+ }
+#endif /*ATTENTION*/
+}
+
+int
+CheckFlags()
+{
+ if (whiteTimeRemaining <= 0) {
+ if (!whiteFlag) {
+ whiteFlag = TRUE;
+ if (appData.icsActive) {
+ if (appData.autoCallFlag &&
+ gameMode == IcsPlayingBlack && !blackFlag) {
+ SendToICS(ics_prefix);
+ SendToICS("flag\n");
+ }
+ } else {
+ if (blackFlag) {
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
+ } else {
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
+ if (appData.autoCallFlag) {
+ GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
+ return TRUE;
+ }
+ }
+ }
+ }
+ }
+ if (blackTimeRemaining <= 0) {
+ if (!blackFlag) {
+ blackFlag = TRUE;
+ if (appData.icsActive) {
+ if (appData.autoCallFlag &&
+ gameMode == IcsPlayingWhite && !whiteFlag) {
+ SendToICS(ics_prefix);
+ SendToICS("flag\n");
+ }
+ } else {
+ if (whiteFlag) {
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
+ } else {
+ if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
+ if (appData.autoCallFlag) {
+ GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
+ return TRUE;
+ }
+ }
+ }
+ }
+ }
+ return FALSE;
+}
+
+void
+CheckTimeControl()
+{
+ if (!appData.clockMode || appData.icsActive ||
+ gameMode == PlayFromGameFile || forwardMostMove == 0) return;
+
+ /*
+ * add time to clocks when time control is achieved ([HGM] now also used for increment)
+ */
+ if ( !WhiteOnMove(forwardMostMove) )
+ /* White made time control */
+ whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+ /* [HGM] time odds: correct new time quota for time odds! */
+ / WhitePlayer()->timeOdds;
+ else
+ /* Black made time control */
+ blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
+ / WhitePlayer()->other->timeOdds;
+}
+
+void
+DisplayBothClocks()
+{
+ int wom = gameMode == EditPosition ?
+ !blackPlaysFirst : WhiteOnMove(currentMove);
+ DisplayWhiteClock(whiteTimeRemaining, wom);
+ DisplayBlackClock(blackTimeRemaining, !wom);
+}
+
+
+/* Timekeeping seems to be a portability nightmare. I think everyone
+ has ftime(), but I'm really not sure, so I'm including some ifdefs
+ to use other calls if you don't. Clocks will be less accurate if
+ you have neither ftime nor gettimeofday.
+*/
+
+/* VS 2008 requires the #include outside of the function */
+#if !HAVE_GETTIMEOFDAY && HAVE_FTIME
+#include <sys/timeb.h>
+#endif
+
+/* Get the current time as a TimeMark */
+void
+GetTimeMark(tm)
+ TimeMark *tm;
+{
+#if HAVE_GETTIMEOFDAY
+
+ struct timeval timeVal;
+ struct timezone timeZone;
+
+ gettimeofday(&timeVal, &timeZone);
+ tm->sec = (long) timeVal.tv_sec;
+ tm->ms = (int) (timeVal.tv_usec / 1000L);
+
+#else /*!HAVE_GETTIMEOFDAY*/
+#if HAVE_FTIME
+
+// include <sys/timeb.h> / moved to just above start of function
+ struct timeb timeB;
+
+ ftime(&timeB);
+ tm->sec = (long) timeB.time;
+ tm->ms = (int) timeB.millitm;
+
+#else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
+ tm->sec = (long) time(NULL);
+ tm->ms = 0;
+#endif
+#endif
+}
+
+/* Return the difference in milliseconds between two
+ time marks. We assume the difference will fit in a long!
+*/
+long
+SubtractTimeMarks(tm2, tm1)
+ TimeMark *tm2, *tm1;
+{
+ return 1000L*(tm2->sec - tm1->sec) +
+ (long) (tm2->ms - tm1->ms);
+}
+
+
+/*
+ * Code to manage the game clocks.
+ *
+ * In tournament play, black starts the clock and then white makes a move.
+ * 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.
+ */
+
+static TimeMark tickStartTM;
+static long intendedTickLength;
+
+long
+NextTickLength(timeRemaining)
+ long timeRemaining;
+{
+ long nominalTickLength, nextTickLength;
+
+ if (timeRemaining > 0L && timeRemaining <= 10000L)
+ nominalTickLength = 100L;
+ else
+ nominalTickLength = 1000L;
+ nextTickLength = timeRemaining % nominalTickLength;
+ if (nextTickLength <= 0) nextTickLength += nominalTickLength;
+
+ 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()
+{
+ (void) StopClockTimer();
+ if (appData.icsActive) {
+ whiteTimeRemaining = blackTimeRemaining = 0;
+ } else { /* [HGM] correct new time quote for time odds */
+ whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
+ blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
+ }
+ if (whiteFlag || blackFlag) {
+ DisplayTitle("");
+ whiteFlag = blackFlag = FALSE;
+ }
+ DisplayBothClocks();
+}
+
+#define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
+
+/* Decrement running clock by amount of time that has passed */
+void
+DecrementClocks()
+{
+ long timeRemaining;
+ long lastTickLength, fudge;
+ TimeMark now;
+
+ if (!appData.clockMode) return;
+ if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
+
+ GetTimeMark(&now);
+
+ lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
+
+ /* Fudge if we woke up a little too soon */
+ fudge = intendedTickLength - lastTickLength;
+ 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);
+
+ /* 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.
+ */
+
+ if (appData.icsActive && appData.icsAlarm) {
+
+ /* make sure we are dealing with the user's clock */
+ if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
+ ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
+ )) return;
+
+ if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
+ alarmSounded = FALSE;
+ } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
+ PlayAlarmSound();
+ alarmSounded = TRUE;
+ }
+ }
+}
+
+
+/* A player has just moved, so stop the previously running
+ clock and (if in clock mode) start the other one.
+ We redisplay both clocks in case we're in ICS mode, because
+ ICS gives us an update to both clocks after every move.
+ Note that this routine is called *after* forwardMostMove
+ is updated, so the last fractional tick must be subtracted
+ from the color that is *not* on move now.
+*/
+void
+SwitchClocks()
+{
+ long lastTickLength;
+ TimeMark now;
+ int flagged = FALSE;
+
+ GetTimeMark(&now);
+
+ 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 {
+ 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();
+ }
+ CheckTimeControl();
+
+ if (flagged || !appData.clockMode) return;
+
+ switch (gameMode) {
+ case MachinePlaysBlack:
+ case MachinePlaysWhite:
+ case BeginningOfGame:
+ if (pausing) return;
+ break;
+
+ case EditGame:
+ case PlayFromGameFile:
+ case IcsExamining:
+ return;
+
+ default:
+ break;
+ }
+
+ tickStartTM = now;
+ intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
+ whiteTimeRemaining : blackTimeRemaining);
+ StartClockTimer(intendedTickLength);
+}
+
+
+/* Stop both clocks */
+void
+StopClocks()
+{
+ long lastTickLength;
+ TimeMark now;
+
+ if (!StopClockTimer()) return;
+ if (!appData.clockMode) return;
+
+ GetTimeMark(&now);
+
+ 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
+StartClocks()
+{
+ (void) StopClockTimer(); /* in case it was running already */
+ DisplayBothClocks();
+ if (CheckFlags()) return;
+
+ if (!appData.clockMode) return;
+ if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
+
+ 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);
+}
+
+char *
+TimeString(ms)
+ long 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 );
+
+ sprintf(buf, " %03.1f ", tenths/10.0);
+ return buf;
+ }
+
+ /* convert milliseconds to seconds, rounding up */
+ /* use floating point to avoid strangeness of integer division
+ with negative dividends on many machines */
+ second = (long) floor(((double) (ms + 999L)) / 1000.0);
+
+ if (second < 0) {
+ 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);
+ else if (hour > 0)
+ sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
+ else
+ sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
+
+ return buf;
+}
+
+
+/*
+ * This is necessary because some C libraries aren't ANSI C compliant yet.
+ */
+char *
+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;
+}
+
+char *
+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]))
+ break;
+ }
+ if (j == length) return string;
+ }
+
+ return NULL;
+}
+
+#ifndef _amigados
+int
+StrCaseCmp(s1, s2)
+ char *s1, *s2;
+{
+ char c1, c2;
+
+ for (;;) {
+ c1 = ToLower(*s1++);
+ c2 = ToLower(*s2++);
+ if (c1 > c2) return 1;
+ if (c1 < c2) return -1;
+ if (c1 == NULLCHAR) return 0;
+ }
+}
+
+
+int
+ToLower(c)
+ int c;
+{
+ return isupper(c) ? tolower(c) : c;
+}
+
+
+int
+ToUpper(c)
+ int c;
+{
+ return islower(c) ? toupper(c) : c;
+}
+#endif /* !_amigados */
+
+char *
+StrSave(s)
+ char *s;
+{
+ char *ret;
+
+ if ((ret = (char *) malloc(strlen(s) + 1))) {
+ strcpy(ret, s);
+ }
+ return ret;
+}
+
+char *
+StrSavePtr(s, savePtr)
+ char *s, **savePtr;
+{
+ if (*savePtr) {
+ free(*savePtr);
+ }
+ if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
+ strcpy(*savePtr, s);
+ }
+ return(*savePtr);
+}
+
+char *
+PGNDate()
+{
+ time_t clock;
+ struct tm *tm;
+ char buf[MSG_SIZ];
+
+ clock = time((time_t *)NULL);
+ tm = localtime(&clock);
+ sprintf(buf, "%04d.%02d.%02d",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
+ return StrSave(buf);
+}
+
+
+char *
+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_HEIGHT - 1; i >= 0; i--) {
+ emptycount = 0;
+ for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
+ if (boards[move][i][j] == EmptySquare) {
+ emptycount++;
+ } else { ChessSquare piece = boards[move][i][j];
+ if (emptycount > 0) {
+ if(emptycount<10) /* [HGM] can be >= 10 */
+ *p++ = '0' + emptycount;
+ else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
+ emptycount = 0;
+ }
+ if(PieceToChar(piece) == '+') {
+ /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
+ *p++ = '+';
+ piece = (ChessSquare)(DEMOTED piece);
+ }
+ *p++ = PieceToChar(piece);
+ if(p[-1] == '~') {
+ /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
+ p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
+ *p++ = '~';
+ }
+ }
+ }
+ if (emptycount > 0) {
+ 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<gameInfo.holdingsSize; i++) { /* white holdings */
+ piece = boards[move][i][BOARD_WIDTH-1];
+ if( piece != EmptySquare )
+ for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
+ *p++ = PieceToChar(piece);
+ }
+ for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
+ piece = boards[move][BOARD_HEIGHT-i-1][0];
+ if( piece != EmptySquare )
+ for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
+ *p++ = PieceToChar(piece);
+ }
+
+ if( q == p ) *p++ = '-';
+ *p++ = ']';
+ *p++ = ' ';
+ }
+
+ /* Active color */
+ *p++ = whiteToPlay ? 'w' : 'b';
+ *p++ = ' ';
+
+ if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
+ while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
+ } else {
+ if(nrCastlingRights) {
+ q = p;
+ if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
+ /* [HGM] write directly from rights */
+ if(castlingRights[move][2] >= 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] - 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 + AAA;
+ *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
+ } else {
+ *p++ = '-';
+ }
+ } else {
+ *p++ = '-';
+ }
+ *p++ = ' ';
+ }
+ }
+
+ /* [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;
+ 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<BOARD_HEIGHT; i++) {
+ board[i][0] = EmptySquare; /* black holdings */
+ board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
+ board[i][1] = (ChessSquare) 0; /* black counts */
+ board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
+ }
+ }
+
+ /* Piece placement data */
+ for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
+ j = 0;
+ for (;;) {
+ if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
+ if (*p == '/') p++;
+ emptycount = gameInfo.boardWidth - j;
+ while (emptycount--)
+ board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
+ break;
+#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';
+ 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;
+ }
+ }
+ }
+ 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++) {
+ case 'w':
+ *blackPlaysFirst = FALSE;
+ break;
+ case 'b':
+ *blackPlaysFirst = TRUE;
+ break;
+ default:
+ return FALSE;
+ }
+
+ /* [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<nrCastlingRights; i++ ) {
+ FENcastlingRights[i] =
+ gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
+ } /* assume possible unless obviously impossible */
+ if(initialRights[0]>=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<nrCastlingRights; i++ ) {
+ FENcastlingRights[i] = -1;
+ }
+ }
+ while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
+ (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
+ ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
+ ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
+ char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
+
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
+ if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
+ if(board[0 ][i] == WhiteKing) whiteKingFile = i;
+ }
+ switch(c) {
+ case'K':
+ for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
+ FENcastlingRights[0] = i != whiteKingFile ? i : -1;
+ FENcastlingRights[2] = whiteKingFile;
+ break;
+ case'Q':
+ for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+ FENcastlingRights[1] = i != whiteKingFile ? i : -1;
+ FENcastlingRights[2] = whiteKingFile;
+ break;
+ case'k':
+ for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
+ FENcastlingRights[3] = i != blackKingFile ? i : -1;
+ FENcastlingRights[5] = blackKingFile;
+ break;
+ case'q':
+ for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+ FENcastlingRights[4] = i != blackKingFile ? i : -1;
+ FENcastlingRights[5] = blackKingFile;
+ case '-':
+ break;
+ default: /* FRC castlings */
+ if(c >= 'a') { /* black rights */
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+ if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
+ if(i == BOARD_RGHT) break;
+ FENcastlingRights[5] = i;
+ c -= AAA;
+ if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
+ board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
+ if(c > i)
+ FENcastlingRights[3] = c;
+ else
+ FENcastlingRights[4] = c;
+ } else { /* white rights */
+ for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
+ if(board[0][i] == WhiteKing) break;
+ if(i == BOARD_RGHT) break;
+ FENcastlingRights[2] = i;
+ c -= AAA - 'a' + 'A';
+ if(board[0][c] >= WhiteKing) break;
+ if(c > i)
+ FENcastlingRights[0] = c;
+ else
+ FENcastlingRights[1] = c;
+ }
+ }
+ }
+ if (appData.debugMode) {
+ fprintf(debugFP, "FEN castling rights:");
+ for(i=0; i<nrCastlingRights; i++)
+ fprintf(debugFP, " %d", FENcastlingRights[i]);
+ fprintf(debugFP, "\n");
+ }
+
+ while(*p==' ') p++;
+ }
+
+ /* read e.p. field in games that know e.p. capture */
+ if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
+ if(*p=='-') {
+ p++; FENepStatus = EP_NONE;
+ } else {
+ char c = *p++ - AAA;
+
+ if(c < BOARD_LEFT || c >= 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)
+{
+ if (fen != NULL) {
+ Board initial_position;
+
+ if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
+ DisplayError(_("Bad FEN position in clipboard"), 0);
+ return ;
+ } else {
+ int savedBlackPlaysFirst = blackPlaysFirst;
+ EditPositionEvent();
+ blackPlaysFirst = savedBlackPlaysFirst;
+ CopyBoard(boards[0], initial_position);
+ /* [HGM] copy FEN attributes as well */
+ { int i;
+ initialRulePlies = FENrulePlies;
+ epStatus[0] = FENepStatus;
+ for( i=0; i<nrCastlingRights; i++ )
+ castlingRights[0][i] = FENcastlingRights[i];
+ }
+ EditPositionDone();
+ DisplayBothClocks();
+ DrawPosition(FALSE, boards[currentMove]);
+ }
+ }
+}