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) {
3947 if (appData.premove)
3949 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951 ClearPremoveHighlights();
3953 DrawPosition(FALSE, boards[currentMove]);
3954 DisplayMove(moveNum - 1);
3955 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3958 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3962 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 if(bookHit) { // [HGM] book: simulate book reply
3965 static char bookMove[MSG_SIZ]; // a bit generous?
3967 programStats.nodes = programStats.depth = programStats.time =
3968 programStats.score = programStats.got_only_move = 0;
3969 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971 strcpy(bookMove, "move ");
3972 strcat(bookMove, bookHit);
3973 HandleMachineMove(bookMove, &first);
3982 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983 ics_getting_history = H_REQUESTED;
3984 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3990 AnalysisPeriodicEvent(force)
3993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994 && !force) || !appData.periodicUpdates)
3997 /* Send . command to Crafty to collect stats */
3998 SendToProgram(".\n", &first);
4000 /* Don't send another until we get a response (this makes
4001 us stop sending to old Crafty's which don't understand
4002 the "." command (sending illegal cmds resets node count & time,
4003 which looks bad)) */
4004 programStats.ok_to_send = 0;
4008 SendMoveToProgram(moveNum, cps)
4010 ChessProgramState *cps;
4014 if (cps->useUsermove) {
4015 SendToProgram("usermove ", cps);
4019 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4020 int len = space - parseList[moveNum];
4021 memcpy(buf, parseList[moveNum], len);
4023 buf[len] = NULLCHAR;
4025 sprintf(buf, "%s\n", parseList[moveNum]);
4027 SendToProgram(buf, cps);
4029 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4030 AlphaRank(moveList[moveNum], 4);
4031 SendToProgram(moveList[moveNum], cps);
4032 AlphaRank(moveList[moveNum], 4); // and back
4034 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4035 * the engine. It would be nice to have a better way to identify castle
4037 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4038 && cps->useOOCastle) {
4039 int fromX = moveList[moveNum][0] - AAA;
4040 int fromY = moveList[moveNum][1] - ONE;
4041 int toX = moveList[moveNum][2] - AAA;
4042 int toY = moveList[moveNum][3] - ONE;
4043 if((boards[moveNum][fromY][fromX] == WhiteKing
4044 && boards[moveNum][toY][toX] == WhiteRook)
4045 || (boards[moveNum][fromY][fromX] == BlackKing
4046 && boards[moveNum][toY][toX] == BlackRook)) {
4047 if(toX > fromX) SendToProgram("O-O\n", cps);
4048 else SendToProgram("O-O-O\n", cps);
4050 else SendToProgram(moveList[moveNum], cps);
4052 else SendToProgram(moveList[moveNum], cps);
4053 /* End of additions by Tord */
4056 /* [HGM] setting up the opening has brought engine in force mode! */
4057 /* Send 'go' if we are in a mode where machine should play. */
4058 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4059 (gameMode == TwoMachinesPlay ||
4061 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4063 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4064 SendToProgram("go\n", cps);
4065 if (appData.debugMode) {
4066 fprintf(debugFP, "(extra)\n");
4069 setboardSpoiledMachineBlack = 0;
4073 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4075 int fromX, fromY, toX, toY;
4077 char user_move[MSG_SIZ];
4081 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4082 (int)moveType, fromX, fromY, toX, toY);
4083 DisplayError(user_move + strlen("say "), 0);
4085 case WhiteKingSideCastle:
4086 case BlackKingSideCastle:
4087 case WhiteQueenSideCastleWild:
4088 case BlackQueenSideCastleWild:
4090 case WhiteHSideCastleFR:
4091 case BlackHSideCastleFR:
4093 sprintf(user_move, "o-o\n");
4095 case WhiteQueenSideCastle:
4096 case BlackQueenSideCastle:
4097 case WhiteKingSideCastleWild:
4098 case BlackKingSideCastleWild:
4100 case WhiteASideCastleFR:
4101 case BlackASideCastleFR:
4103 sprintf(user_move, "o-o-o\n");
4105 case WhitePromotionQueen:
4106 case BlackPromotionQueen:
4107 case WhitePromotionRook:
4108 case BlackPromotionRook:
4109 case WhitePromotionBishop:
4110 case BlackPromotionBishop:
4111 case WhitePromotionKnight:
4112 case BlackPromotionKnight:
4113 case WhitePromotionKing:
4114 case BlackPromotionKing:
4115 case WhitePromotionChancellor:
4116 case BlackPromotionChancellor:
4117 case WhitePromotionArchbishop:
4118 case BlackPromotionArchbishop:
4119 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4120 sprintf(user_move, "%c%c%c%c=%c\n",
4121 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4122 PieceToChar(WhiteFerz));
4123 else if(gameInfo.variant == VariantGreat)
4124 sprintf(user_move, "%c%c%c%c=%c\n",
4125 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4126 PieceToChar(WhiteMan));
4128 sprintf(user_move, "%c%c%c%c=%c\n",
4129 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4130 PieceToChar(PromoPiece(moveType)));
4134 sprintf(user_move, "%c@%c%c\n",
4135 ToUpper(PieceToChar((ChessSquare) fromX)),
4136 AAA + toX, ONE + toY);
4139 case WhiteCapturesEnPassant:
4140 case BlackCapturesEnPassant:
4141 case IllegalMove: /* could be a variant we don't quite understand */
4142 sprintf(user_move, "%c%c%c%c\n",
4143 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4146 SendToICS(user_move);
4147 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4148 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4152 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4157 if (rf == DROP_RANK) {
4158 sprintf(move, "%c@%c%c\n",
4159 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4161 if (promoChar == 'x' || promoChar == NULLCHAR) {
4162 sprintf(move, "%c%c%c%c\n",
4163 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4165 sprintf(move, "%c%c%c%c%c\n",
4166 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4172 ProcessICSInitScript(f)
4177 while (fgets(buf, MSG_SIZ, f)) {
4178 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4185 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4187 AlphaRank(char *move, int n)
4189 // char *p = move, c; int x, y;
4191 if (appData.debugMode) {
4192 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4196 move[2]>='0' && move[2]<='9' &&
4197 move[3]>='a' && move[3]<='x' ) {
4199 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4200 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4202 if(move[0]>='0' && move[0]<='9' &&
4203 move[1]>='a' && move[1]<='x' &&
4204 move[2]>='0' && move[2]<='9' &&
4205 move[3]>='a' && move[3]<='x' ) {
4206 /* input move, Shogi -> normal */
4207 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4208 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4209 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4210 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4213 move[3]>='0' && move[3]<='9' &&
4214 move[2]>='a' && move[2]<='x' ) {
4216 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4217 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4220 move[0]>='a' && move[0]<='x' &&
4221 move[3]>='0' && move[3]<='9' &&
4222 move[2]>='a' && move[2]<='x' ) {
4223 /* output move, normal -> Shogi */
4224 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4225 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4226 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4227 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4228 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4230 if (appData.debugMode) {
4231 fprintf(debugFP, " out = '%s'\n", move);
4235 /* Parser for moves from gnuchess, ICS, or user typein box */
4237 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4240 ChessMove *moveType;
4241 int *fromX, *fromY, *toX, *toY;
4244 if (appData.debugMode) {
4245 fprintf(debugFP, "move to parse: %s\n", move);
4247 *moveType = yylexstr(moveNum, move);
4249 switch (*moveType) {
4250 case WhitePromotionChancellor:
4251 case BlackPromotionChancellor:
4252 case WhitePromotionArchbishop:
4253 case BlackPromotionArchbishop:
4254 case WhitePromotionQueen:
4255 case BlackPromotionQueen:
4256 case WhitePromotionRook:
4257 case BlackPromotionRook:
4258 case WhitePromotionBishop:
4259 case BlackPromotionBishop:
4260 case WhitePromotionKnight:
4261 case BlackPromotionKnight:
4262 case WhitePromotionKing:
4263 case BlackPromotionKing:
4265 case WhiteCapturesEnPassant:
4266 case BlackCapturesEnPassant:
4267 case WhiteKingSideCastle:
4268 case WhiteQueenSideCastle:
4269 case BlackKingSideCastle:
4270 case BlackQueenSideCastle:
4271 case WhiteKingSideCastleWild:
4272 case WhiteQueenSideCastleWild:
4273 case BlackKingSideCastleWild:
4274 case BlackQueenSideCastleWild:
4275 /* Code added by Tord: */
4276 case WhiteHSideCastleFR:
4277 case WhiteASideCastleFR:
4278 case BlackHSideCastleFR:
4279 case BlackASideCastleFR:
4280 /* End of code added by Tord */
4281 case IllegalMove: /* bug or odd chess variant */
4282 *fromX = currentMoveString[0] - AAA;
4283 *fromY = currentMoveString[1] - ONE;
4284 *toX = currentMoveString[2] - AAA;
4285 *toY = currentMoveString[3] - ONE;
4286 *promoChar = currentMoveString[4];
4287 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4288 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4289 if (appData.debugMode) {
4290 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4292 *fromX = *fromY = *toX = *toY = 0;
4295 if (appData.testLegality) {
4296 return (*moveType != IllegalMove);
4298 return !(fromX == fromY && toX == toY);
4303 *fromX = *moveType == WhiteDrop ?
4304 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4305 (int) CharToPiece(ToLower(currentMoveString[0]));
4307 *toX = currentMoveString[2] - AAA;
4308 *toY = currentMoveString[3] - ONE;
4309 *promoChar = NULLCHAR;
4313 case ImpossibleMove:
4314 case (ChessMove) 0: /* end of file */
4323 if (appData.debugMode) {
4324 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4327 *fromX = *fromY = *toX = *toY = 0;
4328 *promoChar = NULLCHAR;
4333 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4334 // All positions will have equal probability, but the current method will not provide a unique
4335 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4341 int piecesLeft[(int)BlackPawn];
4342 int seed, nrOfShuffles;
4344 void GetPositionNumber()
4345 { // sets global variable seed
4348 seed = appData.defaultFrcPosition;
4349 if(seed < 0) { // randomize based on time for negative FRC position numbers
4350 for(i=0; i<50; i++) seed += random();
4351 seed = random() ^ random() >> 8 ^ random() << 8;
4352 if(seed<0) seed = -seed;
4356 int put(Board board, int pieceType, int rank, int n, int shade)
4357 // put the piece on the (n-1)-th empty squares of the given shade
4361 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4362 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4363 board[rank][i] = (ChessSquare) pieceType;
4364 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4366 piecesLeft[pieceType]--;
4374 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4375 // calculate where the next piece goes, (any empty square), and put it there
4379 i = seed % squaresLeft[shade];
4380 nrOfShuffles *= squaresLeft[shade];
4381 seed /= squaresLeft[shade];
4382 put(board, pieceType, rank, i, shade);
4385 void AddTwoPieces(Board board, int pieceType, int rank)
4386 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4388 int i, n=squaresLeft[ANY], j=n-1, k;
4390 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4391 i = seed % k; // pick one
4394 while(i >= j) i -= j--;
4395 j = n - 1 - j; i += j;
4396 put(board, pieceType, rank, j, ANY);
4397 put(board, pieceType, rank, i, ANY);
4400 void SetUpShuffle(Board board, int number)
4404 GetPositionNumber(); nrOfShuffles = 1;
4406 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4407 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4408 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4410 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4412 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4413 p = (int) board[0][i];
4414 if(p < (int) BlackPawn) piecesLeft[p] ++;
4415 board[0][i] = EmptySquare;
4418 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4419 // shuffles restricted to allow normal castling put KRR first
4420 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4421 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4422 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4423 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4424 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4425 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4426 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4427 put(board, WhiteRook, 0, 0, ANY);
4428 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4431 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4432 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4433 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4434 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4435 while(piecesLeft[p] >= 2) {
4436 AddOnePiece(board, p, 0, LITE);
4437 AddOnePiece(board, p, 0, DARK);
4439 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4442 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4443 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4444 // but we leave King and Rooks for last, to possibly obey FRC restriction
4445 if(p == (int)WhiteRook) continue;
4446 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4447 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4450 // now everything is placed, except perhaps King (Unicorn) and Rooks
4452 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4453 // Last King gets castling rights
4454 while(piecesLeft[(int)WhiteUnicorn]) {
4455 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4456 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4459 while(piecesLeft[(int)WhiteKing]) {
4460 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4461 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4466 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4467 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4470 // Only Rooks can be left; simply place them all
4471 while(piecesLeft[(int)WhiteRook]) {
4472 i = put(board, WhiteRook, 0, 0, ANY);
4473 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4476 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4478 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4481 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4482 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4485 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4488 int SetCharTable( char *table, const char * map )
4489 /* [HGM] moved here from winboard.c because of its general usefulness */
4490 /* Basically a safe strcpy that uses the last character as King */
4492 int result = FALSE; int NrPieces;
4494 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4495 && NrPieces >= 12 && !(NrPieces&1)) {
4496 int i; /* [HGM] Accept even length from 12 to 34 */
4498 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4499 for( i=0; i<NrPieces/2-1; i++ ) {
4501 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4503 table[(int) WhiteKing] = map[NrPieces/2-1];
4504 table[(int) BlackKing] = map[NrPieces-1];
4512 void Prelude(Board board)
4513 { // [HGM] superchess: random selection of exo-pieces
4514 int i, j, k; ChessSquare p;
4515 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4517 GetPositionNumber(); // use FRC position number
4519 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4520 SetCharTable(pieceToChar, appData.pieceToCharTable);
4521 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4522 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4525 j = seed%4; seed /= 4;
4526 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4527 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4528 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4529 j = seed%3 + (seed%3 >= j); seed /= 3;
4530 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4531 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4532 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4533 j = seed%3; seed /= 3;
4534 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4535 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4536 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4537 j = seed%2 + (seed%2 >= j); seed /= 2;
4538 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4539 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4540 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4541 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4542 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4543 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4544 put(board, exoPieces[0], 0, 0, ANY);
4545 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4549 InitPosition(redraw)
4552 ChessSquare (* pieces)[BOARD_SIZE];
4553 int i, j, pawnRow, overrule,
4554 oldx = gameInfo.boardWidth,
4555 oldy = gameInfo.boardHeight,
4556 oldh = gameInfo.holdingsWidth,
4557 oldv = gameInfo.variant;
4559 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4561 /* [AS] Initialize pv info list [HGM] and game status */
4563 for( i=0; i<MAX_MOVES; i++ ) {
4564 pvInfoList[i].depth = 0;
4565 epStatus[i]=EP_NONE;
4566 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4569 initialRulePlies = 0; /* 50-move counter start */
4571 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4572 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4576 /* [HGM] logic here is completely changed. In stead of full positions */
4577 /* the initialized data only consist of the two backranks. The switch */
4578 /* selects which one we will use, which is than copied to the Board */
4579 /* initialPosition, which for the rest is initialized by Pawns and */
4580 /* empty squares. This initial position is then copied to boards[0], */
4581 /* possibly after shuffling, so that it remains available. */
4583 gameInfo.holdingsWidth = 0; /* default board sizes */
4584 gameInfo.boardWidth = 8;
4585 gameInfo.boardHeight = 8;
4586 gameInfo.holdingsSize = 0;
4587 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4588 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4589 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4591 switch (gameInfo.variant) {
4592 case VariantFischeRandom:
4593 shuffleOpenings = TRUE;
4597 case VariantShatranj:
4598 pieces = ShatranjArray;
4599 nrCastlingRights = 0;
4600 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4602 case VariantTwoKings:
4603 pieces = twoKingsArray;
4605 case VariantCapaRandom:
4606 shuffleOpenings = TRUE;
4607 case VariantCapablanca:
4608 pieces = CapablancaArray;
4609 gameInfo.boardWidth = 10;
4610 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4613 pieces = GothicArray;
4614 gameInfo.boardWidth = 10;
4615 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4618 pieces = JanusArray;
4619 gameInfo.boardWidth = 10;
4620 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4621 nrCastlingRights = 6;
4622 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4623 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4624 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4625 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4626 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4627 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4630 pieces = FalconArray;
4631 gameInfo.boardWidth = 10;
4632 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4634 case VariantXiangqi:
4635 pieces = XiangqiArray;
4636 gameInfo.boardWidth = 9;
4637 gameInfo.boardHeight = 10;
4638 nrCastlingRights = 0;
4639 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4642 pieces = ShogiArray;
4643 gameInfo.boardWidth = 9;
4644 gameInfo.boardHeight = 9;
4645 gameInfo.holdingsSize = 7;
4646 nrCastlingRights = 0;
4647 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4649 case VariantCourier:
4650 pieces = CourierArray;
4651 gameInfo.boardWidth = 12;
4652 nrCastlingRights = 0;
4653 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4654 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4656 case VariantKnightmate:
4657 pieces = KnightmateArray;
4658 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4661 pieces = fairyArray;
4662 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4665 pieces = GreatArray;
4666 gameInfo.boardWidth = 10;
4667 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4668 gameInfo.holdingsSize = 8;
4672 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4673 gameInfo.holdingsSize = 8;
4674 startedFromSetupPosition = TRUE;
4676 case VariantCrazyhouse:
4677 case VariantBughouse:
4679 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4680 gameInfo.holdingsSize = 5;
4682 case VariantWildCastle:
4684 /* !!?shuffle with kings guaranteed to be on d or e file */
4685 shuffleOpenings = 1;
4687 case VariantNoCastle:
4689 nrCastlingRights = 0;
4690 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4691 /* !!?unconstrained back-rank shuffle */
4692 shuffleOpenings = 1;
4697 if(appData.NrFiles >= 0) {
4698 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4699 gameInfo.boardWidth = appData.NrFiles;
4701 if(appData.NrRanks >= 0) {
4702 gameInfo.boardHeight = appData.NrRanks;
4704 if(appData.holdingsSize >= 0) {
4705 i = appData.holdingsSize;
4706 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4707 gameInfo.holdingsSize = i;
4709 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4710 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4711 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4713 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4714 if(pawnRow < 1) pawnRow = 1;
4716 /* User pieceToChar list overrules defaults */
4717 if(appData.pieceToCharTable != NULL)
4718 SetCharTable(pieceToChar, appData.pieceToCharTable);
4720 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4722 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4723 s = (ChessSquare) 0; /* account holding counts in guard band */
4724 for( i=0; i<BOARD_HEIGHT; i++ )
4725 initialPosition[i][j] = s;
4727 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4728 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4729 initialPosition[pawnRow][j] = WhitePawn;
4730 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4731 if(gameInfo.variant == VariantXiangqi) {
4733 initialPosition[pawnRow][j] =
4734 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4735 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4736 initialPosition[2][j] = WhiteCannon;
4737 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4741 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4743 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4746 initialPosition[1][j] = WhiteBishop;
4747 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4749 initialPosition[1][j] = WhiteRook;
4750 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4753 if( nrCastlingRights == -1) {
4754 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4755 /* This sets default castling rights from none to normal corners */
4756 /* Variants with other castling rights must set them themselves above */
4757 nrCastlingRights = 6;
4759 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4760 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4761 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4762 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4763 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4764 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4767 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4768 if(gameInfo.variant == VariantGreat) { // promotion commoners
4769 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4770 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4771 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4772 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4775 if(gameInfo.variant == VariantFischeRandom) {
4776 if( appData.defaultFrcPosition < 0 ) {
4777 ShuffleFRC( initialPosition );
4780 SetupFRC( initialPosition, appData.defaultFrcPosition );
4782 startedFromSetupPosition = TRUE;
4785 if (appData.debugMode) {
4786 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4788 if(shuffleOpenings) {
4789 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4790 startedFromSetupPosition = TRUE;
4793 if(startedFromPositionFile) {
4794 /* [HGM] loadPos: use PositionFile for every new game */
4795 CopyBoard(initialPosition, filePosition);
4796 for(i=0; i<nrCastlingRights; i++)
4797 castlingRights[0][i] = initialRights[i] = fileRights[i];
4798 startedFromSetupPosition = TRUE;
4801 CopyBoard(boards[0], initialPosition);
4802 if(oldx != gameInfo.boardWidth ||
4803 oldy != gameInfo.boardHeight ||
4804 oldh != gameInfo.holdingsWidth
4806 || oldv == VariantGothic || // For licensing popups
4807 gameInfo.variant == VariantGothic
4810 || oldv == VariantFalcon ||
4811 gameInfo.variant == VariantFalcon
4815 InitDrawingSizes(-2 ,0);
4819 DrawPosition(TRUE, boards[currentMove]);
4824 SendBoard(cps, moveNum)
4825 ChessProgramState *cps;
4828 char message[MSG_SIZ];
4830 if (cps->useSetboard) {
4831 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4832 sprintf(message, "setboard %s\n", fen);
4833 SendToProgram(message, cps);
4839 /* Kludge to set black to move, avoiding the troublesome and now
4840 * deprecated "black" command.
4842 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4844 SendToProgram("edit\n", cps);
4845 SendToProgram("#\n", cps);
4846 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4847 bp = &boards[moveNum][i][BOARD_LEFT];
4848 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4849 if ((int) *bp < (int) BlackPawn) {
4850 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4852 if(message[0] == '+' || message[0] == '~') {
4853 sprintf(message, "%c%c%c+\n",
4854 PieceToChar((ChessSquare)(DEMOTED *bp)),
4857 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858 message[1] = BOARD_RGHT - 1 - j + '1';
4859 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4861 SendToProgram(message, cps);
4866 SendToProgram("c\n", cps);
4867 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868 bp = &boards[moveNum][i][BOARD_LEFT];
4869 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870 if (((int) *bp != (int) EmptySquare)
4871 && ((int) *bp >= (int) BlackPawn)) {
4872 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4874 if(message[0] == '+' || message[0] == '~') {
4875 sprintf(message, "%c%c%c+\n",
4876 PieceToChar((ChessSquare)(DEMOTED *bp)),
4879 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880 message[1] = BOARD_RGHT - 1 - j + '1';
4881 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4883 SendToProgram(message, cps);
4888 SendToProgram(".\n", cps);
4890 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4894 IsPromotion(fromX, fromY, toX, toY)
4895 int fromX, fromY, toX, toY;
4897 /* [HGM] add Shogi promotions */
4898 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4901 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4902 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4903 /* [HGM] Note to self: line above also weeds out drops */
4904 piece = boards[currentMove][fromY][fromX];
4905 if(gameInfo.variant == VariantShogi) {
4906 promotionZoneSize = 3;
4907 highestPromotingPiece = (int)WhiteKing;
4908 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4909 and if in normal chess we then allow promotion to King, why not
4910 allow promotion of other piece in Shogi? */
4912 if((int)piece >= BlackPawn) {
4913 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4915 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4917 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4918 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4920 return ( (int)piece <= highestPromotingPiece );
4924 InPalace(row, column)
4926 { /* [HGM] for Xiangqi */
4927 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4928 column < (BOARD_WIDTH + 4)/2 &&
4929 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4934 PieceForSquare (x, y)
4938 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4941 return boards[currentMove][y][x];
4945 OKToStartUserMove(x, y)
4948 ChessSquare from_piece;
4951 if (matchMode) return FALSE;
4952 if (gameMode == EditPosition) return TRUE;
4954 if (x >= 0 && y >= 0)
4955 from_piece = boards[currentMove][y][x];
4957 from_piece = EmptySquare;
4959 if (from_piece == EmptySquare) return FALSE;
4961 white_piece = (int)from_piece >= (int)WhitePawn &&
4962 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4965 case PlayFromGameFile:
4967 case TwoMachinesPlay:
4975 case MachinePlaysWhite:
4976 case IcsPlayingBlack:
4977 if (appData.zippyPlay) return FALSE;
4979 DisplayMoveError(_("You are playing Black"));
4984 case MachinePlaysBlack:
4985 case IcsPlayingWhite:
4986 if (appData.zippyPlay) return FALSE;
4988 DisplayMoveError(_("You are playing White"));
4994 if (!white_piece && WhiteOnMove(currentMove)) {
4995 DisplayMoveError(_("It is White's turn"));
4998 if (white_piece && !WhiteOnMove(currentMove)) {
4999 DisplayMoveError(_("It is Black's turn"));
5002 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5003 /* Editing correspondence game history */
5004 /* Could disallow this or prompt for confirmation */
5007 if (currentMove < forwardMostMove) {
5008 /* Discarding moves */
5009 /* Could prompt for confirmation here,
5010 but I don't think that's such a good idea */
5011 forwardMostMove = currentMove;
5015 case BeginningOfGame:
5016 if (appData.icsActive) return FALSE;
5017 if (!appData.noChessProgram) {
5019 DisplayMoveError(_("You are playing White"));
5026 if (!white_piece && WhiteOnMove(currentMove)) {
5027 DisplayMoveError(_("It is White's turn"));
5030 if (white_piece && !WhiteOnMove(currentMove)) {
5031 DisplayMoveError(_("It is Black's turn"));
5040 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5041 && gameMode != AnalyzeFile && gameMode != Training) {
5042 DisplayMoveError(_("Displayed position is not current"));
5048 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5049 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5050 int lastLoadGameUseList = FALSE;
5051 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5052 ChessMove lastLoadGameStart = (ChessMove) 0;
5056 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5057 int fromX, fromY, toX, toY;
5061 ChessSquare pdown, pup;
5063 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064 if ((fromX == toX) && (fromY == toY)) {
5065 return ImpossibleMove;
5068 /* [HGM] suppress all moves into holdings area and guard band */
5069 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5070 return ImpossibleMove;
5072 /* [HGM] <sameColor> moved to here from winboard.c */
5073 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5074 pdown = boards[currentMove][fromY][fromX];
5075 pup = boards[currentMove][toY][toX];
5076 if ( gameMode != EditPosition &&
5077 (WhitePawn <= pdown && pdown < BlackPawn &&
5078 WhitePawn <= pup && pup < BlackPawn ||
5079 BlackPawn <= pdown && pdown < EmptySquare &&
5080 BlackPawn <= pup && pup < EmptySquare
5081 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5082 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5083 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5085 return ImpossibleMove;
5087 /* Check if the user is playing in turn. This is complicated because we
5088 let the user "pick up" a piece before it is his turn. So the piece he
5089 tried to pick up may have been captured by the time he puts it down!
5090 Therefore we use the color the user is supposed to be playing in this
5091 test, not the color of the piece that is currently on the starting
5092 square---except in EditGame mode, where the user is playing both
5093 sides; fortunately there the capture race can't happen. (It can
5094 now happen in IcsExamining mode, but that's just too bad. The user
5095 will get a somewhat confusing message in that case.)
5099 case PlayFromGameFile:
5101 case TwoMachinesPlay:
5105 /* We switched into a game mode where moves are not accepted,
5106 perhaps while the mouse button was down. */
5107 return ImpossibleMove;
5109 case MachinePlaysWhite:
5110 /* User is moving for Black */
5111 if (WhiteOnMove(currentMove)) {
5112 DisplayMoveError(_("It is White's turn"));
5113 return ImpossibleMove;
5117 case MachinePlaysBlack:
5118 /* User is moving for White */
5119 if (!WhiteOnMove(currentMove)) {
5120 DisplayMoveError(_("It is Black's turn"));
5121 return ImpossibleMove;
5127 case BeginningOfGame:
5130 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5131 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5132 /* User is moving for Black */
5133 if (WhiteOnMove(currentMove)) {
5134 DisplayMoveError(_("It is White's turn"));
5135 return ImpossibleMove;
5138 /* User is moving for White */
5139 if (!WhiteOnMove(currentMove)) {
5140 DisplayMoveError(_("It is Black's turn"));
5141 return ImpossibleMove;
5146 case IcsPlayingBlack:
5147 /* User is moving for Black */
5148 if (WhiteOnMove(currentMove)) {
5149 if (!appData.premove) {
5150 DisplayMoveError(_("It is White's turn"));
5151 } else if (toX >= 0 && toY >= 0) {
5154 premoveFromX = fromX;
5155 premoveFromY = fromY;
5156 premovePromoChar = promoChar;
5158 if (appData.debugMode)
5159 fprintf(debugFP, "Got premove: fromX %d,"
5160 "fromY %d, toX %d, toY %d\n",
5161 fromX, fromY, toX, toY);
5162 if(!WhiteOnMove(currentMove) && gotPremove == 1) {
5163 // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5164 if (appData.debugMode)
5165 fprintf(debugFP, "Execute as normal move\n");
5166 gotPremove = 0; break;
5169 return ImpossibleMove;
5173 case IcsPlayingWhite:
5174 /* User is moving for White */
5175 if (!WhiteOnMove(currentMove)) {
5176 if (!appData.premove) {
5177 DisplayMoveError(_("It is Black's turn"));
5178 } else if (toX >= 0 && toY >= 0) {
5181 premoveFromX = fromX;
5182 premoveFromY = fromY;
5183 premovePromoChar = promoChar;
5185 if (appData.debugMode)
5186 fprintf(debugFP, "Got premove: fromX %d,"
5187 "fromY %d, toX %d, toY %d\n",
5188 fromX, fromY, toX, toY);
5189 if(WhiteOnMove(currentMove) && gotPremove == 1) {
5190 // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5191 if (appData.debugMode)
5192 fprintf(debugFP, "Execute as normal move\n");
5193 gotPremove = 0; break;
5196 return ImpossibleMove;
5204 /* EditPosition, empty square, or different color piece;
5205 click-click move is possible */
5206 if (toX == -2 || toY == -2) {
5207 boards[0][fromY][fromX] = EmptySquare;
5208 return AmbiguousMove;
5209 } else if (toX >= 0 && toY >= 0) {
5210 boards[0][toY][toX] = boards[0][fromY][fromX];
5211 boards[0][fromY][fromX] = EmptySquare;
5212 return AmbiguousMove;
5214 return ImpossibleMove;
5217 /* [HGM] If move started in holdings, it means a drop */
5218 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5219 if( pup != EmptySquare ) return ImpossibleMove;
5220 if(appData.testLegality) {
5221 /* it would be more logical if LegalityTest() also figured out
5222 * which drops are legal. For now we forbid pawns on back rank.
5223 * Shogi is on its own here...
5225 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5226 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5227 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5229 return WhiteDrop; /* Not needed to specify white or black yet */
5232 userOfferedDraw = FALSE;
5234 /* [HGM] always test for legality, to get promotion info */
5235 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5236 epStatus[currentMove], castlingRights[currentMove],
5237 fromY, fromX, toY, toX, promoChar);
5239 /* [HGM] but possibly ignore an IllegalMove result */
5240 if (appData.testLegality) {
5241 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5242 DisplayMoveError(_("Illegal move"));
5243 return ImpossibleMove;
5246 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5248 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5249 function is made into one that returns an OK move type if FinishMove
5250 should be called. This to give the calling driver routine the
5251 opportunity to finish the userMove input with a promotion popup,
5252 without bothering the user with this for invalid or illegal moves */
5254 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5257 /* Common tail of UserMoveEvent and DropMenuEvent */
5259 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5261 int fromX, fromY, toX, toY;
5262 /*char*/int promoChar;
5266 if(appData.debugMode)
5267 fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5269 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5271 // [HGM] superchess: suppress promotions to non-available piece
5272 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5273 if(WhiteOnMove(currentMove))
5275 if(!boards[currentMove][k][BOARD_WIDTH-2])
5280 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5285 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5286 move type in caller when we know the move is a legal promotion */
5287 if(moveType == NormalMove && promoChar)
5288 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5290 if(appData.debugMode)
5291 fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5293 /* [HGM] convert drag-and-drop piece drops to standard form */
5294 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5296 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5297 if(appData.debugMode)
5298 fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5299 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5300 // fromX = boards[currentMove][fromY][fromX];
5301 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5303 fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5305 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5307 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare)
5313 /* [HGM] <popupFix> The following if has been moved here from
5314 UserMoveEvent(). Because it seemed to belon here (why not allow
5315 piece drops in training games?), and because it can only be
5316 performed after it is known to what we promote. */
5317 if (gameMode == Training)
5319 /* compare the move played on the board to the next move in the
5320 * game. If they match, display the move and the opponent's response.
5321 * If they don't match, display an error message.
5324 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5325 CopyBoard(testBoard, boards[currentMove]);
5326 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5328 if (CompareBoards(testBoard, boards[currentMove+1]))
5330 ForwardInner(currentMove+1);
5332 /* Autoplay the opponent's response.
5333 * if appData.animate was TRUE when Training mode was entered,
5334 * the response will be animated.
5336 saveAnimate = appData.animate;
5337 appData.animate = animateTraining;
5338 ForwardInner(currentMove+1);
5339 appData.animate = saveAnimate;
5341 /* check for the end of the game */
5342 if (currentMove >= forwardMostMove)
5344 gameMode = PlayFromGameFile;
5346 SetTrainingModeOff();
5347 DisplayInformation(_("End of game"));
5352 DisplayError(_("Incorrect move"), 0);
5357 /* Ok, now we know that the move is good, so we can kill
5358 the previous line in Analysis Mode */
5359 if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5361 forwardMostMove = currentMove;
5364 /* If we need the chess program but it's dead, restart it */
5365 ResurrectChessProgram();
5367 /* A user move restarts a paused game*/
5371 thinkOutput[0] = NULLCHAR;
5373 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5375 if (gameMode == BeginningOfGame)
5377 if (appData.noChessProgram)
5379 gameMode = EditGame;
5385 gameMode = MachinePlaysBlack;
5388 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5392 sprintf(buf, "name %s\n", gameInfo.white);
5393 SendToProgram(buf, &first);
5399 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5401 /* Relay move to ICS or chess engine */
5402 if (appData.icsActive)
5404 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5405 gameMode == IcsExamining)
5407 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5413 if (first.sendTime && (gameMode == BeginningOfGame ||
5414 gameMode == MachinePlaysWhite ||
5415 gameMode == MachinePlaysBlack))
5417 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5419 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5421 // [HGM] book: if program might be playing, let it use book
5422 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5423 first.maybeThinking = TRUE;
5426 SendMoveToProgram(forwardMostMove-1, &first);
5427 if (currentMove == cmailOldMove + 1)
5429 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5433 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5438 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5439 EP_UNKNOWN, castlingRights[currentMove]) )
5446 if (WhiteOnMove(currentMove))
5448 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5452 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5456 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5461 case MachinePlaysBlack:
5462 case MachinePlaysWhite:
5463 /* disable certain menu options while machine is thinking */
5464 SetMachineThinkingEnables();
5472 { // [HGM] book: simulate book reply
5473 static char bookMove[MSG_SIZ]; // a bit generous?
5475 programStats.nodes = programStats.depth = programStats.time =
5476 programStats.score = programStats.got_only_move = 0;
5477 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5479 strcpy(bookMove, "move ");
5480 strcat(bookMove, bookHit);
5481 HandleMachineMove(bookMove, &first);
5488 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5489 int fromX, fromY, toX, toY;
5492 /* [HGM] This routine was added to allow calling of its two logical
5493 parts from other modules in the old way. Before, UserMoveEvent()
5494 automatically called FinishMove() if the move was OK, and returned
5495 otherwise. I separated the two, in order to make it possible to
5496 slip a promotion popup in between. But that it always needs two
5497 calls, to the first part, (now called UserMoveTest() ), and to
5498 FinishMove if the first part succeeded. Calls that do not need
5499 to do anything in between, can call this routine the old way.
5501 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5502 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5503 if(moveType == AmbiguousMove)
5504 DrawPosition(FALSE, boards[currentMove]);
5505 else if(moveType != ImpossibleMove)
5506 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5509 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5511 // char * hint = lastHint;
5512 FrontEndProgramStats stats;
5514 stats.which = cps == &first ? 0 : 1;
5515 stats.depth = cpstats->depth;
5516 stats.nodes = cpstats->nodes;
5517 stats.score = cpstats->score;
5518 stats.time = cpstats->time;
5519 stats.pv = cpstats->movelist;
5520 stats.hint = lastHint;
5521 stats.an_move_index = 0;
5522 stats.an_move_count = 0;
5524 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5525 stats.hint = cpstats->move_name;
5526 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5527 stats.an_move_count = cpstats->nr_moves;
5530 SetProgramStats( &stats );
5533 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5534 { // [HGM] book: this routine intercepts moves to simulate book replies
5535 char *bookHit = NULL;
5537 //first determine if the incoming move brings opponent into his book
5538 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5539 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5540 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5541 if(bookHit != NULL && !cps->bookSuspend) {
5542 // make sure opponent is not going to reply after receiving move to book position
5543 SendToProgram("force\n", cps);
5544 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5546 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5547 // now arrange restart after book miss
5549 // after a book hit we never send 'go', and the code after the call to this routine
5550 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5552 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5553 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5554 SendToProgram(buf, cps);
5555 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5556 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5557 SendToProgram("go\n", cps);
5558 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5559 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5560 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5561 SendToProgram("go\n", cps);
5562 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5564 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5568 ChessProgramState *savedState;
5569 void DeferredBookMove(void)
5571 if(savedState->lastPing != savedState->lastPong)
5572 ScheduleDelayedEvent(DeferredBookMove, 10);
5574 HandleMachineMove(savedMessage, savedState);
5578 HandleMachineMove(message, cps)
5580 ChessProgramState *cps;
5582 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5583 char realname[MSG_SIZ];
5584 int fromX, fromY, toX, toY;
5591 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5593 * Kludge to ignore BEL characters
5595 while (*message == '\007') message++;
5598 * [HGM] engine debug message: ignore lines starting with '#' character
5600 if(cps->debug && *message == '#') return;
5603 * Look for book output
5605 if (cps == &first && bookRequested) {
5606 if (message[0] == '\t' || message[0] == ' ') {
5607 /* Part of the book output is here; append it */
5608 strcat(bookOutput, message);
5609 strcat(bookOutput, " \n");
5611 } else if (bookOutput[0] != NULLCHAR) {
5612 /* All of book output has arrived; display it */
5613 char *p = bookOutput;
5614 while (*p != NULLCHAR) {
5615 if (*p == '\t') *p = ' ';
5618 DisplayInformation(bookOutput);
5619 bookRequested = FALSE;
5620 /* Fall through to parse the current output */
5625 * Look for machine move.
5627 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5628 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5630 /* This method is only useful on engines that support ping */
5631 if (cps->lastPing != cps->lastPong) {
5632 if (gameMode == BeginningOfGame) {
5633 /* Extra move from before last new; ignore */
5634 if (appData.debugMode) {
5635 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5638 if (appData.debugMode) {
5639 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5640 cps->which, gameMode);
5643 SendToProgram("undo\n", cps);
5649 case BeginningOfGame:
5650 /* Extra move from before last reset; ignore */
5651 if (appData.debugMode) {
5652 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5659 /* Extra move after we tried to stop. The mode test is
5660 not a reliable way of detecting this problem, but it's
5661 the best we can do on engines that don't support ping.
5663 if (appData.debugMode) {
5664 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5665 cps->which, gameMode);
5667 SendToProgram("undo\n", cps);
5670 case MachinePlaysWhite:
5671 case IcsPlayingWhite:
5672 machineWhite = TRUE;
5675 case MachinePlaysBlack:
5676 case IcsPlayingBlack:
5677 machineWhite = FALSE;
5680 case TwoMachinesPlay:
5681 machineWhite = (cps->twoMachinesColor[0] == 'w');
5684 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5685 if (appData.debugMode) {
5687 "Ignoring move out of turn by %s, gameMode %d"
5688 ", forwardMost %d\n",
5689 cps->which, gameMode, forwardMostMove);
5694 if (appData.debugMode) { int f = forwardMostMove;
5695 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5696 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5698 if(cps->alphaRank) AlphaRank(machineMove, 4);
5699 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5700 &fromX, &fromY, &toX, &toY, &promoChar)) {
5701 /* Machine move could not be parsed; ignore it. */
5702 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5703 machineMove, cps->which);
5704 DisplayError(buf1, 0);
5705 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5706 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5707 if (gameMode == TwoMachinesPlay) {
5708 GameEnds(machineWhite ? BlackWins : WhiteWins,
5714 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5715 /* So we have to redo legality test with true e.p. status here, */
5716 /* to make sure an illegal e.p. capture does not slip through, */
5717 /* to cause a forfeit on a justified illegal-move complaint */
5718 /* of the opponent. */
5719 if( gameMode==TwoMachinesPlay && appData.testLegality
5720 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5723 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5724 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5725 fromY, fromX, toY, toX, promoChar);
5726 if (appData.debugMode) {
5728 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5729 castlingRights[forwardMostMove][i], castlingRank[i]);
5730 fprintf(debugFP, "castling rights\n");
5732 if(moveType == IllegalMove) {
5733 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5734 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5735 GameEnds(machineWhite ? BlackWins : WhiteWins,
5738 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5739 /* [HGM] Kludge to handle engines that send FRC-style castling
5740 when they shouldn't (like TSCP-Gothic) */
5742 case WhiteASideCastleFR:
5743 case BlackASideCastleFR:
5745 currentMoveString[2]++;
5747 case WhiteHSideCastleFR:
5748 case BlackHSideCastleFR:
5750 currentMoveString[2]--;
5752 default: ; // nothing to do, but suppresses warning of pedantic compilers
5755 hintRequested = FALSE;
5756 lastHint[0] = NULLCHAR;
5757 bookRequested = FALSE;
5758 /* Program may be pondering now */
5759 cps->maybeThinking = TRUE;
5760 if (cps->sendTime == 2) cps->sendTime = 1;
5761 if (cps->offeredDraw) cps->offeredDraw--;
5764 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5766 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5768 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5769 char buf[3*MSG_SIZ];
5771 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5772 programStats.score / 100.,
5774 programStats.time / 100.,
5775 (unsigned int)programStats.nodes,
5776 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5777 programStats.movelist);
5779 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5783 /* currentMoveString is set as a side-effect of ParseOneMove */
5784 strcpy(machineMove, currentMoveString);
5785 strcat(machineMove, "\n");
5786 strcpy(moveList[forwardMostMove], machineMove);
5788 /* [AS] Save move info and clear stats for next move */
5789 pvInfoList[ forwardMostMove ].score = programStats.score;
5790 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5791 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5792 ClearProgramStats();
5793 thinkOutput[0] = NULLCHAR;
5794 hiddenThinkOutputState = 0;
5796 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5798 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5799 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5802 while( count < adjudicateLossPlies ) {
5803 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5806 score = -score; /* Flip score for winning side */
5809 if( score > adjudicateLossThreshold ) {
5816 if( count >= adjudicateLossPlies ) {
5817 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5819 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5820 "Xboard adjudication",
5827 if( gameMode == TwoMachinesPlay ) {
5828 // [HGM] some adjudications useful with buggy engines
5829 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5830 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5833 if( appData.testLegality )
5834 { /* [HGM] Some more adjudications for obstinate engines */
5835 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5836 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5837 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5838 static int moveCount = 6;
5840 char *reason = NULL;
5842 /* Count what is on board. */
5843 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5844 { ChessSquare p = boards[forwardMostMove][i][j];
5848 { /* count B,N,R and other of each side */
5851 NrK++; break; // [HGM] atomic: count Kings
5855 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5856 bishopsColor |= 1 << ((i^j)&1);
5861 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5862 bishopsColor |= 1 << ((i^j)&1);
5877 PawnAdvance += m; NrPawns++;
5879 NrPieces += (p != EmptySquare);
5880 NrW += ((int)p < (int)BlackPawn);
5881 if(gameInfo.variant == VariantXiangqi &&
5882 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5883 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5884 NrW -= ((int)p < (int)BlackPawn);
5888 /* Some material-based adjudications that have to be made before stalemate test */
5889 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5890 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5891 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5892 if(appData.checkMates) {
5893 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5894 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5895 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5896 "Xboard adjudication: King destroyed", GE_XBOARD );
5901 /* Bare King in Shatranj (loses) or Losers (wins) */
5902 if( NrW == 1 || NrPieces - NrW == 1) {
5903 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5904 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5905 if(appData.checkMates) {
5906 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5907 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5908 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5909 "Xboard adjudication: Bare king", GE_XBOARD );
5913 if( gameInfo.variant == VariantShatranj && --bare < 0)
5915 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5916 if(appData.checkMates) {
5917 /* but only adjudicate if adjudication enabled */
5918 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5919 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5920 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5921 "Xboard adjudication: Bare king", GE_XBOARD );
5928 // don't wait for engine to announce game end if we can judge ourselves
5929 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5930 castlingRights[forwardMostMove]) ) {
5932 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5933 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5934 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5935 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5938 reason = "Xboard adjudication: 3rd check";
5939 epStatus[forwardMostMove] = EP_CHECKMATE;
5949 reason = "Xboard adjudication: Stalemate";
5950 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5951 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5952 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5953 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5954 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5955 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5956 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5957 EP_CHECKMATE : EP_WINS);
5958 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5959 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5963 reason = "Xboard adjudication: Checkmate";
5964 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5968 switch(i = epStatus[forwardMostMove]) {
5970 result = GameIsDrawn; break;
5972 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5974 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5976 result = (ChessMove) 0;
5978 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5979 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5980 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5981 GameEnds( result, reason, GE_XBOARD );
5985 /* Next absolutely insufficient mating material. */
5986 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5987 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5988 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5989 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5990 { /* KBK, KNK, KK of KBKB with like Bishops */
5992 /* always flag draws, for judging claims */
5993 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5995 if(appData.materialDraws) {
5996 /* but only adjudicate them if adjudication enabled */
5997 SendToProgram("force\n", cps->other); // suppress reply
5998 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5999 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6000 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6005 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6007 ( NrWR == 1 && NrBR == 1 /* KRKR */
6008 || NrWQ==1 && NrBQ==1 /* KQKQ */
6009 || NrWN==2 || NrBN==2 /* KNNK */
6010 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6012 if(--moveCount < 0 && appData.trivialDraws)
6013 { /* if the first 3 moves do not show a tactical win, declare draw */
6014 SendToProgram("force\n", cps->other); // suppress reply
6015 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6016 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6017 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6020 } else moveCount = 6;
6024 if (appData.debugMode) { int i;
6025 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6026 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6027 appData.drawRepeats);
6028 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6029 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6033 /* Check for rep-draws */
6035 for(k = forwardMostMove-2;
6036 k>=backwardMostMove && k>=forwardMostMove-100 &&
6037 epStatus[k] < EP_UNKNOWN &&
6038 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6042 if (appData.debugMode) {
6043 fprintf(debugFP, " loop\n");
6046 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6048 if (appData.debugMode) {
6049 fprintf(debugFP, "match\n");
6052 /* compare castling rights */
6053 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6054 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6055 rights++; /* King lost rights, while rook still had them */
6056 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6057 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6058 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6059 rights++; /* but at least one rook lost them */
6061 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6062 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6064 if( castlingRights[forwardMostMove][5] >= 0 ) {
6065 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6066 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6070 if (appData.debugMode) {
6071 for(i=0; i<nrCastlingRights; i++)
6072 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6075 if (appData.debugMode) {
6076 fprintf(debugFP, " %d %d\n", rights, k);
6079 if( rights == 0 && ++count > appData.drawRepeats-2
6080 && appData.drawRepeats > 1) {
6081 /* adjudicate after user-specified nr of repeats */
6082 SendToProgram("force\n", cps->other); // suppress reply
6083 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6084 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6085 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6086 // [HGM] xiangqi: check for forbidden perpetuals
6087 int m, ourPerpetual = 1, hisPerpetual = 1;
6088 for(m=forwardMostMove; m>k; m-=2) {
6089 if(MateTest(boards[m], PosFlags(m),
6090 EP_NONE, castlingRights[m]) != MT_CHECK)
6091 ourPerpetual = 0; // the current mover did not always check
6092 if(MateTest(boards[m-1], PosFlags(m-1),
6093 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6094 hisPerpetual = 0; // the opponent did not always check
6096 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6097 ourPerpetual, hisPerpetual);
6098 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6099 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6100 "Xboard adjudication: perpetual checking", GE_XBOARD );
6103 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6104 break; // (or we would have caught him before). Abort repetition-checking loop.
6105 // Now check for perpetual chases
6106 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6107 hisPerpetual = PerpetualChase(k, forwardMostMove);
6108 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6109 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6110 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6111 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6114 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6115 break; // Abort repetition-checking loop.
6117 // if neither of us is checking or chasing all the time, or both are, it is draw
6119 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6122 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6123 epStatus[forwardMostMove] = EP_REP_DRAW;
6127 /* Now we test for 50-move draws. Determine ply count */
6128 count = forwardMostMove;
6129 /* look for last irreversble move */
6130 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6132 /* if we hit starting position, add initial plies */
6133 if( count == backwardMostMove )
6134 count -= initialRulePlies;
6135 count = forwardMostMove - count;
6137 epStatus[forwardMostMove] = EP_RULE_DRAW;
6138 /* this is used to judge if draw claims are legal */
6139 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6140 SendToProgram("force\n", cps->other); // suppress reply
6141 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6142 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6147 /* if draw offer is pending, treat it as a draw claim
6148 * when draw condition present, to allow engines a way to
6149 * claim draws before making their move to avoid a race
6150 * condition occurring after their move
6152 if( cps->other->offeredDraw || cps->offeredDraw ) {
6154 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6155 p = "Draw claim: 50-move rule";
6156 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6157 p = "Draw claim: 3-fold repetition";
6158 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6159 p = "Draw claim: insufficient mating material";
6161 SendToProgram("force\n", cps->other); // suppress reply
6162 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6163 GameEnds( GameIsDrawn, p, GE_XBOARD );
6164 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6170 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6171 SendToProgram("force\n", cps->other); // suppress reply
6172 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6173 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6175 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6182 if (gameMode == TwoMachinesPlay) {
6183 /* [HGM] relaying draw offers moved to after reception of move */
6184 /* and interpreting offer as claim if it brings draw condition */
6185 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6186 SendToProgram("draw\n", cps->other);
6188 if (cps->other->sendTime) {
6189 SendTimeRemaining(cps->other,
6190 cps->other->twoMachinesColor[0] == 'w');
6192 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6193 if (firstMove && !bookHit) {
6195 if (cps->other->useColors) {
6196 SendToProgram(cps->other->twoMachinesColor, cps->other);
6198 SendToProgram("go\n", cps->other);
6200 cps->other->maybeThinking = TRUE;
6203 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6205 if (!pausing && appData.ringBellAfterMoves) {
6210 * Reenable menu items that were disabled while
6211 * machine was thinking
6213 if (gameMode != TwoMachinesPlay)
6214 SetUserThinkingEnables();
6216 // [HGM] book: after book hit opponent has received move and is now in force mode
6217 // force the book reply into it, and then fake that it outputted this move by jumping
6218 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6220 static char bookMove[MSG_SIZ]; // a bit generous?
6222 strcpy(bookMove, "move ");
6223 strcat(bookMove, bookHit);
6226 programStats.nodes = programStats.depth = programStats.time =
6227 programStats.score = programStats.got_only_move = 0;
6228 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6230 if(cps->lastPing != cps->lastPong) {
6231 savedMessage = message; // args for deferred call
6233 ScheduleDelayedEvent(DeferredBookMove, 10);
6242 /* Set special modes for chess engines. Later something general
6243 * could be added here; for now there is just one kludge feature,
6244 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6245 * when "xboard" is given as an interactive command.
6247 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6248 cps->useSigint = FALSE;
6249 cps->useSigterm = FALSE;
6251 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6252 ParseFeatures(message+8, cps);
6253 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6256 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6257 * want this, I was asked to put it in, and obliged.
6259 if (!strncmp(message, "setboard ", 9)) {
6260 Board initial_position; int i;
6262 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6264 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6265 DisplayError(_("Bad FEN received from engine"), 0);
6268 Reset(FALSE, FALSE);
6269 CopyBoard(boards[0], initial_position);
6270 initialRulePlies = FENrulePlies;
6271 epStatus[0] = FENepStatus;
6272 for( i=0; i<nrCastlingRights; i++ )
6273 castlingRights[0][i] = FENcastlingRights[i];
6274 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6275 else gameMode = MachinePlaysBlack;
6276 DrawPosition(FALSE, boards[currentMove]);
6282 * Look for communication commands
6284 if (!strncmp(message, "telluser ", 9)) {
6285 DisplayNote(message + 9);
6288 if (!strncmp(message, "tellusererror ", 14)) {
6289 DisplayError(message + 14, 0);
6292 if (!strncmp(message, "tellopponent ", 13)) {
6293 if (appData.icsActive) {
6295 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6299 DisplayNote(message + 13);
6303 if (!strncmp(message, "tellothers ", 11)) {
6304 if (appData.icsActive) {
6306 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6312 if (!strncmp(message, "tellall ", 8)) {
6313 if (appData.icsActive) {
6315 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6319 DisplayNote(message + 8);
6323 if (strncmp(message, "warning", 7) == 0) {
6324 /* Undocumented feature, use tellusererror in new code */
6325 DisplayError(message, 0);
6328 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6329 strcpy(realname, cps->tidy);
6330 strcat(realname, " query");
6331 AskQuestion(realname, buf2, buf1, cps->pr);
6334 /* Commands from the engine directly to ICS. We don't allow these to be
6335 * sent until we are logged on. Crafty kibitzes have been known to
6336 * interfere with the login process.
6339 if (!strncmp(message, "tellics ", 8)) {
6340 SendToICS(message + 8);
6344 if (!strncmp(message, "tellicsnoalias ", 15)) {
6345 SendToICS(ics_prefix);
6346 SendToICS(message + 15);
6350 /* The following are for backward compatibility only */
6351 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6352 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6353 SendToICS(ics_prefix);
6359 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6363 * If the move is illegal, cancel it and redraw the board.
6364 * Also deal with other error cases. Matching is rather loose
6365 * here to accommodate engines written before the spec.
6367 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6368 strncmp(message, "Error", 5) == 0) {
6369 if (StrStr(message, "name") ||
6370 StrStr(message, "rating") || StrStr(message, "?") ||
6371 StrStr(message, "result") || StrStr(message, "board") ||
6372 StrStr(message, "bk") || StrStr(message, "computer") ||
6373 StrStr(message, "variant") || StrStr(message, "hint") ||
6374 StrStr(message, "random") || StrStr(message, "depth") ||
6375 StrStr(message, "accepted")) {
6378 if (StrStr(message, "protover")) {
6379 /* Program is responding to input, so it's apparently done
6380 initializing, and this error message indicates it is
6381 protocol version 1. So we don't need to wait any longer
6382 for it to initialize and send feature commands. */
6383 FeatureDone(cps, 1);
6384 cps->protocolVersion = 1;
6387 cps->maybeThinking = FALSE;
6389 if (StrStr(message, "draw")) {
6390 /* Program doesn't have "draw" command */
6391 cps->sendDrawOffers = 0;
6394 if (cps->sendTime != 1 &&
6395 (StrStr(message, "time") || StrStr(message, "otim"))) {
6396 /* Program apparently doesn't have "time" or "otim" command */
6400 if (StrStr(message, "analyze")) {
6401 cps->analysisSupport = FALSE;
6402 cps->analyzing = FALSE;
6404 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6405 DisplayError(buf2, 0);
6408 if (StrStr(message, "(no matching move)st")) {
6409 /* Special kludge for GNU Chess 4 only */
6410 cps->stKludge = TRUE;
6411 SendTimeControl(cps, movesPerSession, timeControl,
6412 timeIncrement, appData.searchDepth,
6416 if (StrStr(message, "(no matching move)sd")) {
6417 /* Special kludge for GNU Chess 4 only */
6418 cps->sdKludge = TRUE;
6419 SendTimeControl(cps, movesPerSession, timeControl,
6420 timeIncrement, appData.searchDepth,
6424 if (!StrStr(message, "llegal")) {
6427 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6428 gameMode == IcsIdle) return;
6429 if (forwardMostMove <= backwardMostMove) return;
6431 /* Following removed: it caused a bug where a real illegal move
6432 message in analyze mored would be ignored. */
6433 if (cps == &first && programStats.ok_to_send == 0) {
6434 /* Bogus message from Crafty responding to "." This filtering
6435 can miss some of the bad messages, but fortunately the bug
6436 is fixed in current Crafty versions, so it doesn't matter. */
6440 if (pausing) PauseEvent();
6441 if (gameMode == PlayFromGameFile) {
6442 /* Stop reading this game file */
6443 gameMode = EditGame;
6446 currentMove = --forwardMostMove;
6447 DisplayMove(currentMove-1); /* before DisplayMoveError */
6449 DisplayBothClocks();
6450 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6451 parseList[currentMove], cps->which);
6452 DisplayMoveError(buf1);
6453 DrawPosition(FALSE, boards[currentMove]);
6455 /* [HGM] illegal-move claim should forfeit game when Xboard */
6456 /* only passes fully legal moves */
6457 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6458 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6459 "False illegal-move claim", GE_XBOARD );
6463 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6464 /* Program has a broken "time" command that
6465 outputs a string not ending in newline.
6471 * If chess program startup fails, exit with an error message.
6472 * Attempts to recover here are futile.
6474 if ((StrStr(message, "unknown host") != NULL)
6475 || (StrStr(message, "No remote directory") != NULL)
6476 || (StrStr(message, "not found") != NULL)
6477 || (StrStr(message, "No such file") != NULL)
6478 || (StrStr(message, "can't alloc") != NULL)
6479 || (StrStr(message, "Permission denied") != NULL)) {
6481 cps->maybeThinking = FALSE;
6482 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6483 cps->which, cps->program, cps->host, message);
6484 RemoveInputSource(cps->isr);
6485 DisplayFatalError(buf1, 0, 1);
6490 * Look for hint output
6492 if (sscanf(message, "Hint: %s", buf1) == 1) {
6493 if (cps == &first && hintRequested) {
6494 hintRequested = FALSE;
6495 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6496 &fromX, &fromY, &toX, &toY, &promoChar)) {
6497 (void) CoordsToAlgebraic(boards[forwardMostMove],
6498 PosFlags(forwardMostMove), EP_UNKNOWN,
6499 fromY, fromX, toY, toX, promoChar, buf1);
6500 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6501 DisplayInformation(buf2);
6503 /* Hint move could not be parsed!? */
6504 snprintf(buf2, sizeof(buf2),
6505 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6507 DisplayError(buf2, 0);
6510 strcpy(lastHint, buf1);
6516 * Ignore other messages if game is not in progress
6518 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6519 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6522 * look for win, lose, draw, or draw offer
6524 if (strncmp(message, "1-0", 3) == 0) {
6525 char *p, *q, *r = "";
6526 p = strchr(message, '{');
6534 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6536 } else if (strncmp(message, "0-1", 3) == 0) {
6537 char *p, *q, *r = "";
6538 p = strchr(message, '{');
6546 /* Kludge for Arasan 4.1 bug */
6547 if (strcmp(r, "Black resigns") == 0) {
6548 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6551 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6553 } else if (strncmp(message, "1/2", 3) == 0) {
6554 char *p, *q, *r = "";
6555 p = strchr(message, '{');
6564 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6567 } else if (strncmp(message, "White resign", 12) == 0) {
6568 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6570 } else if (strncmp(message, "Black resign", 12) == 0) {
6571 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6573 } else if (strncmp(message, "White matches", 13) == 0 ||
6574 strncmp(message, "Black matches", 13) == 0 ) {
6575 /* [HGM] ignore GNUShogi noises */
6577 } else if (strncmp(message, "White", 5) == 0 &&
6578 message[5] != '(' &&
6579 StrStr(message, "Black") == NULL) {
6580 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6582 } else if (strncmp(message, "Black", 5) == 0 &&
6583 message[5] != '(') {
6584 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6586 } else if (strcmp(message, "resign") == 0 ||
6587 strcmp(message, "computer resigns") == 0) {
6589 case MachinePlaysBlack:
6590 case IcsPlayingBlack:
6591 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6593 case MachinePlaysWhite:
6594 case IcsPlayingWhite:
6595 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6597 case TwoMachinesPlay:
6598 if (cps->twoMachinesColor[0] == 'w')
6599 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6601 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6608 } else if (strncmp(message, "opponent mates", 14) == 0) {
6610 case MachinePlaysBlack:
6611 case IcsPlayingBlack:
6612 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6614 case MachinePlaysWhite:
6615 case IcsPlayingWhite:
6616 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6618 case TwoMachinesPlay:
6619 if (cps->twoMachinesColor[0] == 'w')
6620 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6622 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6629 } else if (strncmp(message, "computer mates", 14) == 0) {
6631 case MachinePlaysBlack:
6632 case IcsPlayingBlack:
6633 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6635 case MachinePlaysWhite:
6636 case IcsPlayingWhite:
6637 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6639 case TwoMachinesPlay:
6640 if (cps->twoMachinesColor[0] == 'w')
6641 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6643 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6650 } else if (strncmp(message, "checkmate", 9) == 0) {
6651 if (WhiteOnMove(forwardMostMove)) {
6652 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6654 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6657 } else if (strstr(message, "Draw") != NULL ||
6658 strstr(message, "game is a draw") != NULL) {
6659 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6661 } else if (strstr(message, "offer") != NULL &&
6662 strstr(message, "draw") != NULL) {
6664 if (appData.zippyPlay && first.initDone) {
6665 /* Relay offer to ICS */
6666 SendToICS(ics_prefix);
6667 SendToICS("draw\n");
6670 cps->offeredDraw = 2; /* valid until this engine moves twice */
6671 if (gameMode == TwoMachinesPlay) {
6672 if (cps->other->offeredDraw) {
6673 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6674 /* [HGM] in two-machine mode we delay relaying draw offer */
6675 /* until after we also have move, to see if it is really claim */
6679 if (cps->other->sendDrawOffers) {
6680 SendToProgram("draw\n", cps->other);
6684 } else if (gameMode == MachinePlaysWhite ||
6685 gameMode == MachinePlaysBlack) {
6686 if (userOfferedDraw) {
6687 DisplayInformation(_("Machine accepts your draw offer"));
6688 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6690 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6697 * Look for thinking output
6699 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6700 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6702 int plylev, mvleft, mvtot, curscore, time;
6703 char mvname[MOVE_LEN];
6707 int prefixHint = FALSE;
6708 mvname[0] = NULLCHAR;
6711 case MachinePlaysBlack:
6712 case IcsPlayingBlack:
6713 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6715 case MachinePlaysWhite:
6716 case IcsPlayingWhite:
6717 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6722 case IcsObserving: /* [DM] icsEngineAnalyze */
6723 if (!appData.icsEngineAnalyze) ignore = TRUE;
6725 case TwoMachinesPlay:
6726 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6737 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6738 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6740 if (plyext != ' ' && plyext != '\t') {
6744 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6745 if( cps->scoreIsAbsolute &&
6746 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6748 curscore = -curscore;
6752 programStats.depth = plylev;
6753 programStats.nodes = nodes;
6754 programStats.time = time;
6755 programStats.score = curscore;
6756 programStats.got_only_move = 0;
6758 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6761 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6762 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6763 if(WhiteOnMove(forwardMostMove))
6764 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6765 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6768 /* Buffer overflow protection */
6769 if (buf1[0] != NULLCHAR) {
6770 if (strlen(buf1) >= sizeof(programStats.movelist)
6771 && appData.debugMode) {
6773 "PV is too long; using the first %d bytes.\n",
6774 sizeof(programStats.movelist) - 1);
6777 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6779 sprintf(programStats.movelist, " no PV\n");
6782 if (programStats.seen_stat) {
6783 programStats.ok_to_send = 1;
6786 if (strchr(programStats.movelist, '(') != NULL) {
6787 programStats.line_is_book = 1;
6788 programStats.nr_moves = 0;
6789 programStats.moves_left = 0;
6791 programStats.line_is_book = 0;
6794 SendProgramStatsToFrontend( cps, &programStats );
6797 [AS] Protect the thinkOutput buffer from overflow... this
6798 is only useful if buf1 hasn't overflowed first!
6800 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6802 (gameMode == TwoMachinesPlay ?
6803 ToUpper(cps->twoMachinesColor[0]) : ' '),
6804 ((double) curscore) / 100.0,
6805 prefixHint ? lastHint : "",
6806 prefixHint ? " " : "" );
6808 if( buf1[0] != NULLCHAR ) {
6809 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6811 if( strlen(buf1) > max_len ) {
6812 if( appData.debugMode) {
6813 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6815 buf1[max_len+1] = '\0';
6818 strcat( thinkOutput, buf1 );
6821 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6822 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6823 DisplayMove(currentMove - 1);
6828 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6829 /* crafty (9.25+) says "(only move) <move>"
6830 * if there is only 1 legal move
6832 sscanf(p, "(only move) %s", buf1);
6833 sprintf(thinkOutput, "%s (only move)", buf1);
6834 sprintf(programStats.movelist, "%s (only move)", buf1);
6835 programStats.depth = 1;
6836 programStats.nr_moves = 1;
6837 programStats.moves_left = 1;
6838 programStats.nodes = 1;
6839 programStats.time = 1;
6840 programStats.got_only_move = 1;
6842 /* Not really, but we also use this member to
6843 mean "line isn't going to change" (Crafty
6844 isn't searching, so stats won't change) */
6845 programStats.line_is_book = 1;
6847 SendProgramStatsToFrontend( cps, &programStats );
6849 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6850 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6851 DisplayMove(currentMove - 1);
6855 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6856 &time, &nodes, &plylev, &mvleft,
6857 &mvtot, mvname) >= 5) {
6858 /* The stat01: line is from Crafty (9.29+) in response
6859 to the "." command */
6860 programStats.seen_stat = 1;
6861 cps->maybeThinking = TRUE;
6863 if (programStats.got_only_move || !appData.periodicUpdates)
6866 programStats.depth = plylev;
6867 programStats.time = time;
6868 programStats.nodes = nodes;
6869 programStats.moves_left = mvleft;
6870 programStats.nr_moves = mvtot;
6871 strcpy(programStats.move_name, mvname);
6872 programStats.ok_to_send = 1;
6873 programStats.movelist[0] = '\0';
6875 SendProgramStatsToFrontend( cps, &programStats );
6880 } else if (strncmp(message,"++",2) == 0) {
6881 /* Crafty 9.29+ outputs this */
6882 programStats.got_fail = 2;
6885 } else if (strncmp(message,"--",2) == 0) {
6886 /* Crafty 9.29+ outputs this */
6887 programStats.got_fail = 1;
6890 } else if (thinkOutput[0] != NULLCHAR &&
6891 strncmp(message, " ", 4) == 0) {
6892 unsigned message_len;
6895 while (*p && *p == ' ') p++;
6897 message_len = strlen( p );
6899 /* [AS] Avoid buffer overflow */
6900 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6901 strcat(thinkOutput, " ");
6902 strcat(thinkOutput, p);
6905 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6906 strcat(programStats.movelist, " ");
6907 strcat(programStats.movelist, p);
6910 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6911 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6912 DisplayMove(currentMove - 1);
6921 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6922 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6924 ChessProgramStats cpstats;
6926 if (plyext != ' ' && plyext != '\t') {
6930 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6931 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6932 curscore = -curscore;
6935 cpstats.depth = plylev;
6936 cpstats.nodes = nodes;
6937 cpstats.time = time;
6938 cpstats.score = curscore;
6939 cpstats.got_only_move = 0;
6940 cpstats.movelist[0] = '\0';
6942 if (buf1[0] != NULLCHAR) {
6943 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6946 cpstats.ok_to_send = 0;
6947 cpstats.line_is_book = 0;
6948 cpstats.nr_moves = 0;
6949 cpstats.moves_left = 0;
6951 SendProgramStatsToFrontend( cps, &cpstats );
6958 /* Parse a game score from the character string "game", and
6959 record it as the history of the current game. The game
6960 score is NOT assumed to start from the standard position.
6961 The display is not updated in any way.
6964 ParseGameHistory(game)
6968 int fromX, fromY, toX, toY, boardIndex;
6973 if (appData.debugMode)
6974 fprintf(debugFP, "Parsing game history: %s\n", game);
6976 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6977 gameInfo.site = StrSave(appData.icsHost);
6978 gameInfo.date = PGNDate();
6979 gameInfo.round = StrSave("-");
6981 /* Parse out names of players */
6982 while (*game == ' ') game++;
6984 while (*game != ' ') *p++ = *game++;
6986 gameInfo.white = StrSave(buf);
6987 while (*game == ' ') game++;
6989 while (*game != ' ' && *game != '\n') *p++ = *game++;
6991 gameInfo.black = StrSave(buf);
6994 boardIndex = blackPlaysFirst ? 1 : 0;
6997 yyboardindex = boardIndex;
6998 moveType = (ChessMove) yylex();
7000 case IllegalMove: /* maybe suicide chess, etc. */
7001 if (appData.debugMode) {
7002 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7003 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7004 setbuf(debugFP, NULL);
7006 case WhitePromotionChancellor:
7007 case BlackPromotionChancellor:
7008 case WhitePromotionArchbishop:
7009 case BlackPromotionArchbishop:
7010 case WhitePromotionQueen:
7011 case BlackPromotionQueen:
7012 case WhitePromotionRook:
7013 case BlackPromotionRook:
7014 case WhitePromotionBishop:
7015 case BlackPromotionBishop:
7016 case WhitePromotionKnight:
7017 case BlackPromotionKnight:
7018 case WhitePromotionKing:
7019 case BlackPromotionKing:
7021 case WhiteCapturesEnPassant:
7022 case BlackCapturesEnPassant:
7023 case WhiteKingSideCastle:
7024 case WhiteQueenSideCastle:
7025 case BlackKingSideCastle:
7026 case BlackQueenSideCastle:
7027 case WhiteKingSideCastleWild:
7028 case WhiteQueenSideCastleWild:
7029 case BlackKingSideCastleWild:
7030 case BlackQueenSideCastleWild:
7032 case WhiteHSideCastleFR:
7033 case WhiteASideCastleFR:
7034 case BlackHSideCastleFR:
7035 case BlackASideCastleFR:
7037 fromX = currentMoveString[0] - AAA;
7038 fromY = currentMoveString[1] - ONE;
7039 toX = currentMoveString[2] - AAA;
7040 toY = currentMoveString[3] - ONE;
7041 promoChar = currentMoveString[4];
7045 fromX = moveType == WhiteDrop ?
7046 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7047 (int) CharToPiece(ToLower(currentMoveString[0]));
7049 toX = currentMoveString[2] - AAA;
7050 toY = currentMoveString[3] - ONE;
7051 promoChar = NULLCHAR;
7055 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7056 if (appData.debugMode) {
7057 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7058 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7059 setbuf(debugFP, NULL);
7061 DisplayError(buf, 0);
7063 case ImpossibleMove:
7065 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7066 if (appData.debugMode) {
7067 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7068 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7069 setbuf(debugFP, NULL);
7071 DisplayError(buf, 0);
7073 case (ChessMove) 0: /* end of file */
7074 if (boardIndex < backwardMostMove) {
7075 /* Oops, gap. How did that happen? */
7076 DisplayError(_("Gap in move list"), 0);
7079 backwardMostMove = blackPlaysFirst ? 1 : 0;
7080 if (boardIndex > forwardMostMove) {
7081 forwardMostMove = boardIndex;
7085 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7086 strcat(parseList[boardIndex-1], " ");
7087 strcat(parseList[boardIndex-1], yy_text);
7099 case GameUnfinished:
7100 if (gameMode == IcsExamining) {
7101 if (boardIndex < backwardMostMove) {
7102 /* Oops, gap. How did that happen? */
7105 backwardMostMove = blackPlaysFirst ? 1 : 0;
7108 gameInfo.result = moveType;
7109 p = strchr(yy_text, '{');
7110 if (p == NULL) p = strchr(yy_text, '(');
7113 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7115 q = strchr(p, *p == '{' ? '}' : ')');
7116 if (q != NULL) *q = NULLCHAR;
7119 gameInfo.resultDetails = StrSave(p);
7122 if (boardIndex >= forwardMostMove &&
7123 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7124 backwardMostMove = blackPlaysFirst ? 1 : 0;
7127 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7128 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7129 parseList[boardIndex]);
7130 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7131 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7132 /* currentMoveString is set as a side-effect of yylex */
7133 strcpy(moveList[boardIndex], currentMoveString);
7134 strcat(moveList[boardIndex], "\n");
7136 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7137 castlingRights[boardIndex], &epStatus[boardIndex]);
7138 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7139 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7145 if(gameInfo.variant != VariantShogi)
7146 strcat(parseList[boardIndex - 1], "+");
7150 strcat(parseList[boardIndex - 1], "#");
7157 /* Apply a move to the given board */
7159 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7160 int fromX, fromY, toX, toY;
7166 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7168 /* [HGM] compute & store e.p. status and castling rights for new position */
7169 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7172 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7176 if( board[toY][toX] != EmptySquare )
7179 if( board[fromY][fromX] == WhitePawn ) {
7180 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7183 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7184 gameInfo.variant != VariantBerolina || toX < fromX)
7185 *ep = toX | berolina;
7186 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7187 gameInfo.variant != VariantBerolina || toX > fromX)
7191 if( board[fromY][fromX] == BlackPawn ) {
7192 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7194 if( toY-fromY== -2) {
7195 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7196 gameInfo.variant != VariantBerolina || toX < fromX)
7197 *ep = toX | berolina;
7198 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7199 gameInfo.variant != VariantBerolina || toX > fromX)
7204 for(i=0; i<nrCastlingRights; i++) {
7205 if(castling[i] == fromX && castlingRank[i] == fromY ||
7206 castling[i] == toX && castlingRank[i] == toY
7207 ) castling[i] = -1; // revoke for moved or captured piece
7212 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7213 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7214 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7216 if (fromX == toX && fromY == toY) return;
7218 if (fromY == DROP_RANK) {
7220 piece = board[toY][toX] = (ChessSquare) fromX;
7222 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7223 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7224 if(gameInfo.variant == VariantKnightmate)
7225 king += (int) WhiteUnicorn - (int) WhiteKing;
7227 /* Code added by Tord: */
7228 /* FRC castling assumed when king captures friendly rook. */
7229 if (board[fromY][fromX] == WhiteKing &&
7230 board[toY][toX] == WhiteRook) {
7231 board[fromY][fromX] = EmptySquare;
7232 board[toY][toX] = EmptySquare;
7234 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7236 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7238 } else if (board[fromY][fromX] == BlackKing &&
7239 board[toY][toX] == BlackRook) {
7240 board[fromY][fromX] = EmptySquare;
7241 board[toY][toX] = EmptySquare;
7243 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7245 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7247 /* End of code added by Tord */
7249 } else if (board[fromY][fromX] == king
7250 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7251 && toY == fromY && toX > fromX+1) {
7252 board[fromY][fromX] = EmptySquare;
7253 board[toY][toX] = king;
7254 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7255 board[fromY][BOARD_RGHT-1] = EmptySquare;
7256 } else if (board[fromY][fromX] == king
7257 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7258 && toY == fromY && toX < fromX-1) {
7259 board[fromY][fromX] = EmptySquare;
7260 board[toY][toX] = king;
7261 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7262 board[fromY][BOARD_LEFT] = EmptySquare;
7263 } else if (board[fromY][fromX] == WhitePawn
7264 && toY == BOARD_HEIGHT-1
7265 && gameInfo.variant != VariantXiangqi
7267 /* white pawn promotion */
7268 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7269 if (board[toY][toX] == EmptySquare) {
7270 board[toY][toX] = WhiteQueen;
7272 if(gameInfo.variant==VariantBughouse ||
7273 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7274 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7275 board[fromY][fromX] = EmptySquare;
7276 } else if ((fromY == BOARD_HEIGHT-4)
7278 && gameInfo.variant != VariantXiangqi
7279 && gameInfo.variant != VariantBerolina
7280 && (board[fromY][fromX] == WhitePawn)
7281 && (board[toY][toX] == EmptySquare)) {
7282 board[fromY][fromX] = EmptySquare;
7283 board[toY][toX] = WhitePawn;
7284 captured = board[toY - 1][toX];
7285 board[toY - 1][toX] = EmptySquare;
7286 } else if ((fromY == BOARD_HEIGHT-4)
7288 && gameInfo.variant == VariantBerolina
7289 && (board[fromY][fromX] == WhitePawn)
7290 && (board[toY][toX] == EmptySquare)) {
7291 board[fromY][fromX] = EmptySquare;
7292 board[toY][toX] = WhitePawn;
7293 if(oldEP & EP_BEROLIN_A) {
7294 captured = board[fromY][fromX-1];
7295 board[fromY][fromX-1] = EmptySquare;
7296 }else{ captured = board[fromY][fromX+1];
7297 board[fromY][fromX+1] = EmptySquare;
7299 } else if (board[fromY][fromX] == king
7300 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7301 && toY == fromY && toX > fromX+1) {
7302 board[fromY][fromX] = EmptySquare;
7303 board[toY][toX] = king;
7304 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7305 board[fromY][BOARD_RGHT-1] = EmptySquare;
7306 } else if (board[fromY][fromX] == king
7307 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7308 && toY == fromY && toX < fromX-1) {
7309 board[fromY][fromX] = EmptySquare;
7310 board[toY][toX] = king;
7311 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7312 board[fromY][BOARD_LEFT] = EmptySquare;
7313 } else if (fromY == 7 && fromX == 3
7314 && board[fromY][fromX] == BlackKing
7315 && toY == 7 && toX == 5) {
7316 board[fromY][fromX] = EmptySquare;
7317 board[toY][toX] = BlackKing;
7318 board[fromY][7] = EmptySquare;
7319 board[toY][4] = BlackRook;
7320 } else if (fromY == 7 && fromX == 3
7321 && board[fromY][fromX] == BlackKing
7322 && toY == 7 && toX == 1) {
7323 board[fromY][fromX] = EmptySquare;
7324 board[toY][toX] = BlackKing;
7325 board[fromY][0] = EmptySquare;
7326 board[toY][2] = BlackRook;
7327 } else if (board[fromY][fromX] == BlackPawn
7329 && gameInfo.variant != VariantXiangqi
7331 /* black pawn promotion */
7332 board[0][toX] = CharToPiece(ToLower(promoChar));
7333 if (board[0][toX] == EmptySquare) {
7334 board[0][toX] = BlackQueen;
7336 if(gameInfo.variant==VariantBughouse ||
7337 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7338 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7339 board[fromY][fromX] = EmptySquare;
7340 } else if ((fromY == 3)
7342 && gameInfo.variant != VariantXiangqi
7343 && gameInfo.variant != VariantBerolina
7344 && (board[fromY][fromX] == BlackPawn)
7345 && (board[toY][toX] == EmptySquare)) {
7346 board[fromY][fromX] = EmptySquare;
7347 board[toY][toX] = BlackPawn;
7348 captured = board[toY + 1][toX];
7349 board[toY + 1][toX] = EmptySquare;
7350 } else if ((fromY == 3)
7352 && gameInfo.variant == VariantBerolina
7353 && (board[fromY][fromX] == BlackPawn)
7354 && (board[toY][toX] == EmptySquare)) {
7355 board[fromY][fromX] = EmptySquare;
7356 board[toY][toX] = BlackPawn;
7357 if(oldEP & EP_BEROLIN_A) {
7358 captured = board[fromY][fromX-1];
7359 board[fromY][fromX-1] = EmptySquare;
7360 }else{ captured = board[fromY][fromX+1];
7361 board[fromY][fromX+1] = EmptySquare;
7364 board[toY][toX] = board[fromY][fromX];
7365 board[fromY][fromX] = EmptySquare;
7368 /* [HGM] now we promote for Shogi, if needed */
7369 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7370 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7373 if (gameInfo.holdingsWidth != 0) {
7375 /* !!A lot more code needs to be written to support holdings */
7376 /* [HGM] OK, so I have written it. Holdings are stored in the */
7377 /* penultimate board files, so they are automaticlly stored */
7378 /* in the game history. */
7379 if (fromY == DROP_RANK) {
7380 /* Delete from holdings, by decreasing count */
7381 /* and erasing image if necessary */
7383 if(p < (int) BlackPawn) { /* white drop */
7384 p -= (int)WhitePawn;
7385 if(p >= gameInfo.holdingsSize) p = 0;
7386 if(--board[p][BOARD_WIDTH-2] == 0)
7387 board[p][BOARD_WIDTH-1] = EmptySquare;
7388 } else { /* black drop */
7389 p -= (int)BlackPawn;
7390 if(p >= gameInfo.holdingsSize) p = 0;
7391 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7392 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7395 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7396 && gameInfo.variant != VariantBughouse ) {
7397 /* [HGM] holdings: Add to holdings, if holdings exist */
7398 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7399 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7400 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7403 if (p >= (int) BlackPawn) {
7404 p -= (int)BlackPawn;
7405 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7406 /* in Shogi restore piece to its original first */
7407 captured = (ChessSquare) (DEMOTED captured);
7410 p = PieceToNumber((ChessSquare)p);
7411 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7412 board[p][BOARD_WIDTH-2]++;
7413 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7415 p -= (int)WhitePawn;
7416 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7417 captured = (ChessSquare) (DEMOTED captured);
7420 p = PieceToNumber((ChessSquare)p);
7421 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7422 board[BOARD_HEIGHT-1-p][1]++;
7423 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7427 } else if (gameInfo.variant == VariantAtomic) {
7428 if (captured != EmptySquare) {
7430 for (y = toY-1; y <= toY+1; y++) {
7431 for (x = toX-1; x <= toX+1; x++) {
7432 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7433 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7434 board[y][x] = EmptySquare;
7438 board[toY][toX] = EmptySquare;
7441 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7442 /* [HGM] Shogi promotions */
7443 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7446 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7447 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7448 // [HGM] superchess: take promotion piece out of holdings
7449 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7450 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7451 if(!--board[k][BOARD_WIDTH-2])
7452 board[k][BOARD_WIDTH-1] = EmptySquare;
7454 if(!--board[BOARD_HEIGHT-1-k][1])
7455 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7461 /* Updates forwardMostMove */
7463 MakeMove(fromX, fromY, toX, toY, promoChar)
7464 int fromX, fromY, toX, toY;
7467 // forwardMostMove++; // [HGM] bare: moved downstream
7469 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7470 int timeLeft; static int lastLoadFlag=0; int king, piece;
7471 piece = boards[forwardMostMove][fromY][fromX];
7472 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7473 if(gameInfo.variant == VariantKnightmate)
7474 king += (int) WhiteUnicorn - (int) WhiteKing;
7475 if(forwardMostMove == 0) {
7477 fprintf(serverMoves, "%s;", second.tidy);
7478 fprintf(serverMoves, "%s;", first.tidy);
7479 if(!blackPlaysFirst)
7480 fprintf(serverMoves, "%s;", second.tidy);
7481 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7482 lastLoadFlag = loadFlag;
7484 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7485 // print castling suffix
7486 if( toY == fromY && piece == king ) {
7488 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7490 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7493 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7494 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7495 boards[forwardMostMove][toY][toX] == EmptySquare
7497 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7499 if(promoChar != NULLCHAR)
7500 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7502 fprintf(serverMoves, "/%d/%d",
7503 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7504 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7505 else timeLeft = blackTimeRemaining/1000;
7506 fprintf(serverMoves, "/%d", timeLeft);
7508 fflush(serverMoves);
7511 if (forwardMostMove+1 >= MAX_MOVES) {
7512 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7517 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7518 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7519 if (commentList[forwardMostMove+1] != NULL) {
7520 free(commentList[forwardMostMove+1]);
7521 commentList[forwardMostMove+1] = NULL;
7523 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7524 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7525 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7526 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7527 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7528 gameInfo.result = GameUnfinished;
7529 if (gameInfo.resultDetails != NULL) {
7530 free(gameInfo.resultDetails);
7531 gameInfo.resultDetails = NULL;
7533 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7534 moveList[forwardMostMove - 1]);
7535 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7536 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7537 fromY, fromX, toY, toX, promoChar,
7538 parseList[forwardMostMove - 1]);
7539 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7540 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7541 castlingRights[forwardMostMove]) ) {
7547 if(gameInfo.variant != VariantShogi)
7548 strcat(parseList[forwardMostMove - 1], "+");
7552 strcat(parseList[forwardMostMove - 1], "#");
7555 if (appData.debugMode) {
7556 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7561 /* Updates currentMove if not pausing */
7563 ShowMove(fromX, fromY, toX, toY)
7565 int instant = (gameMode == PlayFromGameFile) ?
7566 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7568 if(appData.noGUI) return;
7570 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7574 if (forwardMostMove == currentMove + 1)
7577 // AnimateMove(boards[forwardMostMove - 1],
7578 // fromX, fromY, toX, toY);
7580 if (appData.highlightLastMove)
7582 SetHighlights(fromX, fromY, toX, toY);
7585 currentMove = forwardMostMove;
7588 if (instant) return;
7590 DisplayMove(currentMove - 1);
7591 DrawPosition(FALSE, boards[currentMove]);
7592 DisplayBothClocks();
7593 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7598 void SendEgtPath(ChessProgramState *cps)
7599 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7600 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7602 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7605 char c, *q = name+1, *r, *s;
7607 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7608 while(*p && *p != ',') *q++ = *p++;
7610 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7611 strcmp(name, ",nalimov:") == 0 ) {
7612 // take nalimov path from the menu-changeable option first, if it is defined
7613 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7614 SendToProgram(buf,cps); // send egtbpath command for nalimov
7616 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7617 (s = StrStr(appData.egtFormats, name)) != NULL) {
7618 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7619 s = r = StrStr(s, ":") + 1; // beginning of path info
7620 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7621 c = *r; *r = 0; // temporarily null-terminate path info
7622 *--q = 0; // strip of trailig ':' from name
7623 sprintf(buf, "egtpath %s %s\n", name+1, s);
7625 SendToProgram(buf,cps); // send egtbpath command for this format
7627 if(*p == ',') p++; // read away comma to position for next format name
7632 InitChessProgram(cps, setup)
7633 ChessProgramState *cps;
7634 int setup; /* [HGM] needed to setup FRC opening position */
7636 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7637 if (appData.noChessProgram) return;
7638 hintRequested = FALSE;
7639 bookRequested = FALSE;
7641 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7642 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7643 if(cps->memSize) { /* [HGM] memory */
7644 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7645 SendToProgram(buf, cps);
7647 SendEgtPath(cps); /* [HGM] EGT */
7648 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7649 sprintf(buf, "cores %d\n", appData.smpCores);
7650 SendToProgram(buf, cps);
7653 SendToProgram(cps->initString, cps);
7654 if (gameInfo.variant != VariantNormal &&
7655 gameInfo.variant != VariantLoadable
7656 /* [HGM] also send variant if board size non-standard */
7657 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7659 char *v = VariantName(gameInfo.variant);
7660 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7661 /* [HGM] in protocol 1 we have to assume all variants valid */
7662 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7663 DisplayFatalError(buf, 0, 1);
7667 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7668 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7669 if( gameInfo.variant == VariantXiangqi )
7670 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7671 if( gameInfo.variant == VariantShogi )
7672 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7673 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7674 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7675 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7676 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7677 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7678 if( gameInfo.variant == VariantCourier )
7679 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7680 if( gameInfo.variant == VariantSuper )
7681 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7682 if( gameInfo.variant == VariantGreat )
7683 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7686 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7687 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7688 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7689 if(StrStr(cps->variants, b) == NULL) {
7690 // specific sized variant not known, check if general sizing allowed
7691 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7692 if(StrStr(cps->variants, "boardsize") == NULL) {
7693 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7694 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7695 DisplayFatalError(buf, 0, 1);
7698 /* [HGM] here we really should compare with the maximum supported board size */
7701 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7702 sprintf(buf, "variant %s\n", b);
7703 SendToProgram(buf, cps);
7705 currentlyInitializedVariant = gameInfo.variant;
7707 /* [HGM] send opening position in FRC to first engine */
7709 SendToProgram("force\n", cps);
7711 /* engine is now in force mode! Set flag to wake it up after first move. */
7712 setboardSpoiledMachineBlack = 1;
7716 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7717 SendToProgram(buf, cps);
7719 cps->maybeThinking = FALSE;
7720 cps->offeredDraw = 0;
7721 if (!appData.icsActive) {
7722 SendTimeControl(cps, movesPerSession, timeControl,
7723 timeIncrement, appData.searchDepth,
7726 if (appData.showThinking
7727 // [HGM] thinking: four options require thinking output to be sent
7728 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7730 SendToProgram("post\n", cps);
7732 SendToProgram("hard\n", cps);
7733 if (!appData.ponderNextMove) {
7734 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7735 it without being sure what state we are in first. "hard"
7736 is not a toggle, so that one is OK.
7738 SendToProgram("easy\n", cps);
7741 sprintf(buf, "ping %d\n", ++cps->lastPing);
7742 SendToProgram(buf, cps);
7744 cps->initDone = TRUE;
7749 StartChessProgram(cps)
7750 ChessProgramState *cps;
7755 if (appData.noChessProgram) return;
7756 cps->initDone = FALSE;
7758 if (strcmp(cps->host, "localhost") == 0) {
7759 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7760 } else if (*appData.remoteShell == NULLCHAR) {
7761 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7763 if (*appData.remoteUser == NULLCHAR) {
7764 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7767 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7768 cps->host, appData.remoteUser, cps->program);
7770 err = StartChildProcess(buf, "", &cps->pr);
7774 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7775 DisplayFatalError(buf, err, 1);
7781 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7782 if (cps->protocolVersion > 1) {
7783 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7784 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7785 cps->comboCnt = 0; // and values of combo boxes
7786 SendToProgram(buf, cps);
7788 SendToProgram("xboard\n", cps);
7794 TwoMachinesEventIfReady P((void))
7796 if (first.lastPing != first.lastPong) {
7797 DisplayMessage("", _("Waiting for first chess program"));
7798 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7801 if (second.lastPing != second.lastPong) {
7802 DisplayMessage("", _("Waiting for second chess program"));
7803 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7811 NextMatchGame P((void))
7813 int index; /* [HGM] autoinc: step lod index during match */
7815 if (*appData.loadGameFile != NULLCHAR) {
7816 index = appData.loadGameIndex;
7817 if(index < 0) { // [HGM] autoinc
7818 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7819 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7821 LoadGameFromFile(appData.loadGameFile,
7823 appData.loadGameFile, FALSE);
7824 } else if (*appData.loadPositionFile != NULLCHAR) {
7825 index = appData.loadPositionIndex;
7826 if(index < 0) { // [HGM] autoinc
7827 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7828 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7830 LoadPositionFromFile(appData.loadPositionFile,
7832 appData.loadPositionFile);
7834 TwoMachinesEventIfReady();
7837 void UserAdjudicationEvent( int result )
7839 ChessMove gameResult = GameIsDrawn;
7842 gameResult = WhiteWins;
7844 else if( result < 0 ) {
7845 gameResult = BlackWins;
7848 if( gameMode == TwoMachinesPlay ) {
7849 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7854 // [HGM] save: calculate checksum of game to make games easily identifiable
7855 int StringCheckSum(char *s)
7858 if(s==NULL) return 0;
7859 while(*s) i = i*259 + *s++;
7866 for(i=backwardMostMove; i<forwardMostMove; i++) {
7867 sum += pvInfoList[i].depth;
7868 sum += StringCheckSum(parseList[i]);
7869 sum += StringCheckSum(commentList[i]);
7872 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7873 return sum + StringCheckSum(commentList[i]);
7874 } // end of save patch
7877 GameEnds(result, resultDetails, whosays)
7879 char *resultDetails;
7882 GameMode nextGameMode;
7886 if(endingGame) return; /* [HGM] crash: forbid recursion */
7889 if (appData.debugMode) {
7890 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7891 result, resultDetails ? resultDetails : "(null)", whosays);
7894 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7895 /* If we are playing on ICS, the server decides when the
7896 game is over, but the engine can offer to draw, claim
7900 if (appData.zippyPlay && first.initDone) {
7901 if (result == GameIsDrawn) {
7902 /* In case draw still needs to be claimed */
7903 SendToICS(ics_prefix);
7904 SendToICS("draw\n");
7905 } else if (StrCaseStr(resultDetails, "resign")) {
7906 SendToICS(ics_prefix);
7907 SendToICS("resign\n");
7911 endingGame = 0; /* [HGM] crash */
7915 /* If we're loading the game from a file, stop */
7916 if (whosays == GE_FILE) {
7917 (void) StopLoadGameTimer();
7921 /* Cancel draw offers */
7922 first.offeredDraw = second.offeredDraw = 0;
7924 /* If this is an ICS game, only ICS can really say it's done;
7925 if not, anyone can. */
7926 isIcsGame = (gameMode == IcsPlayingWhite ||
7927 gameMode == IcsPlayingBlack ||
7928 gameMode == IcsObserving ||
7929 gameMode == IcsExamining);
7931 if (!isIcsGame || whosays == GE_ICS) {
7932 /* OK -- not an ICS game, or ICS said it was done */
7934 if (!isIcsGame && !appData.noChessProgram)
7935 SetUserThinkingEnables();
7937 /* [HGM] if a machine claims the game end we verify this claim */
7938 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7939 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7941 ChessMove trueResult = (ChessMove) -1;
7943 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7944 first.twoMachinesColor[0] :
7945 second.twoMachinesColor[0] ;
7947 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7948 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7949 /* [HGM] verify: engine mate claims accepted if they were flagged */
7950 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7952 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7953 /* [HGM] verify: engine mate claims accepted if they were flagged */
7954 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7956 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7957 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7960 // now verify win claims, but not in drop games, as we don't understand those yet
7961 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7962 || gameInfo.variant == VariantGreat) &&
7963 (result == WhiteWins && claimer == 'w' ||
7964 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7965 if (appData.debugMode) {
7966 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7967 result, epStatus[forwardMostMove], forwardMostMove);
7969 if(result != trueResult) {
7970 sprintf(buf, "False win claim: '%s'", resultDetails);
7971 result = claimer == 'w' ? BlackWins : WhiteWins;
7972 resultDetails = buf;
7975 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7976 && (forwardMostMove <= backwardMostMove ||
7977 epStatus[forwardMostMove-1] > EP_DRAWS ||
7978 (claimer=='b')==(forwardMostMove&1))
7980 /* [HGM] verify: draws that were not flagged are false claims */
7981 sprintf(buf, "False draw claim: '%s'", resultDetails);
7982 result = claimer == 'w' ? BlackWins : WhiteWins;
7983 resultDetails = buf;
7985 /* (Claiming a loss is accepted no questions asked!) */
7988 /* [HGM] bare: don't allow bare King to win */
7989 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7990 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7991 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7992 && result != GameIsDrawn)
7993 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7994 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7995 int p = (int)boards[forwardMostMove][i][j] - color;
7996 if(p >= 0 && p <= (int)WhiteKing) k++;
7998 if (appData.debugMode) {
7999 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8000 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8003 result = GameIsDrawn;
8004 sprintf(buf, "%s but bare king", resultDetails);
8005 resultDetails = buf;
8010 if(serverMoves != NULL && !loadFlag) { char c = '=';
8011 if(result==WhiteWins) c = '+';
8012 if(result==BlackWins) c = '-';
8013 if(resultDetails != NULL)
8014 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8016 if (resultDetails != NULL) {
8017 gameInfo.result = result;
8018 gameInfo.resultDetails = StrSave(resultDetails);
8020 /* display last move only if game was not loaded from file */
8021 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8022 DisplayMove(currentMove - 1);
8024 if (forwardMostMove != 0) {
8025 if (gameMode != PlayFromGameFile && gameMode != EditGame
8026 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8028 if (*appData.saveGameFile != NULLCHAR) {
8029 SaveGameToFile(appData.saveGameFile, TRUE);
8030 } else if (appData.autoSaveGames) {
8033 if (*appData.savePositionFile != NULLCHAR) {
8034 SavePositionToFile(appData.savePositionFile);
8039 /* Tell program how game ended in case it is learning */
8040 /* [HGM] Moved this to after saving the PGN, just in case */
8041 /* engine died and we got here through time loss. In that */
8042 /* case we will get a fatal error writing the pipe, which */
8043 /* would otherwise lose us the PGN. */
8044 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8045 /* output during GameEnds should never be fatal anymore */
8046 if (gameMode == MachinePlaysWhite ||
8047 gameMode == MachinePlaysBlack ||
8048 gameMode == TwoMachinesPlay ||
8049 gameMode == IcsPlayingWhite ||
8050 gameMode == IcsPlayingBlack ||
8051 gameMode == BeginningOfGame) {
8053 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8055 if (first.pr != NoProc) {
8056 SendToProgram(buf, &first);
8058 if (second.pr != NoProc &&
8059 gameMode == TwoMachinesPlay) {
8060 SendToProgram(buf, &second);
8065 if (appData.icsActive) {
8066 if (appData.quietPlay &&
8067 (gameMode == IcsPlayingWhite ||
8068 gameMode == IcsPlayingBlack)) {
8069 SendToICS(ics_prefix);
8070 SendToICS("set shout 1\n");
8072 nextGameMode = IcsIdle;
8073 ics_user_moved = FALSE;
8074 /* clean up premove. It's ugly when the game has ended and the
8075 * premove highlights are still on the board.
8079 ClearPremoveHighlights();
8080 DrawPosition(FALSE, boards[currentMove]);
8082 if (whosays == GE_ICS) {
8085 if (gameMode == IcsPlayingWhite)
8087 else if(gameMode == IcsPlayingBlack)
8091 if (gameMode == IcsPlayingBlack)
8093 else if(gameMode == IcsPlayingWhite)
8100 PlayIcsUnfinishedSound();
8103 } else if (gameMode == EditGame ||
8104 gameMode == PlayFromGameFile ||
8105 gameMode == AnalyzeMode ||
8106 gameMode == AnalyzeFile) {
8107 nextGameMode = gameMode;
8109 nextGameMode = EndOfGame;
8114 nextGameMode = gameMode;
8117 if (appData.noChessProgram) {
8118 gameMode = nextGameMode;
8120 endingGame = 0; /* [HGM] crash */
8125 /* Put first chess program into idle state */
8126 if (first.pr != NoProc &&
8127 (gameMode == MachinePlaysWhite ||
8128 gameMode == MachinePlaysBlack ||
8129 gameMode == TwoMachinesPlay ||
8130 gameMode == IcsPlayingWhite ||
8131 gameMode == IcsPlayingBlack ||
8132 gameMode == BeginningOfGame)) {
8133 SendToProgram("force\n", &first);
8134 if (first.usePing) {
8136 sprintf(buf, "ping %d\n", ++first.lastPing);
8137 SendToProgram(buf, &first);
8140 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8141 /* Kill off first chess program */
8142 if (first.isr != NULL)
8143 RemoveInputSource(first.isr);
8146 if (first.pr != NoProc) {
8148 DoSleep( appData.delayBeforeQuit );
8149 SendToProgram("quit\n", &first);
8150 DoSleep( appData.delayAfterQuit );
8151 DestroyChildProcess(first.pr, first.useSigterm);
8156 /* Put second chess program into idle state */
8157 if (second.pr != NoProc &&
8158 gameMode == TwoMachinesPlay) {
8159 SendToProgram("force\n", &second);
8160 if (second.usePing) {
8162 sprintf(buf, "ping %d\n", ++second.lastPing);
8163 SendToProgram(buf, &second);
8166 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8167 /* Kill off second chess program */
8168 if (second.isr != NULL)
8169 RemoveInputSource(second.isr);
8172 if (second.pr != NoProc) {
8173 DoSleep( appData.delayBeforeQuit );
8174 SendToProgram("quit\n", &second);
8175 DoSleep( appData.delayAfterQuit );
8176 DestroyChildProcess(second.pr, second.useSigterm);
8181 if (matchMode && gameMode == TwoMachinesPlay) {
8184 if (first.twoMachinesColor[0] == 'w') {
8191 if (first.twoMachinesColor[0] == 'b') {
8200 if (matchGame < appData.matchGames) {
8202 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8203 tmp = first.twoMachinesColor;
8204 first.twoMachinesColor = second.twoMachinesColor;
8205 second.twoMachinesColor = tmp;
8207 gameMode = nextGameMode;
8209 if(appData.matchPause>10000 || appData.matchPause<10)
8210 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8211 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8212 endingGame = 0; /* [HGM] crash */
8216 gameMode = nextGameMode;
8217 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8218 first.tidy, second.tidy,
8219 first.matchWins, second.matchWins,
8220 appData.matchGames - (first.matchWins + second.matchWins));
8221 DisplayFatalError(buf, 0, 0);
8224 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8225 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8227 gameMode = nextGameMode;
8229 endingGame = 0; /* [HGM] crash */
8232 /* Assumes program was just initialized (initString sent).
8233 Leaves program in force mode. */
8235 FeedMovesToProgram(cps, upto)
8236 ChessProgramState *cps;
8241 if (appData.debugMode)
8242 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8243 startedFromSetupPosition ? "position and " : "",
8244 backwardMostMove, upto, cps->which);
8245 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8246 // [HGM] variantswitch: make engine aware of new variant
8247 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8248 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8249 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8250 SendToProgram(buf, cps);
8251 currentlyInitializedVariant = gameInfo.variant;
8253 SendToProgram("force\n", cps);
8254 if (startedFromSetupPosition) {
8255 SendBoard(cps, backwardMostMove);
8256 if (appData.debugMode) {
8257 fprintf(debugFP, "feedMoves\n");
8260 for (i = backwardMostMove; i < upto; i++) {
8261 SendMoveToProgram(i, cps);
8267 ResurrectChessProgram()
8269 /* The chess program may have exited.
8270 If so, restart it and feed it all the moves made so far. */
8272 if (appData.noChessProgram || first.pr != NoProc) return;
8274 StartChessProgram(&first);
8275 InitChessProgram(&first, FALSE);
8276 FeedMovesToProgram(&first, currentMove);
8278 if (!first.sendTime) {
8279 /* can't tell gnuchess what its clock should read,
8280 so we bow to its notion. */
8282 timeRemaining[0][currentMove] = whiteTimeRemaining;
8283 timeRemaining[1][currentMove] = blackTimeRemaining;
8286 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8287 appData.icsEngineAnalyze) && first.analysisSupport) {
8288 SendToProgram("analyze\n", &first);
8289 first.analyzing = TRUE;
8302 if (appData.debugMode) {
8303 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8304 redraw, init, gameMode);
8306 pausing = pauseExamInvalid = FALSE;
8307 startedFromSetupPosition = blackPlaysFirst = FALSE;
8309 whiteFlag = blackFlag = FALSE;
8310 userOfferedDraw = FALSE;
8311 hintRequested = bookRequested = FALSE;
8312 first.maybeThinking = FALSE;
8313 second.maybeThinking = FALSE;
8314 first.bookSuspend = FALSE; // [HGM] book
8315 second.bookSuspend = FALSE;
8316 thinkOutput[0] = NULLCHAR;
8317 lastHint[0] = NULLCHAR;
8318 ClearGameInfo(&gameInfo);
8319 gameInfo.variant = StringToVariant(appData.variant);
8320 ics_user_moved = ics_clock_paused = FALSE;
8321 ics_getting_history = H_FALSE;
8323 white_holding[0] = black_holding[0] = NULLCHAR;
8324 ClearProgramStats();
8325 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8329 flipView = appData.flipView;
8330 ClearPremoveHighlights();
8332 alarmSounded = FALSE;
8334 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8335 if(appData.serverMovesName != NULL) {
8336 /* [HGM] prepare to make moves file for broadcasting */
8337 clock_t t = clock();
8338 if(serverMoves != NULL) fclose(serverMoves);
8339 serverMoves = fopen(appData.serverMovesName, "r");
8340 if(serverMoves != NULL) {
8341 fclose(serverMoves);
8342 /* delay 15 sec before overwriting, so all clients can see end */
8343 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8345 serverMoves = fopen(appData.serverMovesName, "w");
8349 gameMode = BeginningOfGame;
8352 if(appData.icsActive) gameInfo.variant = VariantNormal;
8353 currentMove = forwardMostMove = backwardMostMove = 0;
8354 InitPosition(redraw);
8355 for (i = 0; i < MAX_MOVES; i++) {
8356 if (commentList[i] != NULL) {
8357 free(commentList[i]);
8358 commentList[i] = NULL;
8363 timeRemaining[0][0] = whiteTimeRemaining;
8364 timeRemaining[1][0] = blackTimeRemaining;
8365 if (first.pr == NULL) {
8366 StartChessProgram(&first);
8369 InitChessProgram(&first, startedFromSetupPosition);
8373 DisplayMessage("", "");
8374 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8375 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8383 if (!AutoPlayOneMove())
8385 if (matchMode || appData.timeDelay == 0)
8387 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8389 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8398 int fromX, fromY, toX, toY;
8400 if (appData.debugMode) {
8401 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8404 if (gameMode != PlayFromGameFile)
8407 if (currentMove >= forwardMostMove) {
8408 gameMode = EditGame;
8411 /* [AS] Clear current move marker at the end of a game */
8412 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8417 toX = moveList[currentMove][2] - AAA;
8418 toY = moveList[currentMove][3] - ONE;
8420 if (moveList[currentMove][1] == '@') {
8421 if (appData.highlightLastMove) {
8422 SetHighlights(-1, -1, toX, toY);
8425 fromX = moveList[currentMove][0] - AAA;
8426 fromY = moveList[currentMove][1] - ONE;
8428 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8430 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8432 if (appData.highlightLastMove) {
8433 SetHighlights(fromX, fromY, toX, toY);
8436 DisplayMove(currentMove);
8437 SendMoveToProgram(currentMove++, &first);
8438 DisplayBothClocks();
8439 DrawPosition(FALSE, boards[currentMove]);
8440 // [HGM] PV info: always display, routine tests if empty
8441 DisplayComment(currentMove - 1, commentList[currentMove]);
8447 LoadGameOneMove(readAhead)
8448 ChessMove readAhead;
8450 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8451 char promoChar = NULLCHAR;
8456 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8457 gameMode != AnalyzeMode && gameMode != Training) {
8462 yyboardindex = forwardMostMove;
8463 if (readAhead != (ChessMove)0) {
8464 moveType = readAhead;
8466 if (gameFileFP == NULL)
8468 moveType = (ChessMove) yylex();
8474 if (appData.debugMode)
8475 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8477 if (*p == '{' || *p == '[' || *p == '(') {
8478 p[strlen(p) - 1] = NULLCHAR;
8482 /* append the comment but don't display it */
8483 while (*p == '\n') p++;
8484 AppendComment(currentMove, p);
8487 case WhiteCapturesEnPassant:
8488 case BlackCapturesEnPassant:
8489 case WhitePromotionChancellor:
8490 case BlackPromotionChancellor:
8491 case WhitePromotionArchbishop:
8492 case BlackPromotionArchbishop:
8493 case WhitePromotionCentaur:
8494 case BlackPromotionCentaur:
8495 case WhitePromotionQueen:
8496 case BlackPromotionQueen:
8497 case WhitePromotionRook:
8498 case BlackPromotionRook:
8499 case WhitePromotionBishop:
8500 case BlackPromotionBishop:
8501 case WhitePromotionKnight:
8502 case BlackPromotionKnight:
8503 case WhitePromotionKing:
8504 case BlackPromotionKing:
8506 case WhiteKingSideCastle:
8507 case WhiteQueenSideCastle:
8508 case BlackKingSideCastle:
8509 case BlackQueenSideCastle:
8510 case WhiteKingSideCastleWild:
8511 case WhiteQueenSideCastleWild:
8512 case BlackKingSideCastleWild:
8513 case BlackQueenSideCastleWild:
8515 case WhiteHSideCastleFR:
8516 case WhiteASideCastleFR:
8517 case BlackHSideCastleFR:
8518 case BlackASideCastleFR:
8520 if (appData.debugMode)
8521 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8522 fromX = currentMoveString[0] - AAA;
8523 fromY = currentMoveString[1] - ONE;
8524 toX = currentMoveString[2] - AAA;
8525 toY = currentMoveString[3] - ONE;
8526 promoChar = currentMoveString[4];
8531 if (appData.debugMode)
8532 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8533 fromX = moveType == WhiteDrop ?
8534 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8535 (int) CharToPiece(ToLower(currentMoveString[0]));
8537 toX = currentMoveString[2] - AAA;
8538 toY = currentMoveString[3] - ONE;
8544 case GameUnfinished:
8545 if (appData.debugMode)
8546 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8547 p = strchr(yy_text, '{');
8548 if (p == NULL) p = strchr(yy_text, '(');
8551 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8553 q = strchr(p, *p == '{' ? '}' : ')');
8554 if (q != NULL) *q = NULLCHAR;
8557 GameEnds(moveType, p, GE_FILE);
8559 if (cmailMsgLoaded) {
8561 flipView = WhiteOnMove(currentMove);
8562 if (moveType == GameUnfinished) flipView = !flipView;
8563 if (appData.debugMode)
8564 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8568 case (ChessMove) 0: /* end of file */
8569 if (appData.debugMode)
8570 fprintf(debugFP, "Parser hit end of file\n");
8571 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8572 EP_UNKNOWN, castlingRights[currentMove]) ) {
8578 if (WhiteOnMove(currentMove)) {
8579 GameEnds(BlackWins, "Black mates", GE_FILE);
8581 GameEnds(WhiteWins, "White mates", GE_FILE);
8585 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8592 if (lastLoadGameStart == GNUChessGame) {
8593 /* GNUChessGames have numbers, but they aren't move numbers */
8594 if (appData.debugMode)
8595 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8596 yy_text, (int) moveType);
8597 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8599 /* else fall thru */
8604 /* Reached start of next game in file */
8605 if (appData.debugMode)
8606 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8607 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8608 EP_UNKNOWN, castlingRights[currentMove]) ) {
8614 if (WhiteOnMove(currentMove)) {
8615 GameEnds(BlackWins, "Black mates", GE_FILE);
8617 GameEnds(WhiteWins, "White mates", GE_FILE);
8621 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8627 case PositionDiagram: /* should not happen; ignore */
8628 case ElapsedTime: /* ignore */
8629 case NAG: /* ignore */
8630 if (appData.debugMode)
8631 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8632 yy_text, (int) moveType);
8633 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8636 if (appData.testLegality) {
8637 if (appData.debugMode)
8638 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8639 sprintf(move, _("Illegal move: %d.%s%s"),
8640 (forwardMostMove / 2) + 1,
8641 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8642 DisplayError(move, 0);
8645 if (appData.debugMode)
8646 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8647 yy_text, currentMoveString);
8648 fromX = currentMoveString[0] - AAA;
8649 fromY = currentMoveString[1] - ONE;
8650 toX = currentMoveString[2] - AAA;
8651 toY = currentMoveString[3] - ONE;
8652 promoChar = currentMoveString[4];
8657 if (appData.debugMode)
8658 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8659 sprintf(move, _("Ambiguous move: %d.%s%s"),
8660 (forwardMostMove / 2) + 1,
8661 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8662 DisplayError(move, 0);
8667 case ImpossibleMove:
8668 if (appData.debugMode)
8669 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8670 sprintf(move, _("Illegal move: %d.%s%s"),
8671 (forwardMostMove / 2) + 1,
8672 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8673 DisplayError(move, 0);
8679 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8680 DrawPosition(FALSE, boards[currentMove]);
8681 DisplayBothClocks();
8682 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8683 DisplayComment(currentMove - 1, commentList[currentMove]);
8685 (void) StopLoadGameTimer();
8687 cmailOldMove = forwardMostMove;
8690 /* currentMoveString is set as a side-effect of yylex */
8691 strcat(currentMoveString, "\n");
8692 strcpy(moveList[forwardMostMove], currentMoveString);
8694 thinkOutput[0] = NULLCHAR;
8695 MakeMove(fromX, fromY, toX, toY, promoChar);
8696 currentMove = forwardMostMove;
8701 /* Load the nth game from the given file */
8703 LoadGameFromFile(filename, n, title, useList)
8707 /*Boolean*/ int useList;
8712 if (strcmp(filename, "-") == 0) {
8716 f = fopen(filename, "rb");
8718 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8719 DisplayError(buf, errno);
8723 if (fseek(f, 0, 0) == -1) {
8724 /* f is not seekable; probably a pipe */
8727 if (useList && n == 0) {
8728 int error = GameListBuild(f);
8730 DisplayError(_("Cannot build game list"), error);
8731 } else if (!ListEmpty(&gameList) &&
8732 ((ListGame *) gameList.tailPred)->number > 1) {
8733 GameListPopUp(f, title);
8740 return LoadGame(f, n, title, FALSE);
8745 MakeRegisteredMove()
8747 int fromX, fromY, toX, toY;
8749 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8750 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8753 if (appData.debugMode)
8754 fprintf(debugFP, "Restoring %s for game %d\n",
8755 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8757 thinkOutput[0] = NULLCHAR;
8758 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8759 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8760 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8761 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8762 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8763 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8764 MakeMove(fromX, fromY, toX, toY, promoChar);
8765 ShowMove(fromX, fromY, toX, toY);
8767 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8768 EP_UNKNOWN, castlingRights[currentMove]) ) {
8775 if (WhiteOnMove(currentMove)) {
8776 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8778 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8783 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8790 if (WhiteOnMove(currentMove)) {
8791 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8793 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8798 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8809 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8811 CmailLoadGame(f, gameNumber, title, useList)
8819 if (gameNumber > nCmailGames) {
8820 DisplayError(_("No more games in this message"), 0);
8823 if (f == lastLoadGameFP) {
8824 int offset = gameNumber - lastLoadGameNumber;
8826 cmailMsg[0] = NULLCHAR;
8827 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8828 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8829 nCmailMovesRegistered--;
8831 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8832 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8833 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8836 if (! RegisterMove()) return FALSE;
8840 retVal = LoadGame(f, gameNumber, title, useList);
8842 /* Make move registered during previous look at this game, if any */
8843 MakeRegisteredMove();
8845 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8846 commentList[currentMove]
8847 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8848 DisplayComment(currentMove - 1, commentList[currentMove]);
8854 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8859 int gameNumber = lastLoadGameNumber + offset;
8860 if (lastLoadGameFP == NULL) {
8861 DisplayError(_("No game has been loaded yet"), 0);
8864 if (gameNumber <= 0) {
8865 DisplayError(_("Can't back up any further"), 0);
8868 if (cmailMsgLoaded) {
8869 return CmailLoadGame(lastLoadGameFP, gameNumber,
8870 lastLoadGameTitle, lastLoadGameUseList);
8872 return LoadGame(lastLoadGameFP, gameNumber,
8873 lastLoadGameTitle, lastLoadGameUseList);
8879 /* Load the nth game from open file f */
8881 LoadGame(f, gameNumber, title, useList)
8889 int gn = gameNumber;
8890 ListGame *lg = NULL;
8893 GameMode oldGameMode;
8894 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8896 if (appData.debugMode)
8897 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8899 if (gameMode == Training )
8900 SetTrainingModeOff();
8902 oldGameMode = gameMode;
8903 if (gameMode != BeginningOfGame)
8909 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
8911 fclose(lastLoadGameFP);
8916 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8920 fseek(f, lg->offset, 0);
8921 GameListHighlight(gameNumber);
8926 DisplayError(_("Game number out of range"), 0);
8933 if (fseek(f, 0, 0) == -1)
8935 if (f == lastLoadGameFP ?
8936 gameNumber == lastLoadGameNumber + 1 :
8943 DisplayError(_("Can't seek on game file"), 0);
8950 lastLoadGameNumber = gameNumber;
8951 strcpy(lastLoadGameTitle, title);
8952 lastLoadGameUseList = useList;
8956 if (lg && lg->gameInfo.white && lg->gameInfo.black)
8958 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8959 lg->gameInfo.black);
8962 else if (*title != NULLCHAR)
8966 sprintf(buf, "%s %d", title, gameNumber);
8971 DisplayTitle(title);
8975 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
8977 gameMode = PlayFromGameFile;
8981 currentMove = forwardMostMove = backwardMostMove = 0;
8982 CopyBoard(boards[0], initialPosition);
8986 * Skip the first gn-1 games in the file.
8987 * Also skip over anything that precedes an identifiable
8988 * start of game marker, to avoid being confused by
8989 * garbage at the start of the file. Currently
8990 * recognized start of game markers are the move number "1",
8991 * the pattern "gnuchess .* game", the pattern
8992 * "^[#;%] [^ ]* game file", and a PGN tag block.
8993 * A game that starts with one of the latter two patterns
8994 * will also have a move number 1, possibly
8995 * following a position diagram.
8996 * 5-4-02: Let's try being more lenient and allowing a game to
8997 * start with an unnumbered move. Does that break anything?
8999 cm = lastLoadGameStart = (ChessMove) 0;
9001 yyboardindex = forwardMostMove;
9002 cm = (ChessMove) yylex();
9005 if (cmailMsgLoaded) {
9006 nCmailGames = CMAIL_MAX_GAMES - gn;
9009 DisplayError(_("Game not found in file"), 0);
9016 lastLoadGameStart = cm;
9020 switch (lastLoadGameStart) {
9027 gn--; /* count this game */
9028 lastLoadGameStart = cm;
9037 switch (lastLoadGameStart) {
9042 gn--; /* count this game */
9043 lastLoadGameStart = cm;
9046 lastLoadGameStart = cm; /* game counted already */
9054 yyboardindex = forwardMostMove;
9055 cm = (ChessMove) yylex();
9056 } while (cm == PGNTag || cm == Comment);
9063 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9064 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9065 != CMAIL_OLD_RESULT) {
9067 cmailResult[ CMAIL_MAX_GAMES
9068 - gn - 1] = CMAIL_OLD_RESULT;
9074 /* Only a NormalMove can be at the start of a game
9075 * without a position diagram. */
9076 if (lastLoadGameStart == (ChessMove) 0) {
9078 lastLoadGameStart = MoveNumberOne;
9087 if (appData.debugMode)
9088 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9090 if (cm == XBoardGame) {
9091 /* Skip any header junk before position diagram and/or move 1 */
9093 yyboardindex = forwardMostMove;
9094 cm = (ChessMove) yylex();
9096 if (cm == (ChessMove) 0 ||
9097 cm == GNUChessGame || cm == XBoardGame) {
9098 /* Empty game; pretend end-of-file and handle later */
9103 if (cm == MoveNumberOne || cm == PositionDiagram ||
9104 cm == PGNTag || cm == Comment)
9107 } else if (cm == GNUChessGame) {
9108 if (gameInfo.event != NULL) {
9109 free(gameInfo.event);
9111 gameInfo.event = StrSave(yy_text);
9114 startedFromSetupPosition = FALSE;
9115 while (cm == PGNTag) {
9116 if (appData.debugMode)
9117 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9118 err = ParsePGNTag(yy_text, &gameInfo);
9119 if (!err) numPGNTags++;
9121 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9122 if(gameInfo.variant != oldVariant) {
9123 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9125 oldVariant = gameInfo.variant;
9126 if (appData.debugMode)
9127 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9131 if (gameInfo.fen != NULL) {
9132 Board initial_position;
9133 startedFromSetupPosition = TRUE;
9134 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9136 DisplayError(_("Bad FEN position in file"), 0);
9139 CopyBoard(boards[0], initial_position);
9140 if (blackPlaysFirst) {
9141 currentMove = forwardMostMove = backwardMostMove = 1;
9142 CopyBoard(boards[1], initial_position);
9143 strcpy(moveList[0], "");
9144 strcpy(parseList[0], "");
9145 timeRemaining[0][1] = whiteTimeRemaining;
9146 timeRemaining[1][1] = blackTimeRemaining;
9147 if (commentList[0] != NULL) {
9148 commentList[1] = commentList[0];
9149 commentList[0] = NULL;
9152 currentMove = forwardMostMove = backwardMostMove = 0;
9154 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9156 initialRulePlies = FENrulePlies;
9157 epStatus[forwardMostMove] = FENepStatus;
9158 for( i=0; i< nrCastlingRights; i++ )
9159 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9161 yyboardindex = forwardMostMove;
9163 gameInfo.fen = NULL;
9166 yyboardindex = forwardMostMove;
9167 cm = (ChessMove) yylex();
9169 /* Handle comments interspersed among the tags */
9170 while (cm == Comment) {
9172 if (appData.debugMode)
9173 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9175 if (*p == '{' || *p == '[' || *p == '(') {
9176 p[strlen(p) - 1] = NULLCHAR;
9179 while (*p == '\n') p++;
9180 AppendComment(currentMove, p);
9181 yyboardindex = forwardMostMove;
9182 cm = (ChessMove) yylex();
9186 /* don't rely on existence of Event tag since if game was
9187 * pasted from clipboard the Event tag may not exist
9189 if (numPGNTags > 0){
9191 if (gameInfo.variant == VariantNormal) {
9192 gameInfo.variant = StringToVariant(gameInfo.event);
9195 if( appData.autoDisplayTags ) {
9196 tags = PGNTags(&gameInfo);
9197 TagsPopUp(tags, CmailMsg());
9202 /* Make something up, but don't display it now */
9207 if (cm == PositionDiagram) {
9210 Board initial_position;
9212 if (appData.debugMode)
9213 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9215 if (!startedFromSetupPosition) {
9217 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9218 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9228 initial_position[i][j++] = CharToPiece(*p);
9231 while (*p == ' ' || *p == '\t' ||
9232 *p == '\n' || *p == '\r') p++;
9234 if (strncmp(p, "black", strlen("black"))==0)
9235 blackPlaysFirst = TRUE;
9237 blackPlaysFirst = FALSE;
9238 startedFromSetupPosition = TRUE;
9240 CopyBoard(boards[0], initial_position);
9241 if (blackPlaysFirst) {
9242 currentMove = forwardMostMove = backwardMostMove = 1;
9243 CopyBoard(boards[1], initial_position);
9244 strcpy(moveList[0], "");
9245 strcpy(parseList[0], "");
9246 timeRemaining[0][1] = whiteTimeRemaining;
9247 timeRemaining[1][1] = blackTimeRemaining;
9248 if (commentList[0] != NULL) {
9249 commentList[1] = commentList[0];
9250 commentList[0] = NULL;
9253 currentMove = forwardMostMove = backwardMostMove = 0;
9256 yyboardindex = forwardMostMove;
9257 cm = (ChessMove) yylex();
9260 if (first.pr == NoProc) {
9261 StartChessProgram(&first);
9263 InitChessProgram(&first, FALSE);
9264 SendToProgram("force\n", &first);
9265 if (startedFromSetupPosition) {
9266 SendBoard(&first, forwardMostMove);
9267 if (appData.debugMode) {
9268 fprintf(debugFP, "Load Game\n");
9270 DisplayBothClocks();
9273 /* [HGM] server: flag to write setup moves in broadcast file as one */
9274 loadFlag = appData.suppressLoadMoves;
9276 while (cm == Comment) {
9278 if (appData.debugMode)
9279 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9281 if (*p == '{' || *p == '[' || *p == '(') {
9282 p[strlen(p) - 1] = NULLCHAR;
9285 while (*p == '\n') p++;
9286 AppendComment(currentMove, p);
9287 yyboardindex = forwardMostMove;
9288 cm = (ChessMove) yylex();
9291 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9292 cm == WhiteWins || cm == BlackWins ||
9293 cm == GameIsDrawn || cm == GameUnfinished) {
9294 DisplayMessage("", _("No moves in game"));
9295 if (cmailMsgLoaded) {
9296 if (appData.debugMode)
9297 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9301 DrawPosition(FALSE, boards[currentMove]);
9302 DisplayBothClocks();
9303 gameMode = EditGame;
9310 // [HGM] PV info: routine tests if comment empty
9311 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9312 DisplayComment(currentMove - 1, commentList[currentMove]);
9314 if (!matchMode && appData.timeDelay != 0)
9315 DrawPosition(FALSE, boards[currentMove]);
9317 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9318 programStats.ok_to_send = 1;
9321 /* if the first token after the PGN tags is a move
9322 * and not move number 1, retrieve it from the parser
9324 if (cm != MoveNumberOne)
9325 LoadGameOneMove(cm);
9327 /* load the remaining moves from the file */
9328 while (LoadGameOneMove((ChessMove)0)) {
9329 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9330 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9333 /* rewind to the start of the game */
9334 currentMove = backwardMostMove;
9336 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9338 if (oldGameMode == AnalyzeFile ||
9339 oldGameMode == AnalyzeMode) {
9343 if (matchMode || appData.timeDelay == 0) {
9345 gameMode = EditGame;
9347 } else if (appData.timeDelay > 0) {
9351 if (appData.debugMode)
9352 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9354 loadFlag = 0; /* [HGM] true game starts */
9358 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9360 ReloadPosition(offset)
9363 int positionNumber = lastLoadPositionNumber + offset;
9364 if (lastLoadPositionFP == NULL) {
9365 DisplayError(_("No position has been loaded yet"), 0);
9368 if (positionNumber <= 0) {
9369 DisplayError(_("Can't back up any further"), 0);
9372 return LoadPosition(lastLoadPositionFP, positionNumber,
9373 lastLoadPositionTitle);
9376 /* Load the nth position from the given file */
9378 LoadPositionFromFile(filename, n, title)
9386 if (strcmp(filename, "-") == 0) {
9387 return LoadPosition(stdin, n, "stdin");
9389 f = fopen(filename, "rb");
9391 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9392 DisplayError(buf, errno);
9395 return LoadPosition(f, n, title);
9400 /* Load the nth position from the given open file, and close it */
9402 LoadPosition(f, positionNumber, title)
9407 char *p, line[MSG_SIZ];
9408 Board initial_position;
9409 int i, j, fenMode, pn;
9411 if (gameMode == Training )
9412 SetTrainingModeOff();
9414 if (gameMode != BeginningOfGame) {
9417 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9418 fclose(lastLoadPositionFP);
9420 if (positionNumber == 0) positionNumber = 1;
9421 lastLoadPositionFP = f;
9422 lastLoadPositionNumber = positionNumber;
9423 strcpy(lastLoadPositionTitle, title);
9424 if (first.pr == NoProc) {
9425 StartChessProgram(&first);
9426 InitChessProgram(&first, FALSE);
9428 pn = positionNumber;
9429 if (positionNumber < 0) {
9430 /* Negative position number means to seek to that byte offset */
9431 if (fseek(f, -positionNumber, 0) == -1) {
9432 DisplayError(_("Can't seek on position file"), 0);
9437 if (fseek(f, 0, 0) == -1) {
9438 if (f == lastLoadPositionFP ?
9439 positionNumber == lastLoadPositionNumber + 1 :
9440 positionNumber == 1) {
9443 DisplayError(_("Can't seek on position file"), 0);
9448 /* See if this file is FEN or old-style xboard */
9449 if (fgets(line, MSG_SIZ, f) == NULL) {
9450 DisplayError(_("Position not found in file"), 0);
9459 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9460 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9461 case '1': case '2': case '3': case '4': case '5': case '6':
9462 case '7': case '8': case '9':
9463 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9464 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9465 case 'C': case 'W': case 'c': case 'w':
9470 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9471 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9475 if (fenMode || line[0] == '#') pn--;
9477 /* skip positions before number pn */
9478 if (fgets(line, MSG_SIZ, f) == NULL) {
9480 DisplayError(_("Position not found in file"), 0);
9483 if (fenMode || line[0] == '#') pn--;
9488 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9489 DisplayError(_("Bad FEN position in file"), 0);
9493 (void) fgets(line, MSG_SIZ, f);
9494 (void) fgets(line, MSG_SIZ, f);
9496 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9497 (void) fgets(line, MSG_SIZ, f);
9498 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9501 initial_position[i][j++] = CharToPiece(*p);
9505 blackPlaysFirst = FALSE;
9507 (void) fgets(line, MSG_SIZ, f);
9508 if (strncmp(line, "black", strlen("black"))==0)
9509 blackPlaysFirst = TRUE;
9512 startedFromSetupPosition = TRUE;
9514 SendToProgram("force\n", &first);
9515 CopyBoard(boards[0], initial_position);
9516 if (blackPlaysFirst) {
9517 currentMove = forwardMostMove = backwardMostMove = 1;
9518 strcpy(moveList[0], "");
9519 strcpy(parseList[0], "");
9520 CopyBoard(boards[1], initial_position);
9521 DisplayMessage("", _("Black to play"));
9523 currentMove = forwardMostMove = backwardMostMove = 0;
9524 DisplayMessage("", _("White to play"));
9526 /* [HGM] copy FEN attributes as well */
9528 initialRulePlies = FENrulePlies;
9529 epStatus[forwardMostMove] = FENepStatus;
9530 for( i=0; i< nrCastlingRights; i++ )
9531 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9533 SendBoard(&first, forwardMostMove);
9534 if (appData.debugMode) {
9536 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9537 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9538 fprintf(debugFP, "Load Position\n");
9541 if (positionNumber > 1) {
9542 sprintf(line, "%s %d", title, positionNumber);
9545 DisplayTitle(title);
9547 gameMode = EditGame;
9550 timeRemaining[0][1] = whiteTimeRemaining;
9551 timeRemaining[1][1] = blackTimeRemaining;
9552 DrawPosition(FALSE, boards[currentMove]);
9559 CopyPlayerNameIntoFileName(dest, src)
9562 while (*src != NULLCHAR && *src != ',') {
9567 *(*dest)++ = *src++;
9572 char *DefaultFileName(ext)
9575 static char def[MSG_SIZ];
9578 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9580 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9582 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9591 /* Save the current game to the given file */
9593 SaveGameToFile(filename, append)
9600 if (strcmp(filename, "-") == 0) {
9601 return SaveGame(stdout, 0, NULL);
9603 f = fopen(filename, append ? "a" : "w");
9605 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9606 DisplayError(buf, errno);
9609 return SaveGame(f, 0, NULL);
9618 static char buf[MSG_SIZ];
9621 p = strchr(str, ' ');
9622 if (p == NULL) return str;
9623 strncpy(buf, str, p - str);
9624 buf[p - str] = NULLCHAR;
9628 #define PGN_MAX_LINE 75
9630 #define PGN_SIDE_WHITE 0
9631 #define PGN_SIDE_BLACK 1
9634 static int FindFirstMoveOutOfBook( int side )
9638 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9639 int index = backwardMostMove;
9640 int has_book_hit = 0;
9642 if( (index % 2) != side ) {
9646 while( index < forwardMostMove ) {
9647 /* Check to see if engine is in book */
9648 int depth = pvInfoList[index].depth;
9649 int score = pvInfoList[index].score;
9655 else if( score == 0 && depth == 63 ) {
9656 in_book = 1; /* Zappa */
9658 else if( score == 2 && depth == 99 ) {
9659 in_book = 1; /* Abrok */
9662 has_book_hit += in_book;
9678 void GetOutOfBookInfo( char * buf )
9682 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9684 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9685 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9689 if( oob[0] >= 0 || oob[1] >= 0 ) {
9690 for( i=0; i<2; i++ ) {
9694 if( i > 0 && oob[0] >= 0 ) {
9698 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9699 sprintf( buf+strlen(buf), "%s%.2f",
9700 pvInfoList[idx].score >= 0 ? "+" : "",
9701 pvInfoList[idx].score / 100.0 );
9707 /* Save game in PGN style and close the file */
9712 int i, offset, linelen, newblock;
9716 int movelen, numlen, blank;
9717 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9719 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9721 tm = time((time_t *) NULL);
9723 PrintPGNTags(f, &gameInfo);
9725 if (backwardMostMove > 0 || startedFromSetupPosition) {
9726 char *fen = PositionToFEN(backwardMostMove, NULL);
9727 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9728 fprintf(f, "\n{--------------\n");
9729 PrintPosition(f, backwardMostMove);
9730 fprintf(f, "--------------}\n");
9734 /* [AS] Out of book annotation */
9735 if( appData.saveOutOfBookInfo ) {
9738 GetOutOfBookInfo( buf );
9740 if( buf[0] != '\0' ) {
9741 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9748 i = backwardMostMove;
9752 while (i < forwardMostMove) {
9753 /* Print comments preceding this move */
9754 if (commentList[i] != NULL) {
9755 if (linelen > 0) fprintf(f, "\n");
9756 fprintf(f, "{\n%s}\n", commentList[i]);
9761 /* Format move number */
9763 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9766 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9768 numtext[0] = NULLCHAR;
9771 numlen = strlen(numtext);
9774 /* Print move number */
9775 blank = linelen > 0 && numlen > 0;
9776 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9785 fprintf(f, numtext);
9789 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9790 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9792 // SavePart already does this!
9793 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9794 int p = movelen - 1;
9795 if(move_buffer[p] == ' ') p--;
9796 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9797 while(p && move_buffer[--p] != '(');
9798 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9803 blank = linelen > 0 && movelen > 0;
9804 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9813 fprintf(f, move_buffer);
9816 /* [AS] Add PV info if present */
9817 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9818 /* [HGM] add time */
9819 char buf[MSG_SIZ]; int seconds = 0;
9822 if(i >= backwardMostMove) {
9824 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9825 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9827 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9828 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9830 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9832 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9835 if( seconds <= 0) buf[0] = 0; else
9836 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9837 seconds = (seconds + 4)/10; // round to full seconds
9838 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9839 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9842 sprintf( move_buffer, "{%s%.2f/%d%s}",
9843 pvInfoList[i].score >= 0 ? "+" : "",
9844 pvInfoList[i].score / 100.0,
9845 pvInfoList[i].depth,
9848 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9850 /* Print score/depth */
9851 blank = linelen > 0 && movelen > 0;
9852 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9861 fprintf(f, move_buffer);
9868 /* Start a new line */
9869 if (linelen > 0) fprintf(f, "\n");
9871 /* Print comments after last move */
9872 if (commentList[i] != NULL) {
9873 fprintf(f, "{\n%s}\n", commentList[i]);
9877 if (gameInfo.resultDetails != NULL &&
9878 gameInfo.resultDetails[0] != NULLCHAR) {
9879 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9880 PGNResult(gameInfo.result));
9882 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9886 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9890 /* Save game in old style and close the file */
9898 tm = time((time_t *) NULL);
9900 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9903 if (backwardMostMove > 0 || startedFromSetupPosition) {
9904 fprintf(f, "\n[--------------\n");
9905 PrintPosition(f, backwardMostMove);
9906 fprintf(f, "--------------]\n");
9911 i = backwardMostMove;
9912 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9914 while (i < forwardMostMove) {
9915 if (commentList[i] != NULL) {
9916 fprintf(f, "[%s]\n", commentList[i]);
9920 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9923 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9925 if (commentList[i] != NULL) {
9929 if (i >= forwardMostMove) {
9933 fprintf(f, "%s\n", parseList[i]);
9938 if (commentList[i] != NULL) {
9939 fprintf(f, "[%s]\n", commentList[i]);
9942 /* This isn't really the old style, but it's close enough */
9943 if (gameInfo.resultDetails != NULL &&
9944 gameInfo.resultDetails[0] != NULLCHAR) {
9945 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9946 gameInfo.resultDetails);
9948 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9955 /* Save the current game to open file f and close the file */
9957 SaveGame(f, dummy, dummy2)
9962 if (gameMode == EditPosition) EditPositionDone();
9963 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9964 if (appData.oldSaveStyle)
9965 return SaveGameOldStyle(f);
9967 return SaveGamePGN(f);
9970 /* Save the current position to the given file */
9972 SavePositionToFile(filename)
9978 if (strcmp(filename, "-") == 0) {
9979 return SavePosition(stdout, 0, NULL);
9981 f = fopen(filename, "a");
9983 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9984 DisplayError(buf, errno);
9987 SavePosition(f, 0, NULL);
9993 /* Save the current position to the given open file and close the file */
9995 SavePosition(f, dummy, dummy2)
10003 if (appData.oldSaveStyle) {
10004 tm = time((time_t *) NULL);
10006 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10008 fprintf(f, "[--------------\n");
10009 PrintPosition(f, currentMove);
10010 fprintf(f, "--------------]\n");
10012 fen = PositionToFEN(currentMove, NULL);
10013 fprintf(f, "%s\n", fen);
10021 ReloadCmailMsgEvent(unregister)
10025 static char *inFilename = NULL;
10026 static char *outFilename;
10028 struct stat inbuf, outbuf;
10031 /* Any registered moves are unregistered if unregister is set, */
10032 /* i.e. invoked by the signal handler */
10034 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10035 cmailMoveRegistered[i] = FALSE;
10036 if (cmailCommentList[i] != NULL) {
10037 free(cmailCommentList[i]);
10038 cmailCommentList[i] = NULL;
10041 nCmailMovesRegistered = 0;
10044 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10045 cmailResult[i] = CMAIL_NOT_RESULT;
10049 if (inFilename == NULL) {
10050 /* Because the filenames are static they only get malloced once */
10051 /* and they never get freed */
10052 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10053 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10055 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10056 sprintf(outFilename, "%s.out", appData.cmailGameName);
10059 status = stat(outFilename, &outbuf);
10061 cmailMailedMove = FALSE;
10063 status = stat(inFilename, &inbuf);
10064 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10067 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10068 counts the games, notes how each one terminated, etc.
10070 It would be nice to remove this kludge and instead gather all
10071 the information while building the game list. (And to keep it
10072 in the game list nodes instead of having a bunch of fixed-size
10073 parallel arrays.) Note this will require getting each game's
10074 termination from the PGN tags, as the game list builder does
10075 not process the game moves. --mann
10077 cmailMsgLoaded = TRUE;
10078 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10080 /* Load first game in the file or popup game menu */
10081 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10083 #endif /* !WIN32 */
10091 char string[MSG_SIZ];
10093 if ( cmailMailedMove
10094 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10095 return TRUE; /* Allow free viewing */
10098 /* Unregister move to ensure that we don't leave RegisterMove */
10099 /* with the move registered when the conditions for registering no */
10101 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10102 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10103 nCmailMovesRegistered --;
10105 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10107 free(cmailCommentList[lastLoadGameNumber - 1]);
10108 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10112 if (cmailOldMove == -1) {
10113 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10117 if (currentMove > cmailOldMove + 1) {
10118 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10122 if (currentMove < cmailOldMove) {
10123 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10127 if (forwardMostMove > currentMove) {
10128 /* Silently truncate extra moves */
10132 if ( (currentMove == cmailOldMove + 1)
10133 || ( (currentMove == cmailOldMove)
10134 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10135 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10136 if (gameInfo.result != GameUnfinished) {
10137 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10140 if (commentList[currentMove] != NULL) {
10141 cmailCommentList[lastLoadGameNumber - 1]
10142 = StrSave(commentList[currentMove]);
10144 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10146 if (appData.debugMode)
10147 fprintf(debugFP, "Saving %s for game %d\n",
10148 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10151 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10153 f = fopen(string, "w");
10154 if (appData.oldSaveStyle) {
10155 SaveGameOldStyle(f); /* also closes the file */
10157 sprintf(string, "%s.pos.out", appData.cmailGameName);
10158 f = fopen(string, "w");
10159 SavePosition(f, 0, NULL); /* also closes the file */
10161 fprintf(f, "{--------------\n");
10162 PrintPosition(f, currentMove);
10163 fprintf(f, "--------------}\n\n");
10165 SaveGame(f, 0, NULL); /* also closes the file*/
10168 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10169 nCmailMovesRegistered ++;
10170 } else if (nCmailGames == 1) {
10171 DisplayError(_("You have not made a move yet"), 0);
10182 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10183 FILE *commandOutput;
10184 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10185 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10191 if (! cmailMsgLoaded) {
10192 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10196 if (nCmailGames == nCmailResults) {
10197 DisplayError(_("No unfinished games"), 0);
10201 #if CMAIL_PROHIBIT_REMAIL
10202 if (cmailMailedMove) {
10203 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);
10204 DisplayError(msg, 0);
10209 if (! (cmailMailedMove || RegisterMove())) return;
10211 if ( cmailMailedMove
10212 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10213 sprintf(string, partCommandString,
10214 appData.debugMode ? " -v" : "", appData.cmailGameName);
10215 commandOutput = popen(string, "r");
10217 if (commandOutput == NULL) {
10218 DisplayError(_("Failed to invoke cmail"), 0);
10220 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10221 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10223 if (nBuffers > 1) {
10224 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10225 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10226 nBytes = MSG_SIZ - 1;
10228 (void) memcpy(msg, buffer, nBytes);
10230 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10232 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10233 cmailMailedMove = TRUE; /* Prevent >1 moves */
10236 for (i = 0; i < nCmailGames; i ++) {
10237 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10242 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10244 sprintf(buffer, "%s/%s.%s.archive",
10246 appData.cmailGameName,
10248 LoadGameFromFile(buffer, 1, buffer, FALSE);
10249 cmailMsgLoaded = FALSE;
10253 DisplayInformation(msg);
10254 pclose(commandOutput);
10257 if ((*cmailMsg) != '\0') {
10258 DisplayInformation(cmailMsg);
10263 #endif /* !WIN32 */
10272 int prependComma = 0;
10274 char string[MSG_SIZ]; /* Space for game-list */
10277 if (!cmailMsgLoaded) return "";
10279 if (cmailMailedMove) {
10280 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10282 /* Create a list of games left */
10283 sprintf(string, "[");
10284 for (i = 0; i < nCmailGames; i ++) {
10285 if (! ( cmailMoveRegistered[i]
10286 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10287 if (prependComma) {
10288 sprintf(number, ",%d", i + 1);
10290 sprintf(number, "%d", i + 1);
10294 strcat(string, number);
10297 strcat(string, "]");
10299 if (nCmailMovesRegistered + nCmailResults == 0) {
10300 switch (nCmailGames) {
10303 _("Still need to make move for game\n"));
10308 _("Still need to make moves for both games\n"));
10313 _("Still need to make moves for all %d games\n"),
10318 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10321 _("Still need to make a move for game %s\n"),
10326 if (nCmailResults == nCmailGames) {
10327 sprintf(cmailMsg, _("No unfinished games\n"));
10329 sprintf(cmailMsg, _("Ready to send mail\n"));
10335 _("Still need to make moves for games %s\n"),
10347 if (gameMode == Training)
10348 SetTrainingModeOff();
10351 cmailMsgLoaded = FALSE;
10352 if (appData.icsActive) {
10353 SendToICS(ics_prefix);
10354 SendToICS("refresh\n");
10364 /* Give up on clean exit */
10368 /* Keep trying for clean exit */
10372 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10374 if (telnetISR != NULL) {
10375 RemoveInputSource(telnetISR);
10377 if (icsPR != NoProc) {
10378 DestroyChildProcess(icsPR, TRUE);
10381 /* Save game if resource set and not already saved by GameEnds() */
10382 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10383 && forwardMostMove > 0) {
10384 if (*appData.saveGameFile != NULLCHAR) {
10385 SaveGameToFile(appData.saveGameFile, TRUE);
10386 } else if (appData.autoSaveGames) {
10389 if (*appData.savePositionFile != NULLCHAR) {
10390 SavePositionToFile(appData.savePositionFile);
10393 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10395 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10396 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10398 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10399 /* make sure this other one finishes before killing it! */
10400 if(endingGame) { int count = 0;
10401 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10402 while(endingGame && count++ < 10) DoSleep(1);
10403 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10406 /* Kill off chess programs */
10407 if (first.pr != NoProc) {
10410 DoSleep( appData.delayBeforeQuit );
10411 SendToProgram("quit\n", &first);
10412 DoSleep( appData.delayAfterQuit );
10413 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10415 if (second.pr != NoProc) {
10416 DoSleep( appData.delayBeforeQuit );
10417 SendToProgram("quit\n", &second);
10418 DoSleep( appData.delayAfterQuit );
10419 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10421 if (first.isr != NULL) {
10422 RemoveInputSource(first.isr);
10424 if (second.isr != NULL) {
10425 RemoveInputSource(second.isr);
10428 ShutDownFrontEnd();
10435 if (appData.debugMode)
10436 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10440 if (gameMode == MachinePlaysWhite ||
10441 gameMode == MachinePlaysBlack) {
10444 DisplayBothClocks();
10446 if (gameMode == PlayFromGameFile) {
10447 if (appData.timeDelay >= 0)
10448 AutoPlayGameLoop();
10449 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10450 Reset(FALSE, TRUE);
10451 SendToICS(ics_prefix);
10452 SendToICS("refresh\n");
10453 } else if (currentMove < forwardMostMove) {
10454 ForwardInner(forwardMostMove);
10456 pauseExamInvalid = FALSE;
10458 switch (gameMode) {
10462 pauseExamForwardMostMove = forwardMostMove;
10463 pauseExamInvalid = FALSE;
10466 case IcsPlayingWhite:
10467 case IcsPlayingBlack:
10471 case PlayFromGameFile:
10472 (void) StopLoadGameTimer();
10476 case BeginningOfGame:
10477 if (appData.icsActive) return;
10478 /* else fall through */
10479 case MachinePlaysWhite:
10480 case MachinePlaysBlack:
10481 case TwoMachinesPlay:
10482 if (forwardMostMove == 0)
10483 return; /* don't pause if no one has moved */
10484 if ((gameMode == MachinePlaysWhite &&
10485 !WhiteOnMove(forwardMostMove)) ||
10486 (gameMode == MachinePlaysBlack &&
10487 WhiteOnMove(forwardMostMove))) {
10500 char title[MSG_SIZ];
10502 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10503 strcpy(title, _("Edit comment"));
10505 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10506 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10507 parseList[currentMove - 1]);
10510 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10517 char *tags = PGNTags(&gameInfo);
10518 EditTagsPopUp(tags);
10525 if (appData.noChessProgram || gameMode == AnalyzeMode)
10528 if (gameMode != AnalyzeFile) {
10529 if (!appData.icsEngineAnalyze) {
10531 if (gameMode != EditGame) return;
10533 ResurrectChessProgram();
10534 SendToProgram("analyze\n", &first);
10535 first.analyzing = TRUE;
10536 /*first.maybeThinking = TRUE;*/
10537 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10538 AnalysisPopUp(_("Analysis"),
10539 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10541 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10546 StartAnalysisClock();
10547 GetTimeMark(&lastNodeCountTime);
10554 if (appData.noChessProgram || gameMode == AnalyzeFile)
10557 if (gameMode != AnalyzeMode) {
10559 if (gameMode != EditGame) return;
10560 ResurrectChessProgram();
10561 SendToProgram("analyze\n", &first);
10562 first.analyzing = TRUE;
10563 /*first.maybeThinking = TRUE;*/
10564 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10565 AnalysisPopUp(_("Analysis"),
10566 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10568 gameMode = AnalyzeFile;
10573 StartAnalysisClock();
10574 GetTimeMark(&lastNodeCountTime);
10579 MachineWhiteEvent()
10582 char *bookHit = NULL;
10584 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10588 if (gameMode == PlayFromGameFile ||
10589 gameMode == TwoMachinesPlay ||
10590 gameMode == Training ||
10591 gameMode == AnalyzeMode ||
10592 gameMode == EndOfGame)
10595 if (gameMode == EditPosition)
10596 EditPositionDone();
10598 if (!WhiteOnMove(currentMove)) {
10599 DisplayError(_("It is not White's turn"), 0);
10603 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10606 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10607 gameMode == AnalyzeFile)
10610 ResurrectChessProgram(); /* in case it isn't running */
10611 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10612 gameMode = MachinePlaysWhite;
10615 gameMode = MachinePlaysWhite;
10619 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10621 if (first.sendName) {
10622 sprintf(buf, "name %s\n", gameInfo.black);
10623 SendToProgram(buf, &first);
10625 if (first.sendTime) {
10626 if (first.useColors) {
10627 SendToProgram("black\n", &first); /*gnu kludge*/
10629 SendTimeRemaining(&first, TRUE);
10631 if (first.useColors) {
10632 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10634 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10635 SetMachineThinkingEnables();
10636 first.maybeThinking = TRUE;
10640 if (appData.autoFlipView && !flipView) {
10641 flipView = !flipView;
10642 DrawPosition(FALSE, NULL);
10643 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10646 if(bookHit) { // [HGM] book: simulate book reply
10647 static char bookMove[MSG_SIZ]; // a bit generous?
10649 programStats.nodes = programStats.depth = programStats.time =
10650 programStats.score = programStats.got_only_move = 0;
10651 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10653 strcpy(bookMove, "move ");
10654 strcat(bookMove, bookHit);
10655 HandleMachineMove(bookMove, &first);
10660 MachineBlackEvent()
10663 char *bookHit = NULL;
10665 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10669 if (gameMode == PlayFromGameFile ||
10670 gameMode == TwoMachinesPlay ||
10671 gameMode == Training ||
10672 gameMode == AnalyzeMode ||
10673 gameMode == EndOfGame)
10676 if (gameMode == EditPosition)
10677 EditPositionDone();
10679 if (WhiteOnMove(currentMove)) {
10680 DisplayError(_("It is not Black's turn"), 0);
10684 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10687 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10688 gameMode == AnalyzeFile)
10691 ResurrectChessProgram(); /* in case it isn't running */
10692 gameMode = MachinePlaysBlack;
10696 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10698 if (first.sendName) {
10699 sprintf(buf, "name %s\n", gameInfo.white);
10700 SendToProgram(buf, &first);
10702 if (first.sendTime) {
10703 if (first.useColors) {
10704 SendToProgram("white\n", &first); /*gnu kludge*/
10706 SendTimeRemaining(&first, FALSE);
10708 if (first.useColors) {
10709 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10711 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10712 SetMachineThinkingEnables();
10713 first.maybeThinking = TRUE;
10716 if (appData.autoFlipView && flipView) {
10717 flipView = !flipView;
10718 DrawPosition(FALSE, NULL);
10719 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10721 if(bookHit) { // [HGM] book: simulate book reply
10722 static char bookMove[MSG_SIZ]; // a bit generous?
10724 programStats.nodes = programStats.depth = programStats.time =
10725 programStats.score = programStats.got_only_move = 0;
10726 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10728 strcpy(bookMove, "move ");
10729 strcat(bookMove, bookHit);
10730 HandleMachineMove(bookMove, &first);
10736 DisplayTwoMachinesTitle()
10739 if (appData.matchGames > 0) {
10740 if (first.twoMachinesColor[0] == 'w') {
10741 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10742 gameInfo.white, gameInfo.black,
10743 first.matchWins, second.matchWins,
10744 matchGame - 1 - (first.matchWins + second.matchWins));
10746 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10747 gameInfo.white, gameInfo.black,
10748 second.matchWins, first.matchWins,
10749 matchGame - 1 - (first.matchWins + second.matchWins));
10752 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10758 TwoMachinesEvent P((void))
10762 ChessProgramState *onmove;
10763 char *bookHit = NULL;
10765 if (appData.noChessProgram) return;
10767 switch (gameMode) {
10768 case TwoMachinesPlay:
10770 case MachinePlaysWhite:
10771 case MachinePlaysBlack:
10772 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10773 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10777 case BeginningOfGame:
10778 case PlayFromGameFile:
10781 if (gameMode != EditGame) return;
10784 EditPositionDone();
10795 forwardMostMove = currentMove;
10796 ResurrectChessProgram(); /* in case first program isn't running */
10798 if (second.pr == NULL) {
10799 StartChessProgram(&second);
10800 if (second.protocolVersion == 1) {
10801 TwoMachinesEventIfReady();
10803 /* kludge: allow timeout for initial "feature" command */
10805 DisplayMessage("", _("Starting second chess program"));
10806 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10810 DisplayMessage("", "");
10811 InitChessProgram(&second, FALSE);
10812 SendToProgram("force\n", &second);
10813 if (startedFromSetupPosition) {
10814 SendBoard(&second, backwardMostMove);
10815 if (appData.debugMode) {
10816 fprintf(debugFP, "Two Machines\n");
10819 for (i = backwardMostMove; i < forwardMostMove; i++) {
10820 SendMoveToProgram(i, &second);
10823 gameMode = TwoMachinesPlay;
10827 DisplayTwoMachinesTitle();
10829 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10835 SendToProgram(first.computerString, &first);
10836 if (first.sendName) {
10837 sprintf(buf, "name %s\n", second.tidy);
10838 SendToProgram(buf, &first);
10840 SendToProgram(second.computerString, &second);
10841 if (second.sendName) {
10842 sprintf(buf, "name %s\n", first.tidy);
10843 SendToProgram(buf, &second);
10847 if (!first.sendTime || !second.sendTime) {
10848 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10849 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10851 if (onmove->sendTime) {
10852 if (onmove->useColors) {
10853 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10855 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10857 if (onmove->useColors) {
10858 SendToProgram(onmove->twoMachinesColor, onmove);
10860 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10861 // SendToProgram("go\n", onmove);
10862 onmove->maybeThinking = TRUE;
10863 SetMachineThinkingEnables();
10867 if(bookHit) { // [HGM] book: simulate book reply
10868 static char bookMove[MSG_SIZ]; // a bit generous?
10870 programStats.nodes = programStats.depth = programStats.time =
10871 programStats.score = programStats.got_only_move = 0;
10872 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10874 strcpy(bookMove, "move ");
10875 strcat(bookMove, bookHit);
10876 HandleMachineMove(bookMove, &first);
10883 if (gameMode == Training) {
10884 SetTrainingModeOff();
10885 gameMode = PlayFromGameFile;
10886 DisplayMessage("", _("Training mode off"));
10888 gameMode = Training;
10889 animateTraining = appData.animate;
10891 /* make sure we are not already at the end of the game */
10892 if (currentMove < forwardMostMove) {
10893 SetTrainingModeOn();
10894 DisplayMessage("", _("Training mode on"));
10896 gameMode = PlayFromGameFile;
10897 DisplayError(_("Already at end of game"), 0);
10906 if (!appData.icsActive) return;
10907 switch (gameMode) {
10908 case IcsPlayingWhite:
10909 case IcsPlayingBlack:
10912 case BeginningOfGame:
10920 EditPositionDone();
10933 gameMode = IcsIdle;
10944 switch (gameMode) {
10946 SetTrainingModeOff();
10948 case MachinePlaysWhite:
10949 case MachinePlaysBlack:
10950 case BeginningOfGame:
10951 SendToProgram("force\n", &first);
10952 SetUserThinkingEnables();
10954 case PlayFromGameFile:
10955 (void) StopLoadGameTimer();
10956 if (gameFileFP != NULL) {
10961 EditPositionDone();
10966 SendToProgram("force\n", &first);
10968 case TwoMachinesPlay:
10969 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10970 ResurrectChessProgram();
10971 SetUserThinkingEnables();
10974 ResurrectChessProgram();
10976 case IcsPlayingBlack:
10977 case IcsPlayingWhite:
10978 DisplayError(_("Warning: You are still playing a game"), 0);
10981 DisplayError(_("Warning: You are still observing a game"), 0);
10984 DisplayError(_("Warning: You are still examining a game"), 0);
10995 first.offeredDraw = second.offeredDraw = 0;
10997 if (gameMode == PlayFromGameFile) {
10998 whiteTimeRemaining = timeRemaining[0][currentMove];
10999 blackTimeRemaining = timeRemaining[1][currentMove];
11003 if (gameMode == MachinePlaysWhite ||
11004 gameMode == MachinePlaysBlack ||
11005 gameMode == TwoMachinesPlay ||
11006 gameMode == EndOfGame) {
11007 i = forwardMostMove;
11008 while (i > currentMove) {
11009 SendToProgram("undo\n", &first);
11012 whiteTimeRemaining = timeRemaining[0][currentMove];
11013 blackTimeRemaining = timeRemaining[1][currentMove];
11014 DisplayBothClocks();
11015 if (whiteFlag || blackFlag) {
11016 whiteFlag = blackFlag = 0;
11021 gameMode = EditGame;
11028 EditPositionEvent()
11030 if (gameMode == EditPosition) {
11036 if (gameMode != EditGame) return;
11038 gameMode = EditPosition;
11041 if (currentMove > 0)
11042 CopyBoard(boards[0], boards[currentMove]);
11044 blackPlaysFirst = !WhiteOnMove(currentMove);
11046 currentMove = forwardMostMove = backwardMostMove = 0;
11047 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11054 /* [DM] icsEngineAnalyze - possible call from other functions */
11055 if (appData.icsEngineAnalyze) {
11056 appData.icsEngineAnalyze = FALSE;
11058 DisplayMessage("",_("Close ICS engine analyze..."));
11060 if (first.analysisSupport && first.analyzing) {
11061 SendToProgram("exit\n", &first);
11062 first.analyzing = FALSE;
11065 thinkOutput[0] = NULLCHAR;
11071 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11073 startedFromSetupPosition = TRUE;
11074 InitChessProgram(&first, FALSE);
11075 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11076 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11077 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11078 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11079 } else castlingRights[0][2] = -1;
11080 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11081 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11082 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11083 } else castlingRights[0][5] = -1;
11084 SendToProgram("force\n", &first);
11085 if (blackPlaysFirst) {
11086 strcpy(moveList[0], "");
11087 strcpy(parseList[0], "");
11088 currentMove = forwardMostMove = backwardMostMove = 1;
11089 CopyBoard(boards[1], boards[0]);
11090 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11092 epStatus[1] = epStatus[0];
11093 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11096 currentMove = forwardMostMove = backwardMostMove = 0;
11098 SendBoard(&first, forwardMostMove);
11099 if (appData.debugMode) {
11100 fprintf(debugFP, "EditPosDone\n");
11103 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11104 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11105 gameMode = EditGame;
11107 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11108 ClearHighlights(); /* [AS] */
11111 /* Pause for `ms' milliseconds */
11112 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11122 } while (SubtractTimeMarks(&m2, &m1) < ms);
11125 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11127 SendMultiLineToICS(buf)
11130 char temp[MSG_SIZ+1], *p;
11137 strncpy(temp, buf, len);
11142 if (*p == '\n' || *p == '\r')
11147 strcat(temp, "\n");
11149 SendToPlayer(temp, strlen(temp));
11153 SetWhiteToPlayEvent()
11155 if (gameMode == EditPosition) {
11156 blackPlaysFirst = FALSE;
11157 DisplayBothClocks(); /* works because currentMove is 0 */
11158 } else if (gameMode == IcsExamining) {
11159 SendToICS(ics_prefix);
11160 SendToICS("tomove white\n");
11165 SetBlackToPlayEvent()
11167 if (gameMode == EditPosition) {
11168 blackPlaysFirst = TRUE;
11169 currentMove = 1; /* kludge */
11170 DisplayBothClocks();
11172 } else if (gameMode == IcsExamining) {
11173 SendToICS(ics_prefix);
11174 SendToICS("tomove black\n");
11179 EditPositionMenuEvent(selection, x, y)
11180 ChessSquare selection;
11184 ChessSquare piece = boards[0][y][x];
11186 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11188 switch (selection) {
11190 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11191 SendToICS(ics_prefix);
11192 SendToICS("bsetup clear\n");
11193 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11194 SendToICS(ics_prefix);
11195 SendToICS("clearboard\n");
11197 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11198 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11199 for (y = 0; y < BOARD_HEIGHT; y++) {
11200 if (gameMode == IcsExamining) {
11201 if (boards[currentMove][y][x] != EmptySquare) {
11202 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11207 boards[0][y][x] = p;
11212 if (gameMode == EditPosition) {
11213 DrawPosition(FALSE, boards[0]);
11218 SetWhiteToPlayEvent();
11222 SetBlackToPlayEvent();
11226 if (gameMode == IcsExamining) {
11227 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11230 boards[0][y][x] = EmptySquare;
11231 DrawPosition(FALSE, boards[0]);
11236 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11237 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11238 selection = (ChessSquare) (PROMOTED piece);
11239 } else if(piece == EmptySquare) selection = WhiteSilver;
11240 else selection = (ChessSquare)((int)piece - 1);
11244 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11245 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11246 selection = (ChessSquare) (DEMOTED piece);
11247 } else if(piece == EmptySquare) selection = BlackSilver;
11248 else selection = (ChessSquare)((int)piece + 1);
11253 if(gameInfo.variant == VariantShatranj ||
11254 gameInfo.variant == VariantXiangqi ||
11255 gameInfo.variant == VariantCourier )
11256 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11261 if(gameInfo.variant == VariantXiangqi)
11262 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11263 if(gameInfo.variant == VariantKnightmate)
11264 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11267 if (gameMode == IcsExamining) {
11268 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11269 PieceToChar(selection), AAA + x, ONE + y);
11272 boards[0][y][x] = selection;
11273 DrawPosition(FALSE, boards[0]);
11281 DropMenuEvent(selection, x, y)
11282 ChessSquare selection;
11285 ChessMove moveType;
11287 switch (gameMode) {
11288 case IcsPlayingWhite:
11289 case MachinePlaysBlack:
11290 if (!WhiteOnMove(currentMove)) {
11291 DisplayMoveError(_("It is Black's turn"));
11294 moveType = WhiteDrop;
11296 case IcsPlayingBlack:
11297 case MachinePlaysWhite:
11298 if (WhiteOnMove(currentMove)) {
11299 DisplayMoveError(_("It is White's turn"));
11302 moveType = BlackDrop;
11305 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11311 if (moveType == BlackDrop && selection < BlackPawn) {
11312 selection = (ChessSquare) ((int) selection
11313 + (int) BlackPawn - (int) WhitePawn);
11315 if (boards[currentMove][y][x] != EmptySquare) {
11316 DisplayMoveError(_("That square is occupied"));
11320 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11326 /* Accept a pending offer of any kind from opponent */
11328 if (appData.icsActive) {
11329 SendToICS(ics_prefix);
11330 SendToICS("accept\n");
11331 } else if (cmailMsgLoaded) {
11332 if (currentMove == cmailOldMove &&
11333 commentList[cmailOldMove] != NULL &&
11334 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11335 "Black offers a draw" : "White offers a draw")) {
11337 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11338 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11340 DisplayError(_("There is no pending offer on this move"), 0);
11341 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11344 /* Not used for offers from chess program */
11351 /* Decline a pending offer of any kind from opponent */
11353 if (appData.icsActive) {
11354 SendToICS(ics_prefix);
11355 SendToICS("decline\n");
11356 } else if (cmailMsgLoaded) {
11357 if (currentMove == cmailOldMove &&
11358 commentList[cmailOldMove] != NULL &&
11359 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11360 "Black offers a draw" : "White offers a draw")) {
11362 AppendComment(cmailOldMove, "Draw declined");
11363 DisplayComment(cmailOldMove - 1, "Draw declined");
11366 DisplayError(_("There is no pending offer on this move"), 0);
11369 /* Not used for offers from chess program */
11376 /* Issue ICS rematch command */
11377 if (appData.icsActive) {
11378 SendToICS(ics_prefix);
11379 SendToICS("rematch\n");
11386 /* Call your opponent's flag (claim a win on time) */
11387 if (appData.icsActive) {
11388 SendToICS(ics_prefix);
11389 SendToICS("flag\n");
11391 switch (gameMode) {
11394 case MachinePlaysWhite:
11397 GameEnds(GameIsDrawn, "Both players ran out of time",
11400 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11402 DisplayError(_("Your opponent is not out of time"), 0);
11405 case MachinePlaysBlack:
11408 GameEnds(GameIsDrawn, "Both players ran out of time",
11411 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11413 DisplayError(_("Your opponent is not out of time"), 0);
11423 /* Offer draw or accept pending draw offer from opponent */
11425 if (appData.icsActive) {
11426 /* Note: tournament rules require draw offers to be
11427 made after you make your move but before you punch
11428 your clock. Currently ICS doesn't let you do that;
11429 instead, you immediately punch your clock after making
11430 a move, but you can offer a draw at any time. */
11432 SendToICS(ics_prefix);
11433 SendToICS("draw\n");
11434 } else if (cmailMsgLoaded) {
11435 if (currentMove == cmailOldMove &&
11436 commentList[cmailOldMove] != NULL &&
11437 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11438 "Black offers a draw" : "White offers a draw")) {
11439 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11440 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11441 } else if (currentMove == cmailOldMove + 1) {
11442 char *offer = WhiteOnMove(cmailOldMove) ?
11443 "White offers a draw" : "Black offers a draw";
11444 AppendComment(currentMove, offer);
11445 DisplayComment(currentMove - 1, offer);
11446 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11448 DisplayError(_("You must make your move before offering a draw"), 0);
11449 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11451 } else if (first.offeredDraw) {
11452 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11454 if (first.sendDrawOffers) {
11455 SendToProgram("draw\n", &first);
11456 userOfferedDraw = TRUE;
11464 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11466 if (appData.icsActive) {
11467 SendToICS(ics_prefix);
11468 SendToICS("adjourn\n");
11470 /* Currently GNU Chess doesn't offer or accept Adjourns */
11478 /* Offer Abort or accept pending Abort offer from opponent */
11480 if (appData.icsActive) {
11481 SendToICS(ics_prefix);
11482 SendToICS("abort\n");
11484 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11491 /* Resign. You can do this even if it's not your turn. */
11493 if (appData.icsActive) {
11494 SendToICS(ics_prefix);
11495 SendToICS("resign\n");
11497 switch (gameMode) {
11498 case MachinePlaysWhite:
11499 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11501 case MachinePlaysBlack:
11502 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11505 if (cmailMsgLoaded) {
11507 if (WhiteOnMove(cmailOldMove)) {
11508 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11510 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11512 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11523 StopObservingEvent()
11525 /* Stop observing current games */
11526 SendToICS(ics_prefix);
11527 SendToICS("unobserve\n");
11531 StopExaminingEvent()
11533 /* Stop observing current game */
11534 SendToICS(ics_prefix);
11535 SendToICS("unexamine\n");
11539 ForwardInner(target)
11544 if (appData.debugMode)
11545 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11546 target, currentMove, forwardMostMove);
11548 if (gameMode == EditPosition)
11551 if (gameMode == PlayFromGameFile && !pausing)
11554 if (gameMode == IcsExamining && pausing)
11555 limit = pauseExamForwardMostMove;
11557 limit = forwardMostMove;
11559 if (target > limit) target = limit;
11561 if (target > 0 && moveList[target - 1][0]) {
11562 int fromX, fromY, toX, toY;
11563 toX = moveList[target - 1][2] - AAA;
11564 toY = moveList[target - 1][3] - ONE;
11565 if (moveList[target - 1][1] == '@') {
11566 if (appData.highlightLastMove) {
11567 SetHighlights(-1, -1, toX, toY);
11570 fromX = moveList[target - 1][0] - AAA;
11571 fromY = moveList[target - 1][1] - ONE;
11572 if (target == currentMove + 1) {
11573 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11575 if (appData.highlightLastMove) {
11576 SetHighlights(fromX, fromY, toX, toY);
11580 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11581 gameMode == Training || gameMode == PlayFromGameFile ||
11582 gameMode == AnalyzeFile) {
11583 while (currentMove < target) {
11584 SendMoveToProgram(currentMove++, &first);
11587 currentMove = target;
11590 if (gameMode == EditGame || gameMode == EndOfGame) {
11591 whiteTimeRemaining = timeRemaining[0][currentMove];
11592 blackTimeRemaining = timeRemaining[1][currentMove];
11594 DisplayBothClocks();
11595 DisplayMove(currentMove - 1);
11596 DrawPosition(FALSE, boards[currentMove]);
11597 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11598 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11599 DisplayComment(currentMove - 1, commentList[currentMove]);
11607 if (gameMode == IcsExamining && !pausing) {
11608 SendToICS(ics_prefix);
11609 SendToICS("forward\n");
11611 ForwardInner(currentMove + 1);
11618 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11619 /* to optimze, we temporarily turn off analysis mode while we feed
11620 * the remaining moves to the engine. Otherwise we get analysis output
11623 if (first.analysisSupport) {
11624 SendToProgram("exit\nforce\n", &first);
11625 first.analyzing = FALSE;
11629 if (gameMode == IcsExamining && !pausing) {
11630 SendToICS(ics_prefix);
11631 SendToICS("forward 999999\n");
11633 ForwardInner(forwardMostMove);
11636 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11637 /* we have fed all the moves, so reactivate analysis mode */
11638 SendToProgram("analyze\n", &first);
11639 first.analyzing = TRUE;
11640 /*first.maybeThinking = TRUE;*/
11641 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11646 BackwardInner(target)
11649 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11651 if (appData.debugMode)
11652 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11653 target, currentMove, forwardMostMove);
11655 if (gameMode == EditPosition) return;
11656 if (currentMove <= backwardMostMove) {
11658 DrawPosition(full_redraw, boards[currentMove]);
11661 if (gameMode == PlayFromGameFile && !pausing)
11664 if (moveList[target][0]) {
11665 int fromX, fromY, toX, toY;
11666 toX = moveList[target][2] - AAA;
11667 toY = moveList[target][3] - ONE;
11668 if (moveList[target][1] == '@') {
11669 if (appData.highlightLastMove) {
11670 SetHighlights(-1, -1, toX, toY);
11673 fromX = moveList[target][0] - AAA;
11674 fromY = moveList[target][1] - ONE;
11675 if (target == currentMove - 1) {
11676 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11678 if (appData.highlightLastMove) {
11679 SetHighlights(fromX, fromY, toX, toY);
11683 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11684 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11685 while (currentMove > target) {
11686 SendToProgram("undo\n", &first);
11690 currentMove = target;
11693 if (gameMode == EditGame || gameMode == EndOfGame) {
11694 whiteTimeRemaining = timeRemaining[0][currentMove];
11695 blackTimeRemaining = timeRemaining[1][currentMove];
11697 DisplayBothClocks();
11698 DisplayMove(currentMove - 1);
11699 DrawPosition(full_redraw, boards[currentMove]);
11700 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11701 // [HGM] PV info: routine tests if comment empty
11702 DisplayComment(currentMove - 1, commentList[currentMove]);
11708 if (gameMode == IcsExamining && !pausing) {
11709 SendToICS(ics_prefix);
11710 SendToICS("backward\n");
11712 BackwardInner(currentMove - 1);
11719 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11720 /* to optimze, we temporarily turn off analysis mode while we undo
11721 * all the moves. Otherwise we get analysis output after each undo.
11723 if (first.analysisSupport) {
11724 SendToProgram("exit\nforce\n", &first);
11725 first.analyzing = FALSE;
11729 if (gameMode == IcsExamining && !pausing) {
11730 SendToICS(ics_prefix);
11731 SendToICS("backward 999999\n");
11733 BackwardInner(backwardMostMove);
11736 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11737 /* we have fed all the moves, so reactivate analysis mode */
11738 SendToProgram("analyze\n", &first);
11739 first.analyzing = TRUE;
11740 /*first.maybeThinking = TRUE;*/
11741 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11748 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11749 if (to >= forwardMostMove) to = forwardMostMove;
11750 if (to <= backwardMostMove) to = backwardMostMove;
11751 if (to < currentMove) {
11761 if (gameMode != IcsExamining) {
11762 DisplayError(_("You are not examining a game"), 0);
11766 DisplayError(_("You can't revert while pausing"), 0);
11769 SendToICS(ics_prefix);
11770 SendToICS("revert\n");
11776 switch (gameMode) {
11777 case MachinePlaysWhite:
11778 case MachinePlaysBlack:
11779 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11780 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11783 if (forwardMostMove < 2) return;
11784 currentMove = forwardMostMove = forwardMostMove - 2;
11785 whiteTimeRemaining = timeRemaining[0][currentMove];
11786 blackTimeRemaining = timeRemaining[1][currentMove];
11787 DisplayBothClocks();
11788 DisplayMove(currentMove - 1);
11789 ClearHighlights();/*!! could figure this out*/
11790 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11791 SendToProgram("remove\n", &first);
11792 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11795 case BeginningOfGame:
11799 case IcsPlayingWhite:
11800 case IcsPlayingBlack:
11801 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11802 SendToICS(ics_prefix);
11803 SendToICS("takeback 2\n");
11805 SendToICS(ics_prefix);
11806 SendToICS("takeback 1\n");
11815 ChessProgramState *cps;
11817 switch (gameMode) {
11818 case MachinePlaysWhite:
11819 if (!WhiteOnMove(forwardMostMove)) {
11820 DisplayError(_("It is your turn"), 0);
11825 case MachinePlaysBlack:
11826 if (WhiteOnMove(forwardMostMove)) {
11827 DisplayError(_("It is your turn"), 0);
11832 case TwoMachinesPlay:
11833 if (WhiteOnMove(forwardMostMove) ==
11834 (first.twoMachinesColor[0] == 'w')) {
11840 case BeginningOfGame:
11844 SendToProgram("?\n", cps);
11848 TruncateGameEvent()
11851 if (gameMode != EditGame) return;
11858 if (forwardMostMove > currentMove) {
11859 if (gameInfo.resultDetails != NULL) {
11860 free(gameInfo.resultDetails);
11861 gameInfo.resultDetails = NULL;
11862 gameInfo.result = GameUnfinished;
11864 forwardMostMove = currentMove;
11865 HistorySet(parseList, backwardMostMove, forwardMostMove,
11873 if (appData.noChessProgram) return;
11874 switch (gameMode) {
11875 case MachinePlaysWhite:
11876 if (WhiteOnMove(forwardMostMove)) {
11877 DisplayError(_("Wait until your turn"), 0);
11881 case BeginningOfGame:
11882 case MachinePlaysBlack:
11883 if (!WhiteOnMove(forwardMostMove)) {
11884 DisplayError(_("Wait until your turn"), 0);
11889 DisplayError(_("No hint available"), 0);
11892 SendToProgram("hint\n", &first);
11893 hintRequested = TRUE;
11899 if (appData.noChessProgram) return;
11900 switch (gameMode) {
11901 case MachinePlaysWhite:
11902 if (WhiteOnMove(forwardMostMove)) {
11903 DisplayError(_("Wait until your turn"), 0);
11907 case BeginningOfGame:
11908 case MachinePlaysBlack:
11909 if (!WhiteOnMove(forwardMostMove)) {
11910 DisplayError(_("Wait until your turn"), 0);
11915 EditPositionDone();
11917 case TwoMachinesPlay:
11922 SendToProgram("bk\n", &first);
11923 bookOutput[0] = NULLCHAR;
11924 bookRequested = TRUE;
11930 char *tags = PGNTags(&gameInfo);
11931 TagsPopUp(tags, CmailMsg());
11935 /* end button procedures */
11938 PrintPosition(fp, move)
11944 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11945 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11946 char c = PieceToChar(boards[move][i][j]);
11947 fputc(c == 'x' ? '.' : c, fp);
11948 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11951 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11952 fprintf(fp, "white to play\n");
11954 fprintf(fp, "black to play\n");
11961 if (gameInfo.white != NULL) {
11962 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11968 /* Find last component of program's own name, using some heuristics */
11970 TidyProgramName(prog, host, buf)
11971 char *prog, *host, buf[MSG_SIZ];
11974 int local = (strcmp(host, "localhost") == 0);
11975 while (!local && (p = strchr(prog, ';')) != NULL) {
11977 while (*p == ' ') p++;
11980 if (*prog == '"' || *prog == '\'') {
11981 q = strchr(prog + 1, *prog);
11983 q = strchr(prog, ' ');
11985 if (q == NULL) q = prog + strlen(prog);
11987 while (p >= prog && *p != '/' && *p != '\\') p--;
11989 if(p == prog && *p == '"') p++;
11990 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11991 memcpy(buf, p, q - p);
11992 buf[q - p] = NULLCHAR;
12000 TimeControlTagValue()
12003 if (!appData.clockMode) {
12005 } else if (movesPerSession > 0) {
12006 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12007 } else if (timeIncrement == 0) {
12008 sprintf(buf, "%ld", timeControl/1000);
12010 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12012 return StrSave(buf);
12018 /* This routine is used only for certain modes */
12019 VariantClass v = gameInfo.variant;
12020 ClearGameInfo(&gameInfo);
12021 gameInfo.variant = v;
12023 switch (gameMode) {
12024 case MachinePlaysWhite:
12025 gameInfo.event = StrSave( appData.pgnEventHeader );
12026 gameInfo.site = StrSave(HostName());
12027 gameInfo.date = PGNDate();
12028 gameInfo.round = StrSave("-");
12029 gameInfo.white = StrSave(first.tidy);
12030 gameInfo.black = StrSave(UserName());
12031 gameInfo.timeControl = TimeControlTagValue();
12034 case MachinePlaysBlack:
12035 gameInfo.event = StrSave( appData.pgnEventHeader );
12036 gameInfo.site = StrSave(HostName());
12037 gameInfo.date = PGNDate();
12038 gameInfo.round = StrSave("-");
12039 gameInfo.white = StrSave(UserName());
12040 gameInfo.black = StrSave(first.tidy);
12041 gameInfo.timeControl = TimeControlTagValue();
12044 case TwoMachinesPlay:
12045 gameInfo.event = StrSave( appData.pgnEventHeader );
12046 gameInfo.site = StrSave(HostName());
12047 gameInfo.date = PGNDate();
12048 if (matchGame > 0) {
12050 sprintf(buf, "%d", matchGame);
12051 gameInfo.round = StrSave(buf);
12053 gameInfo.round = StrSave("-");
12055 if (first.twoMachinesColor[0] == 'w') {
12056 gameInfo.white = StrSave(first.tidy);
12057 gameInfo.black = StrSave(second.tidy);
12059 gameInfo.white = StrSave(second.tidy);
12060 gameInfo.black = StrSave(first.tidy);
12062 gameInfo.timeControl = TimeControlTagValue();
12066 gameInfo.event = StrSave("Edited game");
12067 gameInfo.site = StrSave(HostName());
12068 gameInfo.date = PGNDate();
12069 gameInfo.round = StrSave("-");
12070 gameInfo.white = StrSave("-");
12071 gameInfo.black = StrSave("-");
12075 gameInfo.event = StrSave("Edited position");
12076 gameInfo.site = StrSave(HostName());
12077 gameInfo.date = PGNDate();
12078 gameInfo.round = StrSave("-");
12079 gameInfo.white = StrSave("-");
12080 gameInfo.black = StrSave("-");
12083 case IcsPlayingWhite:
12084 case IcsPlayingBlack:
12089 case PlayFromGameFile:
12090 gameInfo.event = StrSave("Game from non-PGN file");
12091 gameInfo.site = StrSave(HostName());
12092 gameInfo.date = PGNDate();
12093 gameInfo.round = StrSave("-");
12094 gameInfo.white = StrSave("?");
12095 gameInfo.black = StrSave("?");
12104 ReplaceComment(index, text)
12110 while (*text == '\n') text++;
12111 len = strlen(text);
12112 while (len > 0 && text[len - 1] == '\n') len--;
12114 if (commentList[index] != NULL)
12115 free(commentList[index]);
12118 commentList[index] = NULL;
12121 commentList[index] = (char *) malloc(len + 2);
12122 strncpy(commentList[index], text, len);
12123 commentList[index][len] = '\n';
12124 commentList[index][len + 1] = NULLCHAR;
12137 if (ch == '\r') continue;
12139 } while (ch != '\0');
12143 AppendComment(index, text)
12150 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12153 while (*text == '\n') text++;
12154 len = strlen(text);
12155 while (len > 0 && text[len - 1] == '\n') len--;
12157 if (len == 0) return;
12159 if (commentList[index] != NULL) {
12160 old = commentList[index];
12161 oldlen = strlen(old);
12162 commentList[index] = (char *) malloc(oldlen + len + 2);
12163 strcpy(commentList[index], old);
12165 strncpy(&commentList[index][oldlen], text, len);
12166 commentList[index][oldlen + len] = '\n';
12167 commentList[index][oldlen + len + 1] = NULLCHAR;
12169 commentList[index] = (char *) malloc(len + 2);
12170 strncpy(commentList[index], text, len);
12171 commentList[index][len] = '\n';
12172 commentList[index][len + 1] = NULLCHAR;
12176 static char * FindStr( char * text, char * sub_text )
12178 char * result = strstr( text, sub_text );
12180 if( result != NULL ) {
12181 result += strlen( sub_text );
12187 /* [AS] Try to extract PV info from PGN comment */
12188 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12189 char *GetInfoFromComment( int index, char * text )
12193 if( text != NULL && index > 0 ) {
12196 int time = -1, sec = 0, deci;
12197 char * s_eval = FindStr( text, "[%eval " );
12198 char * s_emt = FindStr( text, "[%emt " );
12200 if( s_eval != NULL || s_emt != NULL ) {
12204 if( s_eval != NULL ) {
12205 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12209 if( delim != ']' ) {
12214 if( s_emt != NULL ) {
12218 /* We expect something like: [+|-]nnn.nn/dd */
12221 sep = strchr( text, '/' );
12222 if( sep == NULL || sep < (text+4) ) {
12226 time = -1; sec = -1; deci = -1;
12227 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12228 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12229 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12230 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12234 if( score_lo < 0 || score_lo >= 100 ) {
12238 if(sec >= 0) time = 600*time + 10*sec; else
12239 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12241 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12243 /* [HGM] PV time: now locate end of PV info */
12244 while( *++sep >= '0' && *sep <= '9'); // strip depth
12246 while( *++sep >= '0' && *sep <= '9'); // strip time
12248 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12250 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12251 while(*sep == ' ') sep++;
12262 pvInfoList[index-1].depth = depth;
12263 pvInfoList[index-1].score = score;
12264 pvInfoList[index-1].time = 10*time; // centi-sec
12270 SendToProgram(message, cps)
12272 ChessProgramState *cps;
12274 int count, outCount, error;
12277 if (cps->pr == NULL) return;
12280 if (appData.debugMode) {
12283 fprintf(debugFP, "%ld >%-6s: %s",
12284 SubtractTimeMarks(&now, &programStartTime),
12285 cps->which, message);
12288 count = strlen(message);
12289 outCount = OutputToProcess(cps->pr, message, count, &error);
12290 if (outCount < count && !exiting
12291 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12292 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12293 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12294 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12295 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12296 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12298 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12300 gameInfo.resultDetails = buf;
12302 DisplayFatalError(buf, error, 1);
12307 ReceiveFromProgram(isr, closure, message, count, error)
12308 InputSourceRef isr;
12316 ChessProgramState *cps = (ChessProgramState *)closure;
12318 if (isr != cps->isr) return; /* Killed intentionally */
12322 _("Error: %s chess program (%s) exited unexpectedly"),
12323 cps->which, cps->program);
12324 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12325 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12326 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12327 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12329 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12331 gameInfo.resultDetails = buf;
12333 RemoveInputSource(cps->isr);
12334 DisplayFatalError(buf, 0, 1);
12337 _("Error reading from %s chess program (%s)"),
12338 cps->which, cps->program);
12339 RemoveInputSource(cps->isr);
12341 /* [AS] Program is misbehaving badly... kill it */
12342 if( count == -2 ) {
12343 DestroyChildProcess( cps->pr, 9 );
12347 DisplayFatalError(buf, error, 1);
12352 if ((end_str = strchr(message, '\r')) != NULL)
12353 *end_str = NULLCHAR;
12354 if ((end_str = strchr(message, '\n')) != NULL)
12355 *end_str = NULLCHAR;
12357 if (appData.debugMode) {
12358 TimeMark now; int print = 1;
12359 char *quote = ""; char c; int i;
12361 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12362 char start = message[0];
12363 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12364 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12365 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12366 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12367 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12368 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12369 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12370 sscanf(message, "pong %c", &c)!=1 && start != '#')
12371 { quote = "# "; print = (appData.engineComments == 2); }
12372 message[0] = start; // restore original message
12376 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12377 SubtractTimeMarks(&now, &programStartTime), cps->which,
12383 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12384 if (appData.icsEngineAnalyze) {
12385 if (strstr(message, "whisper") != NULL ||
12386 strstr(message, "kibitz") != NULL ||
12387 strstr(message, "tellics") != NULL) return;
12390 HandleMachineMove(message, cps);
12395 SendTimeControl(cps, mps, tc, inc, sd, st)
12396 ChessProgramState *cps;
12397 int mps, inc, sd, st;
12403 if( timeControl_2 > 0 ) {
12404 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12405 tc = timeControl_2;
12408 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12409 inc /= cps->timeOdds;
12410 st /= cps->timeOdds;
12412 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12415 /* Set exact time per move, normally using st command */
12416 if (cps->stKludge) {
12417 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12419 if (seconds == 0) {
12420 sprintf(buf, "level 1 %d\n", st/60);
12422 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12425 sprintf(buf, "st %d\n", st);
12428 /* Set conventional or incremental time control, using level command */
12429 if (seconds == 0) {
12430 /* Note old gnuchess bug -- minutes:seconds used to not work.
12431 Fixed in later versions, but still avoid :seconds
12432 when seconds is 0. */
12433 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12435 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12436 seconds, inc/1000);
12439 SendToProgram(buf, cps);
12441 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12442 /* Orthogonally, limit search to given depth */
12444 if (cps->sdKludge) {
12445 sprintf(buf, "depth\n%d\n", sd);
12447 sprintf(buf, "sd %d\n", sd);
12449 SendToProgram(buf, cps);
12452 if(cps->nps > 0) { /* [HGM] nps */
12453 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12455 sprintf(buf, "nps %d\n", cps->nps);
12456 SendToProgram(buf, cps);
12461 ChessProgramState *WhitePlayer()
12462 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12464 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12465 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12471 SendTimeRemaining(cps, machineWhite)
12472 ChessProgramState *cps;
12473 int /*boolean*/ machineWhite;
12475 char message[MSG_SIZ];
12478 /* Note: this routine must be called when the clocks are stopped
12479 or when they have *just* been set or switched; otherwise
12480 it will be off by the time since the current tick started.
12482 if (machineWhite) {
12483 time = whiteTimeRemaining / 10;
12484 otime = blackTimeRemaining / 10;
12486 time = blackTimeRemaining / 10;
12487 otime = whiteTimeRemaining / 10;
12489 /* [HGM] translate opponent's time by time-odds factor */
12490 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12491 if (appData.debugMode) {
12492 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12495 if (time <= 0) time = 1;
12496 if (otime <= 0) otime = 1;
12498 sprintf(message, "time %ld\n", time);
12499 SendToProgram(message, cps);
12501 sprintf(message, "otim %ld\n", otime);
12502 SendToProgram(message, cps);
12506 BoolFeature(p, name, loc, cps)
12510 ChessProgramState *cps;
12513 int len = strlen(name);
12515 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12517 sscanf(*p, "%d", &val);
12519 while (**p && **p != ' ') (*p)++;
12520 sprintf(buf, "accepted %s\n", name);
12521 SendToProgram(buf, cps);
12528 IntFeature(p, name, loc, cps)
12532 ChessProgramState *cps;
12535 int len = strlen(name);
12536 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12538 sscanf(*p, "%d", loc);
12539 while (**p && **p != ' ') (*p)++;
12540 sprintf(buf, "accepted %s\n", name);
12541 SendToProgram(buf, cps);
12548 StringFeature(p, name, loc, cps)
12552 ChessProgramState *cps;
12555 int len = strlen(name);
12556 if (strncmp((*p), name, len) == 0
12557 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12559 sscanf(*p, "%[^\"]", loc);
12560 while (**p && **p != '\"') (*p)++;
12561 if (**p == '\"') (*p)++;
12562 sprintf(buf, "accepted %s\n", name);
12563 SendToProgram(buf, cps);
12570 ParseOption(Option *opt, ChessProgramState *cps)
12571 // [HGM] options: process the string that defines an engine option, and determine
12572 // name, type, default value, and allowed value range
12574 char *p, *q, buf[MSG_SIZ];
12575 int n, min = (-1)<<31, max = 1<<31, def;
12577 if(p = strstr(opt->name, " -spin ")) {
12578 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12579 if(max < min) max = min; // enforce consistency
12580 if(def < min) def = min;
12581 if(def > max) def = max;
12586 } else if((p = strstr(opt->name, " -slider "))) {
12587 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12588 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12589 if(max < min) max = min; // enforce consistency
12590 if(def < min) def = min;
12591 if(def > max) def = max;
12595 opt->type = Spin; // Slider;
12596 } else if((p = strstr(opt->name, " -string "))) {
12597 opt->textValue = p+9;
12598 opt->type = TextBox;
12599 } else if((p = strstr(opt->name, " -file "))) {
12600 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12601 opt->textValue = p+7;
12602 opt->type = TextBox; // FileName;
12603 } else if((p = strstr(opt->name, " -path "))) {
12604 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12605 opt->textValue = p+7;
12606 opt->type = TextBox; // PathName;
12607 } else if(p = strstr(opt->name, " -check ")) {
12608 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12609 opt->value = (def != 0);
12610 opt->type = CheckBox;
12611 } else if(p = strstr(opt->name, " -combo ")) {
12612 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12613 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12614 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12615 opt->value = n = 0;
12616 while(q = StrStr(q, " /// ")) {
12617 n++; *q = 0; // count choices, and null-terminate each of them
12619 if(*q == '*') { // remember default, which is marked with * prefix
12623 cps->comboList[cps->comboCnt++] = q;
12625 cps->comboList[cps->comboCnt++] = NULL;
12627 opt->type = ComboBox;
12628 } else if(p = strstr(opt->name, " -button")) {
12629 opt->type = Button;
12630 } else if(p = strstr(opt->name, " -save")) {
12631 opt->type = SaveButton;
12632 } else return FALSE;
12633 *p = 0; // terminate option name
12634 // now look if the command-line options define a setting for this engine option.
12635 if(cps->optionSettings && cps->optionSettings[0])
12636 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12637 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12638 sprintf(buf, "option %s", p);
12639 if(p = strstr(buf, ",")) *p = 0;
12641 SendToProgram(buf, cps);
12647 FeatureDone(cps, val)
12648 ChessProgramState* cps;
12651 DelayedEventCallback cb = GetDelayedEvent();
12652 if ((cb == InitBackEnd3 && cps == &first) ||
12653 (cb == TwoMachinesEventIfReady && cps == &second)) {
12654 CancelDelayedEvent();
12655 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12657 cps->initDone = val;
12660 /* Parse feature command from engine */
12662 ParseFeatures(args, cps)
12664 ChessProgramState *cps;
12672 while (*p == ' ') p++;
12673 if (*p == NULLCHAR) return;
12675 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12676 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12677 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12678 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12679 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12680 if (BoolFeature(&p, "reuse", &val, cps)) {
12681 /* Engine can disable reuse, but can't enable it if user said no */
12682 if (!val) cps->reuse = FALSE;
12685 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12686 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12687 if (gameMode == TwoMachinesPlay) {
12688 DisplayTwoMachinesTitle();
12694 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12695 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12696 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12697 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12698 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12699 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12700 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12701 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12702 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12703 if (IntFeature(&p, "done", &val, cps)) {
12704 FeatureDone(cps, val);
12707 /* Added by Tord: */
12708 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12709 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12710 /* End of additions by Tord */
12712 /* [HGM] added features: */
12713 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12714 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12715 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12716 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12717 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12718 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12719 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12720 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12721 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12722 SendToProgram(buf, cps);
12725 if(cps->nrOptions >= MAX_OPTIONS) {
12727 sprintf(buf, "%s engine has too many options\n", cps->which);
12728 DisplayError(buf, 0);
12732 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12733 /* End of additions by HGM */
12735 /* unknown feature: complain and skip */
12737 while (*q && *q != '=') q++;
12738 sprintf(buf, "rejected %.*s\n", q-p, p);
12739 SendToProgram(buf, cps);
12745 while (*p && *p != '\"') p++;
12746 if (*p == '\"') p++;
12748 while (*p && *p != ' ') p++;
12756 PeriodicUpdatesEvent(newState)
12759 if (newState == appData.periodicUpdates)
12762 appData.periodicUpdates=newState;
12764 /* Display type changes, so update it now */
12767 /* Get the ball rolling again... */
12769 AnalysisPeriodicEvent(1);
12770 StartAnalysisClock();
12775 PonderNextMoveEvent(newState)
12778 if (newState == appData.ponderNextMove) return;
12779 if (gameMode == EditPosition) EditPositionDone();
12781 SendToProgram("hard\n", &first);
12782 if (gameMode == TwoMachinesPlay) {
12783 SendToProgram("hard\n", &second);
12786 SendToProgram("easy\n", &first);
12787 thinkOutput[0] = NULLCHAR;
12788 if (gameMode == TwoMachinesPlay) {
12789 SendToProgram("easy\n", &second);
12792 appData.ponderNextMove = newState;
12796 NewSettingEvent(option, command, value)
12802 if (gameMode == EditPosition) EditPositionDone();
12803 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12804 SendToProgram(buf, &first);
12805 if (gameMode == TwoMachinesPlay) {
12806 SendToProgram(buf, &second);
12811 ShowThinkingEvent()
12812 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12814 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12815 int newState = appData.showThinking
12816 // [HGM] thinking: other features now need thinking output as well
12817 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12819 if (oldState == newState) return;
12820 oldState = newState;
12821 if (gameMode == EditPosition) EditPositionDone();
12823 SendToProgram("post\n", &first);
12824 if (gameMode == TwoMachinesPlay) {
12825 SendToProgram("post\n", &second);
12828 SendToProgram("nopost\n", &first);
12829 thinkOutput[0] = NULLCHAR;
12830 if (gameMode == TwoMachinesPlay) {
12831 SendToProgram("nopost\n", &second);
12834 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12838 AskQuestionEvent(title, question, replyPrefix, which)
12839 char *title; char *question; char *replyPrefix; char *which;
12841 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12842 if (pr == NoProc) return;
12843 AskQuestion(title, question, replyPrefix, pr);
12847 DisplayMove(moveNumber)
12850 char message[MSG_SIZ];
12852 char cpThinkOutput[MSG_SIZ];
12854 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12856 if (moveNumber == forwardMostMove - 1 ||
12857 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12859 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12861 if (strchr(cpThinkOutput, '\n')) {
12862 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12865 *cpThinkOutput = NULLCHAR;
12868 /* [AS] Hide thinking from human user */
12869 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12870 *cpThinkOutput = NULLCHAR;
12871 if( thinkOutput[0] != NULLCHAR ) {
12874 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12875 cpThinkOutput[i] = '.';
12877 cpThinkOutput[i] = NULLCHAR;
12878 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12882 if (moveNumber == forwardMostMove - 1 &&
12883 gameInfo.resultDetails != NULL) {
12884 if (gameInfo.resultDetails[0] == NULLCHAR) {
12885 sprintf(res, " %s", PGNResult(gameInfo.result));
12887 sprintf(res, " {%s} %s",
12888 gameInfo.resultDetails, PGNResult(gameInfo.result));
12894 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12895 DisplayMessage(res, cpThinkOutput);
12897 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12898 WhiteOnMove(moveNumber) ? " " : ".. ",
12899 parseList[moveNumber], res);
12900 DisplayMessage(message, cpThinkOutput);
12905 DisplayAnalysisText(text)
12910 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12911 || appData.icsEngineAnalyze) {
12912 sprintf(buf, "Analysis (%s)", first.tidy);
12913 AnalysisPopUp(buf, text);
12921 while (*str && isspace(*str)) ++str;
12922 while (*str && !isspace(*str)) ++str;
12923 if (!*str) return 1;
12924 while (*str && isspace(*str)) ++str;
12925 if (!*str) return 1;
12933 char lst[MSG_SIZ / 2];
12935 static char *xtra[] = { "", " (--)", " (++)" };
12938 if (programStats.time == 0) {
12939 programStats.time = 1;
12942 if (programStats.got_only_move) {
12943 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12945 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12947 nps = (u64ToDouble(programStats.nodes) /
12948 ((double)programStats.time /100.0));
12950 cs = programStats.time % 100;
12951 s = programStats.time / 100;
12957 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12958 if (programStats.move_name[0] != NULLCHAR) {
12959 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12960 programStats.depth,
12961 programStats.nr_moves-programStats.moves_left,
12962 programStats.nr_moves, programStats.move_name,
12963 ((float)programStats.score)/100.0, lst,
12964 only_one_move(lst)?
12965 xtra[programStats.got_fail] : "",
12966 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12968 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12969 programStats.depth,
12970 programStats.nr_moves-programStats.moves_left,
12971 programStats.nr_moves, ((float)programStats.score)/100.0,
12973 only_one_move(lst)?
12974 xtra[programStats.got_fail] : "",
12975 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12978 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12979 programStats.depth,
12980 ((float)programStats.score)/100.0,
12982 only_one_move(lst)?
12983 xtra[programStats.got_fail] : "",
12984 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12987 DisplayAnalysisText(buf);
12991 DisplayComment(moveNumber, text)
12995 char title[MSG_SIZ];
12996 char buf[8000]; // comment can be long!
12999 if( appData.autoDisplayComment ) {
13000 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13001 strcpy(title, "Comment");
13003 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13004 WhiteOnMove(moveNumber) ? " " : ".. ",
13005 parseList[moveNumber]);
13007 // [HGM] PV info: display PV info together with (or as) comment
13008 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13009 if(text == NULL) text = "";
13010 score = pvInfoList[moveNumber].score;
13011 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13012 depth, (pvInfoList[moveNumber].time+50)/100, text);
13015 } else title[0] = 0;
13018 CommentPopUp(title, text);
13021 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13022 * might be busy thinking or pondering. It can be omitted if your
13023 * gnuchess is configured to stop thinking immediately on any user
13024 * input. However, that gnuchess feature depends on the FIONREAD
13025 * ioctl, which does not work properly on some flavors of Unix.
13029 ChessProgramState *cps;
13032 if (!cps->useSigint) return;
13033 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13034 switch (gameMode) {
13035 case MachinePlaysWhite:
13036 case MachinePlaysBlack:
13037 case TwoMachinesPlay:
13038 case IcsPlayingWhite:
13039 case IcsPlayingBlack:
13042 /* Skip if we know it isn't thinking */
13043 if (!cps->maybeThinking) return;
13044 if (appData.debugMode)
13045 fprintf(debugFP, "Interrupting %s\n", cps->which);
13046 InterruptChildProcess(cps->pr);
13047 cps->maybeThinking = FALSE;
13052 #endif /*ATTENTION*/
13058 if (whiteTimeRemaining <= 0) {
13061 if (appData.icsActive) {
13062 if (appData.autoCallFlag &&
13063 gameMode == IcsPlayingBlack && !blackFlag) {
13064 SendToICS(ics_prefix);
13065 SendToICS("flag\n");
13069 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13071 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13072 if (appData.autoCallFlag) {
13073 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13080 if (blackTimeRemaining <= 0) {
13083 if (appData.icsActive) {
13084 if (appData.autoCallFlag &&
13085 gameMode == IcsPlayingWhite && !whiteFlag) {
13086 SendToICS(ics_prefix);
13087 SendToICS("flag\n");
13091 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13093 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13094 if (appData.autoCallFlag) {
13095 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13108 if (!appData.clockMode || appData.icsActive ||
13109 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13112 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13114 if ( !WhiteOnMove(forwardMostMove) )
13115 /* White made time control */
13116 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13117 /* [HGM] time odds: correct new time quota for time odds! */
13118 / WhitePlayer()->timeOdds;
13120 /* Black made time control */
13121 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13122 / WhitePlayer()->other->timeOdds;
13126 DisplayBothClocks()
13128 int wom = gameMode == EditPosition ?
13129 !blackPlaysFirst : WhiteOnMove(currentMove);
13130 DisplayWhiteClock(whiteTimeRemaining, wom);
13131 DisplayBlackClock(blackTimeRemaining, !wom);
13135 /* Timekeeping seems to be a portability nightmare. I think everyone
13136 has ftime(), but I'm really not sure, so I'm including some ifdefs
13137 to use other calls if you don't. Clocks will be less accurate if
13138 you have neither ftime nor gettimeofday.
13141 /* VS 2008 requires the #include outside of the function */
13142 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13143 #include <sys/timeb.h>
13146 /* Get the current time as a TimeMark */
13151 #if HAVE_GETTIMEOFDAY
13153 struct timeval timeVal;
13154 struct timezone timeZone;
13156 gettimeofday(&timeVal, &timeZone);
13157 tm->sec = (long) timeVal.tv_sec;
13158 tm->ms = (int) (timeVal.tv_usec / 1000L);
13160 #else /*!HAVE_GETTIMEOFDAY*/
13163 // include <sys/timeb.h> / moved to just above start of function
13164 struct timeb timeB;
13167 tm->sec = (long) timeB.time;
13168 tm->ms = (int) timeB.millitm;
13170 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13171 tm->sec = (long) time(NULL);
13177 /* Return the difference in milliseconds between two
13178 time marks. We assume the difference will fit in a long!
13181 SubtractTimeMarks(tm2, tm1)
13182 TimeMark *tm2, *tm1;
13184 return 1000L*(tm2->sec - tm1->sec) +
13185 (long) (tm2->ms - tm1->ms);
13190 * Code to manage the game clocks.
13192 * In tournament play, black starts the clock and then white makes a move.
13193 * We give the human user a slight advantage if he is playing white---the
13194 * clocks don't run until he makes his first move, so it takes zero time.
13195 * Also, we don't account for network lag, so we could get out of sync
13196 * with GNU Chess's clock -- but then, referees are always right.
13199 static TimeMark tickStartTM;
13200 static long intendedTickLength;
13203 NextTickLength(timeRemaining)
13204 long timeRemaining;
13206 long nominalTickLength, nextTickLength;
13208 if (timeRemaining > 0L && timeRemaining <= 10000L)
13209 nominalTickLength = 100L;
13211 nominalTickLength = 1000L;
13212 nextTickLength = timeRemaining % nominalTickLength;
13213 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13215 return nextTickLength;
13218 /* Adjust clock one minute up or down */
13220 AdjustClock(Boolean which, int dir)
13222 if(which) blackTimeRemaining += 60000*dir;
13223 else whiteTimeRemaining += 60000*dir;
13224 DisplayBothClocks();
13227 /* Stop clocks and reset to a fresh time control */
13231 (void) StopClockTimer();
13232 if (appData.icsActive) {
13233 whiteTimeRemaining = blackTimeRemaining = 0;
13234 } else { /* [HGM] correct new time quote for time odds */
13235 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13236 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13238 if (whiteFlag || blackFlag) {
13240 whiteFlag = blackFlag = FALSE;
13242 DisplayBothClocks();
13245 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13247 /* Decrement running clock by amount of time that has passed */
13251 long timeRemaining;
13252 long lastTickLength, fudge;
13255 if (!appData.clockMode) return;
13256 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13260 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13262 /* Fudge if we woke up a little too soon */
13263 fudge = intendedTickLength - lastTickLength;
13264 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13266 if (WhiteOnMove(forwardMostMove)) {
13267 if(whiteNPS >= 0) lastTickLength = 0;
13268 timeRemaining = whiteTimeRemaining -= lastTickLength;
13269 DisplayWhiteClock(whiteTimeRemaining - fudge,
13270 WhiteOnMove(currentMove));
13272 if(blackNPS >= 0) lastTickLength = 0;
13273 timeRemaining = blackTimeRemaining -= lastTickLength;
13274 DisplayBlackClock(blackTimeRemaining - fudge,
13275 !WhiteOnMove(currentMove));
13278 if (CheckFlags()) return;
13281 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13282 StartClockTimer(intendedTickLength);
13284 /* if the time remaining has fallen below the alarm threshold, sound the
13285 * alarm. if the alarm has sounded and (due to a takeback or time control
13286 * with increment) the time remaining has increased to a level above the
13287 * threshold, reset the alarm so it can sound again.
13290 if (appData.icsActive && appData.icsAlarm) {
13292 /* make sure we are dealing with the user's clock */
13293 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13294 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13297 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13298 alarmSounded = FALSE;
13299 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13301 alarmSounded = TRUE;
13307 /* A player has just moved, so stop the previously running
13308 clock and (if in clock mode) start the other one.
13309 We redisplay both clocks in case we're in ICS mode, because
13310 ICS gives us an update to both clocks after every move.
13311 Note that this routine is called *after* forwardMostMove
13312 is updated, so the last fractional tick must be subtracted
13313 from the color that is *not* on move now.
13318 long lastTickLength;
13320 int flagged = FALSE;
13324 if (StopClockTimer() && appData.clockMode) {
13325 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13326 if (WhiteOnMove(forwardMostMove)) {
13327 if(blackNPS >= 0) lastTickLength = 0;
13328 blackTimeRemaining -= lastTickLength;
13329 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13330 // if(pvInfoList[forwardMostMove-1].time == -1)
13331 pvInfoList[forwardMostMove-1].time = // use GUI time
13332 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13334 if(whiteNPS >= 0) lastTickLength = 0;
13335 whiteTimeRemaining -= lastTickLength;
13336 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13337 // if(pvInfoList[forwardMostMove-1].time == -1)
13338 pvInfoList[forwardMostMove-1].time =
13339 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13341 flagged = CheckFlags();
13343 CheckTimeControl();
13345 if (flagged || !appData.clockMode) return;
13347 switch (gameMode) {
13348 case MachinePlaysBlack:
13349 case MachinePlaysWhite:
13350 case BeginningOfGame:
13351 if (pausing) return;
13355 case PlayFromGameFile:
13364 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13365 whiteTimeRemaining : blackTimeRemaining);
13366 StartClockTimer(intendedTickLength);
13370 /* Stop both clocks */
13374 long lastTickLength;
13377 if (!StopClockTimer()) return;
13378 if (!appData.clockMode) return;
13382 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13383 if (WhiteOnMove(forwardMostMove)) {
13384 if(whiteNPS >= 0) lastTickLength = 0;
13385 whiteTimeRemaining -= lastTickLength;
13386 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13388 if(blackNPS >= 0) lastTickLength = 0;
13389 blackTimeRemaining -= lastTickLength;
13390 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13395 /* Start clock of player on move. Time may have been reset, so
13396 if clock is already running, stop and restart it. */
13400 (void) StopClockTimer(); /* in case it was running already */
13401 DisplayBothClocks();
13402 if (CheckFlags()) return;
13404 if (!appData.clockMode) return;
13405 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13407 GetTimeMark(&tickStartTM);
13408 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13409 whiteTimeRemaining : blackTimeRemaining);
13411 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13412 whiteNPS = blackNPS = -1;
13413 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13414 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13415 whiteNPS = first.nps;
13416 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13417 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13418 blackNPS = first.nps;
13419 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13420 whiteNPS = second.nps;
13421 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13422 blackNPS = second.nps;
13423 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13425 StartClockTimer(intendedTickLength);
13432 long second, minute, hour, day;
13434 static char buf[32];
13436 if (ms > 0 && ms <= 9900) {
13437 /* convert milliseconds to tenths, rounding up */
13438 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13440 sprintf(buf, " %03.1f ", tenths/10.0);
13444 /* convert milliseconds to seconds, rounding up */
13445 /* use floating point to avoid strangeness of integer division
13446 with negative dividends on many machines */
13447 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13454 day = second / (60 * 60 * 24);
13455 second = second % (60 * 60 * 24);
13456 hour = second / (60 * 60);
13457 second = second % (60 * 60);
13458 minute = second / 60;
13459 second = second % 60;
13462 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13463 sign, day, hour, minute, second);
13465 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13467 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13474 * This is necessary because some C libraries aren't ANSI C compliant yet.
13477 StrStr(string, match)
13478 char *string, *match;
13482 length = strlen(match);
13484 for (i = strlen(string) - length; i >= 0; i--, string++)
13485 if (!strncmp(match, string, length))
13492 StrCaseStr(string, match)
13493 char *string, *match;
13497 length = strlen(match);
13499 for (i = strlen(string) - length; i >= 0; i--, string++) {
13500 for (j = 0; j < length; j++) {
13501 if (ToLower(match[j]) != ToLower(string[j]))
13504 if (j == length) return string;
13518 c1 = ToLower(*s1++);
13519 c2 = ToLower(*s2++);
13520 if (c1 > c2) return 1;
13521 if (c1 < c2) return -1;
13522 if (c1 == NULLCHAR) return 0;
13531 return isupper(c) ? tolower(c) : c;
13539 return islower(c) ? toupper(c) : c;
13541 #endif /* !_amigados */
13549 if ((ret = (char *) malloc(strlen(s) + 1))) {
13556 StrSavePtr(s, savePtr)
13557 char *s, **savePtr;
13562 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13563 strcpy(*savePtr, s);
13575 clock = time((time_t *)NULL);
13576 tm = localtime(&clock);
13577 sprintf(buf, "%04d.%02d.%02d",
13578 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13579 return StrSave(buf);
13584 PositionToFEN(move, overrideCastling)
13586 char *overrideCastling;
13588 int i, j, fromX, fromY, toX, toY;
13595 whiteToPlay = (gameMode == EditPosition) ?
13596 !blackPlaysFirst : (move % 2 == 0);
13599 /* Piece placement data */
13600 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13602 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13603 if (boards[move][i][j] == EmptySquare) {
13605 } else { ChessSquare piece = boards[move][i][j];
13606 if (emptycount > 0) {
13607 if(emptycount<10) /* [HGM] can be >= 10 */
13608 *p++ = '0' + emptycount;
13609 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13612 if(PieceToChar(piece) == '+') {
13613 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13615 piece = (ChessSquare)(DEMOTED piece);
13617 *p++ = PieceToChar(piece);
13619 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13620 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13625 if (emptycount > 0) {
13626 if(emptycount<10) /* [HGM] can be >= 10 */
13627 *p++ = '0' + emptycount;
13628 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13635 /* [HGM] print Crazyhouse or Shogi holdings */
13636 if( gameInfo.holdingsWidth ) {
13637 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13639 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13640 piece = boards[move][i][BOARD_WIDTH-1];
13641 if( piece != EmptySquare )
13642 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13643 *p++ = PieceToChar(piece);
13645 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13646 piece = boards[move][BOARD_HEIGHT-i-1][0];
13647 if( piece != EmptySquare )
13648 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13649 *p++ = PieceToChar(piece);
13652 if( q == p ) *p++ = '-';
13658 *p++ = whiteToPlay ? 'w' : 'b';
13661 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13662 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13664 if(nrCastlingRights) {
13666 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13667 /* [HGM] write directly from rights */
13668 if(castlingRights[move][2] >= 0 &&
13669 castlingRights[move][0] >= 0 )
13670 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13671 if(castlingRights[move][2] >= 0 &&
13672 castlingRights[move][1] >= 0 )
13673 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13674 if(castlingRights[move][5] >= 0 &&
13675 castlingRights[move][3] >= 0 )
13676 *p++ = castlingRights[move][3] + AAA;
13677 if(castlingRights[move][5] >= 0 &&
13678 castlingRights[move][4] >= 0 )
13679 *p++ = castlingRights[move][4] + AAA;
13682 /* [HGM] write true castling rights */
13683 if( nrCastlingRights == 6 ) {
13684 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13685 castlingRights[move][2] >= 0 ) *p++ = 'K';
13686 if(castlingRights[move][1] == BOARD_LEFT &&
13687 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13688 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13689 castlingRights[move][5] >= 0 ) *p++ = 'k';
13690 if(castlingRights[move][4] == BOARD_LEFT &&
13691 castlingRights[move][5] >= 0 ) *p++ = 'q';
13694 if (q == p) *p++ = '-'; /* No castling rights */
13698 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13699 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13700 /* En passant target square */
13701 if (move > backwardMostMove) {
13702 fromX = moveList[move - 1][0] - AAA;
13703 fromY = moveList[move - 1][1] - ONE;
13704 toX = moveList[move - 1][2] - AAA;
13705 toY = moveList[move - 1][3] - ONE;
13706 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13707 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13708 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13710 /* 2-square pawn move just happened */
13712 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13723 /* [HGM] find reversible plies */
13724 { int i = 0, j=move;
13726 if (appData.debugMode) { int k;
13727 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13728 for(k=backwardMostMove; k<=forwardMostMove; k++)
13729 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13733 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13734 if( j == backwardMostMove ) i += initialRulePlies;
13735 sprintf(p, "%d ", i);
13736 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13738 /* Fullmove number */
13739 sprintf(p, "%d", (move / 2) + 1);
13741 return StrSave(buf);
13745 ParseFEN(board, blackPlaysFirst, fen)
13747 int *blackPlaysFirst;
13757 /* [HGM] by default clear Crazyhouse holdings, if present */
13758 if(gameInfo.holdingsWidth) {
13759 for(i=0; i<BOARD_HEIGHT; i++) {
13760 board[i][0] = EmptySquare; /* black holdings */
13761 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13762 board[i][1] = (ChessSquare) 0; /* black counts */
13763 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13767 /* Piece placement data */
13768 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13771 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13772 if (*p == '/') p++;
13773 emptycount = gameInfo.boardWidth - j;
13774 while (emptycount--)
13775 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13777 #if(BOARD_SIZE >= 10)
13778 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13779 p++; emptycount=10;
13780 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13781 while (emptycount--)
13782 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13784 } else if (isdigit(*p)) {
13785 emptycount = *p++ - '0';
13786 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13787 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13788 while (emptycount--)
13789 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13790 } else if (*p == '+' || isalpha(*p)) {
13791 if (j >= gameInfo.boardWidth) return FALSE;
13793 piece = CharToPiece(*++p);
13794 if(piece == EmptySquare) return FALSE; /* unknown piece */
13795 piece = (ChessSquare) (PROMOTED piece ); p++;
13796 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13797 } else piece = CharToPiece(*p++);
13799 if(piece==EmptySquare) return FALSE; /* unknown piece */
13800 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13801 piece = (ChessSquare) (PROMOTED piece);
13802 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13805 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13811 while (*p == '/' || *p == ' ') p++;
13813 /* [HGM] look for Crazyhouse holdings here */
13814 while(*p==' ') p++;
13815 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13817 if(*p == '-' ) *p++; /* empty holdings */ else {
13818 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13819 /* if we would allow FEN reading to set board size, we would */
13820 /* have to add holdings and shift the board read so far here */
13821 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13823 if((int) piece >= (int) BlackPawn ) {
13824 i = (int)piece - (int)BlackPawn;
13825 i = PieceToNumber((ChessSquare)i);
13826 if( i >= gameInfo.holdingsSize ) return FALSE;
13827 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13828 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13830 i = (int)piece - (int)WhitePawn;
13831 i = PieceToNumber((ChessSquare)i);
13832 if( i >= gameInfo.holdingsSize ) return FALSE;
13833 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13834 board[i][BOARD_WIDTH-2]++; /* black holdings */
13838 if(*p == ']') *p++;
13841 while(*p == ' ') p++;
13846 *blackPlaysFirst = FALSE;
13849 *blackPlaysFirst = TRUE;
13855 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13856 /* return the extra info in global variiables */
13858 /* set defaults in case FEN is incomplete */
13859 FENepStatus = EP_UNKNOWN;
13860 for(i=0; i<nrCastlingRights; i++ ) {
13861 FENcastlingRights[i] =
13862 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13863 } /* assume possible unless obviously impossible */
13864 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13865 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13866 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13867 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13868 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13869 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13872 while(*p==' ') p++;
13873 if(nrCastlingRights) {
13874 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13875 /* castling indicator present, so default becomes no castlings */
13876 for(i=0; i<nrCastlingRights; i++ ) {
13877 FENcastlingRights[i] = -1;
13880 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13881 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13882 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13883 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13884 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13886 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13887 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13888 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13892 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13893 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13894 FENcastlingRights[2] = whiteKingFile;
13897 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13898 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13899 FENcastlingRights[2] = whiteKingFile;
13902 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13903 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13904 FENcastlingRights[5] = blackKingFile;
13907 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13908 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13909 FENcastlingRights[5] = blackKingFile;
13912 default: /* FRC castlings */
13913 if(c >= 'a') { /* black rights */
13914 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13915 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13916 if(i == BOARD_RGHT) break;
13917 FENcastlingRights[5] = i;
13919 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13920 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13922 FENcastlingRights[3] = c;
13924 FENcastlingRights[4] = c;
13925 } else { /* white rights */
13926 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13927 if(board[0][i] == WhiteKing) break;
13928 if(i == BOARD_RGHT) break;
13929 FENcastlingRights[2] = i;
13930 c -= AAA - 'a' + 'A';
13931 if(board[0][c] >= WhiteKing) break;
13933 FENcastlingRights[0] = c;
13935 FENcastlingRights[1] = c;
13939 if (appData.debugMode) {
13940 fprintf(debugFP, "FEN castling rights:");
13941 for(i=0; i<nrCastlingRights; i++)
13942 fprintf(debugFP, " %d", FENcastlingRights[i]);
13943 fprintf(debugFP, "\n");
13946 while(*p==' ') p++;
13949 /* read e.p. field in games that know e.p. capture */
13950 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13951 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13953 p++; FENepStatus = EP_NONE;
13955 char c = *p++ - AAA;
13957 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13958 if(*p >= '0' && *p <='9') *p++;
13964 if(sscanf(p, "%d", &i) == 1) {
13965 FENrulePlies = i; /* 50-move ply counter */
13966 /* (The move number is still ignored) */
13973 EditPositionPasteFEN(char *fen)
13976 Board initial_position;
13978 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13979 DisplayError(_("Bad FEN position in clipboard"), 0);
13982 int savedBlackPlaysFirst = blackPlaysFirst;
13983 EditPositionEvent();
13984 blackPlaysFirst = savedBlackPlaysFirst;
13985 CopyBoard(boards[0], initial_position);
13986 /* [HGM] copy FEN attributes as well */
13988 initialRulePlies = FENrulePlies;
13989 epStatus[0] = FENepStatus;
13990 for( i=0; i<nrCastlingRights; i++ )
13991 castlingRights[0][i] = FENcastlingRights[i];
13993 EditPositionDone();
13994 DisplayBothClocks();
13995 DrawPosition(FALSE, boards[currentMove]);