2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
472 int mute; // mute all sounds
474 ChessSquare FIDEArray[2][BOARD_SIZE] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499 BlackKing, BlackBishop, BlackKnight, BlackRook }
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
529 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
534 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
536 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
541 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
543 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
549 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
551 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
554 #define GothicArray CapablancaArray
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
560 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
562 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
565 #define FalconArray CapablancaArray
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
587 Board initialPosition;
590 /* Convert str to a rating. Checks for special cases of "----",
592 "++++", etc. Also strips ()'s */
594 string_to_rating(str)
597 while(*str && !isdigit(*str)) ++str;
599 return 0; /* One of the special "no rating" cases */
607 /* Init programStats */
608 programStats.movelist[0] = 0;
609 programStats.depth = 0;
610 programStats.nr_moves = 0;
611 programStats.moves_left = 0;
612 programStats.nodes = 0;
613 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
614 programStats.score = 0;
615 programStats.got_only_move = 0;
616 programStats.got_fail = 0;
617 programStats.line_is_book = 0;
623 int matched, min, sec;
625 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
627 GetTimeMark(&programStartTime);
628 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
631 programStats.ok_to_send = 1;
632 programStats.seen_stat = 0;
635 * Initialize game list
641 * Internet chess server status
643 if (appData.icsActive) {
644 appData.matchMode = FALSE;
645 appData.matchGames = 0;
647 appData.noChessProgram = !appData.zippyPlay;
649 appData.zippyPlay = FALSE;
650 appData.zippyTalk = FALSE;
651 appData.noChessProgram = TRUE;
653 if (*appData.icsHelper != NULLCHAR) {
654 appData.useTelnet = TRUE;
655 appData.telnetProgram = appData.icsHelper;
658 appData.zippyTalk = appData.zippyPlay = FALSE;
661 /* [AS] Initialize pv info list [HGM] and game state */
665 for( i=0; i<MAX_MOVES; i++ ) {
666 pvInfoList[i].depth = -1;
668 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
673 * Parse timeControl resource
675 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676 appData.movesPerSession)) {
678 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679 DisplayFatalError(buf, 0, 2);
683 * Parse searchTime resource
685 if (*appData.searchTime != NULLCHAR) {
686 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
688 searchTime = min * 60;
689 } else if (matched == 2) {
690 searchTime = min * 60 + sec;
693 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694 DisplayFatalError(buf, 0, 2);
698 /* [AS] Adjudication threshold */
699 adjudicateLossThreshold = appData.adjudicateLossThreshold;
701 first.which = "first";
702 second.which = "second";
703 first.maybeThinking = second.maybeThinking = FALSE;
704 first.pr = second.pr = NoProc;
705 first.isr = second.isr = NULL;
706 first.sendTime = second.sendTime = 2;
707 first.sendDrawOffers = 1;
708 if (appData.firstPlaysBlack) {
709 first.twoMachinesColor = "black\n";
710 second.twoMachinesColor = "white\n";
712 first.twoMachinesColor = "white\n";
713 second.twoMachinesColor = "black\n";
715 first.program = appData.firstChessProgram;
716 second.program = appData.secondChessProgram;
717 first.host = appData.firstHost;
718 second.host = appData.secondHost;
719 first.dir = appData.firstDirectory;
720 second.dir = appData.secondDirectory;
721 first.other = &second;
722 second.other = &first;
723 first.initString = appData.initString;
724 second.initString = appData.secondInitString;
725 first.computerString = appData.firstComputerString;
726 second.computerString = appData.secondComputerString;
727 first.useSigint = second.useSigint = TRUE;
728 first.useSigterm = second.useSigterm = TRUE;
729 first.reuse = appData.reuseFirst;
730 second.reuse = appData.reuseSecond;
731 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
732 second.nps = appData.secondNPS;
733 first.useSetboard = second.useSetboard = FALSE;
734 first.useSAN = second.useSAN = FALSE;
735 first.usePing = second.usePing = FALSE;
736 first.lastPing = second.lastPing = 0;
737 first.lastPong = second.lastPong = 0;
738 first.usePlayother = second.usePlayother = FALSE;
739 first.useColors = second.useColors = TRUE;
740 first.useUsermove = second.useUsermove = FALSE;
741 first.sendICS = second.sendICS = FALSE;
742 first.sendName = second.sendName = appData.icsActive;
743 first.sdKludge = second.sdKludge = FALSE;
744 first.stKludge = second.stKludge = FALSE;
745 TidyProgramName(first.program, first.host, first.tidy);
746 TidyProgramName(second.program, second.host, second.tidy);
747 first.matchWins = second.matchWins = 0;
748 strcpy(first.variants, appData.variant);
749 strcpy(second.variants, appData.variant);
750 first.analysisSupport = second.analysisSupport = 2; /* detect */
751 first.analyzing = second.analyzing = FALSE;
752 first.initDone = second.initDone = FALSE;
754 /* New features added by Tord: */
755 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757 /* End of new features added by Tord. */
758 first.fenOverride = appData.fenOverride1;
759 second.fenOverride = appData.fenOverride2;
761 /* [HGM] time odds: set factor for each machine */
762 first.timeOdds = appData.firstTimeOdds;
763 second.timeOdds = appData.secondTimeOdds;
765 if(appData.timeOddsMode) {
766 norm = first.timeOdds;
767 if(norm > second.timeOdds) norm = second.timeOdds;
769 first.timeOdds /= norm;
770 second.timeOdds /= norm;
773 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774 first.accumulateTC = appData.firstAccumulateTC;
775 second.accumulateTC = appData.secondAccumulateTC;
776 first.maxNrOfSessions = second.maxNrOfSessions = 1;
779 first.debug = second.debug = FALSE;
780 first.supportsNPS = second.supportsNPS = UNKNOWN;
783 first.optionSettings = appData.firstOptions;
784 second.optionSettings = appData.secondOptions;
786 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788 first.isUCI = appData.firstIsUCI; /* [AS] */
789 second.isUCI = appData.secondIsUCI; /* [AS] */
790 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
793 if (appData.firstProtocolVersion > PROTOVER ||
794 appData.firstProtocolVersion < 1) {
796 sprintf(buf, _("protocol version %d not supported"),
797 appData.firstProtocolVersion);
798 DisplayFatalError(buf, 0, 2);
800 first.protocolVersion = appData.firstProtocolVersion;
803 if (appData.secondProtocolVersion > PROTOVER ||
804 appData.secondProtocolVersion < 1) {
806 sprintf(buf, _("protocol version %d not supported"),
807 appData.secondProtocolVersion);
808 DisplayFatalError(buf, 0, 2);
810 second.protocolVersion = appData.secondProtocolVersion;
813 if (appData.icsActive) {
814 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
815 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816 appData.clockMode = FALSE;
817 first.sendTime = second.sendTime = 0;
821 /* Override some settings from environment variables, for backward
822 compatibility. Unfortunately it's not feasible to have the env
823 vars just set defaults, at least in xboard. Ugh.
825 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
830 if (appData.noChessProgram) {
831 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832 sprintf(programVersion, "%s", PACKAGE_STRING);
837 while (*q != ' ' && *q != NULLCHAR) q++;
839 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841 sprintf(programVersion, "%s + ", PACKAGE_STRING);
842 strncat(programVersion, p, q - p);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
850 if (!appData.icsActive) {
852 /* Check for variants that are supported only in ICS mode,
853 or not at all. Some that are accepted here nevertheless
854 have bugs; see comments below.
856 VariantClass variant = StringToVariant(appData.variant);
858 case VariantBughouse: /* need four players and two boards */
859 case VariantKriegspiel: /* need to hide pieces and move details */
860 /* case VariantFischeRandom: (Fabien: moved below) */
861 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862 DisplayFatalError(buf, 0, 2);
866 case VariantLoadable:
876 sprintf(buf, _("Unknown variant name %s"), appData.variant);
877 DisplayFatalError(buf, 0, 2);
880 case VariantXiangqi: /* [HGM] repetition rules not implemented */
881 case VariantFairy: /* [HGM] TestLegality definitely off! */
882 case VariantGothic: /* [HGM] should work */
883 case VariantCapablanca: /* [HGM] should work */
884 case VariantCourier: /* [HGM] initial forced moves not implemented */
885 case VariantShogi: /* [HGM] drops not tested for legality */
886 case VariantKnightmate: /* [HGM] should work */
887 case VariantCylinder: /* [HGM] untested */
888 case VariantFalcon: /* [HGM] untested */
889 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890 offboard interposition not understood */
891 case VariantNormal: /* definitely works! */
892 case VariantWildCastle: /* pieces not automatically shuffled */
893 case VariantNoCastle: /* pieces not automatically shuffled */
894 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895 case VariantLosers: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantSuicide: /* should work except for win condition,
898 and doesn't know captures are mandatory */
899 case VariantGiveaway: /* should work except for win condition,
900 and doesn't know captures are mandatory */
901 case VariantTwoKings: /* should work */
902 case VariantAtomic: /* should work except for win condition */
903 case Variant3Check: /* should work except for win condition */
904 case VariantShatranj: /* should work except for all win conditions */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1014 int matched, min, sec;
1016 matched = sscanf(tc, "%d:%d", &min, &sec);
1018 timeControl = min * 60 * 1000;
1019 } else if (matched == 2) {
1020 timeControl = (min * 60 + sec) * 1000;
1029 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033 else sprintf(buf, "+%s+%d", tc, ti);
1036 sprintf(buf, "+%d/%s", mps, tc);
1037 else sprintf(buf, "+%s", tc);
1039 fullTimeControlString = StrSave(buf);
1041 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1046 /* Parse second time control */
1049 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1057 timeControl_2 = tc2 * 1000;
1067 timeControl = tc1 * 1000;
1071 timeIncrement = ti * 1000; /* convert to ms */
1072 movesPerSession = 0;
1075 movesPerSession = mps;
1083 if (appData.debugMode) {
1084 fprintf(debugFP, "%s\n", programVersion);
1087 if (appData.matchGames > 0) {
1088 appData.matchMode = TRUE;
1089 } else if (appData.matchMode) {
1090 appData.matchGames = 1;
1092 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093 appData.matchGames = appData.sameColorGames;
1094 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1102 /* kludge: allow timeout for initial "feature" commands */
1104 DisplayMessage("", _("Starting chess program"));
1105 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 InitBackEnd3 P((void))
1112 GameMode initialMode;
1116 InitChessProgram(&first, startedFromSetupPosition);
1119 if (appData.icsActive) {
1121 /* [DM] Make a console window if needed [HGM] merged ifs */
1126 if (*appData.icsCommPort != NULLCHAR) {
1127 sprintf(buf, _("Could not open comm port %s"),
1128 appData.icsCommPort);
1130 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131 appData.icsHost, appData.icsPort);
1133 DisplayFatalError(buf, err, 1);
1138 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1140 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141 } else if (appData.noChessProgram) {
1147 if (*appData.cmailGameName != NULLCHAR) {
1149 OpenLoopback(&cmailPR);
1151 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1155 DisplayMessage("", "");
1156 if (StrCaseCmp(appData.initialMode, "") == 0) {
1157 initialMode = BeginningOfGame;
1158 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159 initialMode = TwoMachinesPlay;
1160 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161 initialMode = AnalyzeFile;
1162 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163 initialMode = AnalyzeMode;
1164 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165 initialMode = MachinePlaysWhite;
1166 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167 initialMode = MachinePlaysBlack;
1168 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169 initialMode = EditGame;
1170 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171 initialMode = EditPosition;
1172 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173 initialMode = Training;
1175 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176 DisplayFatalError(buf, 0, 2);
1180 if (appData.matchMode) {
1181 /* Set up machine vs. machine match */
1182 if (appData.noChessProgram) {
1183 DisplayFatalError(_("Can't have a match with no chess programs"),
1189 if (*appData.loadGameFile != NULLCHAR) {
1190 int index = appData.loadGameIndex; // [HGM] autoinc
1191 if(index<0) lastIndex = index = 1;
1192 if (!LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameFile, FALSE)) {
1195 DisplayFatalError(_("Bad game file"), 0, 1);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 int index = appData.loadPositionIndex; // [HGM] autoinc
1200 if(index<0) lastIndex = index = 1;
1201 if (!LoadPositionFromFile(appData.loadPositionFile,
1203 appData.loadPositionFile)) {
1204 DisplayFatalError(_("Bad position file"), 0, 1);
1209 } else if (*appData.cmailGameName != NULLCHAR) {
1210 /* Set up cmail mode */
1211 ReloadCmailMsgEvent(TRUE);
1213 /* Set up other modes */
1214 if (initialMode == AnalyzeFile) {
1215 if (*appData.loadGameFile == NULLCHAR) {
1216 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1220 if (*appData.loadGameFile != NULLCHAR) {
1221 (void) LoadGameFromFile(appData.loadGameFile,
1222 appData.loadGameIndex,
1223 appData.loadGameFile, TRUE);
1224 } else if (*appData.loadPositionFile != NULLCHAR) {
1225 (void) LoadPositionFromFile(appData.loadPositionFile,
1226 appData.loadPositionIndex,
1227 appData.loadPositionFile);
1228 /* [HGM] try to make self-starting even after FEN load */
1229 /* to allow automatic setup of fairy variants with wtm */
1230 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231 gameMode = BeginningOfGame;
1232 setboardSpoiledMachineBlack = 1;
1234 /* [HGM] loadPos: make that every new game uses the setup */
1235 /* from file as long as we do not switch variant */
1236 if(!blackPlaysFirst) { int i;
1237 startedFromPositionFile = TRUE;
1238 CopyBoard(filePosition, boards[0]);
1239 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1242 if (initialMode == AnalyzeMode) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1252 } else if (initialMode == AnalyzeFile) {
1253 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254 ShowThinkingEvent();
1256 AnalysisPeriodicEvent(1);
1257 } else if (initialMode == MachinePlaysWhite) {
1258 if (appData.noChessProgram) {
1259 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1263 if (appData.icsActive) {
1264 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1268 MachineWhiteEvent();
1269 } else if (initialMode == MachinePlaysBlack) {
1270 if (appData.noChessProgram) {
1271 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1275 if (appData.icsActive) {
1276 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1280 MachineBlackEvent();
1281 } else if (initialMode == TwoMachinesPlay) {
1282 if (appData.noChessProgram) {
1283 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1287 if (appData.icsActive) {
1288 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1293 } else if (initialMode == EditGame) {
1295 } else if (initialMode == EditPosition) {
1296 EditPositionEvent();
1297 } else if (initialMode == Training) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308 * Establish will establish a contact to a remote host.port.
1309 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310 * used to talk to the host.
1311 * Returns 0 if okay, error code if not.
1318 if (*appData.icsCommPort != NULLCHAR) {
1319 /* Talk to the host through a serial comm port */
1320 return OpenCommPort(appData.icsCommPort, &icsPR);
1322 } else if (*appData.gateway != NULLCHAR) {
1323 if (*appData.remoteShell == NULLCHAR) {
1324 /* Use the rcmd protocol to run telnet program on a gateway host */
1325 snprintf(buf, sizeof(buf), "%s %s %s",
1326 appData.telnetProgram, appData.icsHost, appData.icsPort);
1327 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1330 /* Use the rsh program to run telnet program on a gateway host */
1331 if (*appData.remoteUser == NULLCHAR) {
1332 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333 appData.gateway, appData.telnetProgram,
1334 appData.icsHost, appData.icsPort);
1336 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337 appData.remoteShell, appData.gateway,
1338 appData.remoteUser, appData.telnetProgram,
1339 appData.icsHost, appData.icsPort);
1341 return StartChildProcess(buf, "", &icsPR);
1344 } else if (appData.useTelnet) {
1345 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1348 /* TCP socket interface differs somewhat between
1349 Unix and NT; handle details in the front end.
1351 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1356 show_bytes(fp, buf, count)
1362 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363 fprintf(fp, "\\%03o", *buf & 0xff);
1372 /* Returns an errno value */
1374 OutputMaybeTelnet(pr, message, count, outError)
1380 char buf[8192], *p, *q, *buflim;
1381 int left, newcount, outcount;
1383 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384 *appData.gateway != NULLCHAR) {
1385 if (appData.debugMode) {
1386 fprintf(debugFP, ">ICS: ");
1387 show_bytes(debugFP, message, count);
1388 fprintf(debugFP, "\n");
1390 return OutputToProcess(pr, message, count, outError);
1393 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1400 if (appData.debugMode) {
1401 fprintf(debugFP, ">ICS: ");
1402 show_bytes(debugFP, buf, newcount);
1403 fprintf(debugFP, "\n");
1405 outcount = OutputToProcess(pr, buf, newcount, outError);
1406 if (outcount < newcount) return -1; /* to be sure */
1413 } else if (((unsigned char) *p) == TN_IAC) {
1414 *q++ = (char) TN_IAC;
1421 if (appData.debugMode) {
1422 fprintf(debugFP, ">ICS: ");
1423 show_bytes(debugFP, buf, newcount);
1424 fprintf(debugFP, "\n");
1426 outcount = OutputToProcess(pr, buf, newcount, outError);
1427 if (outcount < newcount) return -1; /* to be sure */
1432 read_from_player(isr, closure, message, count, error)
1439 int outError, outCount;
1440 static int gotEof = 0;
1442 /* Pass data read from player on to ICS */
1445 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 } else if (count < 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452 } else if (gotEof++ > 0) {
1453 RemoveInputSource(isr);
1454 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1460 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461 SendToICS("date\n");
1462 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469 int count, outCount, outError;
1471 if (icsPR == NULL) return;
1474 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475 if (outCount < count) {
1476 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480 /* This is used for sending logon scripts to the ICS. Sending
1481 without a delay causes problems when using timestamp on ICC
1482 (at least on my machine). */
1484 SendToICSDelayed(s,msdelay)
1488 int count, outCount, outError;
1490 if (icsPR == NULL) return;
1493 if (appData.debugMode) {
1494 fprintf(debugFP, ">ICS: ");
1495 show_bytes(debugFP, s, count);
1496 fprintf(debugFP, "\n");
1498 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1500 if (outCount < count) {
1501 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 /* Remove all highlighting escape sequences in s
1507 Also deletes any suffix starting with '('
1510 StripHighlightAndTitle(s)
1513 static char retbuf[MSG_SIZ];
1516 while (*s != NULLCHAR) {
1517 while (*s == '\033') {
1518 while (*s != NULLCHAR && !isalpha(*s)) s++;
1519 if (*s != NULLCHAR) s++;
1521 while (*s != NULLCHAR && *s != '\033') {
1522 if (*s == '(' || *s == '[') {
1533 /* Remove all highlighting escape sequences in s */
1538 static char retbuf[MSG_SIZ];
1541 while (*s != NULLCHAR) {
1542 while (*s == '\033') {
1543 while (*s != NULLCHAR && !isalpha(*s)) s++;
1544 if (*s != NULLCHAR) s++;
1546 while (*s != NULLCHAR && *s != '\033') {
1554 char *variantNames[] = VARIANT_NAMES;
1559 return variantNames[v];
1563 /* Identify a variant from the strings the chess servers use or the
1564 PGN Variant tag names we use. */
1571 VariantClass v = VariantNormal;
1572 int i, found = FALSE;
1577 /* [HGM] skip over optional board-size prefixes */
1578 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580 while( *e++ != '_');
1583 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584 if (StrCaseStr(e, variantNames[i])) {
1585 v = (VariantClass) i;
1592 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593 || StrCaseStr(e, "wild/fr")
1594 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595 v = VariantFischeRandom;
1596 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597 (i = 1, p = StrCaseStr(e, "w"))) {
1599 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606 case 0: /* FICS only, actually */
1608 /* Castling legal even if K starts on d-file */
1609 v = VariantWildCastle;
1614 /* Castling illegal even if K & R happen to start in
1615 normal positions. */
1616 v = VariantNoCastle;
1629 /* Castling legal iff K & R start in normal positions */
1635 /* Special wilds for position setup; unclear what to do here */
1636 v = VariantLoadable;
1639 /* Bizarre ICC game */
1640 v = VariantTwoKings;
1643 v = VariantKriegspiel;
1649 v = VariantFischeRandom;
1652 v = VariantCrazyhouse;
1655 v = VariantBughouse;
1661 /* Not quite the same as FICS suicide! */
1662 v = VariantGiveaway;
1668 v = VariantShatranj;
1671 /* Temporary names for future ICC types. The name *will* change in
1672 the next xboard/WinBoard release after ICC defines it. */
1710 v = VariantCapablanca;
1713 v = VariantKnightmate;
1719 v = VariantCylinder;
1725 v = VariantCapaRandom;
1728 v = VariantBerolina;
1740 /* Found "wild" or "w" in the string but no number;
1741 must assume it's normal chess. */
1745 sprintf(buf, _("Unknown wild type %d"), wnum);
1746 DisplayError(buf, 0);
1752 if (appData.debugMode) {
1753 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754 e, wnum, VariantName(v));
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763 advance *index beyond it, and set leftover_start to the new value of
1764 *index; else return FALSE. If pattern contains the character '*', it
1765 matches any sequence of characters not containing '\r', '\n', or the
1766 character following the '*' (if any), and the matched sequence(s) are
1767 copied into star_match.
1770 looking_at(buf, index, pattern)
1775 char *bufp = &buf[*index], *patternp = pattern;
1777 char *matchp = star_match[0];
1780 if (*patternp == NULLCHAR) {
1781 *index = leftover_start = bufp - buf;
1785 if (*bufp == NULLCHAR) return FALSE;
1786 if (*patternp == '*') {
1787 if (*bufp == *(patternp + 1)) {
1789 matchp = star_match[++star_count];
1793 } else if (*bufp == '\n' || *bufp == '\r') {
1795 if (*patternp == NULLCHAR)
1800 *matchp++ = *bufp++;
1804 if (*patternp != *bufp) return FALSE;
1811 SendToPlayer(data, length)
1815 int error, outCount;
1816 outCount = OutputToProcess(NoProc, data, length, &error);
1817 if (outCount < length) {
1818 DisplayFatalError(_("Error writing to display"), error, 1);
1823 PackHolding(packed, holding)
1835 switch (runlength) {
1846 sprintf(q, "%d", runlength);
1858 /* Telnet protocol requests from the front end */
1860 TelnetRequest(ddww, option)
1861 unsigned char ddww, option;
1863 unsigned char msg[3];
1864 int outCount, outError;
1866 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1868 if (appData.debugMode) {
1869 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1885 sprintf(buf1, "%d", ddww);
1894 sprintf(buf2, "%d", option);
1897 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1902 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1904 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911 if (!appData.icsActive) return;
1912 TelnetRequest(TN_DO, TN_ECHO);
1918 if (!appData.icsActive) return;
1919 TelnetRequest(TN_DONT, TN_ECHO);
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1925 /* put the holdings sent to us by the server on the board holdings area */
1926 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1930 if(gameInfo.holdingsWidth < 2) return;
1932 if( (int)lowestPiece >= BlackPawn ) {
1935 holdingsStartRow = BOARD_HEIGHT-1;
1938 holdingsColumn = BOARD_WIDTH-1;
1939 countsColumn = BOARD_WIDTH-2;
1940 holdingsStartRow = 0;
1944 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945 board[i][holdingsColumn] = EmptySquare;
1946 board[i][countsColumn] = (ChessSquare) 0;
1948 while( (p=*holdings++) != NULLCHAR ) {
1949 piece = CharToPiece( ToUpper(p) );
1950 if(piece == EmptySquare) continue;
1951 /*j = (int) piece - (int) WhitePawn;*/
1952 j = PieceToNumber(piece);
1953 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954 if(j < 0) continue; /* should not happen */
1955 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957 board[holdingsStartRow+j*direction][countsColumn]++;
1964 VariantSwitch(Board board, VariantClass newVariant)
1966 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1970 startedFromPositionFile = FALSE;
1971 if(gameInfo.variant == newVariant) return;
1973 /* [HGM] This routine is called each time an assignment is made to
1974 * gameInfo.variant during a game, to make sure the board sizes
1975 * are set to match the new variant. If that means adding or deleting
1976 * holdings, we shift the playing board accordingly
1977 * This kludge is needed because in ICS observe mode, we get boards
1978 * of an ongoing game without knowing the variant, and learn about the
1979 * latter only later. This can be because of the move list we requested,
1980 * in which case the game history is refilled from the beginning anyway,
1981 * but also when receiving holdings of a crazyhouse game. In the latter
1982 * case we want to add those holdings to the already received position.
1986 if (appData.debugMode) {
1987 fprintf(debugFP, "Switch board from %s to %s\n",
1988 VariantName(gameInfo.variant), VariantName(newVariant));
1989 setbuf(debugFP, NULL);
1991 shuffleOpenings = 0; /* [HGM] shuffle */
1992 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993 switch(newVariant) {
1995 newWidth = 9; newHeight = 9;
1996 gameInfo.holdingsSize = 7;
1997 case VariantBughouse:
1998 case VariantCrazyhouse:
1999 newHoldingsWidth = 2; break;
2001 newHoldingsWidth = gameInfo.holdingsSize = 0;
2004 if(newWidth != gameInfo.boardWidth ||
2005 newHeight != gameInfo.boardHeight ||
2006 newHoldingsWidth != gameInfo.holdingsWidth ) {
2008 /* shift position to new playing area, if needed */
2009 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010 for(i=0; i<BOARD_HEIGHT; i++)
2011 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 for(i=0; i<newHeight; i++) {
2015 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019 for(i=0; i<BOARD_HEIGHT; i++)
2020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2031 /* [HGM] The following should definitely be solved in a better way */
2033 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035 saveEP = epStatus[0];
2037 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2039 epStatus[0] = saveEP;
2040 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041 CopyBoard(tempBoard, board); /* restore position received from ICS */
2043 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2045 forwardMostMove = oldForwardMostMove;
2046 backwardMostMove = oldBackwardMostMove;
2047 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2050 static int loggedOn = FALSE;
2052 /*-- Game start info cache: --*/
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2065 read_from_ics(isr, closure, data, count, error)
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2082 static int started = STARTED_NONE;
2083 static char parse[20000];
2084 static int parse_pos = 0;
2085 static char buf[BUF_SIZE + 1];
2086 static int firstTime = TRUE, intfSet = FALSE;
2087 static ColorClass prevColor = ColorNormal;
2088 static int savingComment = FALSE;
2094 int backup; /* [DM] For zippy color lines */
2096 char talker[MSG_SIZ]; // [HGM] chat
2099 if (appData.debugMode) {
2101 fprintf(debugFP, "<ICS: ");
2102 show_bytes(debugFP, data, count);
2103 fprintf(debugFP, "\n");
2107 if (appData.debugMode) { int f = forwardMostMove;
2108 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2112 /* If last read ended with a partial line that we couldn't parse,
2113 prepend it to the new read and try again. */
2114 if (leftover_len > 0) {
2115 for (i=0; i<leftover_len; i++)
2116 buf[i] = buf[leftover_start + i];
2119 /* Copy in new characters, removing nulls and \r's */
2120 buf_len = leftover_len;
2121 for (i = 0; i < count; i++) {
2122 if (data[i] != NULLCHAR && data[i] != '\r')
2123 buf[buf_len++] = data[i];
2124 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2125 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2126 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2131 buf[buf_len] = NULLCHAR;
2132 next_out = leftover_len;
2136 while (i < buf_len) {
2137 /* Deal with part of the TELNET option negotiation
2138 protocol. We refuse to do anything beyond the
2139 defaults, except that we allow the WILL ECHO option,
2140 which ICS uses to turn off password echoing when we are
2141 directly connected to it. We reject this option
2142 if localLineEditing mode is on (always on in xboard)
2143 and we are talking to port 23, which might be a real
2144 telnet server that will try to keep WILL ECHO on permanently.
2146 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148 unsigned char option;
2150 switch ((unsigned char) buf[++i]) {
2152 if (appData.debugMode)
2153 fprintf(debugFP, "\n<WILL ");
2154 switch (option = (unsigned char) buf[++i]) {
2156 if (appData.debugMode)
2157 fprintf(debugFP, "ECHO ");
2158 /* Reply only if this is a change, according
2159 to the protocol rules. */
2160 if (remoteEchoOption) break;
2161 if (appData.localLineEditing &&
2162 atoi(appData.icsPort) == TN_PORT) {
2163 TelnetRequest(TN_DONT, TN_ECHO);
2166 TelnetRequest(TN_DO, TN_ECHO);
2167 remoteEchoOption = TRUE;
2171 if (appData.debugMode)
2172 fprintf(debugFP, "%d ", option);
2173 /* Whatever this is, we don't want it. */
2174 TelnetRequest(TN_DONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<WONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "ECHO ");
2185 /* Reply only if this is a change, according
2186 to the protocol rules. */
2187 if (!remoteEchoOption) break;
2189 TelnetRequest(TN_DONT, TN_ECHO);
2190 remoteEchoOption = FALSE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", (unsigned char) option);
2195 /* Whatever this is, it must already be turned
2196 off, because we never agree to turn on
2197 anything non-default, so according to the
2198 protocol rules, we don't reply. */
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DO ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 /* Whatever this is, we refuse to do it. */
2208 if (appData.debugMode)
2209 fprintf(debugFP, "%d ", option);
2210 TelnetRequest(TN_WONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<DONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "%d ", option);
2221 /* Whatever this is, we are already not doing
2222 it, because we never agree to do anything
2223 non-default, so according to the protocol
2224 rules, we don't reply. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "\n<IAC ");
2231 /* Doubled IAC; pass it through */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237 /* Drop all other telnet commands on the floor */
2240 if (oldi > next_out)
2241 SendToPlayer(&buf[next_out], oldi - next_out);
2247 /* OK, this at least will *usually* work */
2248 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2252 if (loggedOn && !intfSet) {
2253 if (ics_type == ICS_ICC) {
2255 "/set-quietly interface %s\n/set-quietly style 12\n",
2258 } else if (ics_type == ICS_CHESSNET) {
2259 sprintf(str, "/style 12\n");
2261 strcpy(str, "alias $ @\n$set interface ");
2262 strcat(str, programVersion);
2263 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2265 strcat(str, "$iset nohighlight 1\n");
2267 strcat(str, "$iset lock 1\n$style 12\n");
2273 if (started == STARTED_COMMENT) {
2274 /* Accumulate characters in comment */
2275 parse[parse_pos++] = buf[i];
2276 if (buf[i] == '\n') {
2277 parse[parse_pos] = NULLCHAR;
2278 if(chattingPartner>=0) {
2280 sprintf(mess, "%s%s", talker, parse);
2281 OutputChatMessage(chattingPartner, mess);
2282 chattingPartner = -1;
2284 if(!suppressKibitz) // [HGM] kibitz
2285 AppendComment(forwardMostMove, StripHighlight(parse));
2286 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287 int nrDigit = 0, nrAlph = 0, i;
2288 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290 parse[parse_pos] = NULLCHAR;
2291 // try to be smart: if it does not look like search info, it should go to
2292 // ICS interaction window after all, not to engine-output window.
2293 for(i=0; i<parse_pos; i++) { // count letters and digits
2294 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2296 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2298 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299 int depth=0; float score;
2300 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302 pvInfoList[forwardMostMove-1].depth = depth;
2303 pvInfoList[forwardMostMove-1].score = 100*score;
2305 OutputKibitz(suppressKibitz, parse);
2308 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309 SendToPlayer(tmp, strlen(tmp));
2312 started = STARTED_NONE;
2314 /* Don't match patterns against characters in chatter */
2319 if (started == STARTED_CHATTER) {
2320 if (buf[i] != '\n') {
2321 /* Don't match patterns against characters in chatter */
2325 started = STARTED_NONE;
2328 /* Kludge to deal with rcmd protocol */
2329 if (firstTime && looking_at(buf, &i, "\001*")) {
2330 DisplayFatalError(&buf[1], 0, 1);
2336 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2339 if (appData.debugMode)
2340 fprintf(debugFP, "ics_type %d\n", ics_type);
2343 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344 ics_type = ICS_FICS;
2346 if (appData.debugMode)
2347 fprintf(debugFP, "ics_type %d\n", ics_type);
2350 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351 ics_type = ICS_CHESSNET;
2353 if (appData.debugMode)
2354 fprintf(debugFP, "ics_type %d\n", ics_type);
2359 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360 looking_at(buf, &i, "Logging you in as \"*\"") ||
2361 looking_at(buf, &i, "will be \"*\""))) {
2362 strcpy(ics_handle, star_match[0]);
2366 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2368 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369 DisplayIcsInteractionTitle(buf);
2370 have_set_title = TRUE;
2373 /* skip finger notes */
2374 if (started == STARTED_NONE &&
2375 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376 (buf[i] == '1' && buf[i+1] == '0')) &&
2377 buf[i+2] == ':' && buf[i+3] == ' ') {
2378 started = STARTED_CHATTER;
2383 /* skip formula vars */
2384 if (started == STARTED_NONE &&
2385 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386 started = STARTED_CHATTER;
2392 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393 if (appData.autoKibitz && started == STARTED_NONE &&
2394 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2395 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396 if(looking_at(buf, &i, "* kibitzes: ") &&
2397 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2398 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2399 suppressKibitz = TRUE;
2400 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401 && (gameMode == IcsPlayingWhite)) ||
2402 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2404 started = STARTED_CHATTER; // own kibitz we simply discard
2406 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407 parse_pos = 0; parse[0] = NULLCHAR;
2408 savingComment = TRUE;
2409 suppressKibitz = gameMode != IcsObserving ? 2 :
2410 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2414 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415 started = STARTED_CHATTER;
2416 suppressKibitz = TRUE;
2418 } // [HGM] kibitz: end of patch
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2422 // [HGM] chat: intercept tells by users for which we have an open chat window
2424 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2425 looking_at(buf, &i, "* whispers:") ||
2426 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2429 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430 chattingPartner = -1;
2432 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433 for(p=0; p<MAX_CHAT; p++) {
2434 if(channel == atoi(chatPartner[p])) {
2435 talker[0] = '['; strcat(talker, "]");
2436 chattingPartner = p; break;
2439 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440 for(p=0; p<MAX_CHAT; p++) {
2441 if(!strcmp("WHISPER", chatPartner[p])) {
2442 talker[0] = '['; strcat(talker, "]");
2443 chattingPartner = p; break;
2446 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2449 chattingPartner = p; break;
2451 if(chattingPartner<0) i = oldi; else {
2452 started = STARTED_COMMENT;
2453 parse_pos = 0; parse[0] = NULLCHAR;
2454 savingComment = TRUE;
2455 suppressKibitz = TRUE;
2457 } // [HGM] chat: end of patch
2459 if (appData.zippyTalk || appData.zippyPlay) {
2460 /* [DM] Backup address for color zippy lines */
2464 if (loggedOn == TRUE)
2465 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2468 if (ZippyControl(buf, &i) ||
2469 ZippyConverse(buf, &i) ||
2470 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2472 if (!appData.colorize) continue;
2476 } // [DM] 'else { ' deleted
2478 /* Regular tells and says */
2479 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480 looking_at(buf, &i, "* (your partner) tells you: ") ||
2481 looking_at(buf, &i, "* says: ") ||
2482 /* Don't color "message" or "messages" output */
2483 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484 looking_at(buf, &i, "*. * at *:*: ") ||
2485 looking_at(buf, &i, "--* (*:*): ") ||
2486 /* Message notifications (same color as tells) */
2487 looking_at(buf, &i, "* has left a message ") ||
2488 looking_at(buf, &i, "* just sent you a message:\n") ||
2489 /* Whispers and kibitzes */
2490 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491 looking_at(buf, &i, "* kibitzes: ") ||
2493 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2495 if (tkind == 1 && strchr(star_match[0], ':')) {
2496 /* Avoid "tells you:" spoofs in channels */
2499 if (star_match[0][0] == NULLCHAR ||
2500 strchr(star_match[0], ' ') ||
2501 (tkind == 3 && strchr(star_match[1], ' '))) {
2502 /* Reject bogus matches */
2505 if (appData.colorize) {
2506 if (oldi > next_out) {
2507 SendToPlayer(&buf[next_out], oldi - next_out);
2512 Colorize(ColorTell, FALSE);
2513 curColor = ColorTell;
2516 Colorize(ColorKibitz, FALSE);
2517 curColor = ColorKibitz;
2520 p = strrchr(star_match[1], '(');
2527 Colorize(ColorChannel1, FALSE);
2528 curColor = ColorChannel1;
2530 Colorize(ColorChannel, FALSE);
2531 curColor = ColorChannel;
2535 curColor = ColorNormal;
2539 if (started == STARTED_NONE && appData.autoComment &&
2540 (gameMode == IcsObserving ||
2541 gameMode == IcsPlayingWhite ||
2542 gameMode == IcsPlayingBlack)) {
2543 parse_pos = i - oldi;
2544 memcpy(parse, &buf[oldi], parse_pos);
2545 parse[parse_pos] = NULLCHAR;
2546 started = STARTED_COMMENT;
2547 savingComment = TRUE;
2549 started = STARTED_CHATTER;
2550 savingComment = FALSE;
2557 if (looking_at(buf, &i, "* s-shouts: ") ||
2558 looking_at(buf, &i, "* c-shouts: ")) {
2559 if (appData.colorize) {
2560 if (oldi > next_out) {
2561 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorSShout, FALSE);
2565 curColor = ColorSShout;
2568 started = STARTED_CHATTER;
2572 if (looking_at(buf, &i, "--->")) {
2577 if (looking_at(buf, &i, "* shouts: ") ||
2578 looking_at(buf, &i, "--> ")) {
2579 if (appData.colorize) {
2580 if (oldi > next_out) {
2581 SendToPlayer(&buf[next_out], oldi - next_out);
2584 Colorize(ColorShout, FALSE);
2585 curColor = ColorShout;
2588 started = STARTED_CHATTER;
2592 if (looking_at( buf, &i, "Challenge:")) {
2593 if (appData.colorize) {
2594 if (oldi > next_out) {
2595 SendToPlayer(&buf[next_out], oldi - next_out);
2598 Colorize(ColorChallenge, FALSE);
2599 curColor = ColorChallenge;
2605 if (looking_at(buf, &i, "* offers you") ||
2606 looking_at(buf, &i, "* offers to be") ||
2607 looking_at(buf, &i, "* would like to") ||
2608 looking_at(buf, &i, "* requests to") ||
2609 looking_at(buf, &i, "Your opponent offers") ||
2610 looking_at(buf, &i, "Your opponent requests")) {
2612 if (appData.colorize) {
2613 if (oldi > next_out) {
2614 SendToPlayer(&buf[next_out], oldi - next_out);
2617 Colorize(ColorRequest, FALSE);
2618 curColor = ColorRequest;
2623 if (looking_at(buf, &i, "* (*) seeking")) {
2624 if (appData.colorize) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 Colorize(ColorSeek, FALSE);
2630 curColor = ColorSeek;
2635 if (looking_at(buf, &i, "\\ ")) {
2636 if (prevColor != ColorNormal) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(prevColor, TRUE);
2642 curColor = prevColor;
2644 if (savingComment) {
2645 parse_pos = i - oldi;
2646 memcpy(parse, &buf[oldi], parse_pos);
2647 parse[parse_pos] = NULLCHAR;
2648 started = STARTED_COMMENT;
2650 started = STARTED_CHATTER;
2655 if (looking_at(buf, &i, "Black Strength :") ||
2656 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657 looking_at(buf, &i, "<10>") ||
2658 looking_at(buf, &i, "#@#")) {
2659 /* Wrong board style */
2661 SendToICS(ics_prefix);
2662 SendToICS("set style 12\n");
2663 SendToICS(ics_prefix);
2664 SendToICS("refresh\n");
2668 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2670 have_sent_ICS_logon = 1;
2674 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2675 (looking_at(buf, &i, "\n<12> ") ||
2676 looking_at(buf, &i, "<12> "))) {
2678 if (oldi > next_out) {
2679 SendToPlayer(&buf[next_out], oldi - next_out);
2682 started = STARTED_BOARD;
2687 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688 looking_at(buf, &i, "<b1> ")) {
2689 if (oldi > next_out) {
2690 SendToPlayer(&buf[next_out], oldi - next_out);
2693 started = STARTED_HOLDINGS;
2698 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2700 /* Header for a move list -- first line */
2702 switch (ics_getting_history) {
2706 case BeginningOfGame:
2707 /* User typed "moves" or "oldmoves" while we
2708 were idle. Pretend we asked for these
2709 moves and soak them up so user can step
2710 through them and/or save them.
2713 gameMode = IcsObserving;
2716 ics_getting_history = H_GOT_UNREQ_HEADER;
2718 case EditGame: /*?*/
2719 case EditPosition: /*?*/
2720 /* Should above feature work in these modes too? */
2721 /* For now it doesn't */
2722 ics_getting_history = H_GOT_UNWANTED_HEADER;
2725 ics_getting_history = H_GOT_UNWANTED_HEADER;
2730 /* Is this the right one? */
2731 if (gameInfo.white && gameInfo.black &&
2732 strcmp(gameInfo.white, star_match[0]) == 0 &&
2733 strcmp(gameInfo.black, star_match[2]) == 0) {
2735 ics_getting_history = H_GOT_REQ_HEADER;
2738 case H_GOT_REQ_HEADER:
2739 case H_GOT_UNREQ_HEADER:
2740 case H_GOT_UNWANTED_HEADER:
2741 case H_GETTING_MOVES:
2742 /* Should not happen */
2743 DisplayError(_("Error gathering move list: two headers"), 0);
2744 ics_getting_history = H_FALSE;
2748 /* Save player ratings into gameInfo if needed */
2749 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751 (gameInfo.whiteRating == -1 ||
2752 gameInfo.blackRating == -1)) {
2754 gameInfo.whiteRating = string_to_rating(star_match[1]);
2755 gameInfo.blackRating = string_to_rating(star_match[3]);
2756 if (appData.debugMode)
2757 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2758 gameInfo.whiteRating, gameInfo.blackRating);
2763 if (looking_at(buf, &i,
2764 "* * match, initial time: * minute*, increment: * second")) {
2765 /* Header for a move list -- second line */
2766 /* Initial board will follow if this is a wild game */
2767 if (gameInfo.event != NULL) free(gameInfo.event);
2768 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769 gameInfo.event = StrSave(str);
2770 /* [HGM] we switched variant. Translate boards if needed. */
2771 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2775 if (looking_at(buf, &i, "Move ")) {
2776 /* Beginning of a move list */
2777 switch (ics_getting_history) {
2779 /* Normally should not happen */
2780 /* Maybe user hit reset while we were parsing */
2783 /* Happens if we are ignoring a move list that is not
2784 * the one we just requested. Common if the user
2785 * tries to observe two games without turning off
2788 case H_GETTING_MOVES:
2789 /* Should not happen */
2790 DisplayError(_("Error gathering move list: nested"), 0);
2791 ics_getting_history = H_FALSE;
2793 case H_GOT_REQ_HEADER:
2794 ics_getting_history = H_GETTING_MOVES;
2795 started = STARTED_MOVES;
2797 if (oldi > next_out) {
2798 SendToPlayer(&buf[next_out], oldi - next_out);
2801 case H_GOT_UNREQ_HEADER:
2802 ics_getting_history = H_GETTING_MOVES;
2803 started = STARTED_MOVES_NOHIDE;
2806 case H_GOT_UNWANTED_HEADER:
2807 ics_getting_history = H_FALSE;
2813 if (looking_at(buf, &i, "% ") ||
2814 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816 savingComment = FALSE;
2819 case STARTED_MOVES_NOHIDE:
2820 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821 parse[parse_pos + i - oldi] = NULLCHAR;
2822 ParseGameHistory(parse);
2824 if (appData.zippyPlay && first.initDone) {
2825 FeedMovesToProgram(&first, forwardMostMove);
2826 if (gameMode == IcsPlayingWhite) {
2827 if (WhiteOnMove(forwardMostMove)) {
2828 if (first.sendTime) {
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 SendTimeRemaining(&first, TRUE);
2835 if (first.useColors) {
2836 SendToProgram("white\ngo\n", &first);
2838 SendToProgram("go\n", &first);
2841 if (first.useColors) {
2842 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2844 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2846 first.maybeThinking = TRUE;
2848 if (first.usePlayother) {
2849 if (first.sendTime) {
2850 SendTimeRemaining(&first, TRUE);
2852 SendToProgram("playother\n", &first);
2858 } else if (gameMode == IcsPlayingBlack) {
2859 if (!WhiteOnMove(forwardMostMove)) {
2860 if (first.sendTime) {
2861 if (first.useColors) {
2862 SendToProgram("white\n", &first);
2864 SendTimeRemaining(&first, FALSE);
2867 if (first.useColors) {
2868 SendToProgram("black\ngo\n", &first);
2870 SendToProgram("go\n", &first);
2873 if (first.useColors) {
2874 SendToProgram("black\n", &first);
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2878 first.maybeThinking = TRUE;
2880 if (first.usePlayother) {
2881 if (first.sendTime) {
2882 SendTimeRemaining(&first, FALSE);
2884 SendToProgram("playother\n", &first);
2893 if (gameMode == IcsObserving && ics_gamenum == -1) {
2894 /* Moves came from oldmoves or moves command
2895 while we weren't doing anything else.
2897 currentMove = forwardMostMove;
2898 ClearHighlights();/*!!could figure this out*/
2899 flipView = appData.flipView;
2900 DrawPosition(FALSE, boards[currentMove]);
2901 DisplayBothClocks();
2902 sprintf(str, "%s vs. %s",
2903 gameInfo.white, gameInfo.black);
2907 /* Moves were history of an active game */
2908 if (gameInfo.resultDetails != NULL) {
2909 free(gameInfo.resultDetails);
2910 gameInfo.resultDetails = NULL;
2913 HistorySet(parseList, backwardMostMove,
2914 forwardMostMove, currentMove-1);
2915 DisplayMove(currentMove - 1);
2916 if (started == STARTED_MOVES) next_out = i;
2917 started = STARTED_NONE;
2918 ics_getting_history = H_FALSE;
2921 case STARTED_OBSERVE:
2922 started = STARTED_NONE;
2923 SendToICS(ics_prefix);
2924 SendToICS("refresh\n");
2930 if(bookHit) { // [HGM] book: simulate book reply
2931 static char bookMove[MSG_SIZ]; // a bit generous?
2933 programStats.nodes = programStats.depth = programStats.time =
2934 programStats.score = programStats.got_only_move = 0;
2935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2937 strcpy(bookMove, "move ");
2938 strcat(bookMove, bookHit);
2939 HandleMachineMove(bookMove, &first);
2944 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945 started == STARTED_HOLDINGS ||
2946 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947 /* Accumulate characters in move list or board */
2948 parse[parse_pos++] = buf[i];
2951 /* Start of game messages. Mostly we detect start of game
2952 when the first board image arrives. On some versions
2953 of the ICS, though, we need to do a "refresh" after starting
2954 to observe in order to get the current board right away. */
2955 if (looking_at(buf, &i, "Adding game * to observation list")) {
2956 started = STARTED_OBSERVE;
2960 /* Handle auto-observe */
2961 if (appData.autoObserve &&
2962 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2965 /* Choose the player that was highlighted, if any. */
2966 if (star_match[0][0] == '\033' ||
2967 star_match[1][0] != '\033') {
2968 player = star_match[0];
2970 player = star_match[2];
2972 sprintf(str, "%sobserve %s\n",
2973 ics_prefix, StripHighlightAndTitle(player));
2976 /* Save ratings from notify string */
2977 strcpy(player1Name, star_match[0]);
2978 player1Rating = string_to_rating(star_match[1]);
2979 strcpy(player2Name, star_match[2]);
2980 player2Rating = string_to_rating(star_match[3]);
2982 if (appData.debugMode)
2984 "Ratings from 'Game notification:' %s %d, %s %d\n",
2985 player1Name, player1Rating,
2986 player2Name, player2Rating);
2991 /* Deal with automatic examine mode after a game,
2992 and with IcsObserving -> IcsExamining transition */
2993 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994 looking_at(buf, &i, "has made you an examiner of game *")) {
2996 int gamenum = atoi(star_match[0]);
2997 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998 gamenum == ics_gamenum) {
2999 /* We were already playing or observing this game;
3000 no need to refetch history */
3001 gameMode = IcsExamining;
3003 pauseExamForwardMostMove = forwardMostMove;
3004 } else if (currentMove < forwardMostMove) {
3005 ForwardInner(forwardMostMove);
3008 /* I don't think this case really can happen */
3009 SendToICS(ics_prefix);
3010 SendToICS("refresh\n");
3015 /* Error messages */
3016 // if (ics_user_moved) {
3017 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018 if (looking_at(buf, &i, "Illegal move") ||
3019 looking_at(buf, &i, "Not a legal move") ||
3020 looking_at(buf, &i, "Your king is in check") ||
3021 looking_at(buf, &i, "It isn't your turn") ||
3022 looking_at(buf, &i, "It is not your move")) {
3024 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025 currentMove = --forwardMostMove;
3026 DisplayMove(currentMove - 1); /* before DMError */
3027 DrawPosition(FALSE, boards[currentMove]);
3029 DisplayBothClocks();
3031 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037 if (looking_at(buf, &i, "still have time") ||
3038 looking_at(buf, &i, "not out of time") ||
3039 looking_at(buf, &i, "either player is out of time") ||
3040 looking_at(buf, &i, "has timeseal; checking")) {
3041 /* We must have called his flag a little too soon */
3042 whiteFlag = blackFlag = FALSE;
3046 if (looking_at(buf, &i, "added * seconds to") ||
3047 looking_at(buf, &i, "seconds were added to")) {
3048 /* Update the clocks */
3049 SendToICS(ics_prefix);
3050 SendToICS("refresh\n");
3054 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055 ics_clock_paused = TRUE;
3060 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061 ics_clock_paused = FALSE;
3066 /* Grab player ratings from the Creating: message.
3067 Note we have to check for the special case when
3068 the ICS inserts things like [white] or [black]. */
3069 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3072 0 player 1 name (not necessarily white)
3074 2 empty, white, or black (IGNORED)
3075 3 player 2 name (not necessarily black)
3078 The names/ratings are sorted out when the game
3079 actually starts (below).
3081 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082 player1Rating = string_to_rating(star_match[1]);
3083 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084 player2Rating = string_to_rating(star_match[4]);
3086 if (appData.debugMode)
3088 "Ratings from 'Creating:' %s %d, %s %d\n",
3089 player1Name, player1Rating,
3090 player2Name, player2Rating);
3095 /* Improved generic start/end-of-game messages */
3096 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098 /* If tkind == 0: */
3099 /* star_match[0] is the game number */
3100 /* [1] is the white player's name */
3101 /* [2] is the black player's name */
3102 /* For end-of-game: */
3103 /* [3] is the reason for the game end */
3104 /* [4] is a PGN end game-token, preceded by " " */
3105 /* For start-of-game: */
3106 /* [3] begins with "Creating" or "Continuing" */
3107 /* [4] is " *" or empty (don't care). */
3108 int gamenum = atoi(star_match[0]);
3109 char *whitename, *blackname, *why, *endtoken;
3110 ChessMove endtype = (ChessMove) 0;
3113 whitename = star_match[1];
3114 blackname = star_match[2];
3115 why = star_match[3];
3116 endtoken = star_match[4];
3118 whitename = star_match[1];
3119 blackname = star_match[3];
3120 why = star_match[5];
3121 endtoken = star_match[6];
3124 /* Game start messages */
3125 if (strncmp(why, "Creating ", 9) == 0 ||
3126 strncmp(why, "Continuing ", 11) == 0) {
3127 gs_gamenum = gamenum;
3128 strcpy(gs_kind, strchr(why, ' ') + 1);
3130 if (appData.zippyPlay) {
3131 ZippyGameStart(whitename, blackname);
3137 /* Game end messages */
3138 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139 ics_gamenum != gamenum) {
3142 while (endtoken[0] == ' ') endtoken++;
3143 switch (endtoken[0]) {
3146 endtype = GameUnfinished;
3149 endtype = BlackWins;
3152 if (endtoken[1] == '/')
3153 endtype = GameIsDrawn;
3155 endtype = WhiteWins;
3158 GameEnds(endtype, why, GE_ICS);
3160 if (appData.zippyPlay && first.initDone) {
3161 ZippyGameEnd(endtype, why);
3162 if (first.pr == NULL) {
3163 /* Start the next process early so that we'll
3164 be ready for the next challenge */
3165 StartChessProgram(&first);
3167 /* Send "new" early, in case this command takes
3168 a long time to finish, so that we'll be ready
3169 for the next challenge. */
3170 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177 if (looking_at(buf, &i, "Removing game * from observation") ||
3178 looking_at(buf, &i, "no longer observing game *") ||
3179 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180 if (gameMode == IcsObserving &&
3181 atoi(star_match[0]) == ics_gamenum)
3183 /* icsEngineAnalyze */
3184 if (appData.icsEngineAnalyze) {
3191 ics_user_moved = FALSE;
3196 if (looking_at(buf, &i, "no longer examining game *")) {
3197 if (gameMode == IcsExamining &&
3198 atoi(star_match[0]) == ics_gamenum)
3202 ics_user_moved = FALSE;
3207 /* Advance leftover_start past any newlines we find,
3208 so only partial lines can get reparsed */
3209 if (looking_at(buf, &i, "\n")) {
3210 prevColor = curColor;
3211 if (curColor != ColorNormal) {
3212 if (oldi > next_out) {
3213 SendToPlayer(&buf[next_out], oldi - next_out);
3216 Colorize(ColorNormal, FALSE);
3217 curColor = ColorNormal;
3219 if (started == STARTED_BOARD) {
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 ParseBoard12(parse);
3225 /* Send premove here */
3226 if (appData.premove) {
3228 if (currentMove == 0 &&
3229 gameMode == IcsPlayingWhite &&
3230 appData.premoveWhite) {
3231 sprintf(str, "%s%s\n", ics_prefix,
3232 appData.premoveWhiteText);
3233 if (appData.debugMode)
3234 fprintf(debugFP, "Sending premove:\n");
3236 } else if (currentMove == 1 &&
3237 gameMode == IcsPlayingBlack &&
3238 appData.premoveBlack) {
3239 sprintf(str, "%s%s\n", ics_prefix,
3240 appData.premoveBlackText);
3241 if (appData.debugMode)
3242 fprintf(debugFP, "Sending premove:\n");
3244 } else if (gotPremove) {
3246 ClearPremoveHighlights();
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3249 UserMoveEvent(premoveFromX, premoveFromY,
3250 premoveToX, premoveToY,
3255 /* Usually suppress following prompt */
3256 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3262 } else if (started == STARTED_HOLDINGS) {
3264 char new_piece[MSG_SIZ];
3265 started = STARTED_NONE;
3266 parse[parse_pos] = NULLCHAR;
3267 if (appData.debugMode)
3268 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269 parse, currentMove);
3270 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271 gamenum == ics_gamenum) {
3272 if (gameInfo.variant == VariantNormal) {
3273 /* [HGM] We seem to switch variant during a game!
3274 * Presumably no holdings were displayed, so we have
3275 * to move the position two files to the right to
3276 * create room for them!
3278 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279 /* Get a move list just to see the header, which
3280 will tell us whether this is really bug or zh */
3281 if (ics_getting_history == H_FALSE) {
3282 ics_getting_history = H_REQUESTED;
3283 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3287 new_piece[0] = NULLCHAR;
3288 sscanf(parse, "game %d white [%s black [%s <- %s",
3289 &gamenum, white_holding, black_holding,
3291 white_holding[strlen(white_holding)-1] = NULLCHAR;
3292 black_holding[strlen(black_holding)-1] = NULLCHAR;
3293 /* [HGM] copy holdings to board holdings area */
3294 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3297 if (appData.zippyPlay && first.initDone) {
3298 ZippyHoldings(white_holding, black_holding,
3302 if (tinyLayout || smallLayout) {
3303 char wh[16], bh[16];
3304 PackHolding(wh, white_holding);
3305 PackHolding(bh, black_holding);
3306 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307 gameInfo.white, gameInfo.black);
3309 sprintf(str, "%s [%s] vs. %s [%s]",
3310 gameInfo.white, white_holding,
3311 gameInfo.black, black_holding);
3314 DrawPosition(FALSE, boards[currentMove]);
3317 /* Suppress following prompt */
3318 if (looking_at(buf, &i, "*% ")) {
3319 savingComment = FALSE;
3326 i++; /* skip unparsed character and loop back */
3329 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330 started != STARTED_HOLDINGS && i > next_out) {
3331 SendToPlayer(&buf[next_out], i - next_out);
3334 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3336 leftover_len = buf_len - leftover_start;
3337 /* if buffer ends with something we couldn't parse,
3338 reparse it after appending the next read */
3340 } else if (count == 0) {
3341 RemoveInputSource(isr);
3342 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3344 DisplayFatalError(_("Error reading from ICS"), error, 1);
3349 /* Board style 12 looks like this:
3351 <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
3353 * The "<12> " is stripped before it gets to this routine. The two
3354 * trailing 0's (flip state and clock ticking) are later addition, and
3355 * some chess servers may not have them, or may have only the first.
3356 * Additional trailing fields may be added in the future.
3359 #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"
3361 #define RELATION_OBSERVING_PLAYED 0
3362 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE 1
3364 #define RELATION_PLAYING_NOTMYMOVE -1
3365 #define RELATION_EXAMINING 2
3366 #define RELATION_ISOLATED_BOARD -3
3367 #define RELATION_STARTING_POSITION -4 /* FICS only */
3370 ParseBoard12(string)
3373 GameMode newGameMode;
3374 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377 char to_play, board_chars[200];
3378 char move_str[500], str[500], elapsed_time[500];
3379 char black[32], white[32];
3381 int prevMove = currentMove;
3384 int fromX, fromY, toX, toY;
3386 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387 char *bookHit = NULL; // [HGM] book
3389 fromX = fromY = toX = toY = -1;
3393 if (appData.debugMode)
3394 fprintf(debugFP, _("Parsing board: %s\n"), string);
3396 move_str[0] = NULLCHAR;
3397 elapsed_time[0] = NULLCHAR;
3398 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3400 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401 if(string[i] == ' ') { ranks++; files = 0; }
3405 for(j = 0; j <i; j++) board_chars[j] = string[j];
3406 board_chars[i] = '\0';
3409 n = sscanf(string, PATTERN, &to_play, &double_push,
3410 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411 &gamenum, white, black, &relation, &basetime, &increment,
3412 &white_stren, &black_stren, &white_time, &black_time,
3413 &moveNum, str, elapsed_time, move_str, &ics_flip,
3417 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418 DisplayError(str, 0);
3422 /* Convert the move number to internal form */
3423 moveNum = (moveNum - 1) * 2;
3424 if (to_play == 'B') moveNum++;
3425 if (moveNum >= MAX_MOVES) {
3426 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3432 case RELATION_OBSERVING_PLAYED:
3433 case RELATION_OBSERVING_STATIC:
3434 if (gamenum == -1) {
3435 /* Old ICC buglet */
3436 relation = RELATION_OBSERVING_STATIC;
3438 newGameMode = IcsObserving;
3440 case RELATION_PLAYING_MYMOVE:
3441 case RELATION_PLAYING_NOTMYMOVE:
3443 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444 IcsPlayingWhite : IcsPlayingBlack;
3446 case RELATION_EXAMINING:
3447 newGameMode = IcsExamining;
3449 case RELATION_ISOLATED_BOARD:
3451 /* Just display this board. If user was doing something else,
3452 we will forget about it until the next board comes. */
3453 newGameMode = IcsIdle;
3455 case RELATION_STARTING_POSITION:
3456 newGameMode = gameMode;
3460 /* Modify behavior for initial board display on move listing
3463 switch (ics_getting_history) {
3467 case H_GOT_REQ_HEADER:
3468 case H_GOT_UNREQ_HEADER:
3469 /* This is the initial position of the current game */
3470 gamenum = ics_gamenum;
3471 moveNum = 0; /* old ICS bug workaround */
3472 if (to_play == 'B') {
3473 startedFromSetupPosition = TRUE;
3474 blackPlaysFirst = TRUE;
3476 if (forwardMostMove == 0) forwardMostMove = 1;
3477 if (backwardMostMove == 0) backwardMostMove = 1;
3478 if (currentMove == 0) currentMove = 1;
3480 newGameMode = gameMode;
3481 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3483 case H_GOT_UNWANTED_HEADER:
3484 /* This is an initial board that we don't want */
3486 case H_GETTING_MOVES:
3487 /* Should not happen */
3488 DisplayError(_("Error gathering move list: extra board"), 0);
3489 ics_getting_history = H_FALSE;
3493 /* Take action if this is the first board of a new game, or of a
3494 different game than is currently being displayed. */
3495 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496 relation == RELATION_ISOLATED_BOARD) {
3498 /* Forget the old game and get the history (if any) of the new one */
3499 if (gameMode != BeginningOfGame) {
3503 if (appData.autoRaiseBoard) BoardToTop();
3505 if (gamenum == -1) {
3506 newGameMode = IcsIdle;
3507 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508 appData.getMoveList) {
3509 /* Need to get game history */
3510 ics_getting_history = H_REQUESTED;
3511 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3515 /* Initially flip the board to have black on the bottom if playing
3516 black or if the ICS flip flag is set, but let the user change
3517 it with the Flip View button. */
3518 flipView = appData.autoFlipView ?
3519 (newGameMode == IcsPlayingBlack) || ics_flip :
3522 /* Done with values from previous mode; copy in new ones */
3523 gameMode = newGameMode;
3525 ics_gamenum = gamenum;
3526 if (gamenum == gs_gamenum) {
3527 int klen = strlen(gs_kind);
3528 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529 sprintf(str, "ICS %s", gs_kind);
3530 gameInfo.event = StrSave(str);
3532 gameInfo.event = StrSave("ICS game");
3534 gameInfo.site = StrSave(appData.icsHost);
3535 gameInfo.date = PGNDate();
3536 gameInfo.round = StrSave("-");
3537 gameInfo.white = StrSave(white);
3538 gameInfo.black = StrSave(black);
3539 timeControl = basetime * 60 * 1000;
3541 timeIncrement = increment * 1000;
3542 movesPerSession = 0;
3543 gameInfo.timeControl = TimeControlTagValue();
3544 VariantSwitch(board, StringToVariant(gameInfo.event) );
3545 if (appData.debugMode) {
3546 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548 setbuf(debugFP, NULL);
3551 gameInfo.outOfBook = NULL;
3553 /* Do we have the ratings? */
3554 if (strcmp(player1Name, white) == 0 &&
3555 strcmp(player2Name, black) == 0) {
3556 if (appData.debugMode)
3557 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558 player1Rating, player2Rating);
3559 gameInfo.whiteRating = player1Rating;
3560 gameInfo.blackRating = player2Rating;
3561 } else if (strcmp(player2Name, white) == 0 &&
3562 strcmp(player1Name, black) == 0) {
3563 if (appData.debugMode)
3564 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565 player2Rating, player1Rating);
3566 gameInfo.whiteRating = player2Rating;
3567 gameInfo.blackRating = player1Rating;
3569 player1Name[0] = player2Name[0] = NULLCHAR;
3571 /* Silence shouts if requested */
3572 if (appData.quietPlay &&
3573 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574 SendToICS(ics_prefix);
3575 SendToICS("set shout 0\n");
3579 /* Deal with midgame name changes */
3581 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582 if (gameInfo.white) free(gameInfo.white);
3583 gameInfo.white = StrSave(white);
3585 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586 if (gameInfo.black) free(gameInfo.black);
3587 gameInfo.black = StrSave(black);
3591 /* Throw away game result if anything actually changes in examine mode */
3592 if (gameMode == IcsExamining && !newGame) {
3593 gameInfo.result = GameUnfinished;
3594 if (gameInfo.resultDetails != NULL) {
3595 free(gameInfo.resultDetails);
3596 gameInfo.resultDetails = NULL;
3600 /* In pausing && IcsExamining mode, we ignore boards coming
3601 in if they are in a different variation than we are. */
3602 if (pauseExamInvalid) return;
3603 if (pausing && gameMode == IcsExamining) {
3604 if (moveNum <= pauseExamForwardMostMove) {
3605 pauseExamInvalid = TRUE;
3606 forwardMostMove = pauseExamForwardMostMove;
3611 if (appData.debugMode) {
3612 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3614 /* Parse the board */
3615 for (k = 0; k < ranks; k++) {
3616 for (j = 0; j < files; j++)
3617 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618 if(gameInfo.holdingsWidth > 1) {
3619 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3623 CopyBoard(boards[moveNum], board);
3625 startedFromSetupPosition =
3626 !CompareBoards(board, initialPosition);
3627 if(startedFromSetupPosition)
3628 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3631 /* [HGM] Set castling rights. Take the outermost Rooks,
3632 to make it also work for FRC opening positions. Note that board12
3633 is really defective for later FRC positions, as it has no way to
3634 indicate which Rook can castle if they are on the same side of King.
3635 For the initial position we grant rights to the outermost Rooks,
3636 and remember thos rights, and we then copy them on positions
3637 later in an FRC game. This means WB might not recognize castlings with
3638 Rooks that have moved back to their original position as illegal,
3639 but in ICS mode that is not its job anyway.
3641 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3644 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645 if(board[0][i] == WhiteRook) j = i;
3646 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648 if(board[0][i] == WhiteRook) j = i;
3649 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3657 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661 if(board[BOARD_HEIGHT-1][k] == bKing)
3662 initialRights[5] = castlingRights[moveNum][5] = k;
3664 r = castlingRights[moveNum][0] = initialRights[0];
3665 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666 r = castlingRights[moveNum][1] = initialRights[1];
3667 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668 r = castlingRights[moveNum][3] = initialRights[3];
3669 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670 r = castlingRights[moveNum][4] = initialRights[4];
3671 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672 /* wildcastle kludge: always assume King has rights */
3673 r = castlingRights[moveNum][2] = initialRights[2];
3674 r = castlingRights[moveNum][5] = initialRights[5];
3676 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3680 if (ics_getting_history == H_GOT_REQ_HEADER ||
3681 ics_getting_history == H_GOT_UNREQ_HEADER) {
3682 /* This was an initial position from a move list, not
3683 the current position */
3687 /* Update currentMove and known move number limits */
3688 newMove = newGame || moveNum > forwardMostMove;
3690 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692 takeback = forwardMostMove - moveNum;
3693 for (i = 0; i < takeback; i++) {
3694 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695 SendToProgram("undo\n", &first);
3700 forwardMostMove = backwardMostMove = currentMove = moveNum;
3701 if (gameMode == IcsExamining && moveNum == 0) {
3702 /* Workaround for ICS limitation: we are not told the wild
3703 type when starting to examine a game. But if we ask for
3704 the move list, the move list header will tell us */
3705 ics_getting_history = H_REQUESTED;
3706 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3709 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711 forwardMostMove = moveNum;
3712 if (!pausing || currentMove > forwardMostMove)
3713 currentMove = forwardMostMove;
3715 /* New part of history that is not contiguous with old part */
3716 if (pausing && gameMode == IcsExamining) {
3717 pauseExamInvalid = TRUE;
3718 forwardMostMove = pauseExamForwardMostMove;
3721 forwardMostMove = backwardMostMove = currentMove = moveNum;
3722 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723 ics_getting_history = H_REQUESTED;
3724 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3729 /* Update the clocks */
3730 if (strchr(elapsed_time, '.')) {
3732 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3735 /* Time is in seconds */
3736 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3742 if (appData.zippyPlay && newGame &&
3743 gameMode != IcsObserving && gameMode != IcsIdle &&
3744 gameMode != IcsExamining)
3745 ZippyFirstBoard(moveNum, basetime, increment);
3748 /* Put the move on the move list, first converting
3749 to canonical algebraic form. */
3751 if (appData.debugMode) {
3752 if (appData.debugMode) { int f = forwardMostMove;
3753 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3756 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757 fprintf(debugFP, "moveNum = %d\n", moveNum);
3758 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759 setbuf(debugFP, NULL);
3761 if (moveNum <= backwardMostMove) {
3762 /* We don't know what the board looked like before
3764 strcpy(parseList[moveNum - 1], move_str);
3765 strcat(parseList[moveNum - 1], " ");
3766 strcat(parseList[moveNum - 1], elapsed_time);
3767 moveList[moveNum - 1][0] = NULLCHAR;
3768 } else if (strcmp(move_str, "none") == 0) {
3769 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770 /* Again, we don't know what the board looked like;
3771 this is really the start of the game. */
3772 parseList[moveNum - 1][0] = NULLCHAR;
3773 moveList[moveNum - 1][0] = NULLCHAR;
3774 backwardMostMove = moveNum;
3775 startedFromSetupPosition = TRUE;
3776 fromX = fromY = toX = toY = -1;
3778 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3779 // So we parse the long-algebraic move string in stead of the SAN move
3780 int valid; char buf[MSG_SIZ], *prom;
3782 // str looks something like "Q/a1-a2"; kill the slash
3784 sprintf(buf, "%c%s", str[0], str+2);
3785 else strcpy(buf, str); // might be castling
3786 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3787 strcat(buf, prom); // long move lacks promo specification!
3788 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789 if(appData.debugMode)
3790 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791 strcpy(move_str, buf);
3793 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794 &fromX, &fromY, &toX, &toY, &promoChar)
3795 || ParseOneMove(buf, moveNum - 1, &moveType,
3796 &fromX, &fromY, &toX, &toY, &promoChar);
3797 // end of long SAN patch
3799 (void) CoordsToAlgebraic(boards[moveNum - 1],
3800 PosFlags(moveNum - 1), EP_UNKNOWN,
3801 fromY, fromX, toY, toX, promoChar,
3802 parseList[moveNum-1]);
3803 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804 castlingRights[moveNum]) ) {
3810 if(gameInfo.variant != VariantShogi)
3811 strcat(parseList[moveNum - 1], "+");
3814 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815 strcat(parseList[moveNum - 1], "#");
3818 strcat(parseList[moveNum - 1], " ");
3819 strcat(parseList[moveNum - 1], elapsed_time);
3820 /* currentMoveString is set as a side-effect of ParseOneMove */
3821 strcpy(moveList[moveNum - 1], currentMoveString);
3822 strcat(moveList[moveNum - 1], "\n");
3824 /* Move from ICS was illegal!? Punt. */
3825 if (appData.debugMode) {
3826 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3830 if (appData.testLegality && appData.debugMode) {
3831 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832 DisplayError(str, 0);
3835 strcpy(parseList[moveNum - 1], move_str);
3836 strcat(parseList[moveNum - 1], " ");
3837 strcat(parseList[moveNum - 1], elapsed_time);
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 fromX = fromY = toX = toY = -1;
3842 if (appData.debugMode) {
3843 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844 setbuf(debugFP, NULL);
3848 /* Send move to chess program (BEFORE animating it). */
3849 if (appData.zippyPlay && !newGame && newMove &&
3850 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3852 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3857 DisplayError(str, 0);
3859 if (first.sendTime) {
3860 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3862 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863 if (firstMove && !bookHit) {
3865 if (first.useColors) {
3866 SendToProgram(gameMode == IcsPlayingWhite ?
3868 "black\ngo\n", &first);
3870 SendToProgram("go\n", &first);
3872 first.maybeThinking = TRUE;
3875 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876 if (moveList[moveNum - 1][0] == NULLCHAR) {
3877 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878 DisplayError(str, 0);
3880 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881 SendMoveToProgram(moveNum - 1, &first);
3888 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889 /* If move comes from a remote source, animate it. If it
3890 isn't remote, it will have already been animated. */
3891 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3894 if (!pausing && appData.highlightLastMove) {
3895 SetHighlights(fromX, fromY, toX, toY);
3899 /* Start the clocks */
3900 whiteFlag = blackFlag = FALSE;
3901 appData.clockMode = !(basetime == 0 && increment == 0);
3903 ics_clock_paused = TRUE;
3905 } else if (ticking == 1) {
3906 ics_clock_paused = FALSE;
3908 if (gameMode == IcsIdle ||
3909 relation == RELATION_OBSERVING_STATIC ||
3910 relation == RELATION_EXAMINING ||
3912 DisplayBothClocks();
3916 /* Display opponents and material strengths */
3917 if (gameInfo.variant != VariantBughouse &&
3918 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919 if (tinyLayout || smallLayout) {
3920 if(gameInfo.variant == VariantNormal)
3921 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3922 gameInfo.white, white_stren, gameInfo.black, black_stren,
3923 basetime, increment);
3925 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3926 gameInfo.white, white_stren, gameInfo.black, black_stren,
3927 basetime, increment, (int) gameInfo.variant);
3929 if(gameInfo.variant == VariantNormal)
3930 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3931 gameInfo.white, white_stren, gameInfo.black, black_stren,
3932 basetime, increment);
3934 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3935 gameInfo.white, white_stren, gameInfo.black, black_stren,
3936 basetime, increment, VariantName(gameInfo.variant));
3939 if (appData.debugMode) {
3940 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3945 /* Display the board */
3946 if (!pausing && !appData.noGUI) {
3948 if (appData.premove)
3950 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952 ClearPremoveHighlights();
3954 DrawPosition(FALSE, boards[currentMove]);
3955 DisplayMove(moveNum - 1);
3956 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3959 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3963 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3965 if(bookHit) { // [HGM] book: simulate book reply
3966 static char bookMove[MSG_SIZ]; // a bit generous?
3968 programStats.nodes = programStats.depth = programStats.time =
3969 programStats.score = programStats.got_only_move = 0;
3970 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3972 strcpy(bookMove, "move ");
3973 strcat(bookMove, bookHit);
3974 HandleMachineMove(bookMove, &first);
3983 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984 ics_getting_history = H_REQUESTED;
3985 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3991 AnalysisPeriodicEvent(force)
3994 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995 && !force) || !appData.periodicUpdates)
3998 /* Send . command to Crafty to collect stats */
3999 SendToProgram(".\n", &first);
4001 /* Don't send another until we get a response (this makes
4002 us stop sending to old Crafty's which don't understand
4003 the "." command (sending illegal cmds resets node count & time,
4004 which looks bad)) */
4005 programStats.ok_to_send = 0;
4009 SendMoveToProgram(moveNum, cps)
4011 ChessProgramState *cps;
4015 if (cps->useUsermove) {
4016 SendToProgram("usermove ", cps);
4020 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021 int len = space - parseList[moveNum];
4022 memcpy(buf, parseList[moveNum], len);
4024 buf[len] = NULLCHAR;
4026 sprintf(buf, "%s\n", parseList[moveNum]);
4028 SendToProgram(buf, cps);
4030 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031 AlphaRank(moveList[moveNum], 4);
4032 SendToProgram(moveList[moveNum], cps);
4033 AlphaRank(moveList[moveNum], 4); // and back
4035 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036 * the engine. It would be nice to have a better way to identify castle
4038 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039 && cps->useOOCastle) {
4040 int fromX = moveList[moveNum][0] - AAA;
4041 int fromY = moveList[moveNum][1] - ONE;
4042 int toX = moveList[moveNum][2] - AAA;
4043 int toY = moveList[moveNum][3] - ONE;
4044 if((boards[moveNum][fromY][fromX] == WhiteKing
4045 && boards[moveNum][toY][toX] == WhiteRook)
4046 || (boards[moveNum][fromY][fromX] == BlackKing
4047 && boards[moveNum][toY][toX] == BlackRook)) {
4048 if(toX > fromX) SendToProgram("O-O\n", cps);
4049 else SendToProgram("O-O-O\n", cps);
4051 else SendToProgram(moveList[moveNum], cps);
4053 else SendToProgram(moveList[moveNum], cps);
4054 /* End of additions by Tord */
4057 /* [HGM] setting up the opening has brought engine in force mode! */
4058 /* Send 'go' if we are in a mode where machine should play. */
4059 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060 (gameMode == TwoMachinesPlay ||
4062 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4064 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065 SendToProgram("go\n", cps);
4066 if (appData.debugMode) {
4067 fprintf(debugFP, "(extra)\n");
4070 setboardSpoiledMachineBlack = 0;
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4076 int fromX, fromY, toX, toY;
4078 char user_move[MSG_SIZ];
4082 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083 (int)moveType, fromX, fromY, toX, toY);
4084 DisplayError(user_move + strlen("say "), 0);
4086 case WhiteKingSideCastle:
4087 case BlackKingSideCastle:
4088 case WhiteQueenSideCastleWild:
4089 case BlackQueenSideCastleWild:
4091 case WhiteHSideCastleFR:
4092 case BlackHSideCastleFR:
4094 sprintf(user_move, "o-o\n");
4096 case WhiteQueenSideCastle:
4097 case BlackQueenSideCastle:
4098 case WhiteKingSideCastleWild:
4099 case BlackKingSideCastleWild:
4101 case WhiteASideCastleFR:
4102 case BlackASideCastleFR:
4104 sprintf(user_move, "o-o-o\n");
4106 case WhitePromotionQueen:
4107 case BlackPromotionQueen:
4108 case WhitePromotionRook:
4109 case BlackPromotionRook:
4110 case WhitePromotionBishop:
4111 case BlackPromotionBishop:
4112 case WhitePromotionKnight:
4113 case BlackPromotionKnight:
4114 case WhitePromotionKing:
4115 case BlackPromotionKing:
4116 case WhitePromotionChancellor:
4117 case BlackPromotionChancellor:
4118 case WhitePromotionArchbishop:
4119 case BlackPromotionArchbishop:
4120 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121 sprintf(user_move, "%c%c%c%c=%c\n",
4122 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123 PieceToChar(WhiteFerz));
4124 else if(gameInfo.variant == VariantGreat)
4125 sprintf(user_move, "%c%c%c%c=%c\n",
4126 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127 PieceToChar(WhiteMan));
4129 sprintf(user_move, "%c%c%c%c=%c\n",
4130 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131 PieceToChar(PromoPiece(moveType)));
4135 sprintf(user_move, "%c@%c%c\n",
4136 ToUpper(PieceToChar((ChessSquare) fromX)),
4137 AAA + toX, ONE + toY);
4140 case WhiteCapturesEnPassant:
4141 case BlackCapturesEnPassant:
4142 case IllegalMove: /* could be a variant we don't quite understand */
4143 sprintf(user_move, "%c%c%c%c\n",
4144 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4147 SendToICS(user_move);
4148 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4158 if (rf == DROP_RANK) {
4159 sprintf(move, "%c@%c%c\n",
4160 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4162 if (promoChar == 'x' || promoChar == NULLCHAR) {
4163 sprintf(move, "%c%c%c%c\n",
4164 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4166 sprintf(move, "%c%c%c%c%c\n",
4167 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4173 ProcessICSInitScript(f)
4178 while (fgets(buf, MSG_SIZ, f)) {
4179 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4188 AlphaRank(char *move, int n)
4190 // char *p = move, c; int x, y;
4192 if (appData.debugMode) {
4193 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4197 move[2]>='0' && move[2]<='9' &&
4198 move[3]>='a' && move[3]<='x' ) {
4200 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4201 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4203 if(move[0]>='0' && move[0]<='9' &&
4204 move[1]>='a' && move[1]<='x' &&
4205 move[2]>='0' && move[2]<='9' &&
4206 move[3]>='a' && move[3]<='x' ) {
4207 /* input move, Shogi -> normal */
4208 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4209 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4211 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4214 move[3]>='0' && move[3]<='9' &&
4215 move[2]>='a' && move[2]<='x' ) {
4217 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4221 move[0]>='a' && move[0]<='x' &&
4222 move[3]>='0' && move[3]<='9' &&
4223 move[2]>='a' && move[2]<='x' ) {
4224 /* output move, normal -> Shogi */
4225 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4231 if (appData.debugMode) {
4232 fprintf(debugFP, " out = '%s'\n", move);
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4241 ChessMove *moveType;
4242 int *fromX, *fromY, *toX, *toY;
4245 if (appData.debugMode) {
4246 fprintf(debugFP, "move to parse: %s\n", move);
4248 *moveType = yylexstr(moveNum, move);
4250 switch (*moveType) {
4251 case WhitePromotionChancellor:
4252 case BlackPromotionChancellor:
4253 case WhitePromotionArchbishop:
4254 case BlackPromotionArchbishop:
4255 case WhitePromotionQueen:
4256 case BlackPromotionQueen:
4257 case WhitePromotionRook:
4258 case BlackPromotionRook:
4259 case WhitePromotionBishop:
4260 case BlackPromotionBishop:
4261 case WhitePromotionKnight:
4262 case BlackPromotionKnight:
4263 case WhitePromotionKing:
4264 case BlackPromotionKing:
4266 case WhiteCapturesEnPassant:
4267 case BlackCapturesEnPassant:
4268 case WhiteKingSideCastle:
4269 case WhiteQueenSideCastle:
4270 case BlackKingSideCastle:
4271 case BlackQueenSideCastle:
4272 case WhiteKingSideCastleWild:
4273 case WhiteQueenSideCastleWild:
4274 case BlackKingSideCastleWild:
4275 case BlackQueenSideCastleWild:
4276 /* Code added by Tord: */
4277 case WhiteHSideCastleFR:
4278 case WhiteASideCastleFR:
4279 case BlackHSideCastleFR:
4280 case BlackASideCastleFR:
4281 /* End of code added by Tord */
4282 case IllegalMove: /* bug or odd chess variant */
4283 *fromX = currentMoveString[0] - AAA;
4284 *fromY = currentMoveString[1] - ONE;
4285 *toX = currentMoveString[2] - AAA;
4286 *toY = currentMoveString[3] - ONE;
4287 *promoChar = currentMoveString[4];
4288 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290 if (appData.debugMode) {
4291 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4293 *fromX = *fromY = *toX = *toY = 0;
4296 if (appData.testLegality) {
4297 return (*moveType != IllegalMove);
4299 return !(fromX == fromY && toX == toY);
4304 *fromX = *moveType == WhiteDrop ?
4305 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306 (int) CharToPiece(ToLower(currentMoveString[0]));
4308 *toX = currentMoveString[2] - AAA;
4309 *toY = currentMoveString[3] - ONE;
4310 *promoChar = NULLCHAR;
4314 case ImpossibleMove:
4315 case (ChessMove) 0: /* end of file */
4324 if (appData.debugMode) {
4325 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4328 *fromX = *fromY = *toX = *toY = 0;
4329 *promoChar = NULLCHAR;
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4345 void GetPositionNumber()
4346 { // sets global variable seed
4349 seed = appData.defaultFrcPosition;
4350 if(seed < 0) { // randomize based on time for negative FRC position numbers
4351 for(i=0; i<50; i++) seed += random();
4352 seed = random() ^ random() >> 8 ^ random() << 8;
4353 if(seed<0) seed = -seed;
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4362 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364 board[rank][i] = (ChessSquare) pieceType;
4365 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4367 piecesLeft[pieceType]--;
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4380 i = seed % squaresLeft[shade];
4381 nrOfShuffles *= squaresLeft[shade];
4382 seed /= squaresLeft[shade];
4383 put(board, pieceType, rank, i, shade);
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4389 int i, n=squaresLeft[ANY], j=n-1, k;
4391 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392 i = seed % k; // pick one
4395 while(i >= j) i -= j--;
4396 j = n - 1 - j; i += j;
4397 put(board, pieceType, rank, j, ANY);
4398 put(board, pieceType, rank, i, ANY);
4401 void SetUpShuffle(Board board, int number)
4405 GetPositionNumber(); nrOfShuffles = 1;
4407 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4409 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4411 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4413 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414 p = (int) board[0][i];
4415 if(p < (int) BlackPawn) piecesLeft[p] ++;
4416 board[0][i] = EmptySquare;
4419 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420 // shuffles restricted to allow normal castling put KRR first
4421 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428 put(board, WhiteRook, 0, 0, ANY);
4429 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4432 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436 while(piecesLeft[p] >= 2) {
4437 AddOnePiece(board, p, 0, LITE);
4438 AddOnePiece(board, p, 0, DARK);
4440 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4443 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445 // but we leave King and Rooks for last, to possibly obey FRC restriction
4446 if(p == (int)WhiteRook) continue;
4447 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4451 // now everything is placed, except perhaps King (Unicorn) and Rooks
4453 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454 // Last King gets castling rights
4455 while(piecesLeft[(int)WhiteUnicorn]) {
4456 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4460 while(piecesLeft[(int)WhiteKing]) {
4461 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4467 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4468 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4471 // Only Rooks can be left; simply place them all
4472 while(piecesLeft[(int)WhiteRook]) {
4473 i = put(board, WhiteRook, 0, 0, ANY);
4474 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4477 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4479 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4482 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4486 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /* Basically a safe strcpy that uses the last character as King */
4493 int result = FALSE; int NrPieces;
4495 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4496 && NrPieces >= 12 && !(NrPieces&1)) {
4497 int i; /* [HGM] Accept even length from 12 to 34 */
4499 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500 for( i=0; i<NrPieces/2-1; i++ ) {
4502 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4504 table[(int) WhiteKing] = map[NrPieces/2-1];
4505 table[(int) BlackKing] = map[NrPieces-1];
4513 void Prelude(Board board)
4514 { // [HGM] superchess: random selection of exo-pieces
4515 int i, j, k; ChessSquare p;
4516 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4518 GetPositionNumber(); // use FRC position number
4520 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521 SetCharTable(pieceToChar, appData.pieceToCharTable);
4522 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4523 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4526 j = seed%4; seed /= 4;
4527 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4529 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4530 j = seed%3 + (seed%3 >= j); seed /= 3;
4531 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4533 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4534 j = seed%3; seed /= 3;
4535 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4537 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4538 j = seed%2 + (seed%2 >= j); seed /= 2;
4539 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4541 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4542 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4543 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4544 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545 put(board, exoPieces[0], 0, 0, ANY);
4546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4550 InitPosition(redraw)
4553 ChessSquare (* pieces)[BOARD_SIZE];
4554 int i, j, pawnRow, overrule,
4555 oldx = gameInfo.boardWidth,
4556 oldy = gameInfo.boardHeight,
4557 oldh = gameInfo.holdingsWidth,
4558 oldv = gameInfo.variant;
4560 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4562 /* [AS] Initialize pv info list [HGM] and game status */
4564 for( i=0; i<MAX_MOVES; i++ ) {
4565 pvInfoList[i].depth = 0;
4566 epStatus[i]=EP_NONE;
4567 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4570 initialRulePlies = 0; /* 50-move counter start */
4572 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4573 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4577 /* [HGM] logic here is completely changed. In stead of full positions */
4578 /* the initialized data only consist of the two backranks. The switch */
4579 /* selects which one we will use, which is than copied to the Board */
4580 /* initialPosition, which for the rest is initialized by Pawns and */
4581 /* empty squares. This initial position is then copied to boards[0], */
4582 /* possibly after shuffling, so that it remains available. */
4584 gameInfo.holdingsWidth = 0; /* default board sizes */
4585 gameInfo.boardWidth = 8;
4586 gameInfo.boardHeight = 8;
4587 gameInfo.holdingsSize = 0;
4588 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4589 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4590 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4592 switch (gameInfo.variant) {
4593 case VariantFischeRandom:
4594 shuffleOpenings = TRUE;
4598 case VariantShatranj:
4599 pieces = ShatranjArray;
4600 nrCastlingRights = 0;
4601 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4603 case VariantTwoKings:
4604 pieces = twoKingsArray;
4606 case VariantCapaRandom:
4607 shuffleOpenings = TRUE;
4608 case VariantCapablanca:
4609 pieces = CapablancaArray;
4610 gameInfo.boardWidth = 10;
4611 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4614 pieces = GothicArray;
4615 gameInfo.boardWidth = 10;
4616 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4619 pieces = JanusArray;
4620 gameInfo.boardWidth = 10;
4621 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4622 nrCastlingRights = 6;
4623 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4624 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4625 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4626 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4627 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4628 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4631 pieces = FalconArray;
4632 gameInfo.boardWidth = 10;
4633 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4635 case VariantXiangqi:
4636 pieces = XiangqiArray;
4637 gameInfo.boardWidth = 9;
4638 gameInfo.boardHeight = 10;
4639 nrCastlingRights = 0;
4640 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4643 pieces = ShogiArray;
4644 gameInfo.boardWidth = 9;
4645 gameInfo.boardHeight = 9;
4646 gameInfo.holdingsSize = 7;
4647 nrCastlingRights = 0;
4648 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4650 case VariantCourier:
4651 pieces = CourierArray;
4652 gameInfo.boardWidth = 12;
4653 nrCastlingRights = 0;
4654 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4655 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4657 case VariantKnightmate:
4658 pieces = KnightmateArray;
4659 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4662 pieces = fairyArray;
4663 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4666 pieces = GreatArray;
4667 gameInfo.boardWidth = 10;
4668 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4669 gameInfo.holdingsSize = 8;
4673 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4674 gameInfo.holdingsSize = 8;
4675 startedFromSetupPosition = TRUE;
4677 case VariantCrazyhouse:
4678 case VariantBughouse:
4680 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4681 gameInfo.holdingsSize = 5;
4683 case VariantWildCastle:
4685 /* !!?shuffle with kings guaranteed to be on d or e file */
4686 shuffleOpenings = 1;
4688 case VariantNoCastle:
4690 nrCastlingRights = 0;
4691 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692 /* !!?unconstrained back-rank shuffle */
4693 shuffleOpenings = 1;
4698 if(appData.NrFiles >= 0) {
4699 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4700 gameInfo.boardWidth = appData.NrFiles;
4702 if(appData.NrRanks >= 0) {
4703 gameInfo.boardHeight = appData.NrRanks;
4705 if(appData.holdingsSize >= 0) {
4706 i = appData.holdingsSize;
4707 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4708 gameInfo.holdingsSize = i;
4710 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4711 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4712 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4714 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4715 if(pawnRow < 1) pawnRow = 1;
4717 /* User pieceToChar list overrules defaults */
4718 if(appData.pieceToCharTable != NULL)
4719 SetCharTable(pieceToChar, appData.pieceToCharTable);
4721 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4723 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4724 s = (ChessSquare) 0; /* account holding counts in guard band */
4725 for( i=0; i<BOARD_HEIGHT; i++ )
4726 initialPosition[i][j] = s;
4728 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4729 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4730 initialPosition[pawnRow][j] = WhitePawn;
4731 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4732 if(gameInfo.variant == VariantXiangqi) {
4734 initialPosition[pawnRow][j] =
4735 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4736 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4737 initialPosition[2][j] = WhiteCannon;
4738 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4742 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4744 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4747 initialPosition[1][j] = WhiteBishop;
4748 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4750 initialPosition[1][j] = WhiteRook;
4751 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4754 if( nrCastlingRights == -1) {
4755 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4756 /* This sets default castling rights from none to normal corners */
4757 /* Variants with other castling rights must set them themselves above */
4758 nrCastlingRights = 6;
4760 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4761 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4762 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4763 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4764 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4765 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4768 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4769 if(gameInfo.variant == VariantGreat) { // promotion commoners
4770 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4771 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4772 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4773 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4776 if(gameInfo.variant == VariantFischeRandom) {
4777 if( appData.defaultFrcPosition < 0 ) {
4778 ShuffleFRC( initialPosition );
4781 SetupFRC( initialPosition, appData.defaultFrcPosition );
4783 startedFromSetupPosition = TRUE;
4786 if (appData.debugMode) {
4787 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4789 if(shuffleOpenings) {
4790 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4791 startedFromSetupPosition = TRUE;
4794 if(startedFromPositionFile) {
4795 /* [HGM] loadPos: use PositionFile for every new game */
4796 CopyBoard(initialPosition, filePosition);
4797 for(i=0; i<nrCastlingRights; i++)
4798 castlingRights[0][i] = initialRights[i] = fileRights[i];
4799 startedFromSetupPosition = TRUE;
4802 CopyBoard(boards[0], initialPosition);
4804 if(oldx != gameInfo.boardWidth ||
4805 oldy != gameInfo.boardHeight ||
4806 oldh != gameInfo.holdingsWidth
4808 || oldv == VariantGothic || // For licensing popups
4809 gameInfo.variant == VariantGothic
4812 || oldv == VariantFalcon ||
4813 gameInfo.variant == VariantFalcon
4816 InitDrawingSizes(-2 ,0);
4819 DrawPosition(TRUE, boards[currentMove]);
4823 SendBoard(cps, moveNum)
4824 ChessProgramState *cps;
4827 char message[MSG_SIZ];
4829 if (cps->useSetboard) {
4830 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4831 sprintf(message, "setboard %s\n", fen);
4832 SendToProgram(message, cps);
4838 /* Kludge to set black to move, avoiding the troublesome and now
4839 * deprecated "black" command.
4841 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843 SendToProgram("edit\n", cps);
4844 SendToProgram("#\n", cps);
4845 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846 bp = &boards[moveNum][i][BOARD_LEFT];
4847 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848 if ((int) *bp < (int) BlackPawn) {
4849 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4851 if(message[0] == '+' || message[0] == '~') {
4852 sprintf(message, "%c%c%c+\n",
4853 PieceToChar((ChessSquare)(DEMOTED *bp)),
4856 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4857 message[1] = BOARD_RGHT - 1 - j + '1';
4858 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860 SendToProgram(message, cps);
4865 SendToProgram("c\n", cps);
4866 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867 bp = &boards[moveNum][i][BOARD_LEFT];
4868 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869 if (((int) *bp != (int) EmptySquare)
4870 && ((int) *bp >= (int) BlackPawn)) {
4871 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873 if(message[0] == '+' || message[0] == '~') {
4874 sprintf(message, "%c%c%c+\n",
4875 PieceToChar((ChessSquare)(DEMOTED *bp)),
4878 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879 message[1] = BOARD_RGHT - 1 - j + '1';
4880 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882 SendToProgram(message, cps);
4887 SendToProgram(".\n", cps);
4889 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4893 IsPromotion(fromX, fromY, toX, toY)
4894 int fromX, fromY, toX, toY;
4896 /* [HGM] add Shogi promotions */
4897 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4900 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4901 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4902 /* [HGM] Note to self: line above also weeds out drops */
4903 piece = boards[currentMove][fromY][fromX];
4904 if(gameInfo.variant == VariantShogi) {
4905 promotionZoneSize = 3;
4906 highestPromotingPiece = (int)WhiteKing;
4907 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4908 and if in normal chess we then allow promotion to King, why not
4909 allow promotion of other piece in Shogi? */
4911 if((int)piece >= BlackPawn) {
4912 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4917 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919 return ( (int)piece <= highestPromotingPiece );
4923 InPalace(row, column)
4925 { /* [HGM] for Xiangqi */
4926 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4927 column < (BOARD_WIDTH + 4)/2 &&
4928 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4933 PieceForSquare (x, y)
4937 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4940 return boards[currentMove][y][x];
4944 OKToStartUserMove(x, y)
4947 ChessSquare from_piece;
4950 if (matchMode) return FALSE;
4951 if (gameMode == EditPosition) return TRUE;
4953 if (x >= 0 && y >= 0)
4954 from_piece = boards[currentMove][y][x];
4956 from_piece = EmptySquare;
4958 if (from_piece == EmptySquare) return FALSE;
4960 white_piece = (int)from_piece >= (int)WhitePawn &&
4961 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4964 case PlayFromGameFile:
4966 case TwoMachinesPlay:
4974 case MachinePlaysWhite:
4975 case IcsPlayingBlack:
4976 if (appData.zippyPlay) return FALSE;
4978 DisplayMoveError(_("You are playing Black"));
4983 case MachinePlaysBlack:
4984 case IcsPlayingWhite:
4985 if (appData.zippyPlay) return FALSE;
4987 DisplayMoveError(_("You are playing White"));
4993 if (!white_piece && WhiteOnMove(currentMove)) {
4994 DisplayMoveError(_("It is White's turn"));
4997 if (white_piece && !WhiteOnMove(currentMove)) {
4998 DisplayMoveError(_("It is Black's turn"));
5001 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5002 /* Editing correspondence game history */
5003 /* Could disallow this or prompt for confirmation */
5006 if (currentMove < forwardMostMove) {
5007 /* Discarding moves */
5008 /* Could prompt for confirmation here,
5009 but I don't think that's such a good idea */
5010 forwardMostMove = currentMove;
5014 case BeginningOfGame:
5015 if (appData.icsActive) return FALSE;
5016 if (!appData.noChessProgram) {
5018 DisplayMoveError(_("You are playing White"));
5025 if (!white_piece && WhiteOnMove(currentMove)) {
5026 DisplayMoveError(_("It is White's turn"));
5029 if (white_piece && !WhiteOnMove(currentMove)) {
5030 DisplayMoveError(_("It is Black's turn"));
5039 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5040 && gameMode != AnalyzeFile && gameMode != Training) {
5041 DisplayMoveError(_("Displayed position is not current"));
5047 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5048 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5049 int lastLoadGameUseList = FALSE;
5050 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5051 ChessMove lastLoadGameStart = (ChessMove) 0;
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5056 int fromX, fromY, toX, toY;
5061 ChessSquare pdown, pup;
5063 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5065 /* [HGM] suppress all moves into holdings area and guard band */
5066 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5067 return ImpossibleMove;
5069 /* [HGM] <sameColor> moved to here from winboard.c */
5070 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5071 pdown = boards[currentMove][fromY][fromX];
5072 pup = boards[currentMove][toY][toX];
5073 if ( gameMode != EditPosition && !captureOwn &&
5074 (WhitePawn <= pdown && pdown < BlackPawn &&
5075 WhitePawn <= pup && pup < BlackPawn ||
5076 BlackPawn <= pdown && pdown < EmptySquare &&
5077 BlackPawn <= pup && pup < EmptySquare
5078 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5079 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5080 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5081 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5082 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5086 /* Check if the user is playing in turn. This is complicated because we
5087 let the user "pick up" a piece before it is his turn. So the piece he
5088 tried to pick up may have been captured by the time he puts it down!
5089 Therefore we use the color the user is supposed to be playing in this
5090 test, not the color of the piece that is currently on the starting
5091 square---except in EditGame mode, where the user is playing both
5092 sides; fortunately there the capture race can't happen. (It can
5093 now happen in IcsExamining mode, but that's just too bad. The user
5094 will get a somewhat confusing message in that case.)
5098 case PlayFromGameFile:
5100 case TwoMachinesPlay:
5104 /* We switched into a game mode where moves are not accepted,
5105 perhaps while the mouse button was down. */
5106 return ImpossibleMove;
5108 case MachinePlaysWhite:
5109 /* User is moving for Black */
5110 if (WhiteOnMove(currentMove)) {
5111 DisplayMoveError(_("It is White's turn"));
5112 return ImpossibleMove;
5116 case MachinePlaysBlack:
5117 /* User is moving for White */
5118 if (!WhiteOnMove(currentMove)) {
5119 DisplayMoveError(_("It is Black's turn"));
5120 return ImpossibleMove;
5126 case BeginningOfGame:
5129 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131 /* User is moving for Black */
5132 if (WhiteOnMove(currentMove)) {
5133 DisplayMoveError(_("It is White's turn"));
5134 return ImpossibleMove;
5137 /* User is moving for White */
5138 if (!WhiteOnMove(currentMove)) {
5139 DisplayMoveError(_("It is Black's turn"));
5140 return ImpossibleMove;
5145 case IcsPlayingBlack:
5146 /* User is moving for Black */
5147 if (WhiteOnMove(currentMove)) {
5148 if (!appData.premove) {
5149 DisplayMoveError(_("It is White's turn"));
5150 } else if (toX >= 0 && toY >= 0) {
5153 premoveFromX = fromX;
5154 premoveFromY = fromY;
5155 premovePromoChar = promoChar;
5157 if (appData.debugMode)
5158 fprintf(debugFP, "Got premove: fromX %d,"
5159 "fromY %d, toX %d, toY %d\n",
5160 fromX, fromY, toX, toY);
5161 if(!WhiteOnMove(currentMove) && gotPremove == 1) {
5162 // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5163 if (appData.debugMode)
5164 fprintf(debugFP, "Execute as normal move\n");
5165 gotPremove = 0; break;
5168 return ImpossibleMove;
5172 case IcsPlayingWhite:
5173 /* User is moving for White */
5174 if (!WhiteOnMove(currentMove)) {
5175 if (!appData.premove) {
5176 DisplayMoveError(_("It is Black's turn"));
5177 } else if (toX >= 0 && toY >= 0) {
5180 premoveFromX = fromX;
5181 premoveFromY = fromY;
5182 premovePromoChar = promoChar;
5184 if (appData.debugMode)
5185 fprintf(debugFP, "Got premove: fromX %d,"
5186 "fromY %d, toX %d, toY %d\n",
5187 fromX, fromY, toX, toY);
5188 if(WhiteOnMove(currentMove) && gotPremove == 1) {
5189 // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5190 if (appData.debugMode)
5191 fprintf(debugFP, "Execute as normal move\n");
5192 gotPremove = 0; break;
5195 return ImpossibleMove;
5203 /* EditPosition, empty square, or different color piece;
5204 click-click move is possible */
5205 if (toX == -2 || toY == -2) {
5206 boards[0][fromY][fromX] = EmptySquare;
5207 return AmbiguousMove;
5208 } else if (toX >= 0 && toY >= 0) {
5209 boards[0][toY][toX] = boards[0][fromY][fromX];
5210 boards[0][fromY][fromX] = EmptySquare;
5211 return AmbiguousMove;
5213 return ImpossibleMove;
5216 /* [HGM] If move started in holdings, it means a drop */
5217 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5218 if( pup != EmptySquare ) return ImpossibleMove;
5219 if(appData.testLegality) {
5220 /* it would be more logical if LegalityTest() also figured out
5221 * which drops are legal. For now we forbid pawns on back rank.
5222 * Shogi is on its own here...
5224 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5225 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5226 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5228 return WhiteDrop; /* Not needed to specify white or black yet */
5231 userOfferedDraw = FALSE;
5233 /* [HGM] always test for legality, to get promotion info */
5234 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5235 epStatus[currentMove], castlingRights[currentMove],
5236 fromY, fromX, toY, toX, promoChar);
5237 /* [HGM] but possibly ignore an IllegalMove result */
5238 if (appData.testLegality) {
5239 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5240 DisplayMoveError(_("Illegal move"));
5241 return ImpossibleMove;
5244 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5246 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5247 function is made into one that returns an OK move type if FinishMove
5248 should be called. This to give the calling driver routine the
5249 opportunity to finish the userMove input with a promotion popup,
5250 without bothering the user with this for invalid or illegal moves */
5252 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5255 /* Common tail of UserMoveEvent and DropMenuEvent */
5257 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5259 int fromX, fromY, toX, toY;
5260 /*char*/int promoChar;
5263 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5264 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5265 // [HGM] superchess: suppress promotions to non-available piece
5266 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5267 if(WhiteOnMove(currentMove)) {
5268 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5270 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5274 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5275 move type in caller when we know the move is a legal promotion */
5276 if(moveType == NormalMove && promoChar)
5277 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5278 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5279 /* [HGM] convert drag-and-drop piece drops to standard form */
5280 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5281 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5282 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5283 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5284 // fromX = boards[currentMove][fromY][fromX];
5285 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5286 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5287 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5288 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5292 /* [HGM] <popupFix> The following if has been moved here from
5293 UserMoveEvent(). Because it seemed to belon here (why not allow
5294 piece drops in training games?), and because it can only be
5295 performed after it is known to what we promote. */
5296 if (gameMode == Training) {
5297 /* compare the move played on the board to the next move in the
5298 * game. If they match, display the move and the opponent's response.
5299 * If they don't match, display an error message.
5302 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5303 CopyBoard(testBoard, boards[currentMove]);
5304 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5306 if (CompareBoards(testBoard, boards[currentMove+1])) {
5307 ForwardInner(currentMove+1);
5309 /* Autoplay the opponent's response.
5310 * if appData.animate was TRUE when Training mode was entered,
5311 * the response will be animated.
5313 saveAnimate = appData.animate;
5314 appData.animate = animateTraining;
5315 ForwardInner(currentMove+1);
5316 appData.animate = saveAnimate;
5318 /* check for the end of the game */
5319 if (currentMove >= forwardMostMove) {
5320 gameMode = PlayFromGameFile;
5322 SetTrainingModeOff();
5323 DisplayInformation(_("End of game"));
5326 DisplayError(_("Incorrect move"), 0);
5331 /* Ok, now we know that the move is good, so we can kill
5332 the previous line in Analysis Mode */
5333 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5334 forwardMostMove = currentMove;
5337 /* If we need the chess program but it's dead, restart it */
5338 ResurrectChessProgram();
5340 /* A user move restarts a paused game*/
5344 thinkOutput[0] = NULLCHAR;
5346 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5348 if (gameMode == BeginningOfGame) {
5349 if (appData.noChessProgram) {
5350 gameMode = EditGame;
5354 gameMode = MachinePlaysBlack;
5357 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5359 if (first.sendName) {
5360 sprintf(buf, "name %s\n", gameInfo.white);
5361 SendToProgram(buf, &first);
5367 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5368 /* Relay move to ICS or chess engine */
5369 if (appData.icsActive) {
5370 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5371 gameMode == IcsExamining) {
5372 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5376 if (first.sendTime && (gameMode == BeginningOfGame ||
5377 gameMode == MachinePlaysWhite ||
5378 gameMode == MachinePlaysBlack)) {
5379 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5381 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5382 // [HGM] book: if program might be playing, let it use book
5383 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5384 first.maybeThinking = TRUE;
5385 } else SendMoveToProgram(forwardMostMove-1, &first);
5386 if (currentMove == cmailOldMove + 1) {
5387 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5391 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5395 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5396 EP_UNKNOWN, castlingRights[currentMove]) ) {
5402 if (WhiteOnMove(currentMove)) {
5403 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5405 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5409 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5414 case MachinePlaysBlack:
5415 case MachinePlaysWhite:
5416 /* disable certain menu options while machine is thinking */
5417 SetMachineThinkingEnables();
5424 if(bookHit) { // [HGM] book: simulate book reply
5425 static char bookMove[MSG_SIZ]; // a bit generous?
5427 programStats.nodes = programStats.depth = programStats.time =
5428 programStats.score = programStats.got_only_move = 0;
5429 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5431 strcpy(bookMove, "move ");
5432 strcat(bookMove, bookHit);
5433 HandleMachineMove(bookMove, &first);
5439 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5440 int fromX, fromY, toX, toY;
5443 /* [HGM] This routine was added to allow calling of its two logical
5444 parts from other modules in the old way. Before, UserMoveEvent()
5445 automatically called FinishMove() if the move was OK, and returned
5446 otherwise. I separated the two, in order to make it possible to
5447 slip a promotion popup in between. But that it always needs two
5448 calls, to the first part, (now called UserMoveTest() ), and to
5449 FinishMove if the first part succeeded. Calls that do not need
5450 to do anything in between, can call this routine the old way.
5452 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5453 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5454 if(moveType == AmbiguousMove)
5455 DrawPosition(FALSE, boards[currentMove]);
5456 else if(moveType != ImpossibleMove && moveType != Comment)
5457 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5460 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5462 // char * hint = lastHint;
5463 FrontEndProgramStats stats;
5465 stats.which = cps == &first ? 0 : 1;
5466 stats.depth = cpstats->depth;
5467 stats.nodes = cpstats->nodes;
5468 stats.score = cpstats->score;
5469 stats.time = cpstats->time;
5470 stats.pv = cpstats->movelist;
5471 stats.hint = lastHint;
5472 stats.an_move_index = 0;
5473 stats.an_move_count = 0;
5475 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5476 stats.hint = cpstats->move_name;
5477 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5478 stats.an_move_count = cpstats->nr_moves;
5481 SetProgramStats( &stats );
5484 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5485 { // [HGM] book: this routine intercepts moves to simulate book replies
5486 char *bookHit = NULL;
5488 //first determine if the incoming move brings opponent into his book
5489 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5490 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5491 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5492 if(bookHit != NULL && !cps->bookSuspend) {
5493 // make sure opponent is not going to reply after receiving move to book position
5494 SendToProgram("force\n", cps);
5495 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5497 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5498 // now arrange restart after book miss
5500 // after a book hit we never send 'go', and the code after the call to this routine
5501 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5503 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5504 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5505 SendToProgram(buf, cps);
5506 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5507 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5508 SendToProgram("go\n", cps);
5509 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5510 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5511 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5512 SendToProgram("go\n", cps);
5513 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5515 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5519 ChessProgramState *savedState;
5520 void DeferredBookMove(void)
5522 if(savedState->lastPing != savedState->lastPong)
5523 ScheduleDelayedEvent(DeferredBookMove, 10);
5525 HandleMachineMove(savedMessage, savedState);
5529 HandleMachineMove(message, cps)
5531 ChessProgramState *cps;
5533 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5534 char realname[MSG_SIZ];
5535 int fromX, fromY, toX, toY;
5542 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5544 * Kludge to ignore BEL characters
5546 while (*message == '\007') message++;
5549 * [HGM] engine debug message: ignore lines starting with '#' character
5551 if(cps->debug && *message == '#') return;
5554 * Look for book output
5556 if (cps == &first && bookRequested) {
5557 if (message[0] == '\t' || message[0] == ' ') {
5558 /* Part of the book output is here; append it */
5559 strcat(bookOutput, message);
5560 strcat(bookOutput, " \n");
5562 } else if (bookOutput[0] != NULLCHAR) {
5563 /* All of book output has arrived; display it */
5564 char *p = bookOutput;
5565 while (*p != NULLCHAR) {
5566 if (*p == '\t') *p = ' ';
5569 DisplayInformation(bookOutput);
5570 bookRequested = FALSE;
5571 /* Fall through to parse the current output */
5576 * Look for machine move.
5578 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5579 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5581 /* This method is only useful on engines that support ping */
5582 if (cps->lastPing != cps->lastPong) {
5583 if (gameMode == BeginningOfGame) {
5584 /* Extra move from before last new; ignore */
5585 if (appData.debugMode) {
5586 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5589 if (appData.debugMode) {
5590 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5591 cps->which, gameMode);
5594 SendToProgram("undo\n", cps);
5600 case BeginningOfGame:
5601 /* Extra move from before last reset; ignore */
5602 if (appData.debugMode) {
5603 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5610 /* Extra move after we tried to stop. The mode test is
5611 not a reliable way of detecting this problem, but it's
5612 the best we can do on engines that don't support ping.
5614 if (appData.debugMode) {
5615 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5616 cps->which, gameMode);
5618 SendToProgram("undo\n", cps);
5621 case MachinePlaysWhite:
5622 case IcsPlayingWhite:
5623 machineWhite = TRUE;
5626 case MachinePlaysBlack:
5627 case IcsPlayingBlack:
5628 machineWhite = FALSE;
5631 case TwoMachinesPlay:
5632 machineWhite = (cps->twoMachinesColor[0] == 'w');
5635 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5636 if (appData.debugMode) {
5638 "Ignoring move out of turn by %s, gameMode %d"
5639 ", forwardMost %d\n",
5640 cps->which, gameMode, forwardMostMove);
5645 if (appData.debugMode) { int f = forwardMostMove;
5646 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5647 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5649 if(cps->alphaRank) AlphaRank(machineMove, 4);
5650 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5651 &fromX, &fromY, &toX, &toY, &promoChar)) {
5652 /* Machine move could not be parsed; ignore it. */
5653 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5654 machineMove, cps->which);
5655 DisplayError(buf1, 0);
5656 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5657 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5658 if (gameMode == TwoMachinesPlay) {
5659 GameEnds(machineWhite ? BlackWins : WhiteWins,
5665 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5666 /* So we have to redo legality test with true e.p. status here, */
5667 /* to make sure an illegal e.p. capture does not slip through, */
5668 /* to cause a forfeit on a justified illegal-move complaint */
5669 /* of the opponent. */
5670 if( gameMode==TwoMachinesPlay && appData.testLegality
5671 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5674 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5675 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5676 fromY, fromX, toY, toX, promoChar);
5677 if (appData.debugMode) {
5679 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5680 castlingRights[forwardMostMove][i], castlingRank[i]);
5681 fprintf(debugFP, "castling rights\n");
5683 if(moveType == IllegalMove) {
5684 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5685 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5686 GameEnds(machineWhite ? BlackWins : WhiteWins,
5689 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5690 /* [HGM] Kludge to handle engines that send FRC-style castling
5691 when they shouldn't (like TSCP-Gothic) */
5693 case WhiteASideCastleFR:
5694 case BlackASideCastleFR:
5696 currentMoveString[2]++;
5698 case WhiteHSideCastleFR:
5699 case BlackHSideCastleFR:
5701 currentMoveString[2]--;
5703 default: ; // nothing to do, but suppresses warning of pedantic compilers
5706 hintRequested = FALSE;
5707 lastHint[0] = NULLCHAR;
5708 bookRequested = FALSE;
5709 /* Program may be pondering now */
5710 cps->maybeThinking = TRUE;
5711 if (cps->sendTime == 2) cps->sendTime = 1;
5712 if (cps->offeredDraw) cps->offeredDraw--;
5715 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5717 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5719 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5720 char buf[3*MSG_SIZ];
5722 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5723 programStats.score / 100.,
5725 programStats.time / 100.,
5726 (unsigned int)programStats.nodes,
5727 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5728 programStats.movelist);
5730 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5734 /* currentMoveString is set as a side-effect of ParseOneMove */
5735 strcpy(machineMove, currentMoveString);
5736 strcat(machineMove, "\n");
5737 strcpy(moveList[forwardMostMove], machineMove);
5739 /* [AS] Save move info and clear stats for next move */
5740 pvInfoList[ forwardMostMove ].score = programStats.score;
5741 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5742 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5743 ClearProgramStats();
5744 thinkOutput[0] = NULLCHAR;
5745 hiddenThinkOutputState = 0;
5747 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5749 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5750 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5753 while( count < adjudicateLossPlies ) {
5754 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5757 score = -score; /* Flip score for winning side */
5760 if( score > adjudicateLossThreshold ) {
5767 if( count >= adjudicateLossPlies ) {
5768 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5770 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5771 "Xboard adjudication",
5778 if( gameMode == TwoMachinesPlay ) {
5779 // [HGM] some adjudications useful with buggy engines
5780 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5781 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5784 if( appData.testLegality )
5785 { /* [HGM] Some more adjudications for obstinate engines */
5786 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5787 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5788 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5789 static int moveCount = 6;
5791 char *reason = NULL;
5793 /* Count what is on board. */
5794 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5795 { ChessSquare p = boards[forwardMostMove][i][j];
5799 { /* count B,N,R and other of each side */
5802 NrK++; break; // [HGM] atomic: count Kings
5806 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5807 bishopsColor |= 1 << ((i^j)&1);
5812 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5813 bishopsColor |= 1 << ((i^j)&1);
5828 PawnAdvance += m; NrPawns++;
5830 NrPieces += (p != EmptySquare);
5831 NrW += ((int)p < (int)BlackPawn);
5832 if(gameInfo.variant == VariantXiangqi &&
5833 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5834 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5835 NrW -= ((int)p < (int)BlackPawn);
5839 /* Some material-based adjudications that have to be made before stalemate test */
5840 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5841 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5842 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5843 if(appData.checkMates) {
5844 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5845 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5847 "Xboard adjudication: King destroyed", GE_XBOARD );
5852 /* Bare King in Shatranj (loses) or Losers (wins) */
5853 if( NrW == 1 || NrPieces - NrW == 1) {
5854 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5855 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5856 if(appData.checkMates) {
5857 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5858 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5860 "Xboard adjudication: Bare king", GE_XBOARD );
5864 if( gameInfo.variant == VariantShatranj && --bare < 0)
5866 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5867 if(appData.checkMates) {
5868 /* but only adjudicate if adjudication enabled */
5869 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5870 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5871 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5872 "Xboard adjudication: Bare king", GE_XBOARD );
5879 // don't wait for engine to announce game end if we can judge ourselves
5880 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5881 castlingRights[forwardMostMove]) ) {
5883 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5884 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5885 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5886 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5889 reason = "Xboard adjudication: 3rd check";
5890 epStatus[forwardMostMove] = EP_CHECKMATE;
5900 reason = "Xboard adjudication: Stalemate";
5901 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5902 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5903 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5904 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5905 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5906 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5907 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5908 EP_CHECKMATE : EP_WINS);
5909 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5910 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5914 reason = "Xboard adjudication: Checkmate";
5915 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5919 switch(i = epStatus[forwardMostMove]) {
5921 result = GameIsDrawn; break;
5923 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5925 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5927 result = (ChessMove) 0;
5929 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5930 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5931 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5932 GameEnds( result, reason, GE_XBOARD );
5936 /* Next absolutely insufficient mating material. */
5937 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5938 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5939 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5940 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5941 { /* KBK, KNK, KK of KBKB with like Bishops */
5943 /* always flag draws, for judging claims */
5944 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5946 if(appData.materialDraws) {
5947 /* but only adjudicate them if adjudication enabled */
5948 SendToProgram("force\n", cps->other); // suppress reply
5949 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5950 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5951 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5956 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5958 ( NrWR == 1 && NrBR == 1 /* KRKR */
5959 || NrWQ==1 && NrBQ==1 /* KQKQ */
5960 || NrWN==2 || NrBN==2 /* KNNK */
5961 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5963 if(--moveCount < 0 && appData.trivialDraws)
5964 { /* if the first 3 moves do not show a tactical win, declare draw */
5965 SendToProgram("force\n", cps->other); // suppress reply
5966 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5967 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5968 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5971 } else moveCount = 6;
5975 if (appData.debugMode) { int i;
5976 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5977 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5978 appData.drawRepeats);
5979 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5980 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5984 /* Check for rep-draws */
5986 for(k = forwardMostMove-2;
5987 k>=backwardMostMove && k>=forwardMostMove-100 &&
5988 epStatus[k] < EP_UNKNOWN &&
5989 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5993 if (appData.debugMode) {
5994 fprintf(debugFP, " loop\n");
5997 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5999 if (appData.debugMode) {
6000 fprintf(debugFP, "match\n");
6003 /* compare castling rights */
6004 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6005 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6006 rights++; /* King lost rights, while rook still had them */
6007 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6008 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6009 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6010 rights++; /* but at least one rook lost them */
6012 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6013 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6015 if( castlingRights[forwardMostMove][5] >= 0 ) {
6016 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6017 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6021 if (appData.debugMode) {
6022 for(i=0; i<nrCastlingRights; i++)
6023 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6026 if (appData.debugMode) {
6027 fprintf(debugFP, " %d %d\n", rights, k);
6030 if( rights == 0 && ++count > appData.drawRepeats-2
6031 && appData.drawRepeats > 1) {
6032 /* adjudicate after user-specified nr of repeats */
6033 SendToProgram("force\n", cps->other); // suppress reply
6034 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6035 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6037 // [HGM] xiangqi: check for forbidden perpetuals
6038 int m, ourPerpetual = 1, hisPerpetual = 1;
6039 for(m=forwardMostMove; m>k; m-=2) {
6040 if(MateTest(boards[m], PosFlags(m),
6041 EP_NONE, castlingRights[m]) != MT_CHECK)
6042 ourPerpetual = 0; // the current mover did not always check
6043 if(MateTest(boards[m-1], PosFlags(m-1),
6044 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6045 hisPerpetual = 0; // the opponent did not always check
6047 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6048 ourPerpetual, hisPerpetual);
6049 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6050 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6051 "Xboard adjudication: perpetual checking", GE_XBOARD );
6054 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6055 break; // (or we would have caught him before). Abort repetition-checking loop.
6056 // Now check for perpetual chases
6057 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6058 hisPerpetual = PerpetualChase(k, forwardMostMove);
6059 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6060 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6061 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6062 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6065 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6066 break; // Abort repetition-checking loop.
6068 // if neither of us is checking or chasing all the time, or both are, it is draw
6070 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6073 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6074 epStatus[forwardMostMove] = EP_REP_DRAW;
6078 /* Now we test for 50-move draws. Determine ply count */
6079 count = forwardMostMove;
6080 /* look for last irreversble move */
6081 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6083 /* if we hit starting position, add initial plies */
6084 if( count == backwardMostMove )
6085 count -= initialRulePlies;
6086 count = forwardMostMove - count;
6088 epStatus[forwardMostMove] = EP_RULE_DRAW;
6089 /* this is used to judge if draw claims are legal */
6090 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6091 SendToProgram("force\n", cps->other); // suppress reply
6092 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6093 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6094 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6098 /* if draw offer is pending, treat it as a draw claim
6099 * when draw condition present, to allow engines a way to
6100 * claim draws before making their move to avoid a race
6101 * condition occurring after their move
6103 if( cps->other->offeredDraw || cps->offeredDraw ) {
6105 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6106 p = "Draw claim: 50-move rule";
6107 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6108 p = "Draw claim: 3-fold repetition";
6109 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6110 p = "Draw claim: insufficient mating material";
6112 SendToProgram("force\n", cps->other); // suppress reply
6113 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6114 GameEnds( GameIsDrawn, p, GE_XBOARD );
6115 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6121 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6122 SendToProgram("force\n", cps->other); // suppress reply
6123 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6124 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6126 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6133 if (gameMode == TwoMachinesPlay) {
6134 /* [HGM] relaying draw offers moved to after reception of move */
6135 /* and interpreting offer as claim if it brings draw condition */
6136 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6137 SendToProgram("draw\n", cps->other);
6139 if (cps->other->sendTime) {
6140 SendTimeRemaining(cps->other,
6141 cps->other->twoMachinesColor[0] == 'w');
6143 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6144 if (firstMove && !bookHit) {
6146 if (cps->other->useColors) {
6147 SendToProgram(cps->other->twoMachinesColor, cps->other);
6149 SendToProgram("go\n", cps->other);
6151 cps->other->maybeThinking = TRUE;
6154 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6156 if (!pausing && appData.ringBellAfterMoves) {
6161 * Reenable menu items that were disabled while
6162 * machine was thinking
6164 if (gameMode != TwoMachinesPlay)
6165 SetUserThinkingEnables();
6167 // [HGM] book: after book hit opponent has received move and is now in force mode
6168 // force the book reply into it, and then fake that it outputted this move by jumping
6169 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6171 static char bookMove[MSG_SIZ]; // a bit generous?
6173 strcpy(bookMove, "move ");
6174 strcat(bookMove, bookHit);
6177 programStats.nodes = programStats.depth = programStats.time =
6178 programStats.score = programStats.got_only_move = 0;
6179 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6181 if(cps->lastPing != cps->lastPong) {
6182 savedMessage = message; // args for deferred call
6184 ScheduleDelayedEvent(DeferredBookMove, 10);
6193 /* Set special modes for chess engines. Later something general
6194 * could be added here; for now there is just one kludge feature,
6195 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6196 * when "xboard" is given as an interactive command.
6198 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6199 cps->useSigint = FALSE;
6200 cps->useSigterm = FALSE;
6202 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6203 ParseFeatures(message+8, cps);
6204 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6207 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6208 * want this, I was asked to put it in, and obliged.
6210 if (!strncmp(message, "setboard ", 9)) {
6211 Board initial_position; int i;
6213 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6215 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6216 DisplayError(_("Bad FEN received from engine"), 0);
6219 Reset(FALSE, FALSE);
6220 CopyBoard(boards[0], initial_position);
6221 initialRulePlies = FENrulePlies;
6222 epStatus[0] = FENepStatus;
6223 for( i=0; i<nrCastlingRights; i++ )
6224 castlingRights[0][i] = FENcastlingRights[i];
6225 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6226 else gameMode = MachinePlaysBlack;
6227 DrawPosition(FALSE, boards[currentMove]);
6233 * Look for communication commands
6235 if (!strncmp(message, "telluser ", 9)) {
6236 DisplayNote(message + 9);
6239 if (!strncmp(message, "tellusererror ", 14)) {
6240 DisplayError(message + 14, 0);
6243 if (!strncmp(message, "tellopponent ", 13)) {
6244 if (appData.icsActive) {
6246 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6250 DisplayNote(message + 13);
6254 if (!strncmp(message, "tellothers ", 11)) {
6255 if (appData.icsActive) {
6257 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6263 if (!strncmp(message, "tellall ", 8)) {
6264 if (appData.icsActive) {
6266 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6270 DisplayNote(message + 8);
6274 if (strncmp(message, "warning", 7) == 0) {
6275 /* Undocumented feature, use tellusererror in new code */
6276 DisplayError(message, 0);
6279 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6280 strcpy(realname, cps->tidy);
6281 strcat(realname, " query");
6282 AskQuestion(realname, buf2, buf1, cps->pr);
6285 /* Commands from the engine directly to ICS. We don't allow these to be
6286 * sent until we are logged on. Crafty kibitzes have been known to
6287 * interfere with the login process.
6290 if (!strncmp(message, "tellics ", 8)) {
6291 SendToICS(message + 8);
6295 if (!strncmp(message, "tellicsnoalias ", 15)) {
6296 SendToICS(ics_prefix);
6297 SendToICS(message + 15);
6301 /* The following are for backward compatibility only */
6302 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6303 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6304 SendToICS(ics_prefix);
6310 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6314 * If the move is illegal, cancel it and redraw the board.
6315 * Also deal with other error cases. Matching is rather loose
6316 * here to accommodate engines written before the spec.
6318 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6319 strncmp(message, "Error", 5) == 0) {
6320 if (StrStr(message, "name") ||
6321 StrStr(message, "rating") || StrStr(message, "?") ||
6322 StrStr(message, "result") || StrStr(message, "board") ||
6323 StrStr(message, "bk") || StrStr(message, "computer") ||
6324 StrStr(message, "variant") || StrStr(message, "hint") ||
6325 StrStr(message, "random") || StrStr(message, "depth") ||
6326 StrStr(message, "accepted")) {
6329 if (StrStr(message, "protover")) {
6330 /* Program is responding to input, so it's apparently done
6331 initializing, and this error message indicates it is
6332 protocol version 1. So we don't need to wait any longer
6333 for it to initialize and send feature commands. */
6334 FeatureDone(cps, 1);
6335 cps->protocolVersion = 1;
6338 cps->maybeThinking = FALSE;
6340 if (StrStr(message, "draw")) {
6341 /* Program doesn't have "draw" command */
6342 cps->sendDrawOffers = 0;
6345 if (cps->sendTime != 1 &&
6346 (StrStr(message, "time") || StrStr(message, "otim"))) {
6347 /* Program apparently doesn't have "time" or "otim" command */
6351 if (StrStr(message, "analyze")) {
6352 cps->analysisSupport = FALSE;
6353 cps->analyzing = FALSE;
6355 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6356 DisplayError(buf2, 0);
6359 if (StrStr(message, "(no matching move)st")) {
6360 /* Special kludge for GNU Chess 4 only */
6361 cps->stKludge = TRUE;
6362 SendTimeControl(cps, movesPerSession, timeControl,
6363 timeIncrement, appData.searchDepth,
6367 if (StrStr(message, "(no matching move)sd")) {
6368 /* Special kludge for GNU Chess 4 only */
6369 cps->sdKludge = TRUE;
6370 SendTimeControl(cps, movesPerSession, timeControl,
6371 timeIncrement, appData.searchDepth,
6375 if (!StrStr(message, "llegal")) {
6378 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6379 gameMode == IcsIdle) return;
6380 if (forwardMostMove <= backwardMostMove) return;
6382 /* Following removed: it caused a bug where a real illegal move
6383 message in analyze mored would be ignored. */
6384 if (cps == &first && programStats.ok_to_send == 0) {
6385 /* Bogus message from Crafty responding to "." This filtering
6386 can miss some of the bad messages, but fortunately the bug
6387 is fixed in current Crafty versions, so it doesn't matter. */
6391 if (pausing) PauseEvent();
6392 if(appData.forceIllegal) {
6393 // [HGM] illegal: machine refused move; force position after move into it
6394 SendToProgram("force\n", cps);
6395 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6396 // we have a real problem now, as SendBoard will use the a2a3 kludge
6397 // when black is to move, while there might be nothing on a2 or black
6398 // might already have the move. So send the board as if white has the move.
6399 // But first we must change the stm of the engine, as it refused the last move
6400 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6401 if(WhiteOnMove(forwardMostMove)) {
6402 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6403 SendBoard(cps, forwardMostMove); // kludgeless board
6405 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6406 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6407 SendBoard(cps, forwardMostMove+1); // kludgeless board
6409 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6410 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6411 gameMode == TwoMachinesPlay)
6412 SendToProgram("go\n", cps);
6415 if (gameMode == PlayFromGameFile) {
6416 /* Stop reading this game file */
6417 gameMode = EditGame;
6420 currentMove = --forwardMostMove;
6421 DisplayMove(currentMove-1); /* before DisplayMoveError */
6423 DisplayBothClocks();
6424 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6425 parseList[currentMove], cps->which);
6426 DisplayMoveError(buf1);
6427 DrawPosition(FALSE, boards[currentMove]);
6429 /* [HGM] illegal-move claim should forfeit game when Xboard */
6430 /* only passes fully legal moves */
6431 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6432 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6433 "False illegal-move claim", GE_XBOARD );
6437 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6438 /* Program has a broken "time" command that
6439 outputs a string not ending in newline.
6445 * If chess program startup fails, exit with an error message.
6446 * Attempts to recover here are futile.
6448 if ((StrStr(message, "unknown host") != NULL)
6449 || (StrStr(message, "No remote directory") != NULL)
6450 || (StrStr(message, "not found") != NULL)
6451 || (StrStr(message, "No such file") != NULL)
6452 || (StrStr(message, "can't alloc") != NULL)
6453 || (StrStr(message, "Permission denied") != NULL)) {
6455 cps->maybeThinking = FALSE;
6456 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6457 cps->which, cps->program, cps->host, message);
6458 RemoveInputSource(cps->isr);
6459 DisplayFatalError(buf1, 0, 1);
6464 * Look for hint output
6466 if (sscanf(message, "Hint: %s", buf1) == 1) {
6467 if (cps == &first && hintRequested) {
6468 hintRequested = FALSE;
6469 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6470 &fromX, &fromY, &toX, &toY, &promoChar)) {
6471 (void) CoordsToAlgebraic(boards[forwardMostMove],
6472 PosFlags(forwardMostMove), EP_UNKNOWN,
6473 fromY, fromX, toY, toX, promoChar, buf1);
6474 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6475 DisplayInformation(buf2);
6477 /* Hint move could not be parsed!? */
6478 snprintf(buf2, sizeof(buf2),
6479 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6481 DisplayError(buf2, 0);
6484 strcpy(lastHint, buf1);
6490 * Ignore other messages if game is not in progress
6492 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6493 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6496 * look for win, lose, draw, or draw offer
6498 if (strncmp(message, "1-0", 3) == 0) {
6499 char *p, *q, *r = "";
6500 p = strchr(message, '{');
6508 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6510 } else if (strncmp(message, "0-1", 3) == 0) {
6511 char *p, *q, *r = "";
6512 p = strchr(message, '{');
6520 /* Kludge for Arasan 4.1 bug */
6521 if (strcmp(r, "Black resigns") == 0) {
6522 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6525 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6527 } else if (strncmp(message, "1/2", 3) == 0) {
6528 char *p, *q, *r = "";
6529 p = strchr(message, '{');
6538 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6541 } else if (strncmp(message, "White resign", 12) == 0) {
6542 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6544 } else if (strncmp(message, "Black resign", 12) == 0) {
6545 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6547 } else if (strncmp(message, "White matches", 13) == 0 ||
6548 strncmp(message, "Black matches", 13) == 0 ) {
6549 /* [HGM] ignore GNUShogi noises */
6551 } else if (strncmp(message, "White", 5) == 0 &&
6552 message[5] != '(' &&
6553 StrStr(message, "Black") == NULL) {
6554 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6556 } else if (strncmp(message, "Black", 5) == 0 &&
6557 message[5] != '(') {
6558 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6560 } else if (strcmp(message, "resign") == 0 ||
6561 strcmp(message, "computer resigns") == 0) {
6563 case MachinePlaysBlack:
6564 case IcsPlayingBlack:
6565 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6567 case MachinePlaysWhite:
6568 case IcsPlayingWhite:
6569 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6571 case TwoMachinesPlay:
6572 if (cps->twoMachinesColor[0] == 'w')
6573 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6575 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6582 } else if (strncmp(message, "opponent mates", 14) == 0) {
6584 case MachinePlaysBlack:
6585 case IcsPlayingBlack:
6586 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6588 case MachinePlaysWhite:
6589 case IcsPlayingWhite:
6590 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6592 case TwoMachinesPlay:
6593 if (cps->twoMachinesColor[0] == 'w')
6594 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6596 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6603 } else if (strncmp(message, "computer mates", 14) == 0) {
6605 case MachinePlaysBlack:
6606 case IcsPlayingBlack:
6607 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6609 case MachinePlaysWhite:
6610 case IcsPlayingWhite:
6611 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6613 case TwoMachinesPlay:
6614 if (cps->twoMachinesColor[0] == 'w')
6615 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6617 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6624 } else if (strncmp(message, "checkmate", 9) == 0) {
6625 if (WhiteOnMove(forwardMostMove)) {
6626 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6628 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6631 } else if (strstr(message, "Draw") != NULL ||
6632 strstr(message, "game is a draw") != NULL) {
6633 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6635 } else if (strstr(message, "offer") != NULL &&
6636 strstr(message, "draw") != NULL) {
6638 if (appData.zippyPlay && first.initDone) {
6639 /* Relay offer to ICS */
6640 SendToICS(ics_prefix);
6641 SendToICS("draw\n");
6644 cps->offeredDraw = 2; /* valid until this engine moves twice */
6645 if (gameMode == TwoMachinesPlay) {
6646 if (cps->other->offeredDraw) {
6647 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6648 /* [HGM] in two-machine mode we delay relaying draw offer */
6649 /* until after we also have move, to see if it is really claim */
6653 if (cps->other->sendDrawOffers) {
6654 SendToProgram("draw\n", cps->other);
6658 } else if (gameMode == MachinePlaysWhite ||
6659 gameMode == MachinePlaysBlack) {
6660 if (userOfferedDraw) {
6661 DisplayInformation(_("Machine accepts your draw offer"));
6662 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6664 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6671 * Look for thinking output
6673 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6674 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6676 int plylev, mvleft, mvtot, curscore, time;
6677 char mvname[MOVE_LEN];
6681 int prefixHint = FALSE;
6682 mvname[0] = NULLCHAR;
6685 case MachinePlaysBlack:
6686 case IcsPlayingBlack:
6687 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6689 case MachinePlaysWhite:
6690 case IcsPlayingWhite:
6691 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6696 case IcsObserving: /* [DM] icsEngineAnalyze */
6697 if (!appData.icsEngineAnalyze) ignore = TRUE;
6699 case TwoMachinesPlay:
6700 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6711 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6712 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6714 if (plyext != ' ' && plyext != '\t') {
6718 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6719 if( cps->scoreIsAbsolute &&
6720 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6722 curscore = -curscore;
6726 programStats.depth = plylev;
6727 programStats.nodes = nodes;
6728 programStats.time = time;
6729 programStats.score = curscore;
6730 programStats.got_only_move = 0;
6732 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6735 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6736 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6737 if(WhiteOnMove(forwardMostMove))
6738 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6739 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6742 /* Buffer overflow protection */
6743 if (buf1[0] != NULLCHAR) {
6744 if (strlen(buf1) >= sizeof(programStats.movelist)
6745 && appData.debugMode) {
6747 "PV is too long; using the first %d bytes.\n",
6748 sizeof(programStats.movelist) - 1);
6751 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6753 sprintf(programStats.movelist, " no PV\n");
6756 if (programStats.seen_stat) {
6757 programStats.ok_to_send = 1;
6760 if (strchr(programStats.movelist, '(') != NULL) {
6761 programStats.line_is_book = 1;
6762 programStats.nr_moves = 0;
6763 programStats.moves_left = 0;
6765 programStats.line_is_book = 0;
6768 SendProgramStatsToFrontend( cps, &programStats );
6771 [AS] Protect the thinkOutput buffer from overflow... this
6772 is only useful if buf1 hasn't overflowed first!
6774 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6776 (gameMode == TwoMachinesPlay ?
6777 ToUpper(cps->twoMachinesColor[0]) : ' '),
6778 ((double) curscore) / 100.0,
6779 prefixHint ? lastHint : "",
6780 prefixHint ? " " : "" );
6782 if( buf1[0] != NULLCHAR ) {
6783 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6785 if( strlen(buf1) > max_len ) {
6786 if( appData.debugMode) {
6787 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6789 buf1[max_len+1] = '\0';
6792 strcat( thinkOutput, buf1 );
6795 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6796 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6797 DisplayMove(currentMove - 1);
6802 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6803 /* crafty (9.25+) says "(only move) <move>"
6804 * if there is only 1 legal move
6806 sscanf(p, "(only move) %s", buf1);
6807 sprintf(thinkOutput, "%s (only move)", buf1);
6808 sprintf(programStats.movelist, "%s (only move)", buf1);
6809 programStats.depth = 1;
6810 programStats.nr_moves = 1;
6811 programStats.moves_left = 1;
6812 programStats.nodes = 1;
6813 programStats.time = 1;
6814 programStats.got_only_move = 1;
6816 /* Not really, but we also use this member to
6817 mean "line isn't going to change" (Crafty
6818 isn't searching, so stats won't change) */
6819 programStats.line_is_book = 1;
6821 SendProgramStatsToFrontend( cps, &programStats );
6823 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6824 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6825 DisplayMove(currentMove - 1);
6829 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6830 &time, &nodes, &plylev, &mvleft,
6831 &mvtot, mvname) >= 5) {
6832 /* The stat01: line is from Crafty (9.29+) in response
6833 to the "." command */
6834 programStats.seen_stat = 1;
6835 cps->maybeThinking = TRUE;
6837 if (programStats.got_only_move || !appData.periodicUpdates)
6840 programStats.depth = plylev;
6841 programStats.time = time;
6842 programStats.nodes = nodes;
6843 programStats.moves_left = mvleft;
6844 programStats.nr_moves = mvtot;
6845 strcpy(programStats.move_name, mvname);
6846 programStats.ok_to_send = 1;
6847 programStats.movelist[0] = '\0';
6849 SendProgramStatsToFrontend( cps, &programStats );
6854 } else if (strncmp(message,"++",2) == 0) {
6855 /* Crafty 9.29+ outputs this */
6856 programStats.got_fail = 2;
6859 } else if (strncmp(message,"--",2) == 0) {
6860 /* Crafty 9.29+ outputs this */
6861 programStats.got_fail = 1;
6864 } else if (thinkOutput[0] != NULLCHAR &&
6865 strncmp(message, " ", 4) == 0) {
6866 unsigned message_len;
6869 while (*p && *p == ' ') p++;
6871 message_len = strlen( p );
6873 /* [AS] Avoid buffer overflow */
6874 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6875 strcat(thinkOutput, " ");
6876 strcat(thinkOutput, p);
6879 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6880 strcat(programStats.movelist, " ");
6881 strcat(programStats.movelist, p);
6884 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6885 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6886 DisplayMove(currentMove - 1);
6895 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6896 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6898 ChessProgramStats cpstats;
6900 if (plyext != ' ' && plyext != '\t') {
6904 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6905 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6906 curscore = -curscore;
6909 cpstats.depth = plylev;
6910 cpstats.nodes = nodes;
6911 cpstats.time = time;
6912 cpstats.score = curscore;
6913 cpstats.got_only_move = 0;
6914 cpstats.movelist[0] = '\0';
6916 if (buf1[0] != NULLCHAR) {
6917 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6920 cpstats.ok_to_send = 0;
6921 cpstats.line_is_book = 0;
6922 cpstats.nr_moves = 0;
6923 cpstats.moves_left = 0;
6925 SendProgramStatsToFrontend( cps, &cpstats );
6932 /* Parse a game score from the character string "game", and
6933 record it as the history of the current game. The game
6934 score is NOT assumed to start from the standard position.
6935 The display is not updated in any way.
6938 ParseGameHistory(game)
6942 int fromX, fromY, toX, toY, boardIndex;
6947 if (appData.debugMode)
6948 fprintf(debugFP, "Parsing game history: %s\n", game);
6950 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6951 gameInfo.site = StrSave(appData.icsHost);
6952 gameInfo.date = PGNDate();
6953 gameInfo.round = StrSave("-");
6955 /* Parse out names of players */
6956 while (*game == ' ') game++;
6958 while (*game != ' ') *p++ = *game++;
6960 gameInfo.white = StrSave(buf);
6961 while (*game == ' ') game++;
6963 while (*game != ' ' && *game != '\n') *p++ = *game++;
6965 gameInfo.black = StrSave(buf);
6968 boardIndex = blackPlaysFirst ? 1 : 0;
6971 yyboardindex = boardIndex;
6972 moveType = (ChessMove) yylex();
6974 case IllegalMove: /* maybe suicide chess, etc. */
6975 if (appData.debugMode) {
6976 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6977 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6978 setbuf(debugFP, NULL);
6980 case WhitePromotionChancellor:
6981 case BlackPromotionChancellor:
6982 case WhitePromotionArchbishop:
6983 case BlackPromotionArchbishop:
6984 case WhitePromotionQueen:
6985 case BlackPromotionQueen:
6986 case WhitePromotionRook:
6987 case BlackPromotionRook:
6988 case WhitePromotionBishop:
6989 case BlackPromotionBishop:
6990 case WhitePromotionKnight:
6991 case BlackPromotionKnight:
6992 case WhitePromotionKing:
6993 case BlackPromotionKing:
6995 case WhiteCapturesEnPassant:
6996 case BlackCapturesEnPassant:
6997 case WhiteKingSideCastle:
6998 case WhiteQueenSideCastle:
6999 case BlackKingSideCastle:
7000 case BlackQueenSideCastle:
7001 case WhiteKingSideCastleWild:
7002 case WhiteQueenSideCastleWild:
7003 case BlackKingSideCastleWild:
7004 case BlackQueenSideCastleWild:
7006 case WhiteHSideCastleFR:
7007 case WhiteASideCastleFR:
7008 case BlackHSideCastleFR:
7009 case BlackASideCastleFR:
7011 fromX = currentMoveString[0] - AAA;
7012 fromY = currentMoveString[1] - ONE;
7013 toX = currentMoveString[2] - AAA;
7014 toY = currentMoveString[3] - ONE;
7015 promoChar = currentMoveString[4];
7019 fromX = moveType == WhiteDrop ?
7020 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7021 (int) CharToPiece(ToLower(currentMoveString[0]));
7023 toX = currentMoveString[2] - AAA;
7024 toY = currentMoveString[3] - ONE;
7025 promoChar = NULLCHAR;
7029 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7030 if (appData.debugMode) {
7031 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7032 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7033 setbuf(debugFP, NULL);
7035 DisplayError(buf, 0);
7037 case ImpossibleMove:
7039 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7040 if (appData.debugMode) {
7041 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7042 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7043 setbuf(debugFP, NULL);
7045 DisplayError(buf, 0);
7047 case (ChessMove) 0: /* end of file */
7048 if (boardIndex < backwardMostMove) {
7049 /* Oops, gap. How did that happen? */
7050 DisplayError(_("Gap in move list"), 0);
7053 backwardMostMove = blackPlaysFirst ? 1 : 0;
7054 if (boardIndex > forwardMostMove) {
7055 forwardMostMove = boardIndex;
7059 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7060 strcat(parseList[boardIndex-1], " ");
7061 strcat(parseList[boardIndex-1], yy_text);
7073 case GameUnfinished:
7074 if (gameMode == IcsExamining) {
7075 if (boardIndex < backwardMostMove) {
7076 /* Oops, gap. How did that happen? */
7079 backwardMostMove = blackPlaysFirst ? 1 : 0;
7082 gameInfo.result = moveType;
7083 p = strchr(yy_text, '{');
7084 if (p == NULL) p = strchr(yy_text, '(');
7087 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7089 q = strchr(p, *p == '{' ? '}' : ')');
7090 if (q != NULL) *q = NULLCHAR;
7093 gameInfo.resultDetails = StrSave(p);
7096 if (boardIndex >= forwardMostMove &&
7097 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7098 backwardMostMove = blackPlaysFirst ? 1 : 0;
7101 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7102 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7103 parseList[boardIndex]);
7104 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7105 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7106 /* currentMoveString is set as a side-effect of yylex */
7107 strcpy(moveList[boardIndex], currentMoveString);
7108 strcat(moveList[boardIndex], "\n");
7110 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7111 castlingRights[boardIndex], &epStatus[boardIndex]);
7112 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7113 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7119 if(gameInfo.variant != VariantShogi)
7120 strcat(parseList[boardIndex - 1], "+");
7124 strcat(parseList[boardIndex - 1], "#");
7131 /* Apply a move to the given board */
7133 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7134 int fromX, fromY, toX, toY;
7140 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7142 /* [HGM] compute & store e.p. status and castling rights for new position */
7143 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7146 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7150 if( board[toY][toX] != EmptySquare )
7153 if( board[fromY][fromX] == WhitePawn ) {
7154 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7157 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7158 gameInfo.variant != VariantBerolina || toX < fromX)
7159 *ep = toX | berolina;
7160 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7161 gameInfo.variant != VariantBerolina || toX > fromX)
7165 if( board[fromY][fromX] == BlackPawn ) {
7166 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7168 if( toY-fromY== -2) {
7169 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7170 gameInfo.variant != VariantBerolina || toX < fromX)
7171 *ep = toX | berolina;
7172 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7173 gameInfo.variant != VariantBerolina || toX > fromX)
7178 for(i=0; i<nrCastlingRights; i++) {
7179 if(castling[i] == fromX && castlingRank[i] == fromY ||
7180 castling[i] == toX && castlingRank[i] == toY
7181 ) castling[i] = -1; // revoke for moved or captured piece
7186 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7187 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7188 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7190 if (fromX == toX && fromY == toY) return;
7192 if (fromY == DROP_RANK) {
7194 piece = board[toY][toX] = (ChessSquare) fromX;
7196 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7197 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7198 if(gameInfo.variant == VariantKnightmate)
7199 king += (int) WhiteUnicorn - (int) WhiteKing;
7201 /* Code added by Tord: */
7202 /* FRC castling assumed when king captures friendly rook. */
7203 if (board[fromY][fromX] == WhiteKing &&
7204 board[toY][toX] == WhiteRook) {
7205 board[fromY][fromX] = EmptySquare;
7206 board[toY][toX] = EmptySquare;
7208 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7210 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7212 } else if (board[fromY][fromX] == BlackKing &&
7213 board[toY][toX] == BlackRook) {
7214 board[fromY][fromX] = EmptySquare;
7215 board[toY][toX] = EmptySquare;
7217 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7219 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7221 /* End of code added by Tord */
7223 } else if (board[fromY][fromX] == king
7224 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7225 && toY == fromY && toX > fromX+1) {
7226 board[fromY][fromX] = EmptySquare;
7227 board[toY][toX] = king;
7228 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7229 board[fromY][BOARD_RGHT-1] = EmptySquare;
7230 } else if (board[fromY][fromX] == king
7231 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7232 && toY == fromY && toX < fromX-1) {
7233 board[fromY][fromX] = EmptySquare;
7234 board[toY][toX] = king;
7235 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7236 board[fromY][BOARD_LEFT] = EmptySquare;
7237 } else if (board[fromY][fromX] == WhitePawn
7238 && toY == BOARD_HEIGHT-1
7239 && gameInfo.variant != VariantXiangqi
7241 /* white pawn promotion */
7242 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7243 if (board[toY][toX] == EmptySquare) {
7244 board[toY][toX] = WhiteQueen;
7246 if(gameInfo.variant==VariantBughouse ||
7247 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7248 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7249 board[fromY][fromX] = EmptySquare;
7250 } else if ((fromY == BOARD_HEIGHT-4)
7252 && gameInfo.variant != VariantXiangqi
7253 && gameInfo.variant != VariantBerolina
7254 && (board[fromY][fromX] == WhitePawn)
7255 && (board[toY][toX] == EmptySquare)) {
7256 board[fromY][fromX] = EmptySquare;
7257 board[toY][toX] = WhitePawn;
7258 captured = board[toY - 1][toX];
7259 board[toY - 1][toX] = EmptySquare;
7260 } else if ((fromY == BOARD_HEIGHT-4)
7262 && gameInfo.variant == VariantBerolina
7263 && (board[fromY][fromX] == WhitePawn)
7264 && (board[toY][toX] == EmptySquare)) {
7265 board[fromY][fromX] = EmptySquare;
7266 board[toY][toX] = WhitePawn;
7267 if(oldEP & EP_BEROLIN_A) {
7268 captured = board[fromY][fromX-1];
7269 board[fromY][fromX-1] = EmptySquare;
7270 }else{ captured = board[fromY][fromX+1];
7271 board[fromY][fromX+1] = EmptySquare;
7273 } else if (board[fromY][fromX] == king
7274 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7275 && toY == fromY && toX > fromX+1) {
7276 board[fromY][fromX] = EmptySquare;
7277 board[toY][toX] = king;
7278 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7279 board[fromY][BOARD_RGHT-1] = EmptySquare;
7280 } else if (board[fromY][fromX] == king
7281 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7282 && toY == fromY && toX < fromX-1) {
7283 board[fromY][fromX] = EmptySquare;
7284 board[toY][toX] = king;
7285 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7286 board[fromY][BOARD_LEFT] = EmptySquare;
7287 } else if (fromY == 7 && fromX == 3
7288 && board[fromY][fromX] == BlackKing
7289 && toY == 7 && toX == 5) {
7290 board[fromY][fromX] = EmptySquare;
7291 board[toY][toX] = BlackKing;
7292 board[fromY][7] = EmptySquare;
7293 board[toY][4] = BlackRook;
7294 } else if (fromY == 7 && fromX == 3
7295 && board[fromY][fromX] == BlackKing
7296 && toY == 7 && toX == 1) {
7297 board[fromY][fromX] = EmptySquare;
7298 board[toY][toX] = BlackKing;
7299 board[fromY][0] = EmptySquare;
7300 board[toY][2] = BlackRook;
7301 } else if (board[fromY][fromX] == BlackPawn
7303 && gameInfo.variant != VariantXiangqi
7305 /* black pawn promotion */
7306 board[0][toX] = CharToPiece(ToLower(promoChar));
7307 if (board[0][toX] == EmptySquare) {
7308 board[0][toX] = BlackQueen;
7310 if(gameInfo.variant==VariantBughouse ||
7311 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7312 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7313 board[fromY][fromX] = EmptySquare;
7314 } else if ((fromY == 3)
7316 && gameInfo.variant != VariantXiangqi
7317 && gameInfo.variant != VariantBerolina
7318 && (board[fromY][fromX] == BlackPawn)
7319 && (board[toY][toX] == EmptySquare)) {
7320 board[fromY][fromX] = EmptySquare;
7321 board[toY][toX] = BlackPawn;
7322 captured = board[toY + 1][toX];
7323 board[toY + 1][toX] = EmptySquare;
7324 } else if ((fromY == 3)
7326 && gameInfo.variant == VariantBerolina
7327 && (board[fromY][fromX] == BlackPawn)
7328 && (board[toY][toX] == EmptySquare)) {
7329 board[fromY][fromX] = EmptySquare;
7330 board[toY][toX] = BlackPawn;
7331 if(oldEP & EP_BEROLIN_A) {
7332 captured = board[fromY][fromX-1];
7333 board[fromY][fromX-1] = EmptySquare;
7334 }else{ captured = board[fromY][fromX+1];
7335 board[fromY][fromX+1] = EmptySquare;
7338 board[toY][toX] = board[fromY][fromX];
7339 board[fromY][fromX] = EmptySquare;
7342 /* [HGM] now we promote for Shogi, if needed */
7343 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7344 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7347 if (gameInfo.holdingsWidth != 0) {
7349 /* !!A lot more code needs to be written to support holdings */
7350 /* [HGM] OK, so I have written it. Holdings are stored in the */
7351 /* penultimate board files, so they are automaticlly stored */
7352 /* in the game history. */
7353 if (fromY == DROP_RANK) {
7354 /* Delete from holdings, by decreasing count */
7355 /* and erasing image if necessary */
7357 if(p < (int) BlackPawn) { /* white drop */
7358 p -= (int)WhitePawn;
7359 if(p >= gameInfo.holdingsSize) p = 0;
7360 if(--board[p][BOARD_WIDTH-2] == 0)
7361 board[p][BOARD_WIDTH-1] = EmptySquare;
7362 } else { /* black drop */
7363 p -= (int)BlackPawn;
7364 if(p >= gameInfo.holdingsSize) p = 0;
7365 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7366 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7369 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7370 && gameInfo.variant != VariantBughouse ) {
7371 /* [HGM] holdings: Add to holdings, if holdings exist */
7372 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7373 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7374 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7377 if (p >= (int) BlackPawn) {
7378 p -= (int)BlackPawn;
7379 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7380 /* in Shogi restore piece to its original first */
7381 captured = (ChessSquare) (DEMOTED captured);
7384 p = PieceToNumber((ChessSquare)p);
7385 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7386 board[p][BOARD_WIDTH-2]++;
7387 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7389 p -= (int)WhitePawn;
7390 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7391 captured = (ChessSquare) (DEMOTED captured);
7394 p = PieceToNumber((ChessSquare)p);
7395 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7396 board[BOARD_HEIGHT-1-p][1]++;
7397 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7401 } else if (gameInfo.variant == VariantAtomic) {
7402 if (captured != EmptySquare) {
7404 for (y = toY-1; y <= toY+1; y++) {
7405 for (x = toX-1; x <= toX+1; x++) {
7406 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7407 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7408 board[y][x] = EmptySquare;
7412 board[toY][toX] = EmptySquare;
7415 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7416 /* [HGM] Shogi promotions */
7417 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7420 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7421 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7422 // [HGM] superchess: take promotion piece out of holdings
7423 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7424 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7425 if(!--board[k][BOARD_WIDTH-2])
7426 board[k][BOARD_WIDTH-1] = EmptySquare;
7428 if(!--board[BOARD_HEIGHT-1-k][1])
7429 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7435 /* Updates forwardMostMove */
7437 MakeMove(fromX, fromY, toX, toY, promoChar)
7438 int fromX, fromY, toX, toY;
7441 // forwardMostMove++; // [HGM] bare: moved downstream
7443 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7444 int timeLeft; static int lastLoadFlag=0; int king, piece;
7445 piece = boards[forwardMostMove][fromY][fromX];
7446 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7447 if(gameInfo.variant == VariantKnightmate)
7448 king += (int) WhiteUnicorn - (int) WhiteKing;
7449 if(forwardMostMove == 0) {
7451 fprintf(serverMoves, "%s;", second.tidy);
7452 fprintf(serverMoves, "%s;", first.tidy);
7453 if(!blackPlaysFirst)
7454 fprintf(serverMoves, "%s;", second.tidy);
7455 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7456 lastLoadFlag = loadFlag;
7458 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7459 // print castling suffix
7460 if( toY == fromY && piece == king ) {
7462 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7464 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7467 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7468 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7469 boards[forwardMostMove][toY][toX] == EmptySquare
7471 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7473 if(promoChar != NULLCHAR)
7474 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7476 fprintf(serverMoves, "/%d/%d",
7477 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7478 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7479 else timeLeft = blackTimeRemaining/1000;
7480 fprintf(serverMoves, "/%d", timeLeft);
7482 fflush(serverMoves);
7485 if (forwardMostMove+1 >= MAX_MOVES) {
7486 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7491 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7492 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7493 if (commentList[forwardMostMove+1] != NULL) {
7494 free(commentList[forwardMostMove+1]);
7495 commentList[forwardMostMove+1] = NULL;
7497 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7498 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7499 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7500 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7501 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7502 gameInfo.result = GameUnfinished;
7503 if (gameInfo.resultDetails != NULL) {
7504 free(gameInfo.resultDetails);
7505 gameInfo.resultDetails = NULL;
7507 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7508 moveList[forwardMostMove - 1]);
7509 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7510 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7511 fromY, fromX, toY, toX, promoChar,
7512 parseList[forwardMostMove - 1]);
7513 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7514 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7515 castlingRights[forwardMostMove]) ) {
7521 if(gameInfo.variant != VariantShogi)
7522 strcat(parseList[forwardMostMove - 1], "+");
7526 strcat(parseList[forwardMostMove - 1], "#");
7529 if (appData.debugMode) {
7530 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7535 /* Updates currentMove if not pausing */
7537 ShowMove(fromX, fromY, toX, toY)
7539 int instant = (gameMode == PlayFromGameFile) ?
7540 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7541 if(appData.noGUI) return;
7542 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7544 if (forwardMostMove == currentMove + 1) {
7545 AnimateMove(boards[forwardMostMove - 1],
7546 fromX, fromY, toX, toY);
7548 if (appData.highlightLastMove) {
7549 SetHighlights(fromX, fromY, toX, toY);
7552 currentMove = forwardMostMove;
7555 if (instant) return;
7557 DisplayMove(currentMove - 1);
7558 DrawPosition(FALSE, boards[currentMove]);
7559 DisplayBothClocks();
7560 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7563 void SendEgtPath(ChessProgramState *cps)
7564 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7565 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7567 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7570 char c, *q = name+1, *r, *s;
7572 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7573 while(*p && *p != ',') *q++ = *p++;
7575 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7576 strcmp(name, ",nalimov:") == 0 ) {
7577 // take nalimov path from the menu-changeable option first, if it is defined
7578 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7579 SendToProgram(buf,cps); // send egtbpath command for nalimov
7581 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7582 (s = StrStr(appData.egtFormats, name)) != NULL) {
7583 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7584 s = r = StrStr(s, ":") + 1; // beginning of path info
7585 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7586 c = *r; *r = 0; // temporarily null-terminate path info
7587 *--q = 0; // strip of trailig ':' from name
7588 sprintf(buf, "egtpath %s %s\n", name+1, s);
7590 SendToProgram(buf,cps); // send egtbpath command for this format
7592 if(*p == ',') p++; // read away comma to position for next format name
7597 InitChessProgram(cps, setup)
7598 ChessProgramState *cps;
7599 int setup; /* [HGM] needed to setup FRC opening position */
7601 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7602 if (appData.noChessProgram) return;
7603 hintRequested = FALSE;
7604 bookRequested = FALSE;
7606 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7607 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7608 if(cps->memSize) { /* [HGM] memory */
7609 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7610 SendToProgram(buf, cps);
7612 SendEgtPath(cps); /* [HGM] EGT */
7613 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7614 sprintf(buf, "cores %d\n", appData.smpCores);
7615 SendToProgram(buf, cps);
7618 SendToProgram(cps->initString, cps);
7619 if (gameInfo.variant != VariantNormal &&
7620 gameInfo.variant != VariantLoadable
7621 /* [HGM] also send variant if board size non-standard */
7622 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7624 char *v = VariantName(gameInfo.variant);
7625 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7626 /* [HGM] in protocol 1 we have to assume all variants valid */
7627 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7628 DisplayFatalError(buf, 0, 1);
7632 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7633 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7634 if( gameInfo.variant == VariantXiangqi )
7635 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7636 if( gameInfo.variant == VariantShogi )
7637 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7638 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7639 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7640 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7641 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7642 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7643 if( gameInfo.variant == VariantCourier )
7644 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7645 if( gameInfo.variant == VariantSuper )
7646 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7647 if( gameInfo.variant == VariantGreat )
7648 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7651 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7652 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7653 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7654 if(StrStr(cps->variants, b) == NULL) {
7655 // specific sized variant not known, check if general sizing allowed
7656 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7657 if(StrStr(cps->variants, "boardsize") == NULL) {
7658 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7659 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7660 DisplayFatalError(buf, 0, 1);
7663 /* [HGM] here we really should compare with the maximum supported board size */
7666 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7667 sprintf(buf, "variant %s\n", b);
7668 SendToProgram(buf, cps);
7670 currentlyInitializedVariant = gameInfo.variant;
7672 /* [HGM] send opening position in FRC to first engine */
7674 SendToProgram("force\n", cps);
7676 /* engine is now in force mode! Set flag to wake it up after first move. */
7677 setboardSpoiledMachineBlack = 1;
7681 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7682 SendToProgram(buf, cps);
7684 cps->maybeThinking = FALSE;
7685 cps->offeredDraw = 0;
7686 if (!appData.icsActive) {
7687 SendTimeControl(cps, movesPerSession, timeControl,
7688 timeIncrement, appData.searchDepth,
7691 if (appData.showThinking
7692 // [HGM] thinking: four options require thinking output to be sent
7693 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7695 SendToProgram("post\n", cps);
7697 SendToProgram("hard\n", cps);
7698 if (!appData.ponderNextMove) {
7699 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7700 it without being sure what state we are in first. "hard"
7701 is not a toggle, so that one is OK.
7703 SendToProgram("easy\n", cps);
7706 sprintf(buf, "ping %d\n", ++cps->lastPing);
7707 SendToProgram(buf, cps);
7709 cps->initDone = TRUE;
7714 StartChessProgram(cps)
7715 ChessProgramState *cps;
7720 if (appData.noChessProgram) return;
7721 cps->initDone = FALSE;
7723 if (strcmp(cps->host, "localhost") == 0) {
7724 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7725 } else if (*appData.remoteShell == NULLCHAR) {
7726 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7728 if (*appData.remoteUser == NULLCHAR) {
7729 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7732 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7733 cps->host, appData.remoteUser, cps->program);
7735 err = StartChildProcess(buf, "", &cps->pr);
7739 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7740 DisplayFatalError(buf, err, 1);
7746 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7747 if (cps->protocolVersion > 1) {
7748 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7749 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7750 cps->comboCnt = 0; // and values of combo boxes
7751 SendToProgram(buf, cps);
7753 SendToProgram("xboard\n", cps);
7759 TwoMachinesEventIfReady P((void))
7761 if (first.lastPing != first.lastPong) {
7762 DisplayMessage("", _("Waiting for first chess program"));
7763 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7766 if (second.lastPing != second.lastPong) {
7767 DisplayMessage("", _("Waiting for second chess program"));
7768 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7776 NextMatchGame P((void))
7778 int index; /* [HGM] autoinc: step lod index during match */
7780 if (*appData.loadGameFile != NULLCHAR) {
7781 index = appData.loadGameIndex;
7782 if(index < 0) { // [HGM] autoinc
7783 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7784 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7786 LoadGameFromFile(appData.loadGameFile,
7788 appData.loadGameFile, FALSE);
7789 } else if (*appData.loadPositionFile != NULLCHAR) {
7790 index = appData.loadPositionIndex;
7791 if(index < 0) { // [HGM] autoinc
7792 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7793 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7795 LoadPositionFromFile(appData.loadPositionFile,
7797 appData.loadPositionFile);
7799 TwoMachinesEventIfReady();
7802 void UserAdjudicationEvent( int result )
7804 ChessMove gameResult = GameIsDrawn;
7807 gameResult = WhiteWins;
7809 else if( result < 0 ) {
7810 gameResult = BlackWins;
7813 if( gameMode == TwoMachinesPlay ) {
7814 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7819 // [HGM] save: calculate checksum of game to make games easily identifiable
7820 int StringCheckSum(char *s)
7823 if(s==NULL) return 0;
7824 while(*s) i = i*259 + *s++;
7831 for(i=backwardMostMove; i<forwardMostMove; i++) {
7832 sum += pvInfoList[i].depth;
7833 sum += StringCheckSum(parseList[i]);
7834 sum += StringCheckSum(commentList[i]);
7837 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7838 return sum + StringCheckSum(commentList[i]);
7839 } // end of save patch
7842 GameEnds(result, resultDetails, whosays)
7844 char *resultDetails;
7847 GameMode nextGameMode;
7851 if(endingGame) return; /* [HGM] crash: forbid recursion */
7854 if (appData.debugMode) {
7855 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7856 result, resultDetails ? resultDetails : "(null)", whosays);
7859 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7860 /* If we are playing on ICS, the server decides when the
7861 game is over, but the engine can offer to draw, claim
7865 if (appData.zippyPlay && first.initDone) {
7866 if (result == GameIsDrawn) {
7867 /* In case draw still needs to be claimed */
7868 SendToICS(ics_prefix);
7869 SendToICS("draw\n");
7870 } else if (StrCaseStr(resultDetails, "resign")) {
7871 SendToICS(ics_prefix);
7872 SendToICS("resign\n");
7876 endingGame = 0; /* [HGM] crash */
7880 /* If we're loading the game from a file, stop */
7881 if (whosays == GE_FILE) {
7882 (void) StopLoadGameTimer();
7886 /* Cancel draw offers */
7887 first.offeredDraw = second.offeredDraw = 0;
7889 /* If this is an ICS game, only ICS can really say it's done;
7890 if not, anyone can. */
7891 isIcsGame = (gameMode == IcsPlayingWhite ||
7892 gameMode == IcsPlayingBlack ||
7893 gameMode == IcsObserving ||
7894 gameMode == IcsExamining);
7896 if (!isIcsGame || whosays == GE_ICS) {
7897 /* OK -- not an ICS game, or ICS said it was done */
7899 if (!isIcsGame && !appData.noChessProgram)
7900 SetUserThinkingEnables();
7902 /* [HGM] if a machine claims the game end we verify this claim */
7903 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7904 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7906 ChessMove trueResult = (ChessMove) -1;
7908 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7909 first.twoMachinesColor[0] :
7910 second.twoMachinesColor[0] ;
7912 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7913 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7914 /* [HGM] verify: engine mate claims accepted if they were flagged */
7915 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7917 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7918 /* [HGM] verify: engine mate claims accepted if they were flagged */
7919 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7921 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7922 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7925 // now verify win claims, but not in drop games, as we don't understand those yet
7926 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7927 || gameInfo.variant == VariantGreat) &&
7928 (result == WhiteWins && claimer == 'w' ||
7929 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7930 if (appData.debugMode) {
7931 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7932 result, epStatus[forwardMostMove], forwardMostMove);
7934 if(result != trueResult) {
7935 sprintf(buf, "False win claim: '%s'", resultDetails);
7936 result = claimer == 'w' ? BlackWins : WhiteWins;
7937 resultDetails = buf;
7940 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7941 && (forwardMostMove <= backwardMostMove ||
7942 epStatus[forwardMostMove-1] > EP_DRAWS ||
7943 (claimer=='b')==(forwardMostMove&1))
7945 /* [HGM] verify: draws that were not flagged are false claims */
7946 sprintf(buf, "False draw claim: '%s'", resultDetails);
7947 result = claimer == 'w' ? BlackWins : WhiteWins;
7948 resultDetails = buf;
7950 /* (Claiming a loss is accepted no questions asked!) */
7952 /* [HGM] bare: don't allow bare King to win */
7953 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7954 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7955 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7956 && result != GameIsDrawn)
7957 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7958 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7959 int p = (int)boards[forwardMostMove][i][j] - color;
7960 if(p >= 0 && p <= (int)WhiteKing) k++;
7962 if (appData.debugMode) {
7963 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7964 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7967 result = GameIsDrawn;
7968 sprintf(buf, "%s but bare king", resultDetails);
7969 resultDetails = buf;
7975 if(serverMoves != NULL && !loadFlag) { char c = '=';
7976 if(result==WhiteWins) c = '+';
7977 if(result==BlackWins) c = '-';
7978 if(resultDetails != NULL)
7979 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7981 if (resultDetails != NULL) {
7982 gameInfo.result = result;
7983 gameInfo.resultDetails = StrSave(resultDetails);
7985 /* display last move only if game was not loaded from file */
7986 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7987 DisplayMove(currentMove - 1);
7989 if (forwardMostMove != 0) {
7990 if (gameMode != PlayFromGameFile && gameMode != EditGame
7991 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7993 if (*appData.saveGameFile != NULLCHAR) {
7994 SaveGameToFile(appData.saveGameFile, TRUE);
7995 } else if (appData.autoSaveGames) {
7998 if (*appData.savePositionFile != NULLCHAR) {
7999 SavePositionToFile(appData.savePositionFile);
8004 /* Tell program how game ended in case it is learning */
8005 /* [HGM] Moved this to after saving the PGN, just in case */
8006 /* engine died and we got here through time loss. In that */
8007 /* case we will get a fatal error writing the pipe, which */
8008 /* would otherwise lose us the PGN. */
8009 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8010 /* output during GameEnds should never be fatal anymore */
8011 if (gameMode == MachinePlaysWhite ||
8012 gameMode == MachinePlaysBlack ||
8013 gameMode == TwoMachinesPlay ||
8014 gameMode == IcsPlayingWhite ||
8015 gameMode == IcsPlayingBlack ||
8016 gameMode == BeginningOfGame) {
8018 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8020 if (first.pr != NoProc) {
8021 SendToProgram(buf, &first);
8023 if (second.pr != NoProc &&
8024 gameMode == TwoMachinesPlay) {
8025 SendToProgram(buf, &second);
8030 if (appData.icsActive) {
8031 if (appData.quietPlay &&
8032 (gameMode == IcsPlayingWhite ||
8033 gameMode == IcsPlayingBlack)) {
8034 SendToICS(ics_prefix);
8035 SendToICS("set shout 1\n");
8037 nextGameMode = IcsIdle;
8038 ics_user_moved = FALSE;
8039 /* clean up premove. It's ugly when the game has ended and the
8040 * premove highlights are still on the board.
8044 ClearPremoveHighlights();
8045 DrawPosition(FALSE, boards[currentMove]);
8047 if (whosays == GE_ICS) {
8050 if (gameMode == IcsPlayingWhite)
8052 else if(gameMode == IcsPlayingBlack)
8056 if (gameMode == IcsPlayingBlack)
8058 else if(gameMode == IcsPlayingWhite)
8065 PlayIcsUnfinishedSound();
8068 } else if (gameMode == EditGame ||
8069 gameMode == PlayFromGameFile ||
8070 gameMode == AnalyzeMode ||
8071 gameMode == AnalyzeFile) {
8072 nextGameMode = gameMode;
8074 nextGameMode = EndOfGame;
8079 nextGameMode = gameMode;
8082 if (appData.noChessProgram) {
8083 gameMode = nextGameMode;
8085 endingGame = 0; /* [HGM] crash */
8090 /* Put first chess program into idle state */
8091 if (first.pr != NoProc &&
8092 (gameMode == MachinePlaysWhite ||
8093 gameMode == MachinePlaysBlack ||
8094 gameMode == TwoMachinesPlay ||
8095 gameMode == IcsPlayingWhite ||
8096 gameMode == IcsPlayingBlack ||
8097 gameMode == BeginningOfGame)) {
8098 SendToProgram("force\n", &first);
8099 if (first.usePing) {
8101 sprintf(buf, "ping %d\n", ++first.lastPing);
8102 SendToProgram(buf, &first);
8105 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8106 /* Kill off first chess program */
8107 if (first.isr != NULL)
8108 RemoveInputSource(first.isr);
8111 if (first.pr != NoProc) {
8113 DoSleep( appData.delayBeforeQuit );
8114 SendToProgram("quit\n", &first);
8115 DoSleep( appData.delayAfterQuit );
8116 DestroyChildProcess(first.pr, first.useSigterm);
8121 /* Put second chess program into idle state */
8122 if (second.pr != NoProc &&
8123 gameMode == TwoMachinesPlay) {
8124 SendToProgram("force\n", &second);
8125 if (second.usePing) {
8127 sprintf(buf, "ping %d\n", ++second.lastPing);
8128 SendToProgram(buf, &second);
8131 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8132 /* Kill off second chess program */
8133 if (second.isr != NULL)
8134 RemoveInputSource(second.isr);
8137 if (second.pr != NoProc) {
8138 DoSleep( appData.delayBeforeQuit );
8139 SendToProgram("quit\n", &second);
8140 DoSleep( appData.delayAfterQuit );
8141 DestroyChildProcess(second.pr, second.useSigterm);
8146 if (matchMode && gameMode == TwoMachinesPlay) {
8149 if (first.twoMachinesColor[0] == 'w') {
8156 if (first.twoMachinesColor[0] == 'b') {
8165 if (matchGame < appData.matchGames) {
8167 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8168 tmp = first.twoMachinesColor;
8169 first.twoMachinesColor = second.twoMachinesColor;
8170 second.twoMachinesColor = tmp;
8172 gameMode = nextGameMode;
8174 if(appData.matchPause>10000 || appData.matchPause<10)
8175 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8176 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8177 endingGame = 0; /* [HGM] crash */
8181 gameMode = nextGameMode;
8182 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8183 first.tidy, second.tidy,
8184 first.matchWins, second.matchWins,
8185 appData.matchGames - (first.matchWins + second.matchWins));
8186 DisplayFatalError(buf, 0, 0);
8189 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8190 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8192 gameMode = nextGameMode;
8194 endingGame = 0; /* [HGM] crash */
8197 /* Assumes program was just initialized (initString sent).
8198 Leaves program in force mode. */
8200 FeedMovesToProgram(cps, upto)
8201 ChessProgramState *cps;
8206 if (appData.debugMode)
8207 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8208 startedFromSetupPosition ? "position and " : "",
8209 backwardMostMove, upto, cps->which);
8210 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8211 // [HGM] variantswitch: make engine aware of new variant
8212 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8213 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8214 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8215 SendToProgram(buf, cps);
8216 currentlyInitializedVariant = gameInfo.variant;
8218 SendToProgram("force\n", cps);
8219 if (startedFromSetupPosition) {
8220 SendBoard(cps, backwardMostMove);
8221 if (appData.debugMode) {
8222 fprintf(debugFP, "feedMoves\n");
8225 for (i = backwardMostMove; i < upto; i++) {
8226 SendMoveToProgram(i, cps);
8232 ResurrectChessProgram()
8234 /* The chess program may have exited.
8235 If so, restart it and feed it all the moves made so far. */
8237 if (appData.noChessProgram || first.pr != NoProc) return;
8239 StartChessProgram(&first);
8240 InitChessProgram(&first, FALSE);
8241 FeedMovesToProgram(&first, currentMove);
8243 if (!first.sendTime) {
8244 /* can't tell gnuchess what its clock should read,
8245 so we bow to its notion. */
8247 timeRemaining[0][currentMove] = whiteTimeRemaining;
8248 timeRemaining[1][currentMove] = blackTimeRemaining;
8251 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8252 appData.icsEngineAnalyze) && first.analysisSupport) {
8253 SendToProgram("analyze\n", &first);
8254 first.analyzing = TRUE;
8267 if (appData.debugMode) {
8268 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8269 redraw, init, gameMode);
8271 pausing = pauseExamInvalid = FALSE;
8272 startedFromSetupPosition = blackPlaysFirst = FALSE;
8274 whiteFlag = blackFlag = FALSE;
8275 userOfferedDraw = FALSE;
8276 hintRequested = bookRequested = FALSE;
8277 first.maybeThinking = FALSE;
8278 second.maybeThinking = FALSE;
8279 first.bookSuspend = FALSE; // [HGM] book
8280 second.bookSuspend = FALSE;
8281 thinkOutput[0] = NULLCHAR;
8282 lastHint[0] = NULLCHAR;
8283 ClearGameInfo(&gameInfo);
8284 gameInfo.variant = StringToVariant(appData.variant);
8285 ics_user_moved = ics_clock_paused = FALSE;
8286 ics_getting_history = H_FALSE;
8288 white_holding[0] = black_holding[0] = NULLCHAR;
8289 ClearProgramStats();
8290 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8294 flipView = appData.flipView;
8295 ClearPremoveHighlights();
8297 alarmSounded = FALSE;
8299 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8300 if(appData.serverMovesName != NULL) {
8301 /* [HGM] prepare to make moves file for broadcasting */
8302 clock_t t = clock();
8303 if(serverMoves != NULL) fclose(serverMoves);
8304 serverMoves = fopen(appData.serverMovesName, "r");
8305 if(serverMoves != NULL) {
8306 fclose(serverMoves);
8307 /* delay 15 sec before overwriting, so all clients can see end */
8308 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8310 serverMoves = fopen(appData.serverMovesName, "w");
8314 gameMode = BeginningOfGame;
8316 if(appData.icsActive) gameInfo.variant = VariantNormal;
8317 currentMove = forwardMostMove = backwardMostMove = 0;
8318 InitPosition(redraw);
8319 for (i = 0; i < MAX_MOVES; i++) {
8320 if (commentList[i] != NULL) {
8321 free(commentList[i]);
8322 commentList[i] = NULL;
8326 timeRemaining[0][0] = whiteTimeRemaining;
8327 timeRemaining[1][0] = blackTimeRemaining;
8328 if (first.pr == NULL) {
8329 StartChessProgram(&first);
8332 InitChessProgram(&first, startedFromSetupPosition);
8335 DisplayMessage("", "");
8336 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8337 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8344 if (!AutoPlayOneMove())
8346 if (matchMode || appData.timeDelay == 0)
8348 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8350 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8359 int fromX, fromY, toX, toY;
8361 if (appData.debugMode) {
8362 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8365 if (gameMode != PlayFromGameFile)
8368 if (currentMove >= forwardMostMove) {
8369 gameMode = EditGame;
8372 /* [AS] Clear current move marker at the end of a game */
8373 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8378 toX = moveList[currentMove][2] - AAA;
8379 toY = moveList[currentMove][3] - ONE;
8381 if (moveList[currentMove][1] == '@') {
8382 if (appData.highlightLastMove) {
8383 SetHighlights(-1, -1, toX, toY);
8386 fromX = moveList[currentMove][0] - AAA;
8387 fromY = moveList[currentMove][1] - ONE;
8389 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8391 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8393 if (appData.highlightLastMove) {
8394 SetHighlights(fromX, fromY, toX, toY);
8397 DisplayMove(currentMove);
8398 SendMoveToProgram(currentMove++, &first);
8399 DisplayBothClocks();
8400 DrawPosition(FALSE, boards[currentMove]);
8401 // [HGM] PV info: always display, routine tests if empty
8402 DisplayComment(currentMove - 1, commentList[currentMove]);
8408 LoadGameOneMove(readAhead)
8409 ChessMove readAhead;
8411 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8412 char promoChar = NULLCHAR;
8417 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8418 gameMode != AnalyzeMode && gameMode != Training) {
8423 yyboardindex = forwardMostMove;
8424 if (readAhead != (ChessMove)0) {
8425 moveType = readAhead;
8427 if (gameFileFP == NULL)
8429 moveType = (ChessMove) yylex();
8435 if (appData.debugMode)
8436 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8438 if (*p == '{' || *p == '[' || *p == '(') {
8439 p[strlen(p) - 1] = NULLCHAR;
8443 /* append the comment but don't display it */
8444 while (*p == '\n') p++;
8445 AppendComment(currentMove, p);
8448 case WhiteCapturesEnPassant:
8449 case BlackCapturesEnPassant:
8450 case WhitePromotionChancellor:
8451 case BlackPromotionChancellor:
8452 case WhitePromotionArchbishop:
8453 case BlackPromotionArchbishop:
8454 case WhitePromotionCentaur:
8455 case BlackPromotionCentaur:
8456 case WhitePromotionQueen:
8457 case BlackPromotionQueen:
8458 case WhitePromotionRook:
8459 case BlackPromotionRook:
8460 case WhitePromotionBishop:
8461 case BlackPromotionBishop:
8462 case WhitePromotionKnight:
8463 case BlackPromotionKnight:
8464 case WhitePromotionKing:
8465 case BlackPromotionKing:
8467 case WhiteKingSideCastle:
8468 case WhiteQueenSideCastle:
8469 case BlackKingSideCastle:
8470 case BlackQueenSideCastle:
8471 case WhiteKingSideCastleWild:
8472 case WhiteQueenSideCastleWild:
8473 case BlackKingSideCastleWild:
8474 case BlackQueenSideCastleWild:
8476 case WhiteHSideCastleFR:
8477 case WhiteASideCastleFR:
8478 case BlackHSideCastleFR:
8479 case BlackASideCastleFR:
8481 if (appData.debugMode)
8482 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8483 fromX = currentMoveString[0] - AAA;
8484 fromY = currentMoveString[1] - ONE;
8485 toX = currentMoveString[2] - AAA;
8486 toY = currentMoveString[3] - ONE;
8487 promoChar = currentMoveString[4];
8492 if (appData.debugMode)
8493 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8494 fromX = moveType == WhiteDrop ?
8495 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8496 (int) CharToPiece(ToLower(currentMoveString[0]));
8498 toX = currentMoveString[2] - AAA;
8499 toY = currentMoveString[3] - ONE;
8505 case GameUnfinished:
8506 if (appData.debugMode)
8507 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8508 p = strchr(yy_text, '{');
8509 if (p == NULL) p = strchr(yy_text, '(');
8512 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8514 q = strchr(p, *p == '{' ? '}' : ')');
8515 if (q != NULL) *q = NULLCHAR;
8518 GameEnds(moveType, p, GE_FILE);
8520 if (cmailMsgLoaded) {
8522 flipView = WhiteOnMove(currentMove);
8523 if (moveType == GameUnfinished) flipView = !flipView;
8524 if (appData.debugMode)
8525 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8529 case (ChessMove) 0: /* end of file */
8530 if (appData.debugMode)
8531 fprintf(debugFP, "Parser hit end of file\n");
8532 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8533 EP_UNKNOWN, castlingRights[currentMove]) ) {
8539 if (WhiteOnMove(currentMove)) {
8540 GameEnds(BlackWins, "Black mates", GE_FILE);
8542 GameEnds(WhiteWins, "White mates", GE_FILE);
8546 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8553 if (lastLoadGameStart == GNUChessGame) {
8554 /* GNUChessGames have numbers, but they aren't move numbers */
8555 if (appData.debugMode)
8556 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8557 yy_text, (int) moveType);
8558 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8560 /* else fall thru */
8565 /* Reached start of next game in file */
8566 if (appData.debugMode)
8567 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8568 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8569 EP_UNKNOWN, castlingRights[currentMove]) ) {
8575 if (WhiteOnMove(currentMove)) {
8576 GameEnds(BlackWins, "Black mates", GE_FILE);
8578 GameEnds(WhiteWins, "White mates", GE_FILE);
8582 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8588 case PositionDiagram: /* should not happen; ignore */
8589 case ElapsedTime: /* ignore */
8590 case NAG: /* ignore */
8591 if (appData.debugMode)
8592 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8593 yy_text, (int) moveType);
8594 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8597 if (appData.testLegality) {
8598 if (appData.debugMode)
8599 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8600 sprintf(move, _("Illegal move: %d.%s%s"),
8601 (forwardMostMove / 2) + 1,
8602 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8603 DisplayError(move, 0);
8606 if (appData.debugMode)
8607 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8608 yy_text, currentMoveString);
8609 fromX = currentMoveString[0] - AAA;
8610 fromY = currentMoveString[1] - ONE;
8611 toX = currentMoveString[2] - AAA;
8612 toY = currentMoveString[3] - ONE;
8613 promoChar = currentMoveString[4];
8618 if (appData.debugMode)
8619 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8620 sprintf(move, _("Ambiguous move: %d.%s%s"),
8621 (forwardMostMove / 2) + 1,
8622 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8623 DisplayError(move, 0);
8628 case ImpossibleMove:
8629 if (appData.debugMode)
8630 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8631 sprintf(move, _("Illegal move: %d.%s%s"),
8632 (forwardMostMove / 2) + 1,
8633 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8634 DisplayError(move, 0);
8640 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8641 DrawPosition(FALSE, boards[currentMove]);
8642 DisplayBothClocks();
8643 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8644 DisplayComment(currentMove - 1, commentList[currentMove]);
8646 (void) StopLoadGameTimer();
8648 cmailOldMove = forwardMostMove;
8651 /* currentMoveString is set as a side-effect of yylex */
8652 strcat(currentMoveString, "\n");
8653 strcpy(moveList[forwardMostMove], currentMoveString);
8655 thinkOutput[0] = NULLCHAR;
8656 MakeMove(fromX, fromY, toX, toY, promoChar);
8657 currentMove = forwardMostMove;
8662 /* Load the nth game from the given file */
8664 LoadGameFromFile(filename, n, title, useList)
8668 /*Boolean*/ int useList;
8673 if (strcmp(filename, "-") == 0) {
8677 f = fopen(filename, "rb");
8679 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8680 DisplayError(buf, errno);
8684 if (fseek(f, 0, 0) == -1) {
8685 /* f is not seekable; probably a pipe */
8688 if (useList && n == 0) {
8689 int error = GameListBuild(f);
8691 DisplayError(_("Cannot build game list"), error);
8692 } else if (!ListEmpty(&gameList) &&
8693 ((ListGame *) gameList.tailPred)->number > 1) {
8694 GameListPopUp(f, title);
8701 return LoadGame(f, n, title, FALSE);
8706 MakeRegisteredMove()
8708 int fromX, fromY, toX, toY;
8710 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8711 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8714 if (appData.debugMode)
8715 fprintf(debugFP, "Restoring %s for game %d\n",
8716 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8718 thinkOutput[0] = NULLCHAR;
8719 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8720 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8721 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8722 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8723 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8724 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8725 MakeMove(fromX, fromY, toX, toY, promoChar);
8726 ShowMove(fromX, fromY, toX, toY);
8728 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8729 EP_UNKNOWN, castlingRights[currentMove]) ) {
8736 if (WhiteOnMove(currentMove)) {
8737 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8739 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8744 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8751 if (WhiteOnMove(currentMove)) {
8752 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8754 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8759 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8770 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8772 CmailLoadGame(f, gameNumber, title, useList)
8780 if (gameNumber > nCmailGames) {
8781 DisplayError(_("No more games in this message"), 0);
8784 if (f == lastLoadGameFP) {
8785 int offset = gameNumber - lastLoadGameNumber;
8787 cmailMsg[0] = NULLCHAR;
8788 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8789 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8790 nCmailMovesRegistered--;
8792 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8793 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8794 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8797 if (! RegisterMove()) return FALSE;
8801 retVal = LoadGame(f, gameNumber, title, useList);
8803 /* Make move registered during previous look at this game, if any */
8804 MakeRegisteredMove();
8806 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8807 commentList[currentMove]
8808 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8809 DisplayComment(currentMove - 1, commentList[currentMove]);
8815 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8820 int gameNumber = lastLoadGameNumber + offset;
8821 if (lastLoadGameFP == NULL) {
8822 DisplayError(_("No game has been loaded yet"), 0);
8825 if (gameNumber <= 0) {
8826 DisplayError(_("Can't back up any further"), 0);
8829 if (cmailMsgLoaded) {
8830 return CmailLoadGame(lastLoadGameFP, gameNumber,
8831 lastLoadGameTitle, lastLoadGameUseList);
8833 return LoadGame(lastLoadGameFP, gameNumber,
8834 lastLoadGameTitle, lastLoadGameUseList);
8840 /* Load the nth game from open file f */
8842 LoadGame(f, gameNumber, title, useList)
8850 int gn = gameNumber;
8851 ListGame *lg = NULL;
8854 GameMode oldGameMode;
8855 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8857 if (appData.debugMode)
8858 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8860 if (gameMode == Training )
8861 SetTrainingModeOff();
8863 oldGameMode = gameMode;
8864 if (gameMode != BeginningOfGame) {
8869 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8870 fclose(lastLoadGameFP);
8874 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8877 fseek(f, lg->offset, 0);
8878 GameListHighlight(gameNumber);
8882 DisplayError(_("Game number out of range"), 0);
8887 if (fseek(f, 0, 0) == -1) {
8888 if (f == lastLoadGameFP ?
8889 gameNumber == lastLoadGameNumber + 1 :
8893 DisplayError(_("Can't seek on game file"), 0);
8899 lastLoadGameNumber = gameNumber;
8900 strcpy(lastLoadGameTitle, title);
8901 lastLoadGameUseList = useList;
8905 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8906 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8907 lg->gameInfo.black);
8909 } else if (*title != NULLCHAR) {
8910 if (gameNumber > 1) {
8911 sprintf(buf, "%s %d", title, gameNumber);
8914 DisplayTitle(title);
8918 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8919 gameMode = PlayFromGameFile;
8923 currentMove = forwardMostMove = backwardMostMove = 0;
8924 CopyBoard(boards[0], initialPosition);
8928 * Skip the first gn-1 games in the file.
8929 * Also skip over anything that precedes an identifiable
8930 * start of game marker, to avoid being confused by
8931 * garbage at the start of the file. Currently
8932 * recognized start of game markers are the move number "1",
8933 * the pattern "gnuchess .* game", the pattern
8934 * "^[#;%] [^ ]* game file", and a PGN tag block.
8935 * A game that starts with one of the latter two patterns
8936 * will also have a move number 1, possibly
8937 * following a position diagram.
8938 * 5-4-02: Let's try being more lenient and allowing a game to
8939 * start with an unnumbered move. Does that break anything?
8941 cm = lastLoadGameStart = (ChessMove) 0;
8943 yyboardindex = forwardMostMove;
8944 cm = (ChessMove) yylex();
8947 if (cmailMsgLoaded) {
8948 nCmailGames = CMAIL_MAX_GAMES - gn;
8951 DisplayError(_("Game not found in file"), 0);
8958 lastLoadGameStart = cm;
8962 switch (lastLoadGameStart) {
8969 gn--; /* count this game */
8970 lastLoadGameStart = cm;
8979 switch (lastLoadGameStart) {
8984 gn--; /* count this game */
8985 lastLoadGameStart = cm;
8988 lastLoadGameStart = cm; /* game counted already */
8996 yyboardindex = forwardMostMove;
8997 cm = (ChessMove) yylex();
8998 } while (cm == PGNTag || cm == Comment);
9005 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9006 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9007 != CMAIL_OLD_RESULT) {
9009 cmailResult[ CMAIL_MAX_GAMES
9010 - gn - 1] = CMAIL_OLD_RESULT;
9016 /* Only a NormalMove can be at the start of a game
9017 * without a position diagram. */
9018 if (lastLoadGameStart == (ChessMove) 0) {
9020 lastLoadGameStart = MoveNumberOne;
9029 if (appData.debugMode)
9030 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9032 if (cm == XBoardGame) {
9033 /* Skip any header junk before position diagram and/or move 1 */
9035 yyboardindex = forwardMostMove;
9036 cm = (ChessMove) yylex();
9038 if (cm == (ChessMove) 0 ||
9039 cm == GNUChessGame || cm == XBoardGame) {
9040 /* Empty game; pretend end-of-file and handle later */
9045 if (cm == MoveNumberOne || cm == PositionDiagram ||
9046 cm == PGNTag || cm == Comment)
9049 } else if (cm == GNUChessGame) {
9050 if (gameInfo.event != NULL) {
9051 free(gameInfo.event);
9053 gameInfo.event = StrSave(yy_text);
9056 startedFromSetupPosition = FALSE;
9057 while (cm == PGNTag) {
9058 if (appData.debugMode)
9059 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9060 err = ParsePGNTag(yy_text, &gameInfo);
9061 if (!err) numPGNTags++;
9063 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9064 if(gameInfo.variant != oldVariant) {
9065 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9067 oldVariant = gameInfo.variant;
9068 if (appData.debugMode)
9069 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9073 if (gameInfo.fen != NULL) {
9074 Board initial_position;
9075 startedFromSetupPosition = TRUE;
9076 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9078 DisplayError(_("Bad FEN position in file"), 0);
9081 CopyBoard(boards[0], initial_position);
9082 if (blackPlaysFirst) {
9083 currentMove = forwardMostMove = backwardMostMove = 1;
9084 CopyBoard(boards[1], initial_position);
9085 strcpy(moveList[0], "");
9086 strcpy(parseList[0], "");
9087 timeRemaining[0][1] = whiteTimeRemaining;
9088 timeRemaining[1][1] = blackTimeRemaining;
9089 if (commentList[0] != NULL) {
9090 commentList[1] = commentList[0];
9091 commentList[0] = NULL;
9094 currentMove = forwardMostMove = backwardMostMove = 0;
9096 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9098 initialRulePlies = FENrulePlies;
9099 epStatus[forwardMostMove] = FENepStatus;
9100 for( i=0; i< nrCastlingRights; i++ )
9101 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9103 yyboardindex = forwardMostMove;
9105 gameInfo.fen = NULL;
9108 yyboardindex = forwardMostMove;
9109 cm = (ChessMove) yylex();
9111 /* Handle comments interspersed among the tags */
9112 while (cm == Comment) {
9114 if (appData.debugMode)
9115 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9117 if (*p == '{' || *p == '[' || *p == '(') {
9118 p[strlen(p) - 1] = NULLCHAR;
9121 while (*p == '\n') p++;
9122 AppendComment(currentMove, p);
9123 yyboardindex = forwardMostMove;
9124 cm = (ChessMove) yylex();
9128 /* don't rely on existence of Event tag since if game was
9129 * pasted from clipboard the Event tag may not exist
9131 if (numPGNTags > 0){
9133 if (gameInfo.variant == VariantNormal) {
9134 gameInfo.variant = StringToVariant(gameInfo.event);
9137 if( appData.autoDisplayTags ) {
9138 tags = PGNTags(&gameInfo);
9139 TagsPopUp(tags, CmailMsg());
9144 /* Make something up, but don't display it now */
9149 if (cm == PositionDiagram) {
9152 Board initial_position;
9154 if (appData.debugMode)
9155 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9157 if (!startedFromSetupPosition) {
9159 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9160 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9170 initial_position[i][j++] = CharToPiece(*p);
9173 while (*p == ' ' || *p == '\t' ||
9174 *p == '\n' || *p == '\r') p++;
9176 if (strncmp(p, "black", strlen("black"))==0)
9177 blackPlaysFirst = TRUE;
9179 blackPlaysFirst = FALSE;
9180 startedFromSetupPosition = TRUE;
9182 CopyBoard(boards[0], initial_position);
9183 if (blackPlaysFirst) {
9184 currentMove = forwardMostMove = backwardMostMove = 1;
9185 CopyBoard(boards[1], initial_position);
9186 strcpy(moveList[0], "");
9187 strcpy(parseList[0], "");
9188 timeRemaining[0][1] = whiteTimeRemaining;
9189 timeRemaining[1][1] = blackTimeRemaining;
9190 if (commentList[0] != NULL) {
9191 commentList[1] = commentList[0];
9192 commentList[0] = NULL;
9195 currentMove = forwardMostMove = backwardMostMove = 0;
9198 yyboardindex = forwardMostMove;
9199 cm = (ChessMove) yylex();
9202 if (first.pr == NoProc) {
9203 StartChessProgram(&first);
9205 InitChessProgram(&first, FALSE);
9206 SendToProgram("force\n", &first);
9207 if (startedFromSetupPosition) {
9208 SendBoard(&first, forwardMostMove);
9209 if (appData.debugMode) {
9210 fprintf(debugFP, "Load Game\n");
9212 DisplayBothClocks();
9215 /* [HGM] server: flag to write setup moves in broadcast file as one */
9216 loadFlag = appData.suppressLoadMoves;
9218 while (cm == Comment) {
9220 if (appData.debugMode)
9221 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9223 if (*p == '{' || *p == '[' || *p == '(') {
9224 p[strlen(p) - 1] = NULLCHAR;
9227 while (*p == '\n') p++;
9228 AppendComment(currentMove, p);
9229 yyboardindex = forwardMostMove;
9230 cm = (ChessMove) yylex();
9233 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9234 cm == WhiteWins || cm == BlackWins ||
9235 cm == GameIsDrawn || cm == GameUnfinished) {
9236 DisplayMessage("", _("No moves in game"));
9237 if (cmailMsgLoaded) {
9238 if (appData.debugMode)
9239 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9243 DrawPosition(FALSE, boards[currentMove]);
9244 DisplayBothClocks();
9245 gameMode = EditGame;
9252 // [HGM] PV info: routine tests if comment empty
9253 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9254 DisplayComment(currentMove - 1, commentList[currentMove]);
9256 if (!matchMode && appData.timeDelay != 0)
9257 DrawPosition(FALSE, boards[currentMove]);
9259 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9260 programStats.ok_to_send = 1;
9263 /* if the first token after the PGN tags is a move
9264 * and not move number 1, retrieve it from the parser
9266 if (cm != MoveNumberOne)
9267 LoadGameOneMove(cm);
9269 /* load the remaining moves from the file */
9270 while (LoadGameOneMove((ChessMove)0)) {
9271 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9272 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9275 /* rewind to the start of the game */
9276 currentMove = backwardMostMove;
9278 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9280 if (oldGameMode == AnalyzeFile ||
9281 oldGameMode == AnalyzeMode) {
9285 if (matchMode || appData.timeDelay == 0) {
9287 gameMode = EditGame;
9289 } else if (appData.timeDelay > 0) {
9293 if (appData.debugMode)
9294 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9296 loadFlag = 0; /* [HGM] true game starts */
9300 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9302 ReloadPosition(offset)
9305 int positionNumber = lastLoadPositionNumber + offset;
9306 if (lastLoadPositionFP == NULL) {
9307 DisplayError(_("No position has been loaded yet"), 0);
9310 if (positionNumber <= 0) {
9311 DisplayError(_("Can't back up any further"), 0);
9314 return LoadPosition(lastLoadPositionFP, positionNumber,
9315 lastLoadPositionTitle);
9318 /* Load the nth position from the given file */
9320 LoadPositionFromFile(filename, n, title)
9328 if (strcmp(filename, "-") == 0) {
9329 return LoadPosition(stdin, n, "stdin");
9331 f = fopen(filename, "rb");
9333 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9334 DisplayError(buf, errno);
9337 return LoadPosition(f, n, title);
9342 /* Load the nth position from the given open file, and close it */
9344 LoadPosition(f, positionNumber, title)
9349 char *p, line[MSG_SIZ];
9350 Board initial_position;
9351 int i, j, fenMode, pn;
9353 if (gameMode == Training )
9354 SetTrainingModeOff();
9356 if (gameMode != BeginningOfGame) {
9359 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9360 fclose(lastLoadPositionFP);
9362 if (positionNumber == 0) positionNumber = 1;
9363 lastLoadPositionFP = f;
9364 lastLoadPositionNumber = positionNumber;
9365 strcpy(lastLoadPositionTitle, title);
9366 if (first.pr == NoProc) {
9367 StartChessProgram(&first);
9368 InitChessProgram(&first, FALSE);
9370 pn = positionNumber;
9371 if (positionNumber < 0) {
9372 /* Negative position number means to seek to that byte offset */
9373 if (fseek(f, -positionNumber, 0) == -1) {
9374 DisplayError(_("Can't seek on position file"), 0);
9379 if (fseek(f, 0, 0) == -1) {
9380 if (f == lastLoadPositionFP ?
9381 positionNumber == lastLoadPositionNumber + 1 :
9382 positionNumber == 1) {
9385 DisplayError(_("Can't seek on position file"), 0);
9390 /* See if this file is FEN or old-style xboard */
9391 if (fgets(line, MSG_SIZ, f) == NULL) {
9392 DisplayError(_("Position not found in file"), 0);
9401 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9402 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9403 case '1': case '2': case '3': case '4': case '5': case '6':
9404 case '7': case '8': case '9':
9405 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9406 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9407 case 'C': case 'W': case 'c': case 'w':
9412 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9413 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9417 if (fenMode || line[0] == '#') pn--;
9419 /* skip positions before number pn */
9420 if (fgets(line, MSG_SIZ, f) == NULL) {
9422 DisplayError(_("Position not found in file"), 0);
9425 if (fenMode || line[0] == '#') pn--;
9430 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9431 DisplayError(_("Bad FEN position in file"), 0);
9435 (void) fgets(line, MSG_SIZ, f);
9436 (void) fgets(line, MSG_SIZ, f);
9438 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9439 (void) fgets(line, MSG_SIZ, f);
9440 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9443 initial_position[i][j++] = CharToPiece(*p);
9447 blackPlaysFirst = FALSE;
9449 (void) fgets(line, MSG_SIZ, f);
9450 if (strncmp(line, "black", strlen("black"))==0)
9451 blackPlaysFirst = TRUE;
9454 startedFromSetupPosition = TRUE;
9456 SendToProgram("force\n", &first);
9457 CopyBoard(boards[0], initial_position);
9458 if (blackPlaysFirst) {
9459 currentMove = forwardMostMove = backwardMostMove = 1;
9460 strcpy(moveList[0], "");
9461 strcpy(parseList[0], "");
9462 CopyBoard(boards[1], initial_position);
9463 DisplayMessage("", _("Black to play"));
9465 currentMove = forwardMostMove = backwardMostMove = 0;
9466 DisplayMessage("", _("White to play"));
9468 /* [HGM] copy FEN attributes as well */
9470 initialRulePlies = FENrulePlies;
9471 epStatus[forwardMostMove] = FENepStatus;
9472 for( i=0; i< nrCastlingRights; i++ )
9473 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9475 SendBoard(&first, forwardMostMove);
9476 if (appData.debugMode) {
9478 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9479 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9480 fprintf(debugFP, "Load Position\n");
9483 if (positionNumber > 1) {
9484 sprintf(line, "%s %d", title, positionNumber);
9487 DisplayTitle(title);
9489 gameMode = EditGame;
9492 timeRemaining[0][1] = whiteTimeRemaining;
9493 timeRemaining[1][1] = blackTimeRemaining;
9494 DrawPosition(FALSE, boards[currentMove]);
9501 CopyPlayerNameIntoFileName(dest, src)
9504 while (*src != NULLCHAR && *src != ',') {
9509 *(*dest)++ = *src++;
9514 char *DefaultFileName(ext)
9517 static char def[MSG_SIZ];
9520 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9522 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9524 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9533 /* Save the current game to the given file */
9535 SaveGameToFile(filename, append)
9542 if (strcmp(filename, "-") == 0) {
9543 return SaveGame(stdout, 0, NULL);
9545 f = fopen(filename, append ? "a" : "w");
9547 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9548 DisplayError(buf, errno);
9551 return SaveGame(f, 0, NULL);
9560 static char buf[MSG_SIZ];
9563 p = strchr(str, ' ');
9564 if (p == NULL) return str;
9565 strncpy(buf, str, p - str);
9566 buf[p - str] = NULLCHAR;
9570 #define PGN_MAX_LINE 75
9572 #define PGN_SIDE_WHITE 0
9573 #define PGN_SIDE_BLACK 1
9576 static int FindFirstMoveOutOfBook( int side )
9580 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9581 int index = backwardMostMove;
9582 int has_book_hit = 0;
9584 if( (index % 2) != side ) {
9588 while( index < forwardMostMove ) {
9589 /* Check to see if engine is in book */
9590 int depth = pvInfoList[index].depth;
9591 int score = pvInfoList[index].score;
9597 else if( score == 0 && depth == 63 ) {
9598 in_book = 1; /* Zappa */
9600 else if( score == 2 && depth == 99 ) {
9601 in_book = 1; /* Abrok */
9604 has_book_hit += in_book;
9620 void GetOutOfBookInfo( char * buf )
9624 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9626 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9627 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9631 if( oob[0] >= 0 || oob[1] >= 0 ) {
9632 for( i=0; i<2; i++ ) {
9636 if( i > 0 && oob[0] >= 0 ) {
9640 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9641 sprintf( buf+strlen(buf), "%s%.2f",
9642 pvInfoList[idx].score >= 0 ? "+" : "",
9643 pvInfoList[idx].score / 100.0 );
9649 /* Save game in PGN style and close the file */
9654 int i, offset, linelen, newblock;
9658 int movelen, numlen, blank;
9659 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9661 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9663 tm = time((time_t *) NULL);
9665 PrintPGNTags(f, &gameInfo);
9667 if (backwardMostMove > 0 || startedFromSetupPosition) {
9668 char *fen = PositionToFEN(backwardMostMove, NULL);
9669 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9670 fprintf(f, "\n{--------------\n");
9671 PrintPosition(f, backwardMostMove);
9672 fprintf(f, "--------------}\n");
9676 /* [AS] Out of book annotation */
9677 if( appData.saveOutOfBookInfo ) {
9680 GetOutOfBookInfo( buf );
9682 if( buf[0] != '\0' ) {
9683 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9690 i = backwardMostMove;
9694 while (i < forwardMostMove) {
9695 /* Print comments preceding this move */
9696 if (commentList[i] != NULL) {
9697 if (linelen > 0) fprintf(f, "\n");
9698 fprintf(f, "{\n%s}\n", commentList[i]);
9703 /* Format move number */
9705 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9708 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9710 numtext[0] = NULLCHAR;
9713 numlen = strlen(numtext);
9716 /* Print move number */
9717 blank = linelen > 0 && numlen > 0;
9718 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9727 fprintf(f, numtext);
9731 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9732 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9734 // SavePart already does this!
9735 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9736 int p = movelen - 1;
9737 if(move_buffer[p] == ' ') p--;
9738 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9739 while(p && move_buffer[--p] != '(');
9740 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9745 blank = linelen > 0 && movelen > 0;
9746 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9755 fprintf(f, move_buffer);
9758 /* [AS] Add PV info if present */
9759 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9760 /* [HGM] add time */
9761 char buf[MSG_SIZ]; int seconds = 0;
9764 if(i >= backwardMostMove) {
9766 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9767 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9769 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9770 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9772 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9774 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9777 if( seconds <= 0) buf[0] = 0; else
9778 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9779 seconds = (seconds + 4)/10; // round to full seconds
9780 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9781 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9784 sprintf( move_buffer, "{%s%.2f/%d%s}",
9785 pvInfoList[i].score >= 0 ? "+" : "",
9786 pvInfoList[i].score / 100.0,
9787 pvInfoList[i].depth,
9790 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9792 /* Print score/depth */
9793 blank = linelen > 0 && movelen > 0;
9794 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9803 fprintf(f, move_buffer);
9810 /* Start a new line */
9811 if (linelen > 0) fprintf(f, "\n");
9813 /* Print comments after last move */
9814 if (commentList[i] != NULL) {
9815 fprintf(f, "{\n%s}\n", commentList[i]);
9819 if (gameInfo.resultDetails != NULL &&
9820 gameInfo.resultDetails[0] != NULLCHAR) {
9821 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9822 PGNResult(gameInfo.result));
9824 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9828 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9832 /* Save game in old style and close the file */
9840 tm = time((time_t *) NULL);
9842 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9845 if (backwardMostMove > 0 || startedFromSetupPosition) {
9846 fprintf(f, "\n[--------------\n");
9847 PrintPosition(f, backwardMostMove);
9848 fprintf(f, "--------------]\n");
9853 i = backwardMostMove;
9854 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9856 while (i < forwardMostMove) {
9857 if (commentList[i] != NULL) {
9858 fprintf(f, "[%s]\n", commentList[i]);
9862 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9865 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9867 if (commentList[i] != NULL) {
9871 if (i >= forwardMostMove) {
9875 fprintf(f, "%s\n", parseList[i]);
9880 if (commentList[i] != NULL) {
9881 fprintf(f, "[%s]\n", commentList[i]);
9884 /* This isn't really the old style, but it's close enough */
9885 if (gameInfo.resultDetails != NULL &&
9886 gameInfo.resultDetails[0] != NULLCHAR) {
9887 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9888 gameInfo.resultDetails);
9890 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9897 /* Save the current game to open file f and close the file */
9899 SaveGame(f, dummy, dummy2)
9904 if (gameMode == EditPosition) EditPositionDone();
9905 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9906 if (appData.oldSaveStyle)
9907 return SaveGameOldStyle(f);
9909 return SaveGamePGN(f);
9912 /* Save the current position to the given file */
9914 SavePositionToFile(filename)
9920 if (strcmp(filename, "-") == 0) {
9921 return SavePosition(stdout, 0, NULL);
9923 f = fopen(filename, "a");
9925 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9926 DisplayError(buf, errno);
9929 SavePosition(f, 0, NULL);
9935 /* Save the current position to the given open file and close the file */
9937 SavePosition(f, dummy, dummy2)
9945 if (appData.oldSaveStyle) {
9946 tm = time((time_t *) NULL);
9948 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9950 fprintf(f, "[--------------\n");
9951 PrintPosition(f, currentMove);
9952 fprintf(f, "--------------]\n");
9954 fen = PositionToFEN(currentMove, NULL);
9955 fprintf(f, "%s\n", fen);
9963 ReloadCmailMsgEvent(unregister)
9967 static char *inFilename = NULL;
9968 static char *outFilename;
9970 struct stat inbuf, outbuf;
9973 /* Any registered moves are unregistered if unregister is set, */
9974 /* i.e. invoked by the signal handler */
9976 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9977 cmailMoveRegistered[i] = FALSE;
9978 if (cmailCommentList[i] != NULL) {
9979 free(cmailCommentList[i]);
9980 cmailCommentList[i] = NULL;
9983 nCmailMovesRegistered = 0;
9986 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9987 cmailResult[i] = CMAIL_NOT_RESULT;
9991 if (inFilename == NULL) {
9992 /* Because the filenames are static they only get malloced once */
9993 /* and they never get freed */
9994 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9995 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9997 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9998 sprintf(outFilename, "%s.out", appData.cmailGameName);
10001 status = stat(outFilename, &outbuf);
10003 cmailMailedMove = FALSE;
10005 status = stat(inFilename, &inbuf);
10006 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10009 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10010 counts the games, notes how each one terminated, etc.
10012 It would be nice to remove this kludge and instead gather all
10013 the information while building the game list. (And to keep it
10014 in the game list nodes instead of having a bunch of fixed-size
10015 parallel arrays.) Note this will require getting each game's
10016 termination from the PGN tags, as the game list builder does
10017 not process the game moves. --mann
10019 cmailMsgLoaded = TRUE;
10020 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10022 /* Load first game in the file or popup game menu */
10023 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10025 #endif /* !WIN32 */
10033 char string[MSG_SIZ];
10035 if ( cmailMailedMove
10036 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10037 return TRUE; /* Allow free viewing */
10040 /* Unregister move to ensure that we don't leave RegisterMove */
10041 /* with the move registered when the conditions for registering no */
10043 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10044 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10045 nCmailMovesRegistered --;
10047 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10049 free(cmailCommentList[lastLoadGameNumber - 1]);
10050 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10054 if (cmailOldMove == -1) {
10055 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10059 if (currentMove > cmailOldMove + 1) {
10060 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10064 if (currentMove < cmailOldMove) {
10065 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10069 if (forwardMostMove > currentMove) {
10070 /* Silently truncate extra moves */
10074 if ( (currentMove == cmailOldMove + 1)
10075 || ( (currentMove == cmailOldMove)
10076 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10077 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10078 if (gameInfo.result != GameUnfinished) {
10079 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10082 if (commentList[currentMove] != NULL) {
10083 cmailCommentList[lastLoadGameNumber - 1]
10084 = StrSave(commentList[currentMove]);
10086 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10088 if (appData.debugMode)
10089 fprintf(debugFP, "Saving %s for game %d\n",
10090 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10093 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10095 f = fopen(string, "w");
10096 if (appData.oldSaveStyle) {
10097 SaveGameOldStyle(f); /* also closes the file */
10099 sprintf(string, "%s.pos.out", appData.cmailGameName);
10100 f = fopen(string, "w");
10101 SavePosition(f, 0, NULL); /* also closes the file */
10103 fprintf(f, "{--------------\n");
10104 PrintPosition(f, currentMove);
10105 fprintf(f, "--------------}\n\n");
10107 SaveGame(f, 0, NULL); /* also closes the file*/
10110 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10111 nCmailMovesRegistered ++;
10112 } else if (nCmailGames == 1) {
10113 DisplayError(_("You have not made a move yet"), 0);
10124 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10125 FILE *commandOutput;
10126 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10127 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10133 if (! cmailMsgLoaded) {
10134 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10138 if (nCmailGames == nCmailResults) {
10139 DisplayError(_("No unfinished games"), 0);
10143 #if CMAIL_PROHIBIT_REMAIL
10144 if (cmailMailedMove) {
10145 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);
10146 DisplayError(msg, 0);
10151 if (! (cmailMailedMove || RegisterMove())) return;
10153 if ( cmailMailedMove
10154 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10155 sprintf(string, partCommandString,
10156 appData.debugMode ? " -v" : "", appData.cmailGameName);
10157 commandOutput = popen(string, "r");
10159 if (commandOutput == NULL) {
10160 DisplayError(_("Failed to invoke cmail"), 0);
10162 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10163 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10165 if (nBuffers > 1) {
10166 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10167 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10168 nBytes = MSG_SIZ - 1;
10170 (void) memcpy(msg, buffer, nBytes);
10172 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10174 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10175 cmailMailedMove = TRUE; /* Prevent >1 moves */
10178 for (i = 0; i < nCmailGames; i ++) {
10179 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10184 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10186 sprintf(buffer, "%s/%s.%s.archive",
10188 appData.cmailGameName,
10190 LoadGameFromFile(buffer, 1, buffer, FALSE);
10191 cmailMsgLoaded = FALSE;
10195 DisplayInformation(msg);
10196 pclose(commandOutput);
10199 if ((*cmailMsg) != '\0') {
10200 DisplayInformation(cmailMsg);
10205 #endif /* !WIN32 */
10214 int prependComma = 0;
10216 char string[MSG_SIZ]; /* Space for game-list */
10219 if (!cmailMsgLoaded) return "";
10221 if (cmailMailedMove) {
10222 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10224 /* Create a list of games left */
10225 sprintf(string, "[");
10226 for (i = 0; i < nCmailGames; i ++) {
10227 if (! ( cmailMoveRegistered[i]
10228 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10229 if (prependComma) {
10230 sprintf(number, ",%d", i + 1);
10232 sprintf(number, "%d", i + 1);
10236 strcat(string, number);
10239 strcat(string, "]");
10241 if (nCmailMovesRegistered + nCmailResults == 0) {
10242 switch (nCmailGames) {
10245 _("Still need to make move for game\n"));
10250 _("Still need to make moves for both games\n"));
10255 _("Still need to make moves for all %d games\n"),
10260 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10263 _("Still need to make a move for game %s\n"),
10268 if (nCmailResults == nCmailGames) {
10269 sprintf(cmailMsg, _("No unfinished games\n"));
10271 sprintf(cmailMsg, _("Ready to send mail\n"));
10277 _("Still need to make moves for games %s\n"),
10289 if (gameMode == Training)
10290 SetTrainingModeOff();
10293 cmailMsgLoaded = FALSE;
10294 if (appData.icsActive) {
10295 SendToICS(ics_prefix);
10296 SendToICS("refresh\n");
10306 /* Give up on clean exit */
10310 /* Keep trying for clean exit */
10314 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10316 if (telnetISR != NULL) {
10317 RemoveInputSource(telnetISR);
10319 if (icsPR != NoProc) {
10320 DestroyChildProcess(icsPR, TRUE);
10323 /* Save game if resource set and not already saved by GameEnds() */
10324 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10325 && forwardMostMove > 0) {
10326 if (*appData.saveGameFile != NULLCHAR) {
10327 SaveGameToFile(appData.saveGameFile, TRUE);
10328 } else if (appData.autoSaveGames) {
10331 if (*appData.savePositionFile != NULLCHAR) {
10332 SavePositionToFile(appData.savePositionFile);
10335 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10337 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10338 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10340 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10341 /* make sure this other one finishes before killing it! */
10342 if(endingGame) { int count = 0;
10343 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10344 while(endingGame && count++ < 10) DoSleep(1);
10345 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10348 /* Kill off chess programs */
10349 if (first.pr != NoProc) {
10352 DoSleep( appData.delayBeforeQuit );
10353 SendToProgram("quit\n", &first);
10354 DoSleep( appData.delayAfterQuit );
10355 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10357 if (second.pr != NoProc) {
10358 DoSleep( appData.delayBeforeQuit );
10359 SendToProgram("quit\n", &second);
10360 DoSleep( appData.delayAfterQuit );
10361 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10363 if (first.isr != NULL) {
10364 RemoveInputSource(first.isr);
10366 if (second.isr != NULL) {
10367 RemoveInputSource(second.isr);
10370 ShutDownFrontEnd();
10377 if (appData.debugMode)
10378 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10382 if (gameMode == MachinePlaysWhite ||
10383 gameMode == MachinePlaysBlack) {
10386 DisplayBothClocks();
10388 if (gameMode == PlayFromGameFile) {
10389 if (appData.timeDelay >= 0)
10390 AutoPlayGameLoop();
10391 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10392 Reset(FALSE, TRUE);
10393 SendToICS(ics_prefix);
10394 SendToICS("refresh\n");
10395 } else if (currentMove < forwardMostMove) {
10396 ForwardInner(forwardMostMove);
10398 pauseExamInvalid = FALSE;
10400 switch (gameMode) {
10404 pauseExamForwardMostMove = forwardMostMove;
10405 pauseExamInvalid = FALSE;
10408 case IcsPlayingWhite:
10409 case IcsPlayingBlack:
10413 case PlayFromGameFile:
10414 (void) StopLoadGameTimer();
10418 case BeginningOfGame:
10419 if (appData.icsActive) return;
10420 /* else fall through */
10421 case MachinePlaysWhite:
10422 case MachinePlaysBlack:
10423 case TwoMachinesPlay:
10424 if (forwardMostMove == 0)
10425 return; /* don't pause if no one has moved */
10426 if ((gameMode == MachinePlaysWhite &&
10427 !WhiteOnMove(forwardMostMove)) ||
10428 (gameMode == MachinePlaysBlack &&
10429 WhiteOnMove(forwardMostMove))) {
10442 char title[MSG_SIZ];
10444 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10445 strcpy(title, _("Edit comment"));
10447 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10448 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10449 parseList[currentMove - 1]);
10452 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10459 char *tags = PGNTags(&gameInfo);
10460 EditTagsPopUp(tags);
10467 if (appData.noChessProgram || gameMode == AnalyzeMode)
10470 if (gameMode != AnalyzeFile) {
10471 if (!appData.icsEngineAnalyze) {
10473 if (gameMode != EditGame) return;
10475 ResurrectChessProgram();
10476 SendToProgram("analyze\n", &first);
10477 first.analyzing = TRUE;
10478 /*first.maybeThinking = TRUE;*/
10479 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10480 AnalysisPopUp(_("Analysis"),
10481 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10483 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10488 StartAnalysisClock();
10489 GetTimeMark(&lastNodeCountTime);
10496 if (appData.noChessProgram || gameMode == AnalyzeFile)
10499 if (gameMode != AnalyzeMode) {
10501 if (gameMode != EditGame) return;
10502 ResurrectChessProgram();
10503 SendToProgram("analyze\n", &first);
10504 first.analyzing = TRUE;
10505 /*first.maybeThinking = TRUE;*/
10506 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10507 AnalysisPopUp(_("Analysis"),
10508 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10510 gameMode = AnalyzeFile;
10515 StartAnalysisClock();
10516 GetTimeMark(&lastNodeCountTime);
10521 MachineWhiteEvent()
10524 char *bookHit = NULL;
10526 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10530 if (gameMode == PlayFromGameFile ||
10531 gameMode == TwoMachinesPlay ||
10532 gameMode == Training ||
10533 gameMode == AnalyzeMode ||
10534 gameMode == EndOfGame)
10537 if (gameMode == EditPosition)
10538 EditPositionDone();
10540 if (!WhiteOnMove(currentMove)) {
10541 DisplayError(_("It is not White's turn"), 0);
10545 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10548 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10549 gameMode == AnalyzeFile)
10552 ResurrectChessProgram(); /* in case it isn't running */
10553 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10554 gameMode = MachinePlaysWhite;
10557 gameMode = MachinePlaysWhite;
10561 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10563 if (first.sendName) {
10564 sprintf(buf, "name %s\n", gameInfo.black);
10565 SendToProgram(buf, &first);
10567 if (first.sendTime) {
10568 if (first.useColors) {
10569 SendToProgram("black\n", &first); /*gnu kludge*/
10571 SendTimeRemaining(&first, TRUE);
10573 if (first.useColors) {
10574 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10576 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10577 SetMachineThinkingEnables();
10578 first.maybeThinking = TRUE;
10582 if (appData.autoFlipView && !flipView) {
10583 flipView = !flipView;
10584 DrawPosition(FALSE, NULL);
10585 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10588 if(bookHit) { // [HGM] book: simulate book reply
10589 static char bookMove[MSG_SIZ]; // a bit generous?
10591 programStats.nodes = programStats.depth = programStats.time =
10592 programStats.score = programStats.got_only_move = 0;
10593 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10595 strcpy(bookMove, "move ");
10596 strcat(bookMove, bookHit);
10597 HandleMachineMove(bookMove, &first);
10602 MachineBlackEvent()
10605 char *bookHit = NULL;
10607 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10611 if (gameMode == PlayFromGameFile ||
10612 gameMode == TwoMachinesPlay ||
10613 gameMode == Training ||
10614 gameMode == AnalyzeMode ||
10615 gameMode == EndOfGame)
10618 if (gameMode == EditPosition)
10619 EditPositionDone();
10621 if (WhiteOnMove(currentMove)) {
10622 DisplayError(_("It is not Black's turn"), 0);
10626 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10629 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10630 gameMode == AnalyzeFile)
10633 ResurrectChessProgram(); /* in case it isn't running */
10634 gameMode = MachinePlaysBlack;
10638 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10640 if (first.sendName) {
10641 sprintf(buf, "name %s\n", gameInfo.white);
10642 SendToProgram(buf, &first);
10644 if (first.sendTime) {
10645 if (first.useColors) {
10646 SendToProgram("white\n", &first); /*gnu kludge*/
10648 SendTimeRemaining(&first, FALSE);
10650 if (first.useColors) {
10651 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10653 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10654 SetMachineThinkingEnables();
10655 first.maybeThinking = TRUE;
10658 if (appData.autoFlipView && flipView) {
10659 flipView = !flipView;
10660 DrawPosition(FALSE, NULL);
10661 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10663 if(bookHit) { // [HGM] book: simulate book reply
10664 static char bookMove[MSG_SIZ]; // a bit generous?
10666 programStats.nodes = programStats.depth = programStats.time =
10667 programStats.score = programStats.got_only_move = 0;
10668 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10670 strcpy(bookMove, "move ");
10671 strcat(bookMove, bookHit);
10672 HandleMachineMove(bookMove, &first);
10678 DisplayTwoMachinesTitle()
10681 if (appData.matchGames > 0) {
10682 if (first.twoMachinesColor[0] == 'w') {
10683 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10684 gameInfo.white, gameInfo.black,
10685 first.matchWins, second.matchWins,
10686 matchGame - 1 - (first.matchWins + second.matchWins));
10688 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10689 gameInfo.white, gameInfo.black,
10690 second.matchWins, first.matchWins,
10691 matchGame - 1 - (first.matchWins + second.matchWins));
10694 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10700 TwoMachinesEvent P((void))
10704 ChessProgramState *onmove;
10705 char *bookHit = NULL;
10707 if (appData.noChessProgram) return;
10709 switch (gameMode) {
10710 case TwoMachinesPlay:
10712 case MachinePlaysWhite:
10713 case MachinePlaysBlack:
10714 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10715 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10719 case BeginningOfGame:
10720 case PlayFromGameFile:
10723 if (gameMode != EditGame) return;
10726 EditPositionDone();
10737 forwardMostMove = currentMove;
10738 ResurrectChessProgram(); /* in case first program isn't running */
10740 if (second.pr == NULL) {
10741 StartChessProgram(&second);
10742 if (second.protocolVersion == 1) {
10743 TwoMachinesEventIfReady();
10745 /* kludge: allow timeout for initial "feature" command */
10747 DisplayMessage("", _("Starting second chess program"));
10748 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10752 DisplayMessage("", "");
10753 InitChessProgram(&second, FALSE);
10754 SendToProgram("force\n", &second);
10755 if (startedFromSetupPosition) {
10756 SendBoard(&second, backwardMostMove);
10757 if (appData.debugMode) {
10758 fprintf(debugFP, "Two Machines\n");
10761 for (i = backwardMostMove; i < forwardMostMove; i++) {
10762 SendMoveToProgram(i, &second);
10765 gameMode = TwoMachinesPlay;
10769 DisplayTwoMachinesTitle();
10771 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10777 SendToProgram(first.computerString, &first);
10778 if (first.sendName) {
10779 sprintf(buf, "name %s\n", second.tidy);
10780 SendToProgram(buf, &first);
10782 SendToProgram(second.computerString, &second);
10783 if (second.sendName) {
10784 sprintf(buf, "name %s\n", first.tidy);
10785 SendToProgram(buf, &second);
10789 if (!first.sendTime || !second.sendTime) {
10790 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10791 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10793 if (onmove->sendTime) {
10794 if (onmove->useColors) {
10795 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10797 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10799 if (onmove->useColors) {
10800 SendToProgram(onmove->twoMachinesColor, onmove);
10802 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10803 // SendToProgram("go\n", onmove);
10804 onmove->maybeThinking = TRUE;
10805 SetMachineThinkingEnables();
10809 if(bookHit) { // [HGM] book: simulate book reply
10810 static char bookMove[MSG_SIZ]; // a bit generous?
10812 programStats.nodes = programStats.depth = programStats.time =
10813 programStats.score = programStats.got_only_move = 0;
10814 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10816 strcpy(bookMove, "move ");
10817 strcat(bookMove, bookHit);
10818 HandleMachineMove(bookMove, &first);
10825 if (gameMode == Training) {
10826 SetTrainingModeOff();
10827 gameMode = PlayFromGameFile;
10828 DisplayMessage("", _("Training mode off"));
10830 gameMode = Training;
10831 animateTraining = appData.animate;
10833 /* make sure we are not already at the end of the game */
10834 if (currentMove < forwardMostMove) {
10835 SetTrainingModeOn();
10836 DisplayMessage("", _("Training mode on"));
10838 gameMode = PlayFromGameFile;
10839 DisplayError(_("Already at end of game"), 0);
10848 if (!appData.icsActive) return;
10849 switch (gameMode) {
10850 case IcsPlayingWhite:
10851 case IcsPlayingBlack:
10854 case BeginningOfGame:
10862 EditPositionDone();
10875 gameMode = IcsIdle;
10886 switch (gameMode) {
10888 SetTrainingModeOff();
10890 case MachinePlaysWhite:
10891 case MachinePlaysBlack:
10892 case BeginningOfGame:
10893 SendToProgram("force\n", &first);
10894 SetUserThinkingEnables();
10896 case PlayFromGameFile:
10897 (void) StopLoadGameTimer();
10898 if (gameFileFP != NULL) {
10903 EditPositionDone();
10908 SendToProgram("force\n", &first);
10910 case TwoMachinesPlay:
10911 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10912 ResurrectChessProgram();
10913 SetUserThinkingEnables();
10916 ResurrectChessProgram();
10918 case IcsPlayingBlack:
10919 case IcsPlayingWhite:
10920 DisplayError(_("Warning: You are still playing a game"), 0);
10923 DisplayError(_("Warning: You are still observing a game"), 0);
10926 DisplayError(_("Warning: You are still examining a game"), 0);
10937 first.offeredDraw = second.offeredDraw = 0;
10939 if (gameMode == PlayFromGameFile) {
10940 whiteTimeRemaining = timeRemaining[0][currentMove];
10941 blackTimeRemaining = timeRemaining[1][currentMove];
10945 if (gameMode == MachinePlaysWhite ||
10946 gameMode == MachinePlaysBlack ||
10947 gameMode == TwoMachinesPlay ||
10948 gameMode == EndOfGame) {
10949 i = forwardMostMove;
10950 while (i > currentMove) {
10951 SendToProgram("undo\n", &first);
10954 whiteTimeRemaining = timeRemaining[0][currentMove];
10955 blackTimeRemaining = timeRemaining[1][currentMove];
10956 DisplayBothClocks();
10957 if (whiteFlag || blackFlag) {
10958 whiteFlag = blackFlag = 0;
10963 gameMode = EditGame;
10970 EditPositionEvent()
10972 if (gameMode == EditPosition) {
10978 if (gameMode != EditGame) return;
10980 gameMode = EditPosition;
10983 if (currentMove > 0)
10984 CopyBoard(boards[0], boards[currentMove]);
10986 blackPlaysFirst = !WhiteOnMove(currentMove);
10988 currentMove = forwardMostMove = backwardMostMove = 0;
10989 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10996 /* [DM] icsEngineAnalyze - possible call from other functions */
10997 if (appData.icsEngineAnalyze) {
10998 appData.icsEngineAnalyze = FALSE;
11000 DisplayMessage("",_("Close ICS engine analyze..."));
11002 if (first.analysisSupport && first.analyzing) {
11003 SendToProgram("exit\n", &first);
11004 first.analyzing = FALSE;
11007 thinkOutput[0] = NULLCHAR;
11013 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11015 startedFromSetupPosition = TRUE;
11016 InitChessProgram(&first, FALSE);
11017 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11018 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11019 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11020 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11021 } else castlingRights[0][2] = -1;
11022 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11023 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11024 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11025 } else castlingRights[0][5] = -1;
11026 SendToProgram("force\n", &first);
11027 if (blackPlaysFirst) {
11028 strcpy(moveList[0], "");
11029 strcpy(parseList[0], "");
11030 currentMove = forwardMostMove = backwardMostMove = 1;
11031 CopyBoard(boards[1], boards[0]);
11032 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11034 epStatus[1] = epStatus[0];
11035 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11038 currentMove = forwardMostMove = backwardMostMove = 0;
11040 SendBoard(&first, forwardMostMove);
11041 if (appData.debugMode) {
11042 fprintf(debugFP, "EditPosDone\n");
11045 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11046 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11047 gameMode = EditGame;
11049 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11050 ClearHighlights(); /* [AS] */
11053 /* Pause for `ms' milliseconds */
11054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11064 } while (SubtractTimeMarks(&m2, &m1) < ms);
11067 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11069 SendMultiLineToICS(buf)
11072 char temp[MSG_SIZ+1], *p;
11079 strncpy(temp, buf, len);
11084 if (*p == '\n' || *p == '\r')
11089 strcat(temp, "\n");
11091 SendToPlayer(temp, strlen(temp));
11095 SetWhiteToPlayEvent()
11097 if (gameMode == EditPosition) {
11098 blackPlaysFirst = FALSE;
11099 DisplayBothClocks(); /* works because currentMove is 0 */
11100 } else if (gameMode == IcsExamining) {
11101 SendToICS(ics_prefix);
11102 SendToICS("tomove white\n");
11107 SetBlackToPlayEvent()
11109 if (gameMode == EditPosition) {
11110 blackPlaysFirst = TRUE;
11111 currentMove = 1; /* kludge */
11112 DisplayBothClocks();
11114 } else if (gameMode == IcsExamining) {
11115 SendToICS(ics_prefix);
11116 SendToICS("tomove black\n");
11121 EditPositionMenuEvent(selection, x, y)
11122 ChessSquare selection;
11126 ChessSquare piece = boards[0][y][x];
11128 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11130 switch (selection) {
11132 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11133 SendToICS(ics_prefix);
11134 SendToICS("bsetup clear\n");
11135 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11136 SendToICS(ics_prefix);
11137 SendToICS("clearboard\n");
11139 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11140 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11141 for (y = 0; y < BOARD_HEIGHT; y++) {
11142 if (gameMode == IcsExamining) {
11143 if (boards[currentMove][y][x] != EmptySquare) {
11144 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11149 boards[0][y][x] = p;
11154 if (gameMode == EditPosition) {
11155 DrawPosition(FALSE, boards[0]);
11160 SetWhiteToPlayEvent();
11164 SetBlackToPlayEvent();
11168 if (gameMode == IcsExamining) {
11169 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11172 boards[0][y][x] = EmptySquare;
11173 DrawPosition(FALSE, boards[0]);
11178 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11179 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11180 selection = (ChessSquare) (PROMOTED piece);
11181 } else if(piece == EmptySquare) selection = WhiteSilver;
11182 else selection = (ChessSquare)((int)piece - 1);
11186 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11187 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11188 selection = (ChessSquare) (DEMOTED piece);
11189 } else if(piece == EmptySquare) selection = BlackSilver;
11190 else selection = (ChessSquare)((int)piece + 1);
11195 if(gameInfo.variant == VariantShatranj ||
11196 gameInfo.variant == VariantXiangqi ||
11197 gameInfo.variant == VariantCourier )
11198 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11203 if(gameInfo.variant == VariantXiangqi)
11204 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11205 if(gameInfo.variant == VariantKnightmate)
11206 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11209 if (gameMode == IcsExamining) {
11210 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11211 PieceToChar(selection), AAA + x, ONE + y);
11214 boards[0][y][x] = selection;
11215 DrawPosition(FALSE, boards[0]);
11223 DropMenuEvent(selection, x, y)
11224 ChessSquare selection;
11227 ChessMove moveType;
11229 switch (gameMode) {
11230 case IcsPlayingWhite:
11231 case MachinePlaysBlack:
11232 if (!WhiteOnMove(currentMove)) {
11233 DisplayMoveError(_("It is Black's turn"));
11236 moveType = WhiteDrop;
11238 case IcsPlayingBlack:
11239 case MachinePlaysWhite:
11240 if (WhiteOnMove(currentMove)) {
11241 DisplayMoveError(_("It is White's turn"));
11244 moveType = BlackDrop;
11247 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11253 if (moveType == BlackDrop && selection < BlackPawn) {
11254 selection = (ChessSquare) ((int) selection
11255 + (int) BlackPawn - (int) WhitePawn);
11257 if (boards[currentMove][y][x] != EmptySquare) {
11258 DisplayMoveError(_("That square is occupied"));
11262 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11268 /* Accept a pending offer of any kind from opponent */
11270 if (appData.icsActive) {
11271 SendToICS(ics_prefix);
11272 SendToICS("accept\n");
11273 } else if (cmailMsgLoaded) {
11274 if (currentMove == cmailOldMove &&
11275 commentList[cmailOldMove] != NULL &&
11276 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11277 "Black offers a draw" : "White offers a draw")) {
11279 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11280 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11282 DisplayError(_("There is no pending offer on this move"), 0);
11283 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11286 /* Not used for offers from chess program */
11293 /* Decline a pending offer of any kind from opponent */
11295 if (appData.icsActive) {
11296 SendToICS(ics_prefix);
11297 SendToICS("decline\n");
11298 } else if (cmailMsgLoaded) {
11299 if (currentMove == cmailOldMove &&
11300 commentList[cmailOldMove] != NULL &&
11301 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11302 "Black offers a draw" : "White offers a draw")) {
11304 AppendComment(cmailOldMove, "Draw declined");
11305 DisplayComment(cmailOldMove - 1, "Draw declined");
11308 DisplayError(_("There is no pending offer on this move"), 0);
11311 /* Not used for offers from chess program */
11318 /* Issue ICS rematch command */
11319 if (appData.icsActive) {
11320 SendToICS(ics_prefix);
11321 SendToICS("rematch\n");
11328 /* Call your opponent's flag (claim a win on time) */
11329 if (appData.icsActive) {
11330 SendToICS(ics_prefix);
11331 SendToICS("flag\n");
11333 switch (gameMode) {
11336 case MachinePlaysWhite:
11339 GameEnds(GameIsDrawn, "Both players ran out of time",
11342 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11344 DisplayError(_("Your opponent is not out of time"), 0);
11347 case MachinePlaysBlack:
11350 GameEnds(GameIsDrawn, "Both players ran out of time",
11353 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11355 DisplayError(_("Your opponent is not out of time"), 0);
11365 /* Offer draw or accept pending draw offer from opponent */
11367 if (appData.icsActive) {
11368 /* Note: tournament rules require draw offers to be
11369 made after you make your move but before you punch
11370 your clock. Currently ICS doesn't let you do that;
11371 instead, you immediately punch your clock after making
11372 a move, but you can offer a draw at any time. */
11374 SendToICS(ics_prefix);
11375 SendToICS("draw\n");
11376 } else if (cmailMsgLoaded) {
11377 if (currentMove == cmailOldMove &&
11378 commentList[cmailOldMove] != NULL &&
11379 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11380 "Black offers a draw" : "White offers a draw")) {
11381 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11382 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11383 } else if (currentMove == cmailOldMove + 1) {
11384 char *offer = WhiteOnMove(cmailOldMove) ?
11385 "White offers a draw" : "Black offers a draw";
11386 AppendComment(currentMove, offer);
11387 DisplayComment(currentMove - 1, offer);
11388 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11390 DisplayError(_("You must make your move before offering a draw"), 0);
11391 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11393 } else if (first.offeredDraw) {
11394 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11396 if (first.sendDrawOffers) {
11397 SendToProgram("draw\n", &first);
11398 userOfferedDraw = TRUE;
11406 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11408 if (appData.icsActive) {
11409 SendToICS(ics_prefix);
11410 SendToICS("adjourn\n");
11412 /* Currently GNU Chess doesn't offer or accept Adjourns */
11420 /* Offer Abort or accept pending Abort offer from opponent */
11422 if (appData.icsActive) {
11423 SendToICS(ics_prefix);
11424 SendToICS("abort\n");
11426 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11433 /* Resign. You can do this even if it's not your turn. */
11435 if (appData.icsActive) {
11436 SendToICS(ics_prefix);
11437 SendToICS("resign\n");
11439 switch (gameMode) {
11440 case MachinePlaysWhite:
11441 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11443 case MachinePlaysBlack:
11444 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11447 if (cmailMsgLoaded) {
11449 if (WhiteOnMove(cmailOldMove)) {
11450 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11452 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11454 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11465 StopObservingEvent()
11467 /* Stop observing current games */
11468 SendToICS(ics_prefix);
11469 SendToICS("unobserve\n");
11473 StopExaminingEvent()
11475 /* Stop observing current game */
11476 SendToICS(ics_prefix);
11477 SendToICS("unexamine\n");
11481 ForwardInner(target)
11486 if (appData.debugMode)
11487 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11488 target, currentMove, forwardMostMove);
11490 if (gameMode == EditPosition)
11493 if (gameMode == PlayFromGameFile && !pausing)
11496 if (gameMode == IcsExamining && pausing)
11497 limit = pauseExamForwardMostMove;
11499 limit = forwardMostMove;
11501 if (target > limit) target = limit;
11503 if (target > 0 && moveList[target - 1][0]) {
11504 int fromX, fromY, toX, toY;
11505 toX = moveList[target - 1][2] - AAA;
11506 toY = moveList[target - 1][3] - ONE;
11507 if (moveList[target - 1][1] == '@') {
11508 if (appData.highlightLastMove) {
11509 SetHighlights(-1, -1, toX, toY);
11512 fromX = moveList[target - 1][0] - AAA;
11513 fromY = moveList[target - 1][1] - ONE;
11514 if (target == currentMove + 1) {
11515 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11517 if (appData.highlightLastMove) {
11518 SetHighlights(fromX, fromY, toX, toY);
11522 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11523 gameMode == Training || gameMode == PlayFromGameFile ||
11524 gameMode == AnalyzeFile) {
11525 while (currentMove < target) {
11526 SendMoveToProgram(currentMove++, &first);
11529 currentMove = target;
11532 if (gameMode == EditGame || gameMode == EndOfGame) {
11533 whiteTimeRemaining = timeRemaining[0][currentMove];
11534 blackTimeRemaining = timeRemaining[1][currentMove];
11536 DisplayBothClocks();
11537 DisplayMove(currentMove - 1);
11538 DrawPosition(FALSE, boards[currentMove]);
11539 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11540 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11541 DisplayComment(currentMove - 1, commentList[currentMove]);
11549 if (gameMode == IcsExamining && !pausing) {
11550 SendToICS(ics_prefix);
11551 SendToICS("forward\n");
11553 ForwardInner(currentMove + 1);
11560 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11561 /* to optimze, we temporarily turn off analysis mode while we feed
11562 * the remaining moves to the engine. Otherwise we get analysis output
11565 if (first.analysisSupport) {
11566 SendToProgram("exit\nforce\n", &first);
11567 first.analyzing = FALSE;
11571 if (gameMode == IcsExamining && !pausing) {
11572 SendToICS(ics_prefix);
11573 SendToICS("forward 999999\n");
11575 ForwardInner(forwardMostMove);
11578 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11579 /* we have fed all the moves, so reactivate analysis mode */
11580 SendToProgram("analyze\n", &first);
11581 first.analyzing = TRUE;
11582 /*first.maybeThinking = TRUE;*/
11583 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11588 BackwardInner(target)
11591 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11593 if (appData.debugMode)
11594 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11595 target, currentMove, forwardMostMove);
11597 if (gameMode == EditPosition) return;
11598 if (currentMove <= backwardMostMove) {
11600 DrawPosition(full_redraw, boards[currentMove]);
11603 if (gameMode == PlayFromGameFile && !pausing)
11606 if (moveList[target][0]) {
11607 int fromX, fromY, toX, toY;
11608 toX = moveList[target][2] - AAA;
11609 toY = moveList[target][3] - ONE;
11610 if (moveList[target][1] == '@') {
11611 if (appData.highlightLastMove) {
11612 SetHighlights(-1, -1, toX, toY);
11615 fromX = moveList[target][0] - AAA;
11616 fromY = moveList[target][1] - ONE;
11617 if (target == currentMove - 1) {
11618 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11620 if (appData.highlightLastMove) {
11621 SetHighlights(fromX, fromY, toX, toY);
11625 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11626 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11627 while (currentMove > target) {
11628 SendToProgram("undo\n", &first);
11632 currentMove = target;
11635 if (gameMode == EditGame || gameMode == EndOfGame) {
11636 whiteTimeRemaining = timeRemaining[0][currentMove];
11637 blackTimeRemaining = timeRemaining[1][currentMove];
11639 DisplayBothClocks();
11640 DisplayMove(currentMove - 1);
11641 DrawPosition(full_redraw, boards[currentMove]);
11642 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11643 // [HGM] PV info: routine tests if comment empty
11644 DisplayComment(currentMove - 1, commentList[currentMove]);
11650 if (gameMode == IcsExamining && !pausing) {
11651 SendToICS(ics_prefix);
11652 SendToICS("backward\n");
11654 BackwardInner(currentMove - 1);
11661 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11662 /* to optimze, we temporarily turn off analysis mode while we undo
11663 * all the moves. Otherwise we get analysis output after each undo.
11665 if (first.analysisSupport) {
11666 SendToProgram("exit\nforce\n", &first);
11667 first.analyzing = FALSE;
11671 if (gameMode == IcsExamining && !pausing) {
11672 SendToICS(ics_prefix);
11673 SendToICS("backward 999999\n");
11675 BackwardInner(backwardMostMove);
11678 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11679 /* we have fed all the moves, so reactivate analysis mode */
11680 SendToProgram("analyze\n", &first);
11681 first.analyzing = TRUE;
11682 /*first.maybeThinking = TRUE;*/
11683 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11690 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11691 if (to >= forwardMostMove) to = forwardMostMove;
11692 if (to <= backwardMostMove) to = backwardMostMove;
11693 if (to < currentMove) {
11703 if (gameMode != IcsExamining) {
11704 DisplayError(_("You are not examining a game"), 0);
11708 DisplayError(_("You can't revert while pausing"), 0);
11711 SendToICS(ics_prefix);
11712 SendToICS("revert\n");
11718 switch (gameMode) {
11719 case MachinePlaysWhite:
11720 case MachinePlaysBlack:
11721 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11722 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11725 if (forwardMostMove < 2) return;
11726 currentMove = forwardMostMove = forwardMostMove - 2;
11727 whiteTimeRemaining = timeRemaining[0][currentMove];
11728 blackTimeRemaining = timeRemaining[1][currentMove];
11729 DisplayBothClocks();
11730 DisplayMove(currentMove - 1);
11731 ClearHighlights();/*!! could figure this out*/
11732 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11733 SendToProgram("remove\n", &first);
11734 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11737 case BeginningOfGame:
11741 case IcsPlayingWhite:
11742 case IcsPlayingBlack:
11743 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11744 SendToICS(ics_prefix);
11745 SendToICS("takeback 2\n");
11747 SendToICS(ics_prefix);
11748 SendToICS("takeback 1\n");
11757 ChessProgramState *cps;
11759 switch (gameMode) {
11760 case MachinePlaysWhite:
11761 if (!WhiteOnMove(forwardMostMove)) {
11762 DisplayError(_("It is your turn"), 0);
11767 case MachinePlaysBlack:
11768 if (WhiteOnMove(forwardMostMove)) {
11769 DisplayError(_("It is your turn"), 0);
11774 case TwoMachinesPlay:
11775 if (WhiteOnMove(forwardMostMove) ==
11776 (first.twoMachinesColor[0] == 'w')) {
11782 case BeginningOfGame:
11786 SendToProgram("?\n", cps);
11790 TruncateGameEvent()
11793 if (gameMode != EditGame) return;
11800 if (forwardMostMove > currentMove) {
11801 if (gameInfo.resultDetails != NULL) {
11802 free(gameInfo.resultDetails);
11803 gameInfo.resultDetails = NULL;
11804 gameInfo.result = GameUnfinished;
11806 forwardMostMove = currentMove;
11807 HistorySet(parseList, backwardMostMove, forwardMostMove,
11815 if (appData.noChessProgram) return;
11816 switch (gameMode) {
11817 case MachinePlaysWhite:
11818 if (WhiteOnMove(forwardMostMove)) {
11819 DisplayError(_("Wait until your turn"), 0);
11823 case BeginningOfGame:
11824 case MachinePlaysBlack:
11825 if (!WhiteOnMove(forwardMostMove)) {
11826 DisplayError(_("Wait until your turn"), 0);
11831 DisplayError(_("No hint available"), 0);
11834 SendToProgram("hint\n", &first);
11835 hintRequested = TRUE;
11841 if (appData.noChessProgram) return;
11842 switch (gameMode) {
11843 case MachinePlaysWhite:
11844 if (WhiteOnMove(forwardMostMove)) {
11845 DisplayError(_("Wait until your turn"), 0);
11849 case BeginningOfGame:
11850 case MachinePlaysBlack:
11851 if (!WhiteOnMove(forwardMostMove)) {
11852 DisplayError(_("Wait until your turn"), 0);
11857 EditPositionDone();
11859 case TwoMachinesPlay:
11864 SendToProgram("bk\n", &first);
11865 bookOutput[0] = NULLCHAR;
11866 bookRequested = TRUE;
11872 char *tags = PGNTags(&gameInfo);
11873 TagsPopUp(tags, CmailMsg());
11877 /* end button procedures */
11880 PrintPosition(fp, move)
11886 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11887 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11888 char c = PieceToChar(boards[move][i][j]);
11889 fputc(c == 'x' ? '.' : c, fp);
11890 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11893 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11894 fprintf(fp, "white to play\n");
11896 fprintf(fp, "black to play\n");
11903 if (gameInfo.white != NULL) {
11904 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11910 /* Find last component of program's own name, using some heuristics */
11912 TidyProgramName(prog, host, buf)
11913 char *prog, *host, buf[MSG_SIZ];
11916 int local = (strcmp(host, "localhost") == 0);
11917 while (!local && (p = strchr(prog, ';')) != NULL) {
11919 while (*p == ' ') p++;
11922 if (*prog == '"' || *prog == '\'') {
11923 q = strchr(prog + 1, *prog);
11925 q = strchr(prog, ' ');
11927 if (q == NULL) q = prog + strlen(prog);
11929 while (p >= prog && *p != '/' && *p != '\\') p--;
11931 if(p == prog && *p == '"') p++;
11932 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11933 memcpy(buf, p, q - p);
11934 buf[q - p] = NULLCHAR;
11942 TimeControlTagValue()
11945 if (!appData.clockMode) {
11947 } else if (movesPerSession > 0) {
11948 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11949 } else if (timeIncrement == 0) {
11950 sprintf(buf, "%ld", timeControl/1000);
11952 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11954 return StrSave(buf);
11960 /* This routine is used only for certain modes */
11961 VariantClass v = gameInfo.variant;
11962 ClearGameInfo(&gameInfo);
11963 gameInfo.variant = v;
11965 switch (gameMode) {
11966 case MachinePlaysWhite:
11967 gameInfo.event = StrSave( appData.pgnEventHeader );
11968 gameInfo.site = StrSave(HostName());
11969 gameInfo.date = PGNDate();
11970 gameInfo.round = StrSave("-");
11971 gameInfo.white = StrSave(first.tidy);
11972 gameInfo.black = StrSave(UserName());
11973 gameInfo.timeControl = TimeControlTagValue();
11976 case MachinePlaysBlack:
11977 gameInfo.event = StrSave( appData.pgnEventHeader );
11978 gameInfo.site = StrSave(HostName());
11979 gameInfo.date = PGNDate();
11980 gameInfo.round = StrSave("-");
11981 gameInfo.white = StrSave(UserName());
11982 gameInfo.black = StrSave(first.tidy);
11983 gameInfo.timeControl = TimeControlTagValue();
11986 case TwoMachinesPlay:
11987 gameInfo.event = StrSave( appData.pgnEventHeader );
11988 gameInfo.site = StrSave(HostName());
11989 gameInfo.date = PGNDate();
11990 if (matchGame > 0) {
11992 sprintf(buf, "%d", matchGame);
11993 gameInfo.round = StrSave(buf);
11995 gameInfo.round = StrSave("-");
11997 if (first.twoMachinesColor[0] == 'w') {
11998 gameInfo.white = StrSave(first.tidy);
11999 gameInfo.black = StrSave(second.tidy);
12001 gameInfo.white = StrSave(second.tidy);
12002 gameInfo.black = StrSave(first.tidy);
12004 gameInfo.timeControl = TimeControlTagValue();
12008 gameInfo.event = StrSave("Edited game");
12009 gameInfo.site = StrSave(HostName());
12010 gameInfo.date = PGNDate();
12011 gameInfo.round = StrSave("-");
12012 gameInfo.white = StrSave("-");
12013 gameInfo.black = StrSave("-");
12017 gameInfo.event = StrSave("Edited position");
12018 gameInfo.site = StrSave(HostName());
12019 gameInfo.date = PGNDate();
12020 gameInfo.round = StrSave("-");
12021 gameInfo.white = StrSave("-");
12022 gameInfo.black = StrSave("-");
12025 case IcsPlayingWhite:
12026 case IcsPlayingBlack:
12031 case PlayFromGameFile:
12032 gameInfo.event = StrSave("Game from non-PGN file");
12033 gameInfo.site = StrSave(HostName());
12034 gameInfo.date = PGNDate();
12035 gameInfo.round = StrSave("-");
12036 gameInfo.white = StrSave("?");
12037 gameInfo.black = StrSave("?");
12046 ReplaceComment(index, text)
12052 while (*text == '\n') text++;
12053 len = strlen(text);
12054 while (len > 0 && text[len - 1] == '\n') len--;
12056 if (commentList[index] != NULL)
12057 free(commentList[index]);
12060 commentList[index] = NULL;
12063 commentList[index] = (char *) malloc(len + 2);
12064 strncpy(commentList[index], text, len);
12065 commentList[index][len] = '\n';
12066 commentList[index][len + 1] = NULLCHAR;
12079 if (ch == '\r') continue;
12081 } while (ch != '\0');
12085 AppendComment(index, text)
12092 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12095 while (*text == '\n') text++;
12096 len = strlen(text);
12097 while (len > 0 && text[len - 1] == '\n') len--;
12099 if (len == 0) return;
12101 if (commentList[index] != NULL) {
12102 old = commentList[index];
12103 oldlen = strlen(old);
12104 commentList[index] = (char *) malloc(oldlen + len + 2);
12105 strcpy(commentList[index], old);
12107 strncpy(&commentList[index][oldlen], text, len);
12108 commentList[index][oldlen + len] = '\n';
12109 commentList[index][oldlen + len + 1] = NULLCHAR;
12111 commentList[index] = (char *) malloc(len + 2);
12112 strncpy(commentList[index], text, len);
12113 commentList[index][len] = '\n';
12114 commentList[index][len + 1] = NULLCHAR;
12118 static char * FindStr( char * text, char * sub_text )
12120 char * result = strstr( text, sub_text );
12122 if( result != NULL ) {
12123 result += strlen( sub_text );
12129 /* [AS] Try to extract PV info from PGN comment */
12130 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12131 char *GetInfoFromComment( int index, char * text )
12135 if( text != NULL && index > 0 ) {
12138 int time = -1, sec = 0, deci;
12139 char * s_eval = FindStr( text, "[%eval " );
12140 char * s_emt = FindStr( text, "[%emt " );
12142 if( s_eval != NULL || s_emt != NULL ) {
12146 if( s_eval != NULL ) {
12147 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12151 if( delim != ']' ) {
12156 if( s_emt != NULL ) {
12160 /* We expect something like: [+|-]nnn.nn/dd */
12163 sep = strchr( text, '/' );
12164 if( sep == NULL || sep < (text+4) ) {
12168 time = -1; sec = -1; deci = -1;
12169 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12170 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12171 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12172 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12176 if( score_lo < 0 || score_lo >= 100 ) {
12180 if(sec >= 0) time = 600*time + 10*sec; else
12181 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12183 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12185 /* [HGM] PV time: now locate end of PV info */
12186 while( *++sep >= '0' && *sep <= '9'); // strip depth
12188 while( *++sep >= '0' && *sep <= '9'); // strip time
12190 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12192 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12193 while(*sep == ' ') sep++;
12204 pvInfoList[index-1].depth = depth;
12205 pvInfoList[index-1].score = score;
12206 pvInfoList[index-1].time = 10*time; // centi-sec
12212 SendToProgram(message, cps)
12214 ChessProgramState *cps;
12216 int count, outCount, error;
12219 if (cps->pr == NULL) return;
12222 if (appData.debugMode) {
12225 fprintf(debugFP, "%ld >%-6s: %s",
12226 SubtractTimeMarks(&now, &programStartTime),
12227 cps->which, message);
12230 count = strlen(message);
12231 outCount = OutputToProcess(cps->pr, message, count, &error);
12232 if (outCount < count && !exiting
12233 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12234 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12235 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12236 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12237 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12238 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12240 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12242 gameInfo.resultDetails = buf;
12244 DisplayFatalError(buf, error, 1);
12249 ReceiveFromProgram(isr, closure, message, count, error)
12250 InputSourceRef isr;
12258 ChessProgramState *cps = (ChessProgramState *)closure;
12260 if (isr != cps->isr) return; /* Killed intentionally */
12264 _("Error: %s chess program (%s) exited unexpectedly"),
12265 cps->which, cps->program);
12266 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12267 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12268 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12269 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12271 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12273 gameInfo.resultDetails = buf;
12275 RemoveInputSource(cps->isr);
12276 DisplayFatalError(buf, 0, 1);
12279 _("Error reading from %s chess program (%s)"),
12280 cps->which, cps->program);
12281 RemoveInputSource(cps->isr);
12283 /* [AS] Program is misbehaving badly... kill it */
12284 if( count == -2 ) {
12285 DestroyChildProcess( cps->pr, 9 );
12289 DisplayFatalError(buf, error, 1);
12294 if ((end_str = strchr(message, '\r')) != NULL)
12295 *end_str = NULLCHAR;
12296 if ((end_str = strchr(message, '\n')) != NULL)
12297 *end_str = NULLCHAR;
12299 if (appData.debugMode) {
12300 TimeMark now; int print = 1;
12301 char *quote = ""; char c; int i;
12303 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12304 char start = message[0];
12305 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12306 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12307 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12308 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12309 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12310 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12311 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12312 sscanf(message, "pong %c", &c)!=1 && start != '#')
12313 { quote = "# "; print = (appData.engineComments == 2); }
12314 message[0] = start; // restore original message
12318 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12319 SubtractTimeMarks(&now, &programStartTime), cps->which,
12325 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12326 if (appData.icsEngineAnalyze) {
12327 if (strstr(message, "whisper") != NULL ||
12328 strstr(message, "kibitz") != NULL ||
12329 strstr(message, "tellics") != NULL) return;
12332 HandleMachineMove(message, cps);
12337 SendTimeControl(cps, mps, tc, inc, sd, st)
12338 ChessProgramState *cps;
12339 int mps, inc, sd, st;
12345 if( timeControl_2 > 0 ) {
12346 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12347 tc = timeControl_2;
12350 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12351 inc /= cps->timeOdds;
12352 st /= cps->timeOdds;
12354 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12357 /* Set exact time per move, normally using st command */
12358 if (cps->stKludge) {
12359 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12361 if (seconds == 0) {
12362 sprintf(buf, "level 1 %d\n", st/60);
12364 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12367 sprintf(buf, "st %d\n", st);
12370 /* Set conventional or incremental time control, using level command */
12371 if (seconds == 0) {
12372 /* Note old gnuchess bug -- minutes:seconds used to not work.
12373 Fixed in later versions, but still avoid :seconds
12374 when seconds is 0. */
12375 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12377 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12378 seconds, inc/1000);
12381 SendToProgram(buf, cps);
12383 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12384 /* Orthogonally, limit search to given depth */
12386 if (cps->sdKludge) {
12387 sprintf(buf, "depth\n%d\n", sd);
12389 sprintf(buf, "sd %d\n", sd);
12391 SendToProgram(buf, cps);
12394 if(cps->nps > 0) { /* [HGM] nps */
12395 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12397 sprintf(buf, "nps %d\n", cps->nps);
12398 SendToProgram(buf, cps);
12403 ChessProgramState *WhitePlayer()
12404 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12406 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12407 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12413 SendTimeRemaining(cps, machineWhite)
12414 ChessProgramState *cps;
12415 int /*boolean*/ machineWhite;
12417 char message[MSG_SIZ];
12420 /* Note: this routine must be called when the clocks are stopped
12421 or when they have *just* been set or switched; otherwise
12422 it will be off by the time since the current tick started.
12424 if (machineWhite) {
12425 time = whiteTimeRemaining / 10;
12426 otime = blackTimeRemaining / 10;
12428 time = blackTimeRemaining / 10;
12429 otime = whiteTimeRemaining / 10;
12431 /* [HGM] translate opponent's time by time-odds factor */
12432 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12433 if (appData.debugMode) {
12434 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12437 if (time <= 0) time = 1;
12438 if (otime <= 0) otime = 1;
12440 sprintf(message, "time %ld\n", time);
12441 SendToProgram(message, cps);
12443 sprintf(message, "otim %ld\n", otime);
12444 SendToProgram(message, cps);
12448 BoolFeature(p, name, loc, cps)
12452 ChessProgramState *cps;
12455 int len = strlen(name);
12457 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12459 sscanf(*p, "%d", &val);
12461 while (**p && **p != ' ') (*p)++;
12462 sprintf(buf, "accepted %s\n", name);
12463 SendToProgram(buf, cps);
12470 IntFeature(p, name, loc, cps)
12474 ChessProgramState *cps;
12477 int len = strlen(name);
12478 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12480 sscanf(*p, "%d", loc);
12481 while (**p && **p != ' ') (*p)++;
12482 sprintf(buf, "accepted %s\n", name);
12483 SendToProgram(buf, cps);
12490 StringFeature(p, name, loc, cps)
12494 ChessProgramState *cps;
12497 int len = strlen(name);
12498 if (strncmp((*p), name, len) == 0
12499 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12501 sscanf(*p, "%[^\"]", loc);
12502 while (**p && **p != '\"') (*p)++;
12503 if (**p == '\"') (*p)++;
12504 sprintf(buf, "accepted %s\n", name);
12505 SendToProgram(buf, cps);
12512 ParseOption(Option *opt, ChessProgramState *cps)
12513 // [HGM] options: process the string that defines an engine option, and determine
12514 // name, type, default value, and allowed value range
12516 char *p, *q, buf[MSG_SIZ];
12517 int n, min = (-1)<<31, max = 1<<31, def;
12519 if(p = strstr(opt->name, " -spin ")) {
12520 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12521 if(max < min) max = min; // enforce consistency
12522 if(def < min) def = min;
12523 if(def > max) def = max;
12528 } else if((p = strstr(opt->name, " -slider "))) {
12529 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12530 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12531 if(max < min) max = min; // enforce consistency
12532 if(def < min) def = min;
12533 if(def > max) def = max;
12537 opt->type = Spin; // Slider;
12538 } else if((p = strstr(opt->name, " -string "))) {
12539 opt->textValue = p+9;
12540 opt->type = TextBox;
12541 } else if((p = strstr(opt->name, " -file "))) {
12542 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12543 opt->textValue = p+7;
12544 opt->type = TextBox; // FileName;
12545 } else if((p = strstr(opt->name, " -path "))) {
12546 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12547 opt->textValue = p+7;
12548 opt->type = TextBox; // PathName;
12549 } else if(p = strstr(opt->name, " -check ")) {
12550 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12551 opt->value = (def != 0);
12552 opt->type = CheckBox;
12553 } else if(p = strstr(opt->name, " -combo ")) {
12554 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12555 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12556 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12557 opt->value = n = 0;
12558 while(q = StrStr(q, " /// ")) {
12559 n++; *q = 0; // count choices, and null-terminate each of them
12561 if(*q == '*') { // remember default, which is marked with * prefix
12565 cps->comboList[cps->comboCnt++] = q;
12567 cps->comboList[cps->comboCnt++] = NULL;
12569 opt->type = ComboBox;
12570 } else if(p = strstr(opt->name, " -button")) {
12571 opt->type = Button;
12572 } else if(p = strstr(opt->name, " -save")) {
12573 opt->type = SaveButton;
12574 } else return FALSE;
12575 *p = 0; // terminate option name
12576 // now look if the command-line options define a setting for this engine option.
12577 if(cps->optionSettings && cps->optionSettings[0])
12578 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12579 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12580 sprintf(buf, "option %s", p);
12581 if(p = strstr(buf, ",")) *p = 0;
12583 SendToProgram(buf, cps);
12589 FeatureDone(cps, val)
12590 ChessProgramState* cps;
12593 DelayedEventCallback cb = GetDelayedEvent();
12594 if ((cb == InitBackEnd3 && cps == &first) ||
12595 (cb == TwoMachinesEventIfReady && cps == &second)) {
12596 CancelDelayedEvent();
12597 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12599 cps->initDone = val;
12602 /* Parse feature command from engine */
12604 ParseFeatures(args, cps)
12606 ChessProgramState *cps;
12614 while (*p == ' ') p++;
12615 if (*p == NULLCHAR) return;
12617 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12618 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12619 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12620 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12621 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12622 if (BoolFeature(&p, "reuse", &val, cps)) {
12623 /* Engine can disable reuse, but can't enable it if user said no */
12624 if (!val) cps->reuse = FALSE;
12627 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12628 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12629 if (gameMode == TwoMachinesPlay) {
12630 DisplayTwoMachinesTitle();
12636 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12637 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12638 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12639 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12640 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12641 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12642 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12643 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12644 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12645 if (IntFeature(&p, "done", &val, cps)) {
12646 FeatureDone(cps, val);
12649 /* Added by Tord: */
12650 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12651 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12652 /* End of additions by Tord */
12654 /* [HGM] added features: */
12655 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12656 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12657 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12658 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12659 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12660 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12661 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12662 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12663 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12664 SendToProgram(buf, cps);
12667 if(cps->nrOptions >= MAX_OPTIONS) {
12669 sprintf(buf, "%s engine has too many options\n", cps->which);
12670 DisplayError(buf, 0);
12674 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12675 /* End of additions by HGM */
12677 /* unknown feature: complain and skip */
12679 while (*q && *q != '=') q++;
12680 sprintf(buf, "rejected %.*s\n", q-p, p);
12681 SendToProgram(buf, cps);
12687 while (*p && *p != '\"') p++;
12688 if (*p == '\"') p++;
12690 while (*p && *p != ' ') p++;
12698 PeriodicUpdatesEvent(newState)
12701 if (newState == appData.periodicUpdates)
12704 appData.periodicUpdates=newState;
12706 /* Display type changes, so update it now */
12709 /* Get the ball rolling again... */
12711 AnalysisPeriodicEvent(1);
12712 StartAnalysisClock();
12717 PonderNextMoveEvent(newState)
12720 if (newState == appData.ponderNextMove) return;
12721 if (gameMode == EditPosition) EditPositionDone();
12723 SendToProgram("hard\n", &first);
12724 if (gameMode == TwoMachinesPlay) {
12725 SendToProgram("hard\n", &second);
12728 SendToProgram("easy\n", &first);
12729 thinkOutput[0] = NULLCHAR;
12730 if (gameMode == TwoMachinesPlay) {
12731 SendToProgram("easy\n", &second);
12734 appData.ponderNextMove = newState;
12738 NewSettingEvent(option, command, value)
12744 if (gameMode == EditPosition) EditPositionDone();
12745 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12746 SendToProgram(buf, &first);
12747 if (gameMode == TwoMachinesPlay) {
12748 SendToProgram(buf, &second);
12753 ShowThinkingEvent()
12754 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12756 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12757 int newState = appData.showThinking
12758 // [HGM] thinking: other features now need thinking output as well
12759 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12761 if (oldState == newState) return;
12762 oldState = newState;
12763 if (gameMode == EditPosition) EditPositionDone();
12765 SendToProgram("post\n", &first);
12766 if (gameMode == TwoMachinesPlay) {
12767 SendToProgram("post\n", &second);
12770 SendToProgram("nopost\n", &first);
12771 thinkOutput[0] = NULLCHAR;
12772 if (gameMode == TwoMachinesPlay) {
12773 SendToProgram("nopost\n", &second);
12776 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12780 AskQuestionEvent(title, question, replyPrefix, which)
12781 char *title; char *question; char *replyPrefix; char *which;
12783 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12784 if (pr == NoProc) return;
12785 AskQuestion(title, question, replyPrefix, pr);
12789 DisplayMove(moveNumber)
12792 char message[MSG_SIZ];
12794 char cpThinkOutput[MSG_SIZ];
12796 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12798 if (moveNumber == forwardMostMove - 1 ||
12799 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12801 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12803 if (strchr(cpThinkOutput, '\n')) {
12804 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12807 *cpThinkOutput = NULLCHAR;
12810 /* [AS] Hide thinking from human user */
12811 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12812 *cpThinkOutput = NULLCHAR;
12813 if( thinkOutput[0] != NULLCHAR ) {
12816 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12817 cpThinkOutput[i] = '.';
12819 cpThinkOutput[i] = NULLCHAR;
12820 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12824 if (moveNumber == forwardMostMove - 1 &&
12825 gameInfo.resultDetails != NULL) {
12826 if (gameInfo.resultDetails[0] == NULLCHAR) {
12827 sprintf(res, " %s", PGNResult(gameInfo.result));
12829 sprintf(res, " {%s} %s",
12830 gameInfo.resultDetails, PGNResult(gameInfo.result));
12836 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12837 DisplayMessage(res, cpThinkOutput);
12839 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12840 WhiteOnMove(moveNumber) ? " " : ".. ",
12841 parseList[moveNumber], res);
12842 DisplayMessage(message, cpThinkOutput);
12847 DisplayAnalysisText(text)
12852 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12853 || appData.icsEngineAnalyze) {
12854 sprintf(buf, "Analysis (%s)", first.tidy);
12855 AnalysisPopUp(buf, text);
12863 while (*str && isspace(*str)) ++str;
12864 while (*str && !isspace(*str)) ++str;
12865 if (!*str) return 1;
12866 while (*str && isspace(*str)) ++str;
12867 if (!*str) return 1;
12875 char lst[MSG_SIZ / 2];
12877 static char *xtra[] = { "", " (--)", " (++)" };
12880 if (programStats.time == 0) {
12881 programStats.time = 1;
12884 if (programStats.got_only_move) {
12885 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12887 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12889 nps = (u64ToDouble(programStats.nodes) /
12890 ((double)programStats.time /100.0));
12892 cs = programStats.time % 100;
12893 s = programStats.time / 100;
12899 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12900 if (programStats.move_name[0] != NULLCHAR) {
12901 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12902 programStats.depth,
12903 programStats.nr_moves-programStats.moves_left,
12904 programStats.nr_moves, programStats.move_name,
12905 ((float)programStats.score)/100.0, lst,
12906 only_one_move(lst)?
12907 xtra[programStats.got_fail] : "",
12908 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12910 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12911 programStats.depth,
12912 programStats.nr_moves-programStats.moves_left,
12913 programStats.nr_moves, ((float)programStats.score)/100.0,
12915 only_one_move(lst)?
12916 xtra[programStats.got_fail] : "",
12917 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12920 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12921 programStats.depth,
12922 ((float)programStats.score)/100.0,
12924 only_one_move(lst)?
12925 xtra[programStats.got_fail] : "",
12926 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12929 DisplayAnalysisText(buf);
12933 DisplayComment(moveNumber, text)
12937 char title[MSG_SIZ];
12938 char buf[8000]; // comment can be long!
12941 if( appData.autoDisplayComment ) {
12942 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12943 strcpy(title, "Comment");
12945 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12946 WhiteOnMove(moveNumber) ? " " : ".. ",
12947 parseList[moveNumber]);
12949 // [HGM] PV info: display PV info together with (or as) comment
12950 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12951 if(text == NULL) text = "";
12952 score = pvInfoList[moveNumber].score;
12953 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12954 depth, (pvInfoList[moveNumber].time+50)/100, text);
12957 } else title[0] = 0;
12960 CommentPopUp(title, text);
12963 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12964 * might be busy thinking or pondering. It can be omitted if your
12965 * gnuchess is configured to stop thinking immediately on any user
12966 * input. However, that gnuchess feature depends on the FIONREAD
12967 * ioctl, which does not work properly on some flavors of Unix.
12971 ChessProgramState *cps;
12974 if (!cps->useSigint) return;
12975 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12976 switch (gameMode) {
12977 case MachinePlaysWhite:
12978 case MachinePlaysBlack:
12979 case TwoMachinesPlay:
12980 case IcsPlayingWhite:
12981 case IcsPlayingBlack:
12984 /* Skip if we know it isn't thinking */
12985 if (!cps->maybeThinking) return;
12986 if (appData.debugMode)
12987 fprintf(debugFP, "Interrupting %s\n", cps->which);
12988 InterruptChildProcess(cps->pr);
12989 cps->maybeThinking = FALSE;
12994 #endif /*ATTENTION*/
13000 if (whiteTimeRemaining <= 0) {
13003 if (appData.icsActive) {
13004 if (appData.autoCallFlag &&
13005 gameMode == IcsPlayingBlack && !blackFlag) {
13006 SendToICS(ics_prefix);
13007 SendToICS("flag\n");
13011 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13013 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13014 if (appData.autoCallFlag) {
13015 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13022 if (blackTimeRemaining <= 0) {
13025 if (appData.icsActive) {
13026 if (appData.autoCallFlag &&
13027 gameMode == IcsPlayingWhite && !whiteFlag) {
13028 SendToICS(ics_prefix);
13029 SendToICS("flag\n");
13033 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13035 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13036 if (appData.autoCallFlag) {
13037 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13050 if (!appData.clockMode || appData.icsActive ||
13051 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13054 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13056 if ( !WhiteOnMove(forwardMostMove) )
13057 /* White made time control */
13058 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13059 /* [HGM] time odds: correct new time quota for time odds! */
13060 / WhitePlayer()->timeOdds;
13062 /* Black made time control */
13063 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13064 / WhitePlayer()->other->timeOdds;
13068 DisplayBothClocks()
13070 int wom = gameMode == EditPosition ?
13071 !blackPlaysFirst : WhiteOnMove(currentMove);
13072 DisplayWhiteClock(whiteTimeRemaining, wom);
13073 DisplayBlackClock(blackTimeRemaining, !wom);
13077 /* Timekeeping seems to be a portability nightmare. I think everyone
13078 has ftime(), but I'm really not sure, so I'm including some ifdefs
13079 to use other calls if you don't. Clocks will be less accurate if
13080 you have neither ftime nor gettimeofday.
13083 /* VS 2008 requires the #include outside of the function */
13084 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13085 #include <sys/timeb.h>
13088 /* Get the current time as a TimeMark */
13093 #if HAVE_GETTIMEOFDAY
13095 struct timeval timeVal;
13096 struct timezone timeZone;
13098 gettimeofday(&timeVal, &timeZone);
13099 tm->sec = (long) timeVal.tv_sec;
13100 tm->ms = (int) (timeVal.tv_usec / 1000L);
13102 #else /*!HAVE_GETTIMEOFDAY*/
13105 // include <sys/timeb.h> / moved to just above start of function
13106 struct timeb timeB;
13109 tm->sec = (long) timeB.time;
13110 tm->ms = (int) timeB.millitm;
13112 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13113 tm->sec = (long) time(NULL);
13119 /* Return the difference in milliseconds between two
13120 time marks. We assume the difference will fit in a long!
13123 SubtractTimeMarks(tm2, tm1)
13124 TimeMark *tm2, *tm1;
13126 return 1000L*(tm2->sec - tm1->sec) +
13127 (long) (tm2->ms - tm1->ms);
13132 * Code to manage the game clocks.
13134 * In tournament play, black starts the clock and then white makes a move.
13135 * We give the human user a slight advantage if he is playing white---the
13136 * clocks don't run until he makes his first move, so it takes zero time.
13137 * Also, we don't account for network lag, so we could get out of sync
13138 * with GNU Chess's clock -- but then, referees are always right.
13141 static TimeMark tickStartTM;
13142 static long intendedTickLength;
13145 NextTickLength(timeRemaining)
13146 long timeRemaining;
13148 long nominalTickLength, nextTickLength;
13150 if (timeRemaining > 0L && timeRemaining <= 10000L)
13151 nominalTickLength = 100L;
13153 nominalTickLength = 1000L;
13154 nextTickLength = timeRemaining % nominalTickLength;
13155 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13157 return nextTickLength;
13160 /* Adjust clock one minute up or down */
13162 AdjustClock(Boolean which, int dir)
13164 if(which) blackTimeRemaining += 60000*dir;
13165 else whiteTimeRemaining += 60000*dir;
13166 DisplayBothClocks();
13169 /* Stop clocks and reset to a fresh time control */
13173 (void) StopClockTimer();
13174 if (appData.icsActive) {
13175 whiteTimeRemaining = blackTimeRemaining = 0;
13176 } else { /* [HGM] correct new time quote for time odds */
13177 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13178 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13180 if (whiteFlag || blackFlag) {
13182 whiteFlag = blackFlag = FALSE;
13184 DisplayBothClocks();
13187 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13189 /* Decrement running clock by amount of time that has passed */
13193 long timeRemaining;
13194 long lastTickLength, fudge;
13197 if (!appData.clockMode) return;
13198 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13202 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13204 /* Fudge if we woke up a little too soon */
13205 fudge = intendedTickLength - lastTickLength;
13206 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13208 if (WhiteOnMove(forwardMostMove)) {
13209 if(whiteNPS >= 0) lastTickLength = 0;
13210 timeRemaining = whiteTimeRemaining -= lastTickLength;
13211 DisplayWhiteClock(whiteTimeRemaining - fudge,
13212 WhiteOnMove(currentMove));
13214 if(blackNPS >= 0) lastTickLength = 0;
13215 timeRemaining = blackTimeRemaining -= lastTickLength;
13216 DisplayBlackClock(blackTimeRemaining - fudge,
13217 !WhiteOnMove(currentMove));
13220 if (CheckFlags()) return;
13223 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13224 StartClockTimer(intendedTickLength);
13226 /* if the time remaining has fallen below the alarm threshold, sound the
13227 * alarm. if the alarm has sounded and (due to a takeback or time control
13228 * with increment) the time remaining has increased to a level above the
13229 * threshold, reset the alarm so it can sound again.
13232 if (appData.icsActive && appData.icsAlarm) {
13234 /* make sure we are dealing with the user's clock */
13235 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13236 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13239 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13240 alarmSounded = FALSE;
13241 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13243 alarmSounded = TRUE;
13249 /* A player has just moved, so stop the previously running
13250 clock and (if in clock mode) start the other one.
13251 We redisplay both clocks in case we're in ICS mode, because
13252 ICS gives us an update to both clocks after every move.
13253 Note that this routine is called *after* forwardMostMove
13254 is updated, so the last fractional tick must be subtracted
13255 from the color that is *not* on move now.
13260 long lastTickLength;
13262 int flagged = FALSE;
13266 if (StopClockTimer() && appData.clockMode) {
13267 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13268 if (WhiteOnMove(forwardMostMove)) {
13269 if(blackNPS >= 0) lastTickLength = 0;
13270 blackTimeRemaining -= lastTickLength;
13271 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13272 // if(pvInfoList[forwardMostMove-1].time == -1)
13273 pvInfoList[forwardMostMove-1].time = // use GUI time
13274 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13276 if(whiteNPS >= 0) lastTickLength = 0;
13277 whiteTimeRemaining -= lastTickLength;
13278 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13279 // if(pvInfoList[forwardMostMove-1].time == -1)
13280 pvInfoList[forwardMostMove-1].time =
13281 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13283 flagged = CheckFlags();
13285 CheckTimeControl();
13287 if (flagged || !appData.clockMode) return;
13289 switch (gameMode) {
13290 case MachinePlaysBlack:
13291 case MachinePlaysWhite:
13292 case BeginningOfGame:
13293 if (pausing) return;
13297 case PlayFromGameFile:
13306 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13307 whiteTimeRemaining : blackTimeRemaining);
13308 StartClockTimer(intendedTickLength);
13312 /* Stop both clocks */
13316 long lastTickLength;
13319 if (!StopClockTimer()) return;
13320 if (!appData.clockMode) return;
13324 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13325 if (WhiteOnMove(forwardMostMove)) {
13326 if(whiteNPS >= 0) lastTickLength = 0;
13327 whiteTimeRemaining -= lastTickLength;
13328 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13330 if(blackNPS >= 0) lastTickLength = 0;
13331 blackTimeRemaining -= lastTickLength;
13332 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13337 /* Start clock of player on move. Time may have been reset, so
13338 if clock is already running, stop and restart it. */
13342 (void) StopClockTimer(); /* in case it was running already */
13343 DisplayBothClocks();
13344 if (CheckFlags()) return;
13346 if (!appData.clockMode) return;
13347 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13349 GetTimeMark(&tickStartTM);
13350 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13351 whiteTimeRemaining : blackTimeRemaining);
13353 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13354 whiteNPS = blackNPS = -1;
13355 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13356 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13357 whiteNPS = first.nps;
13358 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13359 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13360 blackNPS = first.nps;
13361 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13362 whiteNPS = second.nps;
13363 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13364 blackNPS = second.nps;
13365 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13367 StartClockTimer(intendedTickLength);
13374 long second, minute, hour, day;
13376 static char buf[32];
13378 if (ms > 0 && ms <= 9900) {
13379 /* convert milliseconds to tenths, rounding up */
13380 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13382 sprintf(buf, " %03.1f ", tenths/10.0);
13386 /* convert milliseconds to seconds, rounding up */
13387 /* use floating point to avoid strangeness of integer division
13388 with negative dividends on many machines */
13389 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13396 day = second / (60 * 60 * 24);
13397 second = second % (60 * 60 * 24);
13398 hour = second / (60 * 60);
13399 second = second % (60 * 60);
13400 minute = second / 60;
13401 second = second % 60;
13404 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13405 sign, day, hour, minute, second);
13407 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13409 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13416 * This is necessary because some C libraries aren't ANSI C compliant yet.
13419 StrStr(string, match)
13420 char *string, *match;
13424 length = strlen(match);
13426 for (i = strlen(string) - length; i >= 0; i--, string++)
13427 if (!strncmp(match, string, length))
13434 StrCaseStr(string, match)
13435 char *string, *match;
13439 length = strlen(match);
13441 for (i = strlen(string) - length; i >= 0; i--, string++) {
13442 for (j = 0; j < length; j++) {
13443 if (ToLower(match[j]) != ToLower(string[j]))
13446 if (j == length) return string;
13460 c1 = ToLower(*s1++);
13461 c2 = ToLower(*s2++);
13462 if (c1 > c2) return 1;
13463 if (c1 < c2) return -1;
13464 if (c1 == NULLCHAR) return 0;
13473 return isupper(c) ? tolower(c) : c;
13481 return islower(c) ? toupper(c) : c;
13483 #endif /* !_amigados */
13491 if ((ret = (char *) malloc(strlen(s) + 1))) {
13498 StrSavePtr(s, savePtr)
13499 char *s, **savePtr;
13504 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13505 strcpy(*savePtr, s);
13517 clock = time((time_t *)NULL);
13518 tm = localtime(&clock);
13519 sprintf(buf, "%04d.%02d.%02d",
13520 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13521 return StrSave(buf);
13526 PositionToFEN(move, overrideCastling)
13528 char *overrideCastling;
13530 int i, j, fromX, fromY, toX, toY;
13537 whiteToPlay = (gameMode == EditPosition) ?
13538 !blackPlaysFirst : (move % 2 == 0);
13541 /* Piece placement data */
13542 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13544 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13545 if (boards[move][i][j] == EmptySquare) {
13547 } else { ChessSquare piece = boards[move][i][j];
13548 if (emptycount > 0) {
13549 if(emptycount<10) /* [HGM] can be >= 10 */
13550 *p++ = '0' + emptycount;
13551 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13554 if(PieceToChar(piece) == '+') {
13555 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13557 piece = (ChessSquare)(DEMOTED piece);
13559 *p++ = PieceToChar(piece);
13561 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13562 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13567 if (emptycount > 0) {
13568 if(emptycount<10) /* [HGM] can be >= 10 */
13569 *p++ = '0' + emptycount;
13570 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13577 /* [HGM] print Crazyhouse or Shogi holdings */
13578 if( gameInfo.holdingsWidth ) {
13579 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13581 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13582 piece = boards[move][i][BOARD_WIDTH-1];
13583 if( piece != EmptySquare )
13584 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13585 *p++ = PieceToChar(piece);
13587 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13588 piece = boards[move][BOARD_HEIGHT-i-1][0];
13589 if( piece != EmptySquare )
13590 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13591 *p++ = PieceToChar(piece);
13594 if( q == p ) *p++ = '-';
13600 *p++ = whiteToPlay ? 'w' : 'b';
13603 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13604 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13606 if(nrCastlingRights) {
13608 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13609 /* [HGM] write directly from rights */
13610 if(castlingRights[move][2] >= 0 &&
13611 castlingRights[move][0] >= 0 )
13612 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13613 if(castlingRights[move][2] >= 0 &&
13614 castlingRights[move][1] >= 0 )
13615 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13616 if(castlingRights[move][5] >= 0 &&
13617 castlingRights[move][3] >= 0 )
13618 *p++ = castlingRights[move][3] + AAA;
13619 if(castlingRights[move][5] >= 0 &&
13620 castlingRights[move][4] >= 0 )
13621 *p++ = castlingRights[move][4] + AAA;
13624 /* [HGM] write true castling rights */
13625 if( nrCastlingRights == 6 ) {
13626 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13627 castlingRights[move][2] >= 0 ) *p++ = 'K';
13628 if(castlingRights[move][1] == BOARD_LEFT &&
13629 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13630 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13631 castlingRights[move][5] >= 0 ) *p++ = 'k';
13632 if(castlingRights[move][4] == BOARD_LEFT &&
13633 castlingRights[move][5] >= 0 ) *p++ = 'q';
13636 if (q == p) *p++ = '-'; /* No castling rights */
13640 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13641 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13642 /* En passant target square */
13643 if (move > backwardMostMove) {
13644 fromX = moveList[move - 1][0] - AAA;
13645 fromY = moveList[move - 1][1] - ONE;
13646 toX = moveList[move - 1][2] - AAA;
13647 toY = moveList[move - 1][3] - ONE;
13648 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13649 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13650 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13652 /* 2-square pawn move just happened */
13654 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13665 /* [HGM] find reversible plies */
13666 { int i = 0, j=move;
13668 if (appData.debugMode) { int k;
13669 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13670 for(k=backwardMostMove; k<=forwardMostMove; k++)
13671 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13675 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13676 if( j == backwardMostMove ) i += initialRulePlies;
13677 sprintf(p, "%d ", i);
13678 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13680 /* Fullmove number */
13681 sprintf(p, "%d", (move / 2) + 1);
13683 return StrSave(buf);
13687 ParseFEN(board, blackPlaysFirst, fen)
13689 int *blackPlaysFirst;
13699 /* [HGM] by default clear Crazyhouse holdings, if present */
13700 if(gameInfo.holdingsWidth) {
13701 for(i=0; i<BOARD_HEIGHT; i++) {
13702 board[i][0] = EmptySquare; /* black holdings */
13703 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13704 board[i][1] = (ChessSquare) 0; /* black counts */
13705 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13709 /* Piece placement data */
13710 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13713 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13714 if (*p == '/') p++;
13715 emptycount = gameInfo.boardWidth - j;
13716 while (emptycount--)
13717 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13719 #if(BOARD_SIZE >= 10)
13720 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13721 p++; emptycount=10;
13722 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13723 while (emptycount--)
13724 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13726 } else if (isdigit(*p)) {
13727 emptycount = *p++ - '0';
13728 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13729 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13730 while (emptycount--)
13731 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13732 } else if (*p == '+' || isalpha(*p)) {
13733 if (j >= gameInfo.boardWidth) return FALSE;
13735 piece = CharToPiece(*++p);
13736 if(piece == EmptySquare) return FALSE; /* unknown piece */
13737 piece = (ChessSquare) (PROMOTED piece ); p++;
13738 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13739 } else piece = CharToPiece(*p++);
13741 if(piece==EmptySquare) return FALSE; /* unknown piece */
13742 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13743 piece = (ChessSquare) (PROMOTED piece);
13744 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13747 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13753 while (*p == '/' || *p == ' ') p++;
13755 /* [HGM] look for Crazyhouse holdings here */
13756 while(*p==' ') p++;
13757 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13759 if(*p == '-' ) *p++; /* empty holdings */ else {
13760 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13761 /* if we would allow FEN reading to set board size, we would */
13762 /* have to add holdings and shift the board read so far here */
13763 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13765 if((int) piece >= (int) BlackPawn ) {
13766 i = (int)piece - (int)BlackPawn;
13767 i = PieceToNumber((ChessSquare)i);
13768 if( i >= gameInfo.holdingsSize ) return FALSE;
13769 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13770 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13772 i = (int)piece - (int)WhitePawn;
13773 i = PieceToNumber((ChessSquare)i);
13774 if( i >= gameInfo.holdingsSize ) return FALSE;
13775 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13776 board[i][BOARD_WIDTH-2]++; /* black holdings */
13780 if(*p == ']') *p++;
13783 while(*p == ' ') p++;
13788 *blackPlaysFirst = FALSE;
13791 *blackPlaysFirst = TRUE;
13797 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13798 /* return the extra info in global variiables */
13800 /* set defaults in case FEN is incomplete */
13801 FENepStatus = EP_UNKNOWN;
13802 for(i=0; i<nrCastlingRights; i++ ) {
13803 FENcastlingRights[i] =
13804 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13805 } /* assume possible unless obviously impossible */
13806 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13807 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13808 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13809 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13810 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13811 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13814 while(*p==' ') p++;
13815 if(nrCastlingRights) {
13816 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13817 /* castling indicator present, so default becomes no castlings */
13818 for(i=0; i<nrCastlingRights; i++ ) {
13819 FENcastlingRights[i] = -1;
13822 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13823 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13824 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13825 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13826 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13828 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13829 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13830 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13834 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13835 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13836 FENcastlingRights[2] = whiteKingFile;
13839 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13840 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13841 FENcastlingRights[2] = whiteKingFile;
13844 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13845 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13846 FENcastlingRights[5] = blackKingFile;
13849 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13850 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13851 FENcastlingRights[5] = blackKingFile;
13854 default: /* FRC castlings */
13855 if(c >= 'a') { /* black rights */
13856 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13857 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13858 if(i == BOARD_RGHT) break;
13859 FENcastlingRights[5] = i;
13861 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13862 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13864 FENcastlingRights[3] = c;
13866 FENcastlingRights[4] = c;
13867 } else { /* white rights */
13868 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13869 if(board[0][i] == WhiteKing) break;
13870 if(i == BOARD_RGHT) break;
13871 FENcastlingRights[2] = i;
13872 c -= AAA - 'a' + 'A';
13873 if(board[0][c] >= WhiteKing) break;
13875 FENcastlingRights[0] = c;
13877 FENcastlingRights[1] = c;
13881 if (appData.debugMode) {
13882 fprintf(debugFP, "FEN castling rights:");
13883 for(i=0; i<nrCastlingRights; i++)
13884 fprintf(debugFP, " %d", FENcastlingRights[i]);
13885 fprintf(debugFP, "\n");
13888 while(*p==' ') p++;
13891 /* read e.p. field in games that know e.p. capture */
13892 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13893 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13895 p++; FENepStatus = EP_NONE;
13897 char c = *p++ - AAA;
13899 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13900 if(*p >= '0' && *p <='9') *p++;
13906 if(sscanf(p, "%d", &i) == 1) {
13907 FENrulePlies = i; /* 50-move ply counter */
13908 /* (The move number is still ignored) */
13915 EditPositionPasteFEN(char *fen)
13918 Board initial_position;
13920 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13921 DisplayError(_("Bad FEN position in clipboard"), 0);
13924 int savedBlackPlaysFirst = blackPlaysFirst;
13925 EditPositionEvent();
13926 blackPlaysFirst = savedBlackPlaysFirst;
13927 CopyBoard(boards[0], initial_position);
13928 /* [HGM] copy FEN attributes as well */
13930 initialRulePlies = FENrulePlies;
13931 epStatus[0] = FENepStatus;
13932 for( i=0; i<nrCastlingRights; i++ )
13933 castlingRights[0][i] = FENcastlingRights[i];
13935 EditPositionDone();
13936 DisplayBothClocks();
13937 DrawPosition(FALSE, boards[currentMove]);