2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
472 int mute; // mute all sounds
474 ChessSquare FIDEArray[2][BOARD_SIZE] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499 BlackKing, BlackBishop, BlackKnight, BlackRook }
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
529 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
534 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
536 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
541 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
543 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
549 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
551 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
554 #define GothicArray CapablancaArray
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
560 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
562 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
565 #define FalconArray CapablancaArray
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
587 Board initialPosition;
590 /* Convert str to a rating. Checks for special cases of "----",
592 "++++", etc. Also strips ()'s */
594 string_to_rating(str)
597 while(*str && !isdigit(*str)) ++str;
599 return 0; /* One of the special "no rating" cases */
607 /* Init programStats */
608 programStats.movelist[0] = 0;
609 programStats.depth = 0;
610 programStats.nr_moves = 0;
611 programStats.moves_left = 0;
612 programStats.nodes = 0;
613 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
614 programStats.score = 0;
615 programStats.got_only_move = 0;
616 programStats.got_fail = 0;
617 programStats.line_is_book = 0;
623 int matched, min, sec;
625 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
627 GetTimeMark(&programStartTime);
628 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
631 programStats.ok_to_send = 1;
632 programStats.seen_stat = 0;
635 * Initialize game list
641 * Internet chess server status
643 if (appData.icsActive) {
644 appData.matchMode = FALSE;
645 appData.matchGames = 0;
647 appData.noChessProgram = !appData.zippyPlay;
649 appData.zippyPlay = FALSE;
650 appData.zippyTalk = FALSE;
651 appData.noChessProgram = TRUE;
653 if (*appData.icsHelper != NULLCHAR) {
654 appData.useTelnet = TRUE;
655 appData.telnetProgram = appData.icsHelper;
658 appData.zippyTalk = appData.zippyPlay = FALSE;
661 /* [AS] Initialize pv info list [HGM] and game state */
665 for( i=0; i<MAX_MOVES; i++ ) {
666 pvInfoList[i].depth = -1;
668 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
673 * Parse timeControl resource
675 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676 appData.movesPerSession)) {
678 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679 DisplayFatalError(buf, 0, 2);
683 * Parse searchTime resource
685 if (*appData.searchTime != NULLCHAR) {
686 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
688 searchTime = min * 60;
689 } else if (matched == 2) {
690 searchTime = min * 60 + sec;
693 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694 DisplayFatalError(buf, 0, 2);
698 /* [AS] Adjudication threshold */
699 adjudicateLossThreshold = appData.adjudicateLossThreshold;
701 first.which = "first";
702 second.which = "second";
703 first.maybeThinking = second.maybeThinking = FALSE;
704 first.pr = second.pr = NoProc;
705 first.isr = second.isr = NULL;
706 first.sendTime = second.sendTime = 2;
707 first.sendDrawOffers = 1;
708 if (appData.firstPlaysBlack) {
709 first.twoMachinesColor = "black\n";
710 second.twoMachinesColor = "white\n";
712 first.twoMachinesColor = "white\n";
713 second.twoMachinesColor = "black\n";
715 first.program = appData.firstChessProgram;
716 second.program = appData.secondChessProgram;
717 first.host = appData.firstHost;
718 second.host = appData.secondHost;
719 first.dir = appData.firstDirectory;
720 second.dir = appData.secondDirectory;
721 first.other = &second;
722 second.other = &first;
723 first.initString = appData.initString;
724 second.initString = appData.secondInitString;
725 first.computerString = appData.firstComputerString;
726 second.computerString = appData.secondComputerString;
727 first.useSigint = second.useSigint = TRUE;
728 first.useSigterm = second.useSigterm = TRUE;
729 first.reuse = appData.reuseFirst;
730 second.reuse = appData.reuseSecond;
731 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
732 second.nps = appData.secondNPS;
733 first.useSetboard = second.useSetboard = FALSE;
734 first.useSAN = second.useSAN = FALSE;
735 first.usePing = second.usePing = FALSE;
736 first.lastPing = second.lastPing = 0;
737 first.lastPong = second.lastPong = 0;
738 first.usePlayother = second.usePlayother = FALSE;
739 first.useColors = second.useColors = TRUE;
740 first.useUsermove = second.useUsermove = FALSE;
741 first.sendICS = second.sendICS = FALSE;
742 first.sendName = second.sendName = appData.icsActive;
743 first.sdKludge = second.sdKludge = FALSE;
744 first.stKludge = second.stKludge = FALSE;
745 TidyProgramName(first.program, first.host, first.tidy);
746 TidyProgramName(second.program, second.host, second.tidy);
747 first.matchWins = second.matchWins = 0;
748 strcpy(first.variants, appData.variant);
749 strcpy(second.variants, appData.variant);
750 first.analysisSupport = second.analysisSupport = 2; /* detect */
751 first.analyzing = second.analyzing = FALSE;
752 first.initDone = second.initDone = FALSE;
754 /* New features added by Tord: */
755 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757 /* End of new features added by Tord. */
758 first.fenOverride = appData.fenOverride1;
759 second.fenOverride = appData.fenOverride2;
761 /* [HGM] time odds: set factor for each machine */
762 first.timeOdds = appData.firstTimeOdds;
763 second.timeOdds = appData.secondTimeOdds;
765 if(appData.timeOddsMode) {
766 norm = first.timeOdds;
767 if(norm > second.timeOdds) norm = second.timeOdds;
769 first.timeOdds /= norm;
770 second.timeOdds /= norm;
773 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774 first.accumulateTC = appData.firstAccumulateTC;
775 second.accumulateTC = appData.secondAccumulateTC;
776 first.maxNrOfSessions = second.maxNrOfSessions = 1;
779 first.debug = second.debug = FALSE;
780 first.supportsNPS = second.supportsNPS = UNKNOWN;
783 first.optionSettings = appData.firstOptions;
784 second.optionSettings = appData.secondOptions;
786 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788 first.isUCI = appData.firstIsUCI; /* [AS] */
789 second.isUCI = appData.secondIsUCI; /* [AS] */
790 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
793 if (appData.firstProtocolVersion > PROTOVER ||
794 appData.firstProtocolVersion < 1) {
796 sprintf(buf, _("protocol version %d not supported"),
797 appData.firstProtocolVersion);
798 DisplayFatalError(buf, 0, 2);
800 first.protocolVersion = appData.firstProtocolVersion;
803 if (appData.secondProtocolVersion > PROTOVER ||
804 appData.secondProtocolVersion < 1) {
806 sprintf(buf, _("protocol version %d not supported"),
807 appData.secondProtocolVersion);
808 DisplayFatalError(buf, 0, 2);
810 second.protocolVersion = appData.secondProtocolVersion;
813 if (appData.icsActive) {
814 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
815 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816 appData.clockMode = FALSE;
817 first.sendTime = second.sendTime = 0;
821 /* Override some settings from environment variables, for backward
822 compatibility. Unfortunately it's not feasible to have the env
823 vars just set defaults, at least in xboard. Ugh.
825 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
830 if (appData.noChessProgram) {
831 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832 sprintf(programVersion, "%s", PACKAGE_STRING);
837 while (*q != ' ' && *q != NULLCHAR) q++;
839 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841 sprintf(programVersion, "%s + ", PACKAGE_STRING);
842 strncat(programVersion, p, q - p);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
850 if (!appData.icsActive) {
852 /* Check for variants that are supported only in ICS mode,
853 or not at all. Some that are accepted here nevertheless
854 have bugs; see comments below.
856 VariantClass variant = StringToVariant(appData.variant);
858 case VariantBughouse: /* need four players and two boards */
859 case VariantKriegspiel: /* need to hide pieces and move details */
860 /* case VariantFischeRandom: (Fabien: moved below) */
861 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862 DisplayFatalError(buf, 0, 2);
866 case VariantLoadable:
876 sprintf(buf, _("Unknown variant name %s"), appData.variant);
877 DisplayFatalError(buf, 0, 2);
880 case VariantXiangqi: /* [HGM] repetition rules not implemented */
881 case VariantFairy: /* [HGM] TestLegality definitely off! */
882 case VariantGothic: /* [HGM] should work */
883 case VariantCapablanca: /* [HGM] should work */
884 case VariantCourier: /* [HGM] initial forced moves not implemented */
885 case VariantShogi: /* [HGM] drops not tested for legality */
886 case VariantKnightmate: /* [HGM] should work */
887 case VariantCylinder: /* [HGM] untested */
888 case VariantFalcon: /* [HGM] untested */
889 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890 offboard interposition not understood */
891 case VariantNormal: /* definitely works! */
892 case VariantWildCastle: /* pieces not automatically shuffled */
893 case VariantNoCastle: /* pieces not automatically shuffled */
894 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895 case VariantLosers: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantSuicide: /* should work except for win condition,
898 and doesn't know captures are mandatory */
899 case VariantGiveaway: /* should work except for win condition,
900 and doesn't know captures are mandatory */
901 case VariantTwoKings: /* should work */
902 case VariantAtomic: /* should work except for win condition */
903 case Variant3Check: /* should work except for win condition */
904 case VariantShatranj: /* should work except for all win conditions */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1014 int matched, min, sec;
1016 matched = sscanf(tc, "%d:%d", &min, &sec);
1018 timeControl = min * 60 * 1000;
1019 } else if (matched == 2) {
1020 timeControl = (min * 60 + sec) * 1000;
1029 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033 else sprintf(buf, "+%s+%d", tc, ti);
1036 sprintf(buf, "+%d/%s", mps, tc);
1037 else sprintf(buf, "+%s", tc);
1039 fullTimeControlString = StrSave(buf);
1041 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1046 /* Parse second time control */
1049 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1057 timeControl_2 = tc2 * 1000;
1067 timeControl = tc1 * 1000;
1071 timeIncrement = ti * 1000; /* convert to ms */
1072 movesPerSession = 0;
1075 movesPerSession = mps;
1083 if (appData.debugMode) {
1084 fprintf(debugFP, "%s\n", programVersion);
1087 if (appData.matchGames > 0) {
1088 appData.matchMode = TRUE;
1089 } else if (appData.matchMode) {
1090 appData.matchGames = 1;
1092 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093 appData.matchGames = appData.sameColorGames;
1094 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1102 /* kludge: allow timeout for initial "feature" commands */
1104 DisplayMessage("", _("Starting chess program"));
1105 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 InitBackEnd3 P((void))
1112 GameMode initialMode;
1116 InitChessProgram(&first, startedFromSetupPosition);
1119 if (appData.icsActive) {
1121 /* [DM] Make a console window if needed [HGM] merged ifs */
1126 if (*appData.icsCommPort != NULLCHAR) {
1127 sprintf(buf, _("Could not open comm port %s"),
1128 appData.icsCommPort);
1130 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131 appData.icsHost, appData.icsPort);
1133 DisplayFatalError(buf, err, 1);
1138 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1140 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141 } else if (appData.noChessProgram) {
1147 if (*appData.cmailGameName != NULLCHAR) {
1149 OpenLoopback(&cmailPR);
1151 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1155 DisplayMessage("", "");
1156 if (StrCaseCmp(appData.initialMode, "") == 0) {
1157 initialMode = BeginningOfGame;
1158 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159 initialMode = TwoMachinesPlay;
1160 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161 initialMode = AnalyzeFile;
1162 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163 initialMode = AnalyzeMode;
1164 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165 initialMode = MachinePlaysWhite;
1166 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167 initialMode = MachinePlaysBlack;
1168 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169 initialMode = EditGame;
1170 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171 initialMode = EditPosition;
1172 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173 initialMode = Training;
1175 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176 DisplayFatalError(buf, 0, 2);
1180 if (appData.matchMode) {
1181 /* Set up machine vs. machine match */
1182 if (appData.noChessProgram) {
1183 DisplayFatalError(_("Can't have a match with no chess programs"),
1189 if (*appData.loadGameFile != NULLCHAR) {
1190 int index = appData.loadGameIndex; // [HGM] autoinc
1191 if(index<0) lastIndex = index = 1;
1192 if (!LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameFile, FALSE)) {
1195 DisplayFatalError(_("Bad game file"), 0, 1);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 int index = appData.loadPositionIndex; // [HGM] autoinc
1200 if(index<0) lastIndex = index = 1;
1201 if (!LoadPositionFromFile(appData.loadPositionFile,
1203 appData.loadPositionFile)) {
1204 DisplayFatalError(_("Bad position file"), 0, 1);
1209 } else if (*appData.cmailGameName != NULLCHAR) {
1210 /* Set up cmail mode */
1211 ReloadCmailMsgEvent(TRUE);
1213 /* Set up other modes */
1214 if (initialMode == AnalyzeFile) {
1215 if (*appData.loadGameFile == NULLCHAR) {
1216 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1220 if (*appData.loadGameFile != NULLCHAR) {
1221 (void) LoadGameFromFile(appData.loadGameFile,
1222 appData.loadGameIndex,
1223 appData.loadGameFile, TRUE);
1224 } else if (*appData.loadPositionFile != NULLCHAR) {
1225 (void) LoadPositionFromFile(appData.loadPositionFile,
1226 appData.loadPositionIndex,
1227 appData.loadPositionFile);
1228 /* [HGM] try to make self-starting even after FEN load */
1229 /* to allow automatic setup of fairy variants with wtm */
1230 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231 gameMode = BeginningOfGame;
1232 setboardSpoiledMachineBlack = 1;
1234 /* [HGM] loadPos: make that every new game uses the setup */
1235 /* from file as long as we do not switch variant */
1236 if(!blackPlaysFirst) { int i;
1237 startedFromPositionFile = TRUE;
1238 CopyBoard(filePosition, boards[0]);
1239 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1242 if (initialMode == AnalyzeMode) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1252 } else if (initialMode == AnalyzeFile) {
1253 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254 ShowThinkingEvent();
1256 AnalysisPeriodicEvent(1);
1257 } else if (initialMode == MachinePlaysWhite) {
1258 if (appData.noChessProgram) {
1259 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1263 if (appData.icsActive) {
1264 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1268 MachineWhiteEvent();
1269 } else if (initialMode == MachinePlaysBlack) {
1270 if (appData.noChessProgram) {
1271 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1275 if (appData.icsActive) {
1276 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1280 MachineBlackEvent();
1281 } else if (initialMode == TwoMachinesPlay) {
1282 if (appData.noChessProgram) {
1283 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1287 if (appData.icsActive) {
1288 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1293 } else if (initialMode == EditGame) {
1295 } else if (initialMode == EditPosition) {
1296 EditPositionEvent();
1297 } else if (initialMode == Training) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308 * Establish will establish a contact to a remote host.port.
1309 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310 * used to talk to the host.
1311 * Returns 0 if okay, error code if not.
1318 if (*appData.icsCommPort != NULLCHAR) {
1319 /* Talk to the host through a serial comm port */
1320 return OpenCommPort(appData.icsCommPort, &icsPR);
1322 } else if (*appData.gateway != NULLCHAR) {
1323 if (*appData.remoteShell == NULLCHAR) {
1324 /* Use the rcmd protocol to run telnet program on a gateway host */
1325 snprintf(buf, sizeof(buf), "%s %s %s",
1326 appData.telnetProgram, appData.icsHost, appData.icsPort);
1327 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1330 /* Use the rsh program to run telnet program on a gateway host */
1331 if (*appData.remoteUser == NULLCHAR) {
1332 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333 appData.gateway, appData.telnetProgram,
1334 appData.icsHost, appData.icsPort);
1336 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337 appData.remoteShell, appData.gateway,
1338 appData.remoteUser, appData.telnetProgram,
1339 appData.icsHost, appData.icsPort);
1341 return StartChildProcess(buf, "", &icsPR);
1344 } else if (appData.useTelnet) {
1345 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1348 /* TCP socket interface differs somewhat between
1349 Unix and NT; handle details in the front end.
1351 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1356 show_bytes(fp, buf, count)
1362 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363 fprintf(fp, "\\%03o", *buf & 0xff);
1372 /* Returns an errno value */
1374 OutputMaybeTelnet(pr, message, count, outError)
1380 char buf[8192], *p, *q, *buflim;
1381 int left, newcount, outcount;
1383 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384 *appData.gateway != NULLCHAR) {
1385 if (appData.debugMode) {
1386 fprintf(debugFP, ">ICS: ");
1387 show_bytes(debugFP, message, count);
1388 fprintf(debugFP, "\n");
1390 return OutputToProcess(pr, message, count, outError);
1393 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1400 if (appData.debugMode) {
1401 fprintf(debugFP, ">ICS: ");
1402 show_bytes(debugFP, buf, newcount);
1403 fprintf(debugFP, "\n");
1405 outcount = OutputToProcess(pr, buf, newcount, outError);
1406 if (outcount < newcount) return -1; /* to be sure */
1413 } else if (((unsigned char) *p) == TN_IAC) {
1414 *q++ = (char) TN_IAC;
1421 if (appData.debugMode) {
1422 fprintf(debugFP, ">ICS: ");
1423 show_bytes(debugFP, buf, newcount);
1424 fprintf(debugFP, "\n");
1426 outcount = OutputToProcess(pr, buf, newcount, outError);
1427 if (outcount < newcount) return -1; /* to be sure */
1432 read_from_player(isr, closure, message, count, error)
1439 int outError, outCount;
1440 static int gotEof = 0;
1442 /* Pass data read from player on to ICS */
1445 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 } else if (count < 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452 } else if (gotEof++ > 0) {
1453 RemoveInputSource(isr);
1454 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1460 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461 SendToICS("date\n");
1462 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469 int count, outCount, outError;
1471 if (icsPR == NULL) return;
1474 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475 if (outCount < count) {
1476 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480 /* This is used for sending logon scripts to the ICS. Sending
1481 without a delay causes problems when using timestamp on ICC
1482 (at least on my machine). */
1484 SendToICSDelayed(s,msdelay)
1488 int count, outCount, outError;
1490 if (icsPR == NULL) return;
1493 if (appData.debugMode) {
1494 fprintf(debugFP, ">ICS: ");
1495 show_bytes(debugFP, s, count);
1496 fprintf(debugFP, "\n");
1498 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1500 if (outCount < count) {
1501 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 /* Remove all highlighting escape sequences in s
1507 Also deletes any suffix starting with '('
1510 StripHighlightAndTitle(s)
1513 static char retbuf[MSG_SIZ];
1516 while (*s != NULLCHAR) {
1517 while (*s == '\033') {
1518 while (*s != NULLCHAR && !isalpha(*s)) s++;
1519 if (*s != NULLCHAR) s++;
1521 while (*s != NULLCHAR && *s != '\033') {
1522 if (*s == '(' || *s == '[') {
1533 /* Remove all highlighting escape sequences in s */
1538 static char retbuf[MSG_SIZ];
1541 while (*s != NULLCHAR) {
1542 while (*s == '\033') {
1543 while (*s != NULLCHAR && !isalpha(*s)) s++;
1544 if (*s != NULLCHAR) s++;
1546 while (*s != NULLCHAR && *s != '\033') {
1554 char *variantNames[] = VARIANT_NAMES;
1559 return variantNames[v];
1563 /* Identify a variant from the strings the chess servers use or the
1564 PGN Variant tag names we use. */
1571 VariantClass v = VariantNormal;
1572 int i, found = FALSE;
1577 /* [HGM] skip over optional board-size prefixes */
1578 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580 while( *e++ != '_');
1583 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584 if (StrCaseStr(e, variantNames[i])) {
1585 v = (VariantClass) i;
1592 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593 || StrCaseStr(e, "wild/fr")
1594 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595 v = VariantFischeRandom;
1596 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597 (i = 1, p = StrCaseStr(e, "w"))) {
1599 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606 case 0: /* FICS only, actually */
1608 /* Castling legal even if K starts on d-file */
1609 v = VariantWildCastle;
1614 /* Castling illegal even if K & R happen to start in
1615 normal positions. */
1616 v = VariantNoCastle;
1629 /* Castling legal iff K & R start in normal positions */
1635 /* Special wilds for position setup; unclear what to do here */
1636 v = VariantLoadable;
1639 /* Bizarre ICC game */
1640 v = VariantTwoKings;
1643 v = VariantKriegspiel;
1649 v = VariantFischeRandom;
1652 v = VariantCrazyhouse;
1655 v = VariantBughouse;
1661 /* Not quite the same as FICS suicide! */
1662 v = VariantGiveaway;
1668 v = VariantShatranj;
1671 /* Temporary names for future ICC types. The name *will* change in
1672 the next xboard/WinBoard release after ICC defines it. */
1710 v = VariantCapablanca;
1713 v = VariantKnightmate;
1719 v = VariantCylinder;
1725 v = VariantCapaRandom;
1728 v = VariantBerolina;
1740 /* Found "wild" or "w" in the string but no number;
1741 must assume it's normal chess. */
1745 sprintf(buf, _("Unknown wild type %d"), wnum);
1746 DisplayError(buf, 0);
1752 if (appData.debugMode) {
1753 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754 e, wnum, VariantName(v));
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763 advance *index beyond it, and set leftover_start to the new value of
1764 *index; else return FALSE. If pattern contains the character '*', it
1765 matches any sequence of characters not containing '\r', '\n', or the
1766 character following the '*' (if any), and the matched sequence(s) are
1767 copied into star_match.
1770 looking_at(buf, index, pattern)
1775 char *bufp = &buf[*index], *patternp = pattern;
1777 char *matchp = star_match[0];
1780 if (*patternp == NULLCHAR) {
1781 *index = leftover_start = bufp - buf;
1785 if (*bufp == NULLCHAR) return FALSE;
1786 if (*patternp == '*') {
1787 if (*bufp == *(patternp + 1)) {
1789 matchp = star_match[++star_count];
1793 } else if (*bufp == '\n' || *bufp == '\r') {
1795 if (*patternp == NULLCHAR)
1800 *matchp++ = *bufp++;
1804 if (*patternp != *bufp) return FALSE;
1811 SendToPlayer(data, length)
1815 int error, outCount;
1816 outCount = OutputToProcess(NoProc, data, length, &error);
1817 if (outCount < length) {
1818 DisplayFatalError(_("Error writing to display"), error, 1);
1823 PackHolding(packed, holding)
1835 switch (runlength) {
1846 sprintf(q, "%d", runlength);
1858 /* Telnet protocol requests from the front end */
1860 TelnetRequest(ddww, option)
1861 unsigned char ddww, option;
1863 unsigned char msg[3];
1864 int outCount, outError;
1866 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1868 if (appData.debugMode) {
1869 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1885 sprintf(buf1, "%d", ddww);
1894 sprintf(buf2, "%d", option);
1897 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1902 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1904 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911 if (!appData.icsActive) return;
1912 TelnetRequest(TN_DO, TN_ECHO);
1918 if (!appData.icsActive) return;
1919 TelnetRequest(TN_DONT, TN_ECHO);
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1925 /* put the holdings sent to us by the server on the board holdings area */
1926 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1930 if(gameInfo.holdingsWidth < 2) return;
1932 if( (int)lowestPiece >= BlackPawn ) {
1935 holdingsStartRow = BOARD_HEIGHT-1;
1938 holdingsColumn = BOARD_WIDTH-1;
1939 countsColumn = BOARD_WIDTH-2;
1940 holdingsStartRow = 0;
1944 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945 board[i][holdingsColumn] = EmptySquare;
1946 board[i][countsColumn] = (ChessSquare) 0;
1948 while( (p=*holdings++) != NULLCHAR ) {
1949 piece = CharToPiece( ToUpper(p) );
1950 if(piece == EmptySquare) continue;
1951 /*j = (int) piece - (int) WhitePawn;*/
1952 j = PieceToNumber(piece);
1953 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954 if(j < 0) continue; /* should not happen */
1955 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957 board[holdingsStartRow+j*direction][countsColumn]++;
1964 VariantSwitch(Board board, VariantClass newVariant)
1966 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1970 startedFromPositionFile = FALSE;
1971 if(gameInfo.variant == newVariant) return;
1973 /* [HGM] This routine is called each time an assignment is made to
1974 * gameInfo.variant during a game, to make sure the board sizes
1975 * are set to match the new variant. If that means adding or deleting
1976 * holdings, we shift the playing board accordingly
1977 * This kludge is needed because in ICS observe mode, we get boards
1978 * of an ongoing game without knowing the variant, and learn about the
1979 * latter only later. This can be because of the move list we requested,
1980 * in which case the game history is refilled from the beginning anyway,
1981 * but also when receiving holdings of a crazyhouse game. In the latter
1982 * case we want to add those holdings to the already received position.
1986 if (appData.debugMode) {
1987 fprintf(debugFP, "Switch board from %s to %s\n",
1988 VariantName(gameInfo.variant), VariantName(newVariant));
1989 setbuf(debugFP, NULL);
1991 shuffleOpenings = 0; /* [HGM] shuffle */
1992 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993 switch(newVariant) {
1995 newWidth = 9; newHeight = 9;
1996 gameInfo.holdingsSize = 7;
1997 case VariantBughouse:
1998 case VariantCrazyhouse:
1999 newHoldingsWidth = 2; break;
2001 newHoldingsWidth = gameInfo.holdingsSize = 0;
2004 if(newWidth != gameInfo.boardWidth ||
2005 newHeight != gameInfo.boardHeight ||
2006 newHoldingsWidth != gameInfo.holdingsWidth ) {
2008 /* shift position to new playing area, if needed */
2009 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010 for(i=0; i<BOARD_HEIGHT; i++)
2011 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 for(i=0; i<newHeight; i++) {
2015 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019 for(i=0; i<BOARD_HEIGHT; i++)
2020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2031 /* [HGM] The following should definitely be solved in a better way */
2033 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035 saveEP = epStatus[0];
2037 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2039 epStatus[0] = saveEP;
2040 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041 CopyBoard(tempBoard, board); /* restore position received from ICS */
2043 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2045 forwardMostMove = oldForwardMostMove;
2046 backwardMostMove = oldBackwardMostMove;
2047 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2050 static int loggedOn = FALSE;
2052 /*-- Game start info cache: --*/
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2065 read_from_ics(isr, closure, data, count, error)
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2082 static int started = STARTED_NONE;
2083 static char parse[20000];
2084 static int parse_pos = 0;
2085 static char buf[BUF_SIZE + 1];
2086 static int firstTime = TRUE, intfSet = FALSE;
2087 static ColorClass prevColor = ColorNormal;
2088 static int savingComment = FALSE;
2094 int backup; /* [DM] For zippy color lines */
2096 char talker[MSG_SIZ]; // [HGM] chat
2099 if (appData.debugMode) {
2101 fprintf(debugFP, "<ICS: ");
2102 show_bytes(debugFP, data, count);
2103 fprintf(debugFP, "\n");
2107 if (appData.debugMode) { int f = forwardMostMove;
2108 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2112 /* If last read ended with a partial line that we couldn't parse,
2113 prepend it to the new read and try again. */
2114 if (leftover_len > 0) {
2115 for (i=0; i<leftover_len; i++)
2116 buf[i] = buf[leftover_start + i];
2119 /* Copy in new characters, removing nulls and \r's */
2120 buf_len = leftover_len;
2121 for (i = 0; i < count; i++) {
2122 if (data[i] != NULLCHAR && data[i] != '\r')
2123 buf[buf_len++] = data[i];
2124 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2125 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2126 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2131 buf[buf_len] = NULLCHAR;
2132 next_out = leftover_len;
2136 while (i < buf_len) {
2137 /* Deal with part of the TELNET option negotiation
2138 protocol. We refuse to do anything beyond the
2139 defaults, except that we allow the WILL ECHO option,
2140 which ICS uses to turn off password echoing when we are
2141 directly connected to it. We reject this option
2142 if localLineEditing mode is on (always on in xboard)
2143 and we are talking to port 23, which might be a real
2144 telnet server that will try to keep WILL ECHO on permanently.
2146 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148 unsigned char option;
2150 switch ((unsigned char) buf[++i]) {
2152 if (appData.debugMode)
2153 fprintf(debugFP, "\n<WILL ");
2154 switch (option = (unsigned char) buf[++i]) {
2156 if (appData.debugMode)
2157 fprintf(debugFP, "ECHO ");
2158 /* Reply only if this is a change, according
2159 to the protocol rules. */
2160 if (remoteEchoOption) break;
2161 if (appData.localLineEditing &&
2162 atoi(appData.icsPort) == TN_PORT) {
2163 TelnetRequest(TN_DONT, TN_ECHO);
2166 TelnetRequest(TN_DO, TN_ECHO);
2167 remoteEchoOption = TRUE;
2171 if (appData.debugMode)
2172 fprintf(debugFP, "%d ", option);
2173 /* Whatever this is, we don't want it. */
2174 TelnetRequest(TN_DONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<WONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "ECHO ");
2185 /* Reply only if this is a change, according
2186 to the protocol rules. */
2187 if (!remoteEchoOption) break;
2189 TelnetRequest(TN_DONT, TN_ECHO);
2190 remoteEchoOption = FALSE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", (unsigned char) option);
2195 /* Whatever this is, it must already be turned
2196 off, because we never agree to turn on
2197 anything non-default, so according to the
2198 protocol rules, we don't reply. */
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DO ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 /* Whatever this is, we refuse to do it. */
2208 if (appData.debugMode)
2209 fprintf(debugFP, "%d ", option);
2210 TelnetRequest(TN_WONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<DONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "%d ", option);
2221 /* Whatever this is, we are already not doing
2222 it, because we never agree to do anything
2223 non-default, so according to the protocol
2224 rules, we don't reply. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "\n<IAC ");
2231 /* Doubled IAC; pass it through */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237 /* Drop all other telnet commands on the floor */
2240 if (oldi > next_out)
2241 SendToPlayer(&buf[next_out], oldi - next_out);
2247 /* OK, this at least will *usually* work */
2248 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2252 if (loggedOn && !intfSet) {
2253 if (ics_type == ICS_ICC) {
2255 "/set-quietly interface %s\n/set-quietly style 12\n",
2258 } else if (ics_type == ICS_CHESSNET) {
2259 sprintf(str, "/style 12\n");
2261 strcpy(str, "alias $ @\n$set interface ");
2262 strcat(str, programVersion);
2263 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2265 strcat(str, "$iset nohighlight 1\n");
2267 strcat(str, "$iset lock 1\n$style 12\n");
2273 if (started == STARTED_COMMENT) {
2274 /* Accumulate characters in comment */
2275 parse[parse_pos++] = buf[i];
2276 if (buf[i] == '\n') {
2277 parse[parse_pos] = NULLCHAR;
2278 if(chattingPartner>=0) {
2280 sprintf(mess, "%s%s", talker, parse);
2281 OutputChatMessage(chattingPartner, mess);
2282 chattingPartner = -1;
2284 if(!suppressKibitz) // [HGM] kibitz
2285 AppendComment(forwardMostMove, StripHighlight(parse));
2286 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287 int nrDigit = 0, nrAlph = 0, i;
2288 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290 parse[parse_pos] = NULLCHAR;
2291 // try to be smart: if it does not look like search info, it should go to
2292 // ICS interaction window after all, not to engine-output window.
2293 for(i=0; i<parse_pos; i++) { // count letters and digits
2294 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2296 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2298 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299 int depth=0; float score;
2300 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302 pvInfoList[forwardMostMove-1].depth = depth;
2303 pvInfoList[forwardMostMove-1].score = 100*score;
2305 OutputKibitz(suppressKibitz, parse);
2308 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309 SendToPlayer(tmp, strlen(tmp));
2312 started = STARTED_NONE;
2314 /* Don't match patterns against characters in chatter */
2319 if (started == STARTED_CHATTER) {
2320 if (buf[i] != '\n') {
2321 /* Don't match patterns against characters in chatter */
2325 started = STARTED_NONE;
2328 /* Kludge to deal with rcmd protocol */
2329 if (firstTime && looking_at(buf, &i, "\001*")) {
2330 DisplayFatalError(&buf[1], 0, 1);
2336 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2339 if (appData.debugMode)
2340 fprintf(debugFP, "ics_type %d\n", ics_type);
2343 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344 ics_type = ICS_FICS;
2346 if (appData.debugMode)
2347 fprintf(debugFP, "ics_type %d\n", ics_type);
2350 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351 ics_type = ICS_CHESSNET;
2353 if (appData.debugMode)
2354 fprintf(debugFP, "ics_type %d\n", ics_type);
2359 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360 looking_at(buf, &i, "Logging you in as \"*\"") ||
2361 looking_at(buf, &i, "will be \"*\""))) {
2362 strcpy(ics_handle, star_match[0]);
2366 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2368 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369 DisplayIcsInteractionTitle(buf);
2370 have_set_title = TRUE;
2373 /* skip finger notes */
2374 if (started == STARTED_NONE &&
2375 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376 (buf[i] == '1' && buf[i+1] == '0')) &&
2377 buf[i+2] == ':' && buf[i+3] == ' ') {
2378 started = STARTED_CHATTER;
2383 /* skip formula vars */
2384 if (started == STARTED_NONE &&
2385 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386 started = STARTED_CHATTER;
2392 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393 if (appData.autoKibitz && started == STARTED_NONE &&
2394 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2395 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396 if(looking_at(buf, &i, "* kibitzes: ") &&
2397 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2398 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2399 suppressKibitz = TRUE;
2400 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401 && (gameMode == IcsPlayingWhite)) ||
2402 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2404 started = STARTED_CHATTER; // own kibitz we simply discard
2406 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407 parse_pos = 0; parse[0] = NULLCHAR;
2408 savingComment = TRUE;
2409 suppressKibitz = gameMode != IcsObserving ? 2 :
2410 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2414 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415 started = STARTED_CHATTER;
2416 suppressKibitz = TRUE;
2418 } // [HGM] kibitz: end of patch
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2422 // [HGM] chat: intercept tells by users for which we have an open chat window
2424 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2425 looking_at(buf, &i, "* whispers:") ||
2426 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2429 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430 chattingPartner = -1;
2432 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433 for(p=0; p<MAX_CHAT; p++) {
2434 if(channel == atoi(chatPartner[p])) {
2435 talker[0] = '['; strcat(talker, "]");
2436 chattingPartner = p; break;
2439 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440 for(p=0; p<MAX_CHAT; p++) {
2441 if(!strcmp("WHISPER", chatPartner[p])) {
2442 talker[0] = '['; strcat(talker, "]");
2443 chattingPartner = p; break;
2446 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2449 chattingPartner = p; break;
2451 if(chattingPartner<0) i = oldi; else {
2452 started = STARTED_COMMENT;
2453 parse_pos = 0; parse[0] = NULLCHAR;
2454 savingComment = TRUE;
2455 suppressKibitz = TRUE;
2457 } // [HGM] chat: end of patch
2459 if (appData.zippyTalk || appData.zippyPlay) {
2460 /* [DM] Backup address for color zippy lines */
2464 if (loggedOn == TRUE)
2465 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2468 if (ZippyControl(buf, &i) ||
2469 ZippyConverse(buf, &i) ||
2470 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2472 if (!appData.colorize) continue;
2476 } // [DM] 'else { ' deleted
2478 /* Regular tells and says */
2479 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480 looking_at(buf, &i, "* (your partner) tells you: ") ||
2481 looking_at(buf, &i, "* says: ") ||
2482 /* Don't color "message" or "messages" output */
2483 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484 looking_at(buf, &i, "*. * at *:*: ") ||
2485 looking_at(buf, &i, "--* (*:*): ") ||
2486 /* Message notifications (same color as tells) */
2487 looking_at(buf, &i, "* has left a message ") ||
2488 looking_at(buf, &i, "* just sent you a message:\n") ||
2489 /* Whispers and kibitzes */
2490 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491 looking_at(buf, &i, "* kibitzes: ") ||
2493 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2495 if (tkind == 1 && strchr(star_match[0], ':')) {
2496 /* Avoid "tells you:" spoofs in channels */
2499 if (star_match[0][0] == NULLCHAR ||
2500 strchr(star_match[0], ' ') ||
2501 (tkind == 3 && strchr(star_match[1], ' '))) {
2502 /* Reject bogus matches */
2505 if (appData.colorize) {
2506 if (oldi > next_out) {
2507 SendToPlayer(&buf[next_out], oldi - next_out);
2512 Colorize(ColorTell, FALSE);
2513 curColor = ColorTell;
2516 Colorize(ColorKibitz, FALSE);
2517 curColor = ColorKibitz;
2520 p = strrchr(star_match[1], '(');
2527 Colorize(ColorChannel1, FALSE);
2528 curColor = ColorChannel1;
2530 Colorize(ColorChannel, FALSE);
2531 curColor = ColorChannel;
2535 curColor = ColorNormal;
2539 if (started == STARTED_NONE && appData.autoComment &&
2540 (gameMode == IcsObserving ||
2541 gameMode == IcsPlayingWhite ||
2542 gameMode == IcsPlayingBlack)) {
2543 parse_pos = i - oldi;
2544 memcpy(parse, &buf[oldi], parse_pos);
2545 parse[parse_pos] = NULLCHAR;
2546 started = STARTED_COMMENT;
2547 savingComment = TRUE;
2549 started = STARTED_CHATTER;
2550 savingComment = FALSE;
2557 if (looking_at(buf, &i, "* s-shouts: ") ||
2558 looking_at(buf, &i, "* c-shouts: ")) {
2559 if (appData.colorize) {
2560 if (oldi > next_out) {
2561 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorSShout, FALSE);
2565 curColor = ColorSShout;
2568 started = STARTED_CHATTER;
2572 if (looking_at(buf, &i, "--->")) {
2577 if (looking_at(buf, &i, "* shouts: ") ||
2578 looking_at(buf, &i, "--> ")) {
2579 if (appData.colorize) {
2580 if (oldi > next_out) {
2581 SendToPlayer(&buf[next_out], oldi - next_out);
2584 Colorize(ColorShout, FALSE);
2585 curColor = ColorShout;
2588 started = STARTED_CHATTER;
2592 if (looking_at( buf, &i, "Challenge:")) {
2593 if (appData.colorize) {
2594 if (oldi > next_out) {
2595 SendToPlayer(&buf[next_out], oldi - next_out);
2598 Colorize(ColorChallenge, FALSE);
2599 curColor = ColorChallenge;
2605 if (looking_at(buf, &i, "* offers you") ||
2606 looking_at(buf, &i, "* offers to be") ||
2607 looking_at(buf, &i, "* would like to") ||
2608 looking_at(buf, &i, "* requests to") ||
2609 looking_at(buf, &i, "Your opponent offers") ||
2610 looking_at(buf, &i, "Your opponent requests")) {
2612 if (appData.colorize) {
2613 if (oldi > next_out) {
2614 SendToPlayer(&buf[next_out], oldi - next_out);
2617 Colorize(ColorRequest, FALSE);
2618 curColor = ColorRequest;
2623 if (looking_at(buf, &i, "* (*) seeking")) {
2624 if (appData.colorize) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 Colorize(ColorSeek, FALSE);
2630 curColor = ColorSeek;
2635 if (looking_at(buf, &i, "\\ ")) {
2636 if (prevColor != ColorNormal) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(prevColor, TRUE);
2642 curColor = prevColor;
2644 if (savingComment) {
2645 parse_pos = i - oldi;
2646 memcpy(parse, &buf[oldi], parse_pos);
2647 parse[parse_pos] = NULLCHAR;
2648 started = STARTED_COMMENT;
2650 started = STARTED_CHATTER;
2655 if (looking_at(buf, &i, "Black Strength :") ||
2656 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657 looking_at(buf, &i, "<10>") ||
2658 looking_at(buf, &i, "#@#")) {
2659 /* Wrong board style */
2661 SendToICS(ics_prefix);
2662 SendToICS("set style 12\n");
2663 SendToICS(ics_prefix);
2664 SendToICS("refresh\n");
2668 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2670 have_sent_ICS_logon = 1;
2674 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2675 (looking_at(buf, &i, "\n<12> ") ||
2676 looking_at(buf, &i, "<12> "))) {
2678 if (oldi > next_out) {
2679 SendToPlayer(&buf[next_out], oldi - next_out);
2682 started = STARTED_BOARD;
2687 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688 looking_at(buf, &i, "<b1> ")) {
2689 if (oldi > next_out) {
2690 SendToPlayer(&buf[next_out], oldi - next_out);
2693 started = STARTED_HOLDINGS;
2698 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2700 /* Header for a move list -- first line */
2702 switch (ics_getting_history) {
2706 case BeginningOfGame:
2707 /* User typed "moves" or "oldmoves" while we
2708 were idle. Pretend we asked for these
2709 moves and soak them up so user can step
2710 through them and/or save them.
2713 gameMode = IcsObserving;
2716 ics_getting_history = H_GOT_UNREQ_HEADER;
2718 case EditGame: /*?*/
2719 case EditPosition: /*?*/
2720 /* Should above feature work in these modes too? */
2721 /* For now it doesn't */
2722 ics_getting_history = H_GOT_UNWANTED_HEADER;
2725 ics_getting_history = H_GOT_UNWANTED_HEADER;
2730 /* Is this the right one? */
2731 if (gameInfo.white && gameInfo.black &&
2732 strcmp(gameInfo.white, star_match[0]) == 0 &&
2733 strcmp(gameInfo.black, star_match[2]) == 0) {
2735 ics_getting_history = H_GOT_REQ_HEADER;
2738 case H_GOT_REQ_HEADER:
2739 case H_GOT_UNREQ_HEADER:
2740 case H_GOT_UNWANTED_HEADER:
2741 case H_GETTING_MOVES:
2742 /* Should not happen */
2743 DisplayError(_("Error gathering move list: two headers"), 0);
2744 ics_getting_history = H_FALSE;
2748 /* Save player ratings into gameInfo if needed */
2749 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751 (gameInfo.whiteRating == -1 ||
2752 gameInfo.blackRating == -1)) {
2754 gameInfo.whiteRating = string_to_rating(star_match[1]);
2755 gameInfo.blackRating = string_to_rating(star_match[3]);
2756 if (appData.debugMode)
2757 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2758 gameInfo.whiteRating, gameInfo.blackRating);
2763 if (looking_at(buf, &i,
2764 "* * match, initial time: * minute*, increment: * second")) {
2765 /* Header for a move list -- second line */
2766 /* Initial board will follow if this is a wild game */
2767 if (gameInfo.event != NULL) free(gameInfo.event);
2768 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769 gameInfo.event = StrSave(str);
2770 /* [HGM] we switched variant. Translate boards if needed. */
2771 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2775 if (looking_at(buf, &i, "Move ")) {
2776 /* Beginning of a move list */
2777 switch (ics_getting_history) {
2779 /* Normally should not happen */
2780 /* Maybe user hit reset while we were parsing */
2783 /* Happens if we are ignoring a move list that is not
2784 * the one we just requested. Common if the user
2785 * tries to observe two games without turning off
2788 case H_GETTING_MOVES:
2789 /* Should not happen */
2790 DisplayError(_("Error gathering move list: nested"), 0);
2791 ics_getting_history = H_FALSE;
2793 case H_GOT_REQ_HEADER:
2794 ics_getting_history = H_GETTING_MOVES;
2795 started = STARTED_MOVES;
2797 if (oldi > next_out) {
2798 SendToPlayer(&buf[next_out], oldi - next_out);
2801 case H_GOT_UNREQ_HEADER:
2802 ics_getting_history = H_GETTING_MOVES;
2803 started = STARTED_MOVES_NOHIDE;
2806 case H_GOT_UNWANTED_HEADER:
2807 ics_getting_history = H_FALSE;
2813 if (looking_at(buf, &i, "% ") ||
2814 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816 savingComment = FALSE;
2819 case STARTED_MOVES_NOHIDE:
2820 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821 parse[parse_pos + i - oldi] = NULLCHAR;
2822 ParseGameHistory(parse);
2824 if (appData.zippyPlay && first.initDone) {
2825 FeedMovesToProgram(&first, forwardMostMove);
2826 if (gameMode == IcsPlayingWhite) {
2827 if (WhiteOnMove(forwardMostMove)) {
2828 if (first.sendTime) {
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 SendTimeRemaining(&first, TRUE);
2835 if (first.useColors) {
2836 SendToProgram("white\ngo\n", &first);
2838 SendToProgram("go\n", &first);
2841 if (first.useColors) {
2842 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2844 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2846 first.maybeThinking = TRUE;
2848 if (first.usePlayother) {
2849 if (first.sendTime) {
2850 SendTimeRemaining(&first, TRUE);
2852 SendToProgram("playother\n", &first);
2858 } else if (gameMode == IcsPlayingBlack) {
2859 if (!WhiteOnMove(forwardMostMove)) {
2860 if (first.sendTime) {
2861 if (first.useColors) {
2862 SendToProgram("white\n", &first);
2864 SendTimeRemaining(&first, FALSE);
2867 if (first.useColors) {
2868 SendToProgram("black\ngo\n", &first);
2870 SendToProgram("go\n", &first);
2873 if (first.useColors) {
2874 SendToProgram("black\n", &first);
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2878 first.maybeThinking = TRUE;
2880 if (first.usePlayother) {
2881 if (first.sendTime) {
2882 SendTimeRemaining(&first, FALSE);
2884 SendToProgram("playother\n", &first);
2893 if (gameMode == IcsObserving && ics_gamenum == -1) {
2894 /* Moves came from oldmoves or moves command
2895 while we weren't doing anything else.
2897 currentMove = forwardMostMove;
2898 ClearHighlights();/*!!could figure this out*/
2899 flipView = appData.flipView;
2900 DrawPosition(FALSE, boards[currentMove]);
2901 DisplayBothClocks();
2902 sprintf(str, "%s vs. %s",
2903 gameInfo.white, gameInfo.black);
2907 /* Moves were history of an active game */
2908 if (gameInfo.resultDetails != NULL) {
2909 free(gameInfo.resultDetails);
2910 gameInfo.resultDetails = NULL;
2913 HistorySet(parseList, backwardMostMove,
2914 forwardMostMove, currentMove-1);
2915 DisplayMove(currentMove - 1);
2916 if (started == STARTED_MOVES) next_out = i;
2917 started = STARTED_NONE;
2918 ics_getting_history = H_FALSE;
2921 case STARTED_OBSERVE:
2922 started = STARTED_NONE;
2923 SendToICS(ics_prefix);
2924 SendToICS("refresh\n");
2930 if(bookHit) { // [HGM] book: simulate book reply
2931 static char bookMove[MSG_SIZ]; // a bit generous?
2933 programStats.nodes = programStats.depth = programStats.time =
2934 programStats.score = programStats.got_only_move = 0;
2935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2937 strcpy(bookMove, "move ");
2938 strcat(bookMove, bookHit);
2939 HandleMachineMove(bookMove, &first);
2944 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945 started == STARTED_HOLDINGS ||
2946 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947 /* Accumulate characters in move list or board */
2948 parse[parse_pos++] = buf[i];
2951 /* Start of game messages. Mostly we detect start of game
2952 when the first board image arrives. On some versions
2953 of the ICS, though, we need to do a "refresh" after starting
2954 to observe in order to get the current board right away. */
2955 if (looking_at(buf, &i, "Adding game * to observation list")) {
2956 started = STARTED_OBSERVE;
2960 /* Handle auto-observe */
2961 if (appData.autoObserve &&
2962 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2965 /* Choose the player that was highlighted, if any. */
2966 if (star_match[0][0] == '\033' ||
2967 star_match[1][0] != '\033') {
2968 player = star_match[0];
2970 player = star_match[2];
2972 sprintf(str, "%sobserve %s\n",
2973 ics_prefix, StripHighlightAndTitle(player));
2976 /* Save ratings from notify string */
2977 strcpy(player1Name, star_match[0]);
2978 player1Rating = string_to_rating(star_match[1]);
2979 strcpy(player2Name, star_match[2]);
2980 player2Rating = string_to_rating(star_match[3]);
2982 if (appData.debugMode)
2984 "Ratings from 'Game notification:' %s %d, %s %d\n",
2985 player1Name, player1Rating,
2986 player2Name, player2Rating);
2991 /* Deal with automatic examine mode after a game,
2992 and with IcsObserving -> IcsExamining transition */
2993 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994 looking_at(buf, &i, "has made you an examiner of game *")) {
2996 int gamenum = atoi(star_match[0]);
2997 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998 gamenum == ics_gamenum) {
2999 /* We were already playing or observing this game;
3000 no need to refetch history */
3001 gameMode = IcsExamining;
3003 pauseExamForwardMostMove = forwardMostMove;
3004 } else if (currentMove < forwardMostMove) {
3005 ForwardInner(forwardMostMove);
3008 /* I don't think this case really can happen */
3009 SendToICS(ics_prefix);
3010 SendToICS("refresh\n");
3015 /* Error messages */
3016 // if (ics_user_moved) {
3017 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018 if (looking_at(buf, &i, "Illegal move") ||
3019 looking_at(buf, &i, "Not a legal move") ||
3020 looking_at(buf, &i, "Your king is in check") ||
3021 looking_at(buf, &i, "It isn't your turn") ||
3022 looking_at(buf, &i, "It is not your move")) {
3024 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025 currentMove = --forwardMostMove;
3026 DisplayMove(currentMove - 1); /* before DMError */
3027 DrawPosition(FALSE, boards[currentMove]);
3029 DisplayBothClocks();
3031 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037 if (looking_at(buf, &i, "still have time") ||
3038 looking_at(buf, &i, "not out of time") ||
3039 looking_at(buf, &i, "either player is out of time") ||
3040 looking_at(buf, &i, "has timeseal; checking")) {
3041 /* We must have called his flag a little too soon */
3042 whiteFlag = blackFlag = FALSE;
3046 if (looking_at(buf, &i, "added * seconds to") ||
3047 looking_at(buf, &i, "seconds were added to")) {
3048 /* Update the clocks */
3049 SendToICS(ics_prefix);
3050 SendToICS("refresh\n");
3054 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055 ics_clock_paused = TRUE;
3060 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061 ics_clock_paused = FALSE;
3066 /* Grab player ratings from the Creating: message.
3067 Note we have to check for the special case when
3068 the ICS inserts things like [white] or [black]. */
3069 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3072 0 player 1 name (not necessarily white)
3074 2 empty, white, or black (IGNORED)
3075 3 player 2 name (not necessarily black)
3078 The names/ratings are sorted out when the game
3079 actually starts (below).
3081 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082 player1Rating = string_to_rating(star_match[1]);
3083 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084 player2Rating = string_to_rating(star_match[4]);
3086 if (appData.debugMode)
3088 "Ratings from 'Creating:' %s %d, %s %d\n",
3089 player1Name, player1Rating,
3090 player2Name, player2Rating);
3095 /* Improved generic start/end-of-game messages */
3096 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098 /* If tkind == 0: */
3099 /* star_match[0] is the game number */
3100 /* [1] is the white player's name */
3101 /* [2] is the black player's name */
3102 /* For end-of-game: */
3103 /* [3] is the reason for the game end */
3104 /* [4] is a PGN end game-token, preceded by " " */
3105 /* For start-of-game: */
3106 /* [3] begins with "Creating" or "Continuing" */
3107 /* [4] is " *" or empty (don't care). */
3108 int gamenum = atoi(star_match[0]);
3109 char *whitename, *blackname, *why, *endtoken;
3110 ChessMove endtype = (ChessMove) 0;
3113 whitename = star_match[1];
3114 blackname = star_match[2];
3115 why = star_match[3];
3116 endtoken = star_match[4];
3118 whitename = star_match[1];
3119 blackname = star_match[3];
3120 why = star_match[5];
3121 endtoken = star_match[6];
3124 /* Game start messages */
3125 if (strncmp(why, "Creating ", 9) == 0 ||
3126 strncmp(why, "Continuing ", 11) == 0) {
3127 gs_gamenum = gamenum;
3128 strcpy(gs_kind, strchr(why, ' ') + 1);
3130 if (appData.zippyPlay) {
3131 ZippyGameStart(whitename, blackname);
3137 /* Game end messages */
3138 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139 ics_gamenum != gamenum) {
3142 while (endtoken[0] == ' ') endtoken++;
3143 switch (endtoken[0]) {
3146 endtype = GameUnfinished;
3149 endtype = BlackWins;
3152 if (endtoken[1] == '/')
3153 endtype = GameIsDrawn;
3155 endtype = WhiteWins;
3158 GameEnds(endtype, why, GE_ICS);
3160 if (appData.zippyPlay && first.initDone) {
3161 ZippyGameEnd(endtype, why);
3162 if (first.pr == NULL) {
3163 /* Start the next process early so that we'll
3164 be ready for the next challenge */
3165 StartChessProgram(&first);
3167 /* Send "new" early, in case this command takes
3168 a long time to finish, so that we'll be ready
3169 for the next challenge. */
3170 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177 if (looking_at(buf, &i, "Removing game * from observation") ||
3178 looking_at(buf, &i, "no longer observing game *") ||
3179 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180 if (gameMode == IcsObserving &&
3181 atoi(star_match[0]) == ics_gamenum)
3183 /* icsEngineAnalyze */
3184 if (appData.icsEngineAnalyze) {
3191 ics_user_moved = FALSE;
3196 if (looking_at(buf, &i, "no longer examining game *")) {
3197 if (gameMode == IcsExamining &&
3198 atoi(star_match[0]) == ics_gamenum)
3202 ics_user_moved = FALSE;
3207 /* Advance leftover_start past any newlines we find,
3208 so only partial lines can get reparsed */
3209 if (looking_at(buf, &i, "\n")) {
3210 prevColor = curColor;
3211 if (curColor != ColorNormal) {
3212 if (oldi > next_out) {
3213 SendToPlayer(&buf[next_out], oldi - next_out);
3216 Colorize(ColorNormal, FALSE);
3217 curColor = ColorNormal;
3219 if (started == STARTED_BOARD) {
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 ParseBoard12(parse);
3225 /* Send premove here */
3226 if (appData.premove) {
3228 if (currentMove == 0 &&
3229 gameMode == IcsPlayingWhite &&
3230 appData.premoveWhite) {
3231 sprintf(str, "%s%s\n", ics_prefix,
3232 appData.premoveWhiteText);
3233 if (appData.debugMode)
3234 fprintf(debugFP, "Sending premove:\n");
3236 } else if (currentMove == 1 &&
3237 gameMode == IcsPlayingBlack &&
3238 appData.premoveBlack) {
3239 sprintf(str, "%s%s\n", ics_prefix,
3240 appData.premoveBlackText);
3241 if (appData.debugMode)
3242 fprintf(debugFP, "Sending premove:\n");
3244 } else if (gotPremove) {
3246 ClearPremoveHighlights();
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3249 UserMoveEvent(premoveFromX, premoveFromY,
3250 premoveToX, premoveToY,
3255 /* Usually suppress following prompt */
3256 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3262 } else if (started == STARTED_HOLDINGS) {
3264 char new_piece[MSG_SIZ];
3265 started = STARTED_NONE;
3266 parse[parse_pos] = NULLCHAR;
3267 if (appData.debugMode)
3268 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269 parse, currentMove);
3270 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271 gamenum == ics_gamenum) {
3272 if (gameInfo.variant == VariantNormal) {
3273 /* [HGM] We seem to switch variant during a game!
3274 * Presumably no holdings were displayed, so we have
3275 * to move the position two files to the right to
3276 * create room for them!
3278 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279 /* Get a move list just to see the header, which
3280 will tell us whether this is really bug or zh */
3281 if (ics_getting_history == H_FALSE) {
3282 ics_getting_history = H_REQUESTED;
3283 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3287 new_piece[0] = NULLCHAR;
3288 sscanf(parse, "game %d white [%s black [%s <- %s",
3289 &gamenum, white_holding, black_holding,
3291 white_holding[strlen(white_holding)-1] = NULLCHAR;
3292 black_holding[strlen(black_holding)-1] = NULLCHAR;
3293 /* [HGM] copy holdings to board holdings area */
3294 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3297 if (appData.zippyPlay && first.initDone) {
3298 ZippyHoldings(white_holding, black_holding,
3302 if (tinyLayout || smallLayout) {
3303 char wh[16], bh[16];
3304 PackHolding(wh, white_holding);
3305 PackHolding(bh, black_holding);
3306 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307 gameInfo.white, gameInfo.black);
3309 sprintf(str, "%s [%s] vs. %s [%s]",
3310 gameInfo.white, white_holding,
3311 gameInfo.black, black_holding);
3314 DrawPosition(FALSE, boards[currentMove]);
3317 /* Suppress following prompt */
3318 if (looking_at(buf, &i, "*% ")) {
3319 savingComment = FALSE;
3326 i++; /* skip unparsed character and loop back */
3329 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330 started != STARTED_HOLDINGS && i > next_out) {
3331 SendToPlayer(&buf[next_out], i - next_out);
3334 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3336 leftover_len = buf_len - leftover_start;
3337 /* if buffer ends with something we couldn't parse,
3338 reparse it after appending the next read */
3340 } else if (count == 0) {
3341 RemoveInputSource(isr);
3342 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3344 DisplayFatalError(_("Error reading from ICS"), error, 1);
3349 /* Board style 12 looks like this:
3351 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3353 * The "<12> " is stripped before it gets to this routine. The two
3354 * trailing 0's (flip state and clock ticking) are later addition, and
3355 * some chess servers may not have them, or may have only the first.
3356 * Additional trailing fields may be added in the future.
3359 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3361 #define RELATION_OBSERVING_PLAYED 0
3362 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE 1
3364 #define RELATION_PLAYING_NOTMYMOVE -1
3365 #define RELATION_EXAMINING 2
3366 #define RELATION_ISOLATED_BOARD -3
3367 #define RELATION_STARTING_POSITION -4 /* FICS only */
3370 ParseBoard12(string)
3373 GameMode newGameMode;
3374 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377 char to_play, board_chars[200];
3378 char move_str[500], str[500], elapsed_time[500];
3379 char black[32], white[32];
3381 int prevMove = currentMove;
3384 int fromX, fromY, toX, toY;
3386 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387 char *bookHit = NULL; // [HGM] book
3389 fromX = fromY = toX = toY = -1;
3393 if (appData.debugMode)
3394 fprintf(debugFP, _("Parsing board: %s\n"), string);
3396 move_str[0] = NULLCHAR;
3397 elapsed_time[0] = NULLCHAR;
3398 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3400 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401 if(string[i] == ' ') { ranks++; files = 0; }
3405 for(j = 0; j <i; j++) board_chars[j] = string[j];
3406 board_chars[i] = '\0';
3409 n = sscanf(string, PATTERN, &to_play, &double_push,
3410 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411 &gamenum, white, black, &relation, &basetime, &increment,
3412 &white_stren, &black_stren, &white_time, &black_time,
3413 &moveNum, str, elapsed_time, move_str, &ics_flip,
3417 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418 DisplayError(str, 0);
3422 /* Convert the move number to internal form */
3423 moveNum = (moveNum - 1) * 2;
3424 if (to_play == 'B') moveNum++;
3425 if (moveNum >= MAX_MOVES) {
3426 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3432 case RELATION_OBSERVING_PLAYED:
3433 case RELATION_OBSERVING_STATIC:
3434 if (gamenum == -1) {
3435 /* Old ICC buglet */
3436 relation = RELATION_OBSERVING_STATIC;
3438 newGameMode = IcsObserving;
3440 case RELATION_PLAYING_MYMOVE:
3441 case RELATION_PLAYING_NOTMYMOVE:
3443 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444 IcsPlayingWhite : IcsPlayingBlack;
3446 case RELATION_EXAMINING:
3447 newGameMode = IcsExamining;
3449 case RELATION_ISOLATED_BOARD:
3451 /* Just display this board. If user was doing something else,
3452 we will forget about it until the next board comes. */
3453 newGameMode = IcsIdle;
3455 case RELATION_STARTING_POSITION:
3456 newGameMode = gameMode;
3460 /* Modify behavior for initial board display on move listing
3463 switch (ics_getting_history) {
3467 case H_GOT_REQ_HEADER:
3468 case H_GOT_UNREQ_HEADER:
3469 /* This is the initial position of the current game */
3470 gamenum = ics_gamenum;
3471 moveNum = 0; /* old ICS bug workaround */
3472 if (to_play == 'B') {
3473 startedFromSetupPosition = TRUE;
3474 blackPlaysFirst = TRUE;
3476 if (forwardMostMove == 0) forwardMostMove = 1;
3477 if (backwardMostMove == 0) backwardMostMove = 1;
3478 if (currentMove == 0) currentMove = 1;
3480 newGameMode = gameMode;
3481 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3483 case H_GOT_UNWANTED_HEADER:
3484 /* This is an initial board that we don't want */
3486 case H_GETTING_MOVES:
3487 /* Should not happen */
3488 DisplayError(_("Error gathering move list: extra board"), 0);
3489 ics_getting_history = H_FALSE;
3493 /* Take action if this is the first board of a new game, or of a
3494 different game than is currently being displayed. */
3495 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496 relation == RELATION_ISOLATED_BOARD) {
3498 /* Forget the old game and get the history (if any) of the new one */
3499 if (gameMode != BeginningOfGame) {
3503 if (appData.autoRaiseBoard) BoardToTop();
3505 if (gamenum == -1) {
3506 newGameMode = IcsIdle;
3507 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508 appData.getMoveList) {
3509 /* Need to get game history */
3510 ics_getting_history = H_REQUESTED;
3511 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3515 /* Initially flip the board to have black on the bottom if playing
3516 black or if the ICS flip flag is set, but let the user change
3517 it with the Flip View button. */
3518 flipView = appData.autoFlipView ?
3519 (newGameMode == IcsPlayingBlack) || ics_flip :
3522 /* Done with values from previous mode; copy in new ones */
3523 gameMode = newGameMode;
3525 ics_gamenum = gamenum;
3526 if (gamenum == gs_gamenum) {
3527 int klen = strlen(gs_kind);
3528 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529 sprintf(str, "ICS %s", gs_kind);
3530 gameInfo.event = StrSave(str);
3532 gameInfo.event = StrSave("ICS game");
3534 gameInfo.site = StrSave(appData.icsHost);
3535 gameInfo.date = PGNDate();
3536 gameInfo.round = StrSave("-");
3537 gameInfo.white = StrSave(white);
3538 gameInfo.black = StrSave(black);
3539 timeControl = basetime * 60 * 1000;
3541 timeIncrement = increment * 1000;
3542 movesPerSession = 0;
3543 gameInfo.timeControl = TimeControlTagValue();
3544 VariantSwitch(board, StringToVariant(gameInfo.event) );
3545 if (appData.debugMode) {
3546 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548 setbuf(debugFP, NULL);
3551 gameInfo.outOfBook = NULL;
3553 /* Do we have the ratings? */
3554 if (strcmp(player1Name, white) == 0 &&
3555 strcmp(player2Name, black) == 0) {
3556 if (appData.debugMode)
3557 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558 player1Rating, player2Rating);
3559 gameInfo.whiteRating = player1Rating;
3560 gameInfo.blackRating = player2Rating;
3561 } else if (strcmp(player2Name, white) == 0 &&
3562 strcmp(player1Name, black) == 0) {
3563 if (appData.debugMode)
3564 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565 player2Rating, player1Rating);
3566 gameInfo.whiteRating = player2Rating;
3567 gameInfo.blackRating = player1Rating;
3569 player1Name[0] = player2Name[0] = NULLCHAR;
3571 /* Silence shouts if requested */
3572 if (appData.quietPlay &&
3573 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574 SendToICS(ics_prefix);
3575 SendToICS("set shout 0\n");
3579 /* Deal with midgame name changes */
3581 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582 if (gameInfo.white) free(gameInfo.white);
3583 gameInfo.white = StrSave(white);
3585 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586 if (gameInfo.black) free(gameInfo.black);
3587 gameInfo.black = StrSave(black);
3591 /* Throw away game result if anything actually changes in examine mode */
3592 if (gameMode == IcsExamining && !newGame) {
3593 gameInfo.result = GameUnfinished;
3594 if (gameInfo.resultDetails != NULL) {
3595 free(gameInfo.resultDetails);
3596 gameInfo.resultDetails = NULL;
3600 /* In pausing && IcsExamining mode, we ignore boards coming
3601 in if they are in a different variation than we are. */
3602 if (pauseExamInvalid) return;
3603 if (pausing && gameMode == IcsExamining) {
3604 if (moveNum <= pauseExamForwardMostMove) {
3605 pauseExamInvalid = TRUE;
3606 forwardMostMove = pauseExamForwardMostMove;
3611 if (appData.debugMode) {
3612 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3614 /* Parse the board */
3615 for (k = 0; k < ranks; k++) {
3616 for (j = 0; j < files; j++)
3617 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618 if(gameInfo.holdingsWidth > 1) {
3619 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3623 CopyBoard(boards[moveNum], board);
3625 startedFromSetupPosition =
3626 !CompareBoards(board, initialPosition);
3627 if(startedFromSetupPosition)
3628 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3631 /* [HGM] Set castling rights. Take the outermost Rooks,
3632 to make it also work for FRC opening positions. Note that board12
3633 is really defective for later FRC positions, as it has no way to
3634 indicate which Rook can castle if they are on the same side of King.
3635 For the initial position we grant rights to the outermost Rooks,
3636 and remember thos rights, and we then copy them on positions
3637 later in an FRC game. This means WB might not recognize castlings with
3638 Rooks that have moved back to their original position as illegal,
3639 but in ICS mode that is not its job anyway.
3641 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3644 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645 if(board[0][i] == WhiteRook) j = i;
3646 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648 if(board[0][i] == WhiteRook) j = i;
3649 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3657 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661 if(board[BOARD_HEIGHT-1][k] == bKing)
3662 initialRights[5] = castlingRights[moveNum][5] = k;
3664 r = castlingRights[moveNum][0] = initialRights[0];
3665 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666 r = castlingRights[moveNum][1] = initialRights[1];
3667 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668 r = castlingRights[moveNum][3] = initialRights[3];
3669 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670 r = castlingRights[moveNum][4] = initialRights[4];
3671 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672 /* wildcastle kludge: always assume King has rights */
3673 r = castlingRights[moveNum][2] = initialRights[2];
3674 r = castlingRights[moveNum][5] = initialRights[5];
3676 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3680 if (ics_getting_history == H_GOT_REQ_HEADER ||
3681 ics_getting_history == H_GOT_UNREQ_HEADER) {
3682 /* This was an initial position from a move list, not
3683 the current position */
3687 /* Update currentMove and known move number limits */
3688 newMove = newGame || moveNum > forwardMostMove;
3690 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692 takeback = forwardMostMove - moveNum;
3693 for (i = 0; i < takeback; i++) {
3694 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695 SendToProgram("undo\n", &first);
3700 forwardMostMove = backwardMostMove = currentMove = moveNum;
3701 if (gameMode == IcsExamining && moveNum == 0) {
3702 /* Workaround for ICS limitation: we are not told the wild
3703 type when starting to examine a game. But if we ask for
3704 the move list, the move list header will tell us */
3705 ics_getting_history = H_REQUESTED;
3706 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3709 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711 forwardMostMove = moveNum;
3712 if (!pausing || currentMove > forwardMostMove)
3713 currentMove = forwardMostMove;
3715 /* New part of history that is not contiguous with old part */
3716 if (pausing && gameMode == IcsExamining) {
3717 pauseExamInvalid = TRUE;
3718 forwardMostMove = pauseExamForwardMostMove;
3721 forwardMostMove = backwardMostMove = currentMove = moveNum;
3722 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723 ics_getting_history = H_REQUESTED;
3724 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3729 /* Update the clocks */
3730 if (strchr(elapsed_time, '.')) {
3732 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3735 /* Time is in seconds */
3736 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3742 if (appData.zippyPlay && newGame &&
3743 gameMode != IcsObserving && gameMode != IcsIdle &&
3744 gameMode != IcsExamining)
3745 ZippyFirstBoard(moveNum, basetime, increment);
3748 /* Put the move on the move list, first converting
3749 to canonical algebraic form. */
3751 if (appData.debugMode) {
3752 if (appData.debugMode) { int f = forwardMostMove;
3753 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3756 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757 fprintf(debugFP, "moveNum = %d\n", moveNum);
3758 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759 setbuf(debugFP, NULL);
3761 if (moveNum <= backwardMostMove) {
3762 /* We don't know what the board looked like before
3764 strcpy(parseList[moveNum - 1], move_str);
3765 strcat(parseList[moveNum - 1], " ");
3766 strcat(parseList[moveNum - 1], elapsed_time);
3767 moveList[moveNum - 1][0] = NULLCHAR;
3768 } else if (strcmp(move_str, "none") == 0) {
3769 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770 /* Again, we don't know what the board looked like;
3771 this is really the start of the game. */
3772 parseList[moveNum - 1][0] = NULLCHAR;
3773 moveList[moveNum - 1][0] = NULLCHAR;
3774 backwardMostMove = moveNum;
3775 startedFromSetupPosition = TRUE;
3776 fromX = fromY = toX = toY = -1;
3778 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3779 // So we parse the long-algebraic move string in stead of the SAN move
3780 int valid; char buf[MSG_SIZ], *prom;
3782 // str looks something like "Q/a1-a2"; kill the slash
3784 sprintf(buf, "%c%s", str[0], str+2);
3785 else strcpy(buf, str); // might be castling
3786 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3787 strcat(buf, prom); // long move lacks promo specification!
3788 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789 if(appData.debugMode)
3790 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791 strcpy(move_str, buf);
3793 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794 &fromX, &fromY, &toX, &toY, &promoChar)
3795 || ParseOneMove(buf, moveNum - 1, &moveType,
3796 &fromX, &fromY, &toX, &toY, &promoChar);
3797 // end of long SAN patch
3799 (void) CoordsToAlgebraic(boards[moveNum - 1],
3800 PosFlags(moveNum - 1), EP_UNKNOWN,
3801 fromY, fromX, toY, toX, promoChar,
3802 parseList[moveNum-1]);
3803 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804 castlingRights[moveNum]) ) {
3810 if(gameInfo.variant != VariantShogi)
3811 strcat(parseList[moveNum - 1], "+");
3814 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815 strcat(parseList[moveNum - 1], "#");
3818 strcat(parseList[moveNum - 1], " ");
3819 strcat(parseList[moveNum - 1], elapsed_time);
3820 /* currentMoveString is set as a side-effect of ParseOneMove */
3821 strcpy(moveList[moveNum - 1], currentMoveString);
3822 strcat(moveList[moveNum - 1], "\n");
3824 /* Move from ICS was illegal!? Punt. */
3825 if (appData.debugMode) {
3826 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3830 if (appData.testLegality && appData.debugMode) {
3831 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832 DisplayError(str, 0);
3835 strcpy(parseList[moveNum - 1], move_str);
3836 strcat(parseList[moveNum - 1], " ");
3837 strcat(parseList[moveNum - 1], elapsed_time);
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 fromX = fromY = toX = toY = -1;
3842 if (appData.debugMode) {
3843 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844 setbuf(debugFP, NULL);
3848 /* Send move to chess program (BEFORE animating it). */
3849 if (appData.zippyPlay && !newGame && newMove &&
3850 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3852 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3857 DisplayError(str, 0);
3859 if (first.sendTime) {
3860 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3862 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863 if (firstMove && !bookHit) {
3865 if (first.useColors) {
3866 SendToProgram(gameMode == IcsPlayingWhite ?
3868 "black\ngo\n", &first);
3870 SendToProgram("go\n", &first);
3872 first.maybeThinking = TRUE;
3875 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876 if (moveList[moveNum - 1][0] == NULLCHAR) {
3877 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878 DisplayError(str, 0);
3880 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881 SendMoveToProgram(moveNum - 1, &first);
3888 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889 /* If move comes from a remote source, animate it. If it
3890 isn't remote, it will have already been animated. */
3891 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3894 if (!pausing && appData.highlightLastMove) {
3895 SetHighlights(fromX, fromY, toX, toY);
3899 /* Start the clocks */
3900 whiteFlag = blackFlag = FALSE;
3901 appData.clockMode = !(basetime == 0 && increment == 0);
3903 ics_clock_paused = TRUE;
3905 } else if (ticking == 1) {
3906 ics_clock_paused = FALSE;
3908 if (gameMode == IcsIdle ||
3909 relation == RELATION_OBSERVING_STATIC ||
3910 relation == RELATION_EXAMINING ||
3912 DisplayBothClocks();
3916 /* Display opponents and material strengths */
3917 if (gameInfo.variant != VariantBughouse &&
3918 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919 if (tinyLayout || smallLayout) {
3920 if(gameInfo.variant == VariantNormal)
3921 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3922 gameInfo.white, white_stren, gameInfo.black, black_stren,
3923 basetime, increment);
3925 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3926 gameInfo.white, white_stren, gameInfo.black, black_stren,
3927 basetime, increment, (int) gameInfo.variant);
3929 if(gameInfo.variant == VariantNormal)
3930 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3931 gameInfo.white, white_stren, gameInfo.black, black_stren,
3932 basetime, increment);
3934 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3935 gameInfo.white, white_stren, gameInfo.black, black_stren,
3936 basetime, increment, VariantName(gameInfo.variant));
3939 if (appData.debugMode) {
3940 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3945 /* Display the board */
3946 if (!pausing && !appData.noGUI) {
3948 if (appData.premove)
3950 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952 ClearPremoveHighlights();
3954 DrawPosition(FALSE, boards[currentMove]);
3955 DisplayMove(moveNum - 1);
3956 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3959 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3963 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3965 if(bookHit) { // [HGM] book: simulate book reply
3966 static char bookMove[MSG_SIZ]; // a bit generous?
3968 programStats.nodes = programStats.depth = programStats.time =
3969 programStats.score = programStats.got_only_move = 0;
3970 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3972 strcpy(bookMove, "move ");
3973 strcat(bookMove, bookHit);
3974 HandleMachineMove(bookMove, &first);
3983 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984 ics_getting_history = H_REQUESTED;
3985 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3991 AnalysisPeriodicEvent(force)
3994 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995 && !force) || !appData.periodicUpdates)
3998 /* Send . command to Crafty to collect stats */
3999 SendToProgram(".\n", &first);
4001 /* Don't send another until we get a response (this makes
4002 us stop sending to old Crafty's which don't understand
4003 the "." command (sending illegal cmds resets node count & time,
4004 which looks bad)) */
4005 programStats.ok_to_send = 0;
4009 SendMoveToProgram(moveNum, cps)
4011 ChessProgramState *cps;
4015 if (cps->useUsermove) {
4016 SendToProgram("usermove ", cps);
4020 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021 int len = space - parseList[moveNum];
4022 memcpy(buf, parseList[moveNum], len);
4024 buf[len] = NULLCHAR;
4026 sprintf(buf, "%s\n", parseList[moveNum]);
4028 SendToProgram(buf, cps);
4030 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031 AlphaRank(moveList[moveNum], 4);
4032 SendToProgram(moveList[moveNum], cps);
4033 AlphaRank(moveList[moveNum], 4); // and back
4035 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036 * the engine. It would be nice to have a better way to identify castle
4038 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039 && cps->useOOCastle) {
4040 int fromX = moveList[moveNum][0] - AAA;
4041 int fromY = moveList[moveNum][1] - ONE;
4042 int toX = moveList[moveNum][2] - AAA;
4043 int toY = moveList[moveNum][3] - ONE;
4044 if((boards[moveNum][fromY][fromX] == WhiteKing
4045 && boards[moveNum][toY][toX] == WhiteRook)
4046 || (boards[moveNum][fromY][fromX] == BlackKing
4047 && boards[moveNum][toY][toX] == BlackRook)) {
4048 if(toX > fromX) SendToProgram("O-O\n", cps);
4049 else SendToProgram("O-O-O\n", cps);
4051 else SendToProgram(moveList[moveNum], cps);
4053 else SendToProgram(moveList[moveNum], cps);
4054 /* End of additions by Tord */
4057 /* [HGM] setting up the opening has brought engine in force mode! */
4058 /* Send 'go' if we are in a mode where machine should play. */
4059 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060 (gameMode == TwoMachinesPlay ||
4062 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4064 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065 SendToProgram("go\n", cps);
4066 if (appData.debugMode) {
4067 fprintf(debugFP, "(extra)\n");
4070 setboardSpoiledMachineBlack = 0;
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4076 int fromX, fromY, toX, toY;
4078 char user_move[MSG_SIZ];
4082 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083 (int)moveType, fromX, fromY, toX, toY);
4084 DisplayError(user_move + strlen("say "), 0);
4086 case WhiteKingSideCastle:
4087 case BlackKingSideCastle:
4088 case WhiteQueenSideCastleWild:
4089 case BlackQueenSideCastleWild:
4091 case WhiteHSideCastleFR:
4092 case BlackHSideCastleFR:
4094 sprintf(user_move, "o-o\n");
4096 case WhiteQueenSideCastle:
4097 case BlackQueenSideCastle:
4098 case WhiteKingSideCastleWild:
4099 case BlackKingSideCastleWild:
4101 case WhiteASideCastleFR:
4102 case BlackASideCastleFR:
4104 sprintf(user_move, "o-o-o\n");
4106 case WhitePromotionQueen:
4107 case BlackPromotionQueen:
4108 case WhitePromotionRook:
4109 case BlackPromotionRook:
4110 case WhitePromotionBishop:
4111 case BlackPromotionBishop:
4112 case WhitePromotionKnight:
4113 case BlackPromotionKnight:
4114 case WhitePromotionKing:
4115 case BlackPromotionKing:
4116 case WhitePromotionChancellor:
4117 case BlackPromotionChancellor:
4118 case WhitePromotionArchbishop:
4119 case BlackPromotionArchbishop:
4120 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121 sprintf(user_move, "%c%c%c%c=%c\n",
4122 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123 PieceToChar(WhiteFerz));
4124 else if(gameInfo.variant == VariantGreat)
4125 sprintf(user_move, "%c%c%c%c=%c\n",
4126 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127 PieceToChar(WhiteMan));
4129 sprintf(user_move, "%c%c%c%c=%c\n",
4130 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131 PieceToChar(PromoPiece(moveType)));
4135 sprintf(user_move, "%c@%c%c\n",
4136 ToUpper(PieceToChar((ChessSquare) fromX)),
4137 AAA + toX, ONE + toY);
4140 case WhiteCapturesEnPassant:
4141 case BlackCapturesEnPassant:
4142 case IllegalMove: /* could be a variant we don't quite understand */
4143 sprintf(user_move, "%c%c%c%c\n",
4144 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4147 SendToICS(user_move);
4148 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4158 if (rf == DROP_RANK) {
4159 sprintf(move, "%c@%c%c\n",
4160 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4162 if (promoChar == 'x' || promoChar == NULLCHAR) {
4163 sprintf(move, "%c%c%c%c\n",
4164 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4166 sprintf(move, "%c%c%c%c%c\n",
4167 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4173 ProcessICSInitScript(f)
4178 while (fgets(buf, MSG_SIZ, f)) {
4179 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4188 AlphaRank(char *move, int n)
4190 // char *p = move, c; int x, y;
4192 if (appData.debugMode) {
4193 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4197 move[2]>='0' && move[2]<='9' &&
4198 move[3]>='a' && move[3]<='x' ) {
4200 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4201 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4203 if(move[0]>='0' && move[0]<='9' &&
4204 move[1]>='a' && move[1]<='x' &&
4205 move[2]>='0' && move[2]<='9' &&
4206 move[3]>='a' && move[3]<='x' ) {
4207 /* input move, Shogi -> normal */
4208 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4209 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4211 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4214 move[3]>='0' && move[3]<='9' &&
4215 move[2]>='a' && move[2]<='x' ) {
4217 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4221 move[0]>='a' && move[0]<='x' &&
4222 move[3]>='0' && move[3]<='9' &&
4223 move[2]>='a' && move[2]<='x' ) {
4224 /* output move, normal -> Shogi */
4225 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4231 if (appData.debugMode) {
4232 fprintf(debugFP, " out = '%s'\n", move);
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4241 ChessMove *moveType;
4242 int *fromX, *fromY, *toX, *toY;
4245 if (appData.debugMode) {
4246 fprintf(debugFP, "move to parse: %s\n", move);
4248 *moveType = yylexstr(moveNum, move);
4250 switch (*moveType) {
4251 case WhitePromotionChancellor:
4252 case BlackPromotionChancellor:
4253 case WhitePromotionArchbishop:
4254 case BlackPromotionArchbishop:
4255 case WhitePromotionQueen:
4256 case BlackPromotionQueen:
4257 case WhitePromotionRook:
4258 case BlackPromotionRook:
4259 case WhitePromotionBishop:
4260 case BlackPromotionBishop:
4261 case WhitePromotionKnight:
4262 case BlackPromotionKnight:
4263 case WhitePromotionKing:
4264 case BlackPromotionKing:
4266 case WhiteCapturesEnPassant:
4267 case BlackCapturesEnPassant:
4268 case WhiteKingSideCastle:
4269 case WhiteQueenSideCastle:
4270 case BlackKingSideCastle:
4271 case BlackQueenSideCastle:
4272 case WhiteKingSideCastleWild:
4273 case WhiteQueenSideCastleWild:
4274 case BlackKingSideCastleWild:
4275 case BlackQueenSideCastleWild:
4276 /* Code added by Tord: */
4277 case WhiteHSideCastleFR:
4278 case WhiteASideCastleFR:
4279 case BlackHSideCastleFR:
4280 case BlackASideCastleFR:
4281 /* End of code added by Tord */
4282 case IllegalMove: /* bug or odd chess variant */
4283 *fromX = currentMoveString[0] - AAA;
4284 *fromY = currentMoveString[1] - ONE;
4285 *toX = currentMoveString[2] - AAA;
4286 *toY = currentMoveString[3] - ONE;
4287 *promoChar = currentMoveString[4];
4288 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290 if (appData.debugMode) {
4291 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4293 *fromX = *fromY = *toX = *toY = 0;
4296 if (appData.testLegality) {
4297 return (*moveType != IllegalMove);
4299 return !(fromX == fromY && toX == toY);
4304 *fromX = *moveType == WhiteDrop ?
4305 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306 (int) CharToPiece(ToLower(currentMoveString[0]));
4308 *toX = currentMoveString[2] - AAA;
4309 *toY = currentMoveString[3] - ONE;
4310 *promoChar = NULLCHAR;
4314 case ImpossibleMove:
4315 case (ChessMove) 0: /* end of file */
4324 if (appData.debugMode) {
4325 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4328 *fromX = *fromY = *toX = *toY = 0;
4329 *promoChar = NULLCHAR;
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4345 void GetPositionNumber()
4346 { // sets global variable seed
4349 seed = appData.defaultFrcPosition;
4350 if(seed < 0) { // randomize based on time for negative FRC position numbers
4351 for(i=0; i<50; i++) seed += random();
4352 seed = random() ^ random() >> 8 ^ random() << 8;
4353 if(seed<0) seed = -seed;
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4362 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364 board[rank][i] = (ChessSquare) pieceType;
4365 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4367 piecesLeft[pieceType]--;
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4380 i = seed % squaresLeft[shade];
4381 nrOfShuffles *= squaresLeft[shade];
4382 seed /= squaresLeft[shade];
4383 put(board, pieceType, rank, i, shade);
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4389 int i, n=squaresLeft[ANY], j=n-1, k;
4391 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392 i = seed % k; // pick one
4395 while(i >= j) i -= j--;
4396 j = n - 1 - j; i += j;
4397 put(board, pieceType, rank, j, ANY);
4398 put(board, pieceType, rank, i, ANY);
4401 void SetUpShuffle(Board board, int number)
4405 GetPositionNumber(); nrOfShuffles = 1;
4407 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4409 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4411 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4413 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414 p = (int) board[0][i];
4415 if(p < (int) BlackPawn) piecesLeft[p] ++;
4416 board[0][i] = EmptySquare;
4419 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420 // shuffles restricted to allow normal castling put KRR first
4421 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428 put(board, WhiteRook, 0, 0, ANY);
4429 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4432 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436 while(piecesLeft[p] >= 2) {
4437 AddOnePiece(board, p, 0, LITE);
4438 AddOnePiece(board, p, 0, DARK);
4440 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4443 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445 // but we leave King and Rooks for last, to possibly obey FRC restriction
4446 if(p == (int)WhiteRook) continue;
4447 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4451 // now everything is placed, except perhaps King (Unicorn) and Rooks
4453 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454 // Last King gets castling rights
4455 while(piecesLeft[(int)WhiteUnicorn]) {
4456 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4460 while(piecesLeft[(int)WhiteKing]) {
4461 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4467 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4468 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4471 // Only Rooks can be left; simply place them all
4472 while(piecesLeft[(int)WhiteRook]) {
4473 i = put(board, WhiteRook, 0, 0, ANY);
4474 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4477 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4479 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4482 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4486 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /* Basically a safe strcpy that uses the last character as King */
4493 int result = FALSE; int NrPieces;
4495 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4496 && NrPieces >= 12 && !(NrPieces&1)) {
4497 int i; /* [HGM] Accept even length from 12 to 34 */
4499 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500 for( i=0; i<NrPieces/2-1; i++ ) {
4502 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4504 table[(int) WhiteKing] = map[NrPieces/2-1];
4505 table[(int) BlackKing] = map[NrPieces-1];
4513 void Prelude(Board board)
4514 { // [HGM] superchess: random selection of exo-pieces
4515 int i, j, k; ChessSquare p;
4516 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4518 GetPositionNumber(); // use FRC position number
4520 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521 SetCharTable(pieceToChar, appData.pieceToCharTable);
4522 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4523 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4526 j = seed%4; seed /= 4;
4527 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4529 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4530 j = seed%3 + (seed%3 >= j); seed /= 3;
4531 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4533 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4534 j = seed%3; seed /= 3;
4535 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4537 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4538 j = seed%2 + (seed%2 >= j); seed /= 2;
4539 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4541 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4542 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4543 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4544 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545 put(board, exoPieces[0], 0, 0, ANY);
4546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4550 InitPosition(redraw)
4553 ChessSquare (* pieces)[BOARD_SIZE];
4554 int i, j, pawnRow, overrule,
4555 oldx = gameInfo.boardWidth,
4556 oldy = gameInfo.boardHeight,
4557 oldh = gameInfo.holdingsWidth,
4558 oldv = gameInfo.variant;
4560 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4562 /* [AS] Initialize pv info list [HGM] and game status */
4564 for( i=0; i<MAX_MOVES; i++ ) {
4565 pvInfoList[i].depth = 0;
4566 epStatus[i]=EP_NONE;
4567 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4570 initialRulePlies = 0; /* 50-move counter start */
4572 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4573 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4577 /* [HGM] logic here is completely changed. In stead of full positions */
4578 /* the initialized data only consist of the two backranks. The switch */
4579 /* selects which one we will use, which is than copied to the Board */
4580 /* initialPosition, which for the rest is initialized by Pawns and */
4581 /* empty squares. This initial position is then copied to boards[0], */
4582 /* possibly after shuffling, so that it remains available. */
4584 gameInfo.holdingsWidth = 0; /* default board sizes */
4585 gameInfo.boardWidth = 8;
4586 gameInfo.boardHeight = 8;
4587 gameInfo.holdingsSize = 0;
4588 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4589 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4590 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4592 switch (gameInfo.variant) {
4593 case VariantFischeRandom:
4594 shuffleOpenings = TRUE;
4598 case VariantShatranj:
4599 pieces = ShatranjArray;
4600 nrCastlingRights = 0;
4601 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4603 case VariantTwoKings:
4604 pieces = twoKingsArray;
4606 case VariantCapaRandom:
4607 shuffleOpenings = TRUE;
4608 case VariantCapablanca:
4609 pieces = CapablancaArray;
4610 gameInfo.boardWidth = 10;
4611 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4614 pieces = GothicArray;
4615 gameInfo.boardWidth = 10;
4616 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4619 pieces = JanusArray;
4620 gameInfo.boardWidth = 10;
4621 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4622 nrCastlingRights = 6;
4623 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4624 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4625 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4626 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4627 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4628 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4631 pieces = FalconArray;
4632 gameInfo.boardWidth = 10;
4633 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4635 case VariantXiangqi:
4636 pieces = XiangqiArray;
4637 gameInfo.boardWidth = 9;
4638 gameInfo.boardHeight = 10;
4639 nrCastlingRights = 0;
4640 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4643 pieces = ShogiArray;
4644 gameInfo.boardWidth = 9;
4645 gameInfo.boardHeight = 9;
4646 gameInfo.holdingsSize = 7;
4647 nrCastlingRights = 0;
4648 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4650 case VariantCourier:
4651 pieces = CourierArray;
4652 gameInfo.boardWidth = 12;
4653 nrCastlingRights = 0;
4654 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4655 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4657 case VariantKnightmate:
4658 pieces = KnightmateArray;
4659 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4662 pieces = fairyArray;
4663 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4666 pieces = GreatArray;
4667 gameInfo.boardWidth = 10;
4668 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4669 gameInfo.holdingsSize = 8;
4673 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4674 gameInfo.holdingsSize = 8;
4675 startedFromSetupPosition = TRUE;
4677 case VariantCrazyhouse:
4678 case VariantBughouse:
4680 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4681 gameInfo.holdingsSize = 5;
4683 case VariantWildCastle:
4685 /* !!?shuffle with kings guaranteed to be on d or e file */
4686 shuffleOpenings = 1;
4688 case VariantNoCastle:
4690 nrCastlingRights = 0;
4691 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692 /* !!?unconstrained back-rank shuffle */
4693 shuffleOpenings = 1;
4698 if(appData.NrFiles >= 0) {
4699 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4700 gameInfo.boardWidth = appData.NrFiles;
4702 if(appData.NrRanks >= 0) {
4703 gameInfo.boardHeight = appData.NrRanks;
4705 if(appData.holdingsSize >= 0) {
4706 i = appData.holdingsSize;
4707 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4708 gameInfo.holdingsSize = i;
4710 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4711 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4712 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4714 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4715 if(pawnRow < 1) pawnRow = 1;
4717 /* User pieceToChar list overrules defaults */
4718 if(appData.pieceToCharTable != NULL)
4719 SetCharTable(pieceToChar, appData.pieceToCharTable);
4721 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4723 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4724 s = (ChessSquare) 0; /* account holding counts in guard band */
4725 for( i=0; i<BOARD_HEIGHT; i++ )
4726 initialPosition[i][j] = s;
4728 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4729 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4730 initialPosition[pawnRow][j] = WhitePawn;
4731 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4732 if(gameInfo.variant == VariantXiangqi) {
4734 initialPosition[pawnRow][j] =
4735 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4736 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4737 initialPosition[2][j] = WhiteCannon;
4738 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4742 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4744 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4747 initialPosition[1][j] = WhiteBishop;
4748 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4750 initialPosition[1][j] = WhiteRook;
4751 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4754 if( nrCastlingRights == -1) {
4755 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4756 /* This sets default castling rights from none to normal corners */
4757 /* Variants with other castling rights must set them themselves above */
4758 nrCastlingRights = 6;
4760 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4761 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4762 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4763 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4764 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4765 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4768 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4769 if(gameInfo.variant == VariantGreat) { // promotion commoners
4770 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4771 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4772 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4773 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4776 if(gameInfo.variant == VariantFischeRandom) {
4777 if( appData.defaultFrcPosition < 0 ) {
4778 ShuffleFRC( initialPosition );
4781 SetupFRC( initialPosition, appData.defaultFrcPosition );
4783 startedFromSetupPosition = TRUE;
4786 if (appData.debugMode) {
4787 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4789 if(shuffleOpenings) {
4790 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4791 startedFromSetupPosition = TRUE;
4794 if(startedFromPositionFile) {
4795 /* [HGM] loadPos: use PositionFile for every new game */
4796 CopyBoard(initialPosition, filePosition);
4797 for(i=0; i<nrCastlingRights; i++)
4798 castlingRights[0][i] = initialRights[i] = fileRights[i];
4799 startedFromSetupPosition = TRUE;
4802 CopyBoard(boards[0], initialPosition);
4804 if(oldx != gameInfo.boardWidth ||
4805 oldy != gameInfo.boardHeight ||
4806 oldh != gameInfo.holdingsWidth
4808 || oldv == VariantGothic || // For licensing popups
4809 gameInfo.variant == VariantGothic
4812 || oldv == VariantFalcon ||
4813 gameInfo.variant == VariantFalcon
4816 InitDrawingSizes(-2 ,0);
4819 DrawPosition(TRUE, boards[currentMove]);
4823 SendBoard(cps, moveNum)
4824 ChessProgramState *cps;
4827 char message[MSG_SIZ];
4829 if (cps->useSetboard) {
4830 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4831 sprintf(message, "setboard %s\n", fen);
4832 SendToProgram(message, cps);
4838 /* Kludge to set black to move, avoiding the troublesome and now
4839 * deprecated "black" command.
4841 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843 SendToProgram("edit\n", cps);
4844 SendToProgram("#\n", cps);
4845 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846 bp = &boards[moveNum][i][BOARD_LEFT];
4847 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848 if ((int) *bp < (int) BlackPawn) {
4849 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4851 if(message[0] == '+' || message[0] == '~') {
4852 sprintf(message, "%c%c%c+\n",
4853 PieceToChar((ChessSquare)(DEMOTED *bp)),
4856 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4857 message[1] = BOARD_RGHT - 1 - j + '1';
4858 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860 SendToProgram(message, cps);
4865 SendToProgram("c\n", cps);
4866 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867 bp = &boards[moveNum][i][BOARD_LEFT];
4868 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869 if (((int) *bp != (int) EmptySquare)
4870 && ((int) *bp >= (int) BlackPawn)) {
4871 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873 if(message[0] == '+' || message[0] == '~') {
4874 sprintf(message, "%c%c%c+\n",
4875 PieceToChar((ChessSquare)(DEMOTED *bp)),
4878 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879 message[1] = BOARD_RGHT - 1 - j + '1';
4880 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882 SendToProgram(message, cps);
4887 SendToProgram(".\n", cps);
4889 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4893 IsPromotion(fromX, fromY, toX, toY)
4894 int fromX, fromY, toX, toY;
4896 /* [HGM] add Shogi promotions */
4897 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4900 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4901 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4902 /* [HGM] Note to self: line above also weeds out drops */
4903 piece = boards[currentMove][fromY][fromX];
4904 if(gameInfo.variant == VariantShogi) {
4905 promotionZoneSize = 3;
4906 highestPromotingPiece = (int)WhiteKing;
4907 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4908 and if in normal chess we then allow promotion to King, why not
4909 allow promotion of other piece in Shogi? */
4911 if((int)piece >= BlackPawn) {
4912 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4917 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919 return ( (int)piece <= highestPromotingPiece );
4923 InPalace(row, column)
4925 { /* [HGM] for Xiangqi */
4926 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4927 column < (BOARD_WIDTH + 4)/2 &&
4928 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4933 PieceForSquare (x, y)
4937 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4940 return boards[currentMove][y][x];
4944 OKToStartUserMove(x, y)
4947 ChessSquare from_piece;
4950 if (matchMode) return FALSE;
4951 if (gameMode == EditPosition) return TRUE;
4953 if (x >= 0 && y >= 0)
4954 from_piece = boards[currentMove][y][x];
4956 from_piece = EmptySquare;
4958 if (from_piece == EmptySquare) return FALSE;
4960 white_piece = (int)from_piece >= (int)WhitePawn &&
4961 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4964 case PlayFromGameFile:
4966 case TwoMachinesPlay:
4974 case MachinePlaysWhite:
4975 case IcsPlayingBlack:
4976 if (appData.zippyPlay) return FALSE;
4978 DisplayMoveError(_("You are playing Black"));
4983 case MachinePlaysBlack:
4984 case IcsPlayingWhite:
4985 if (appData.zippyPlay) return FALSE;
4987 DisplayMoveError(_("You are playing White"));
4993 if (!white_piece && WhiteOnMove(currentMove)) {
4994 DisplayMoveError(_("It is White's turn"));
4997 if (white_piece && !WhiteOnMove(currentMove)) {
4998 DisplayMoveError(_("It is Black's turn"));
5001 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5002 /* Editing correspondence game history */
5003 /* Could disallow this or prompt for confirmation */
5006 if (currentMove < forwardMostMove) {
5007 /* Discarding moves */
5008 /* Could prompt for confirmation here,
5009 but I don't think that's such a good idea */
5010 forwardMostMove = currentMove;
5014 case BeginningOfGame:
5015 if (appData.icsActive) return FALSE;
5016 if (!appData.noChessProgram) {
5018 DisplayMoveError(_("You are playing White"));
5025 if (!white_piece && WhiteOnMove(currentMove)) {
5026 DisplayMoveError(_("It is White's turn"));
5029 if (white_piece && !WhiteOnMove(currentMove)) {
5030 DisplayMoveError(_("It is Black's turn"));
5039 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5040 && gameMode != AnalyzeFile && gameMode != Training) {
5041 DisplayMoveError(_("Displayed position is not current"));
5047 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5048 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5049 int lastLoadGameUseList = FALSE;
5050 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5051 ChessMove lastLoadGameStart = (ChessMove) 0;
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5056 int fromX, fromY, toX, toY;
5060 ChessSquare pdown, pup;
5062 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5063 if ((fromX == toX) && (fromY == toY)) {
5064 return ImpossibleMove;
5067 /* [HGM] suppress all moves into holdings area and guard band */
5068 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5069 return ImpossibleMove;
5071 /* [HGM] <sameColor> moved to here from winboard.c */
5072 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5073 pdown = boards[currentMove][fromY][fromX];
5074 pup = boards[currentMove][toY][toX];
5075 if ( gameMode != EditPosition &&
5076 (WhitePawn <= pdown && pdown < BlackPawn &&
5077 WhitePawn <= pup && pup < BlackPawn ||
5078 BlackPawn <= pdown && pdown < EmptySquare &&
5079 BlackPawn <= pup && pup < EmptySquare
5080 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5081 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5082 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5084 return ImpossibleMove;
5086 /* Check if the user is playing in turn. This is complicated because we
5087 let the user "pick up" a piece before it is his turn. So the piece he
5088 tried to pick up may have been captured by the time he puts it down!
5089 Therefore we use the color the user is supposed to be playing in this
5090 test, not the color of the piece that is currently on the starting
5091 square---except in EditGame mode, where the user is playing both
5092 sides; fortunately there the capture race can't happen. (It can
5093 now happen in IcsExamining mode, but that's just too bad. The user
5094 will get a somewhat confusing message in that case.)
5098 case PlayFromGameFile:
5100 case TwoMachinesPlay:
5104 /* We switched into a game mode where moves are not accepted,
5105 perhaps while the mouse button was down. */
5106 return ImpossibleMove;
5108 case MachinePlaysWhite:
5109 /* User is moving for Black */
5110 if (WhiteOnMove(currentMove)) {
5111 DisplayMoveError(_("It is White's turn"));
5112 return ImpossibleMove;
5116 case MachinePlaysBlack:
5117 /* User is moving for White */
5118 if (!WhiteOnMove(currentMove)) {
5119 DisplayMoveError(_("It is Black's turn"));
5120 return ImpossibleMove;
5126 case BeginningOfGame:
5129 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131 /* User is moving for Black */
5132 if (WhiteOnMove(currentMove)) {
5133 DisplayMoveError(_("It is White's turn"));
5134 return ImpossibleMove;
5137 /* User is moving for White */
5138 if (!WhiteOnMove(currentMove)) {
5139 DisplayMoveError(_("It is Black's turn"));
5140 return ImpossibleMove;
5145 case IcsPlayingBlack:
5146 /* User is moving for Black */
5147 if (WhiteOnMove(currentMove)) {
5148 if (!appData.premove) {
5149 DisplayMoveError(_("It is White's turn"));
5150 } else if (toX >= 0 && toY >= 0) {
5153 premoveFromX = fromX;
5154 premoveFromY = fromY;
5155 premovePromoChar = promoChar;
5157 if (appData.debugMode)
5158 fprintf(debugFP, "Got premove: fromX %d,"
5159 "fromY %d, toX %d, toY %d\n",
5160 fromX, fromY, toX, toY);
5161 if(!WhiteOnMove(currentMove) && gotPremove == 1) {
5162 // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5163 if (appData.debugMode)
5164 fprintf(debugFP, "Execute as normal move\n");
5165 gotPremove = 0; break;
5168 return ImpossibleMove;
5172 case IcsPlayingWhite:
5173 /* User is moving for White */
5174 if (!WhiteOnMove(currentMove)) {
5175 if (!appData.premove) {
5176 DisplayMoveError(_("It is Black's turn"));
5177 } else if (toX >= 0 && toY >= 0) {
5180 premoveFromX = fromX;
5181 premoveFromY = fromY;
5182 premovePromoChar = promoChar;
5184 if (appData.debugMode)
5185 fprintf(debugFP, "Got premove: fromX %d,"
5186 "fromY %d, toX %d, toY %d\n",
5187 fromX, fromY, toX, toY);
5188 if(WhiteOnMove(currentMove) && gotPremove == 1) {
5189 // [HGM] race: we must have been hit by an opponent move from the ICS while preparing the premove
5190 if (appData.debugMode)
5191 fprintf(debugFP, "Execute as normal move\n");
5192 gotPremove = 0; break;
5195 return ImpossibleMove;
5203 /* EditPosition, empty square, or different color piece;
5204 click-click move is possible */
5205 if (toX == -2 || toY == -2) {
5206 boards[0][fromY][fromX] = EmptySquare;
5207 return AmbiguousMove;
5208 } else if (toX >= 0 && toY >= 0) {
5209 boards[0][toY][toX] = boards[0][fromY][fromX];
5210 boards[0][fromY][fromX] = EmptySquare;
5211 return AmbiguousMove;
5213 return ImpossibleMove;
5216 /* [HGM] If move started in holdings, it means a drop */
5217 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5218 if( pup != EmptySquare ) return ImpossibleMove;
5219 if(appData.testLegality) {
5220 /* it would be more logical if LegalityTest() also figured out
5221 * which drops are legal. For now we forbid pawns on back rank.
5222 * Shogi is on its own here...
5224 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5225 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5226 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5228 return WhiteDrop; /* Not needed to specify white or black yet */
5231 userOfferedDraw = FALSE;
5233 /* [HGM] always test for legality, to get promotion info */
5234 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5235 epStatus[currentMove], castlingRights[currentMove],
5236 fromY, fromX, toY, toX, promoChar);
5238 /* [HGM] but possibly ignore an IllegalMove result */
5239 if (appData.testLegality) {
5240 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5241 DisplayMoveError(_("Illegal move"));
5242 return ImpossibleMove;
5245 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5247 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5248 function is made into one that returns an OK move type if FinishMove
5249 should be called. This to give the calling driver routine the
5250 opportunity to finish the userMove input with a promotion popup,
5251 without bothering the user with this for invalid or illegal moves */
5253 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5256 /* Common tail of UserMoveEvent and DropMenuEvent */
5258 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5260 int fromX, fromY, toX, toY;
5261 /*char*/int promoChar;
5264 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5265 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5266 // [HGM] superchess: suppress promotions to non-available piece
5267 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5268 if(WhiteOnMove(currentMove)) {
5269 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5271 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5275 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5276 move type in caller when we know the move is a legal promotion */
5277 if(moveType == NormalMove && promoChar)
5278 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5279 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5280 /* [HGM] convert drag-and-drop piece drops to standard form */
5281 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5282 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5283 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5284 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5285 // fromX = boards[currentMove][fromY][fromX];
5286 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5287 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5288 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5289 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5293 /* [HGM] <popupFix> The following if has been moved here from
5294 UserMoveEvent(). Because it seemed to belon here (why not allow
5295 piece drops in training games?), and because it can only be
5296 performed after it is known to what we promote. */
5297 if (gameMode == Training) {
5298 /* compare the move played on the board to the next move in the
5299 * game. If they match, display the move and the opponent's response.
5300 * If they don't match, display an error message.
5303 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5304 CopyBoard(testBoard, boards[currentMove]);
5305 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5307 if (CompareBoards(testBoard, boards[currentMove+1])) {
5308 ForwardInner(currentMove+1);
5310 /* Autoplay the opponent's response.
5311 * if appData.animate was TRUE when Training mode was entered,
5312 * the response will be animated.
5314 saveAnimate = appData.animate;
5315 appData.animate = animateTraining;
5316 ForwardInner(currentMove+1);
5317 appData.animate = saveAnimate;
5319 /* check for the end of the game */
5320 if (currentMove >= forwardMostMove) {
5321 gameMode = PlayFromGameFile;
5323 SetTrainingModeOff();
5324 DisplayInformation(_("End of game"));
5327 DisplayError(_("Incorrect move"), 0);
5332 /* Ok, now we know that the move is good, so we can kill
5333 the previous line in Analysis Mode */
5334 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5335 forwardMostMove = currentMove;
5338 /* If we need the chess program but it's dead, restart it */
5339 ResurrectChessProgram();
5341 /* A user move restarts a paused game*/
5345 thinkOutput[0] = NULLCHAR;
5347 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5349 if (gameMode == BeginningOfGame) {
5350 if (appData.noChessProgram) {
5351 gameMode = EditGame;
5355 gameMode = MachinePlaysBlack;
5358 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5360 if (first.sendName) {
5361 sprintf(buf, "name %s\n", gameInfo.white);
5362 SendToProgram(buf, &first);
5368 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5369 /* Relay move to ICS or chess engine */
5370 if (appData.icsActive) {
5371 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5372 gameMode == IcsExamining) {
5373 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5377 if (first.sendTime && (gameMode == BeginningOfGame ||
5378 gameMode == MachinePlaysWhite ||
5379 gameMode == MachinePlaysBlack)) {
5380 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5382 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5383 // [HGM] book: if program might be playing, let it use book
5384 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5385 first.maybeThinking = TRUE;
5386 } else SendMoveToProgram(forwardMostMove-1, &first);
5387 if (currentMove == cmailOldMove + 1) {
5388 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5392 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5396 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5397 EP_UNKNOWN, castlingRights[currentMove]) ) {
5403 if (WhiteOnMove(currentMove)) {
5404 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5406 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5410 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5415 case MachinePlaysBlack:
5416 case MachinePlaysWhite:
5417 /* disable certain menu options while machine is thinking */
5418 SetMachineThinkingEnables();
5425 if(bookHit) { // [HGM] book: simulate book reply
5426 static char bookMove[MSG_SIZ]; // a bit generous?
5428 programStats.nodes = programStats.depth = programStats.time =
5429 programStats.score = programStats.got_only_move = 0;
5430 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5432 strcpy(bookMove, "move ");
5433 strcat(bookMove, bookHit);
5434 HandleMachineMove(bookMove, &first);
5440 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5441 int fromX, fromY, toX, toY;
5444 /* [HGM] This routine was added to allow calling of its two logical
5445 parts from other modules in the old way. Before, UserMoveEvent()
5446 automatically called FinishMove() if the move was OK, and returned
5447 otherwise. I separated the two, in order to make it possible to
5448 slip a promotion popup in between. But that it always needs two
5449 calls, to the first part, (now called UserMoveTest() ), and to
5450 FinishMove if the first part succeeded. Calls that do not need
5451 to do anything in between, can call this routine the old way.
5453 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5454 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5455 if(moveType == AmbiguousMove)
5456 DrawPosition(FALSE, boards[currentMove]);
5457 else if(moveType != ImpossibleMove)
5458 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5461 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5463 // char * hint = lastHint;
5464 FrontEndProgramStats stats;
5466 stats.which = cps == &first ? 0 : 1;
5467 stats.depth = cpstats->depth;
5468 stats.nodes = cpstats->nodes;
5469 stats.score = cpstats->score;
5470 stats.time = cpstats->time;
5471 stats.pv = cpstats->movelist;
5472 stats.hint = lastHint;
5473 stats.an_move_index = 0;
5474 stats.an_move_count = 0;
5476 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5477 stats.hint = cpstats->move_name;
5478 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5479 stats.an_move_count = cpstats->nr_moves;
5482 SetProgramStats( &stats );
5485 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5486 { // [HGM] book: this routine intercepts moves to simulate book replies
5487 char *bookHit = NULL;
5489 //first determine if the incoming move brings opponent into his book
5490 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5491 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5492 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5493 if(bookHit != NULL && !cps->bookSuspend) {
5494 // make sure opponent is not going to reply after receiving move to book position
5495 SendToProgram("force\n", cps);
5496 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5498 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5499 // now arrange restart after book miss
5501 // after a book hit we never send 'go', and the code after the call to this routine
5502 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5504 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5505 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5506 SendToProgram(buf, cps);
5507 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5508 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5509 SendToProgram("go\n", cps);
5510 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5511 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5512 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5513 SendToProgram("go\n", cps);
5514 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5516 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5520 ChessProgramState *savedState;
5521 void DeferredBookMove(void)
5523 if(savedState->lastPing != savedState->lastPong)
5524 ScheduleDelayedEvent(DeferredBookMove, 10);
5526 HandleMachineMove(savedMessage, savedState);
5530 HandleMachineMove(message, cps)
5532 ChessProgramState *cps;
5534 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5535 char realname[MSG_SIZ];
5536 int fromX, fromY, toX, toY;
5543 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5545 * Kludge to ignore BEL characters
5547 while (*message == '\007') message++;
5550 * [HGM] engine debug message: ignore lines starting with '#' character
5552 if(cps->debug && *message == '#') return;
5555 * Look for book output
5557 if (cps == &first && bookRequested) {
5558 if (message[0] == '\t' || message[0] == ' ') {
5559 /* Part of the book output is here; append it */
5560 strcat(bookOutput, message);
5561 strcat(bookOutput, " \n");
5563 } else if (bookOutput[0] != NULLCHAR) {
5564 /* All of book output has arrived; display it */
5565 char *p = bookOutput;
5566 while (*p != NULLCHAR) {
5567 if (*p == '\t') *p = ' ';
5570 DisplayInformation(bookOutput);
5571 bookRequested = FALSE;
5572 /* Fall through to parse the current output */
5577 * Look for machine move.
5579 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5580 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5582 /* This method is only useful on engines that support ping */
5583 if (cps->lastPing != cps->lastPong) {
5584 if (gameMode == BeginningOfGame) {
5585 /* Extra move from before last new; ignore */
5586 if (appData.debugMode) {
5587 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5590 if (appData.debugMode) {
5591 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5592 cps->which, gameMode);
5595 SendToProgram("undo\n", cps);
5601 case BeginningOfGame:
5602 /* Extra move from before last reset; ignore */
5603 if (appData.debugMode) {
5604 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5611 /* Extra move after we tried to stop. The mode test is
5612 not a reliable way of detecting this problem, but it's
5613 the best we can do on engines that don't support ping.
5615 if (appData.debugMode) {
5616 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5617 cps->which, gameMode);
5619 SendToProgram("undo\n", cps);
5622 case MachinePlaysWhite:
5623 case IcsPlayingWhite:
5624 machineWhite = TRUE;
5627 case MachinePlaysBlack:
5628 case IcsPlayingBlack:
5629 machineWhite = FALSE;
5632 case TwoMachinesPlay:
5633 machineWhite = (cps->twoMachinesColor[0] == 'w');
5636 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5637 if (appData.debugMode) {
5639 "Ignoring move out of turn by %s, gameMode %d"
5640 ", forwardMost %d\n",
5641 cps->which, gameMode, forwardMostMove);
5646 if (appData.debugMode) { int f = forwardMostMove;
5647 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5648 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5650 if(cps->alphaRank) AlphaRank(machineMove, 4);
5651 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5652 &fromX, &fromY, &toX, &toY, &promoChar)) {
5653 /* Machine move could not be parsed; ignore it. */
5654 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5655 machineMove, cps->which);
5656 DisplayError(buf1, 0);
5657 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5658 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5659 if (gameMode == TwoMachinesPlay) {
5660 GameEnds(machineWhite ? BlackWins : WhiteWins,
5666 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5667 /* So we have to redo legality test with true e.p. status here, */
5668 /* to make sure an illegal e.p. capture does not slip through, */
5669 /* to cause a forfeit on a justified illegal-move complaint */
5670 /* of the opponent. */
5671 if( gameMode==TwoMachinesPlay && appData.testLegality
5672 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5675 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5676 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5677 fromY, fromX, toY, toX, promoChar);
5678 if (appData.debugMode) {
5680 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5681 castlingRights[forwardMostMove][i], castlingRank[i]);
5682 fprintf(debugFP, "castling rights\n");
5684 if(moveType == IllegalMove) {
5685 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5686 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5687 GameEnds(machineWhite ? BlackWins : WhiteWins,
5690 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5691 /* [HGM] Kludge to handle engines that send FRC-style castling
5692 when they shouldn't (like TSCP-Gothic) */
5694 case WhiteASideCastleFR:
5695 case BlackASideCastleFR:
5697 currentMoveString[2]++;
5699 case WhiteHSideCastleFR:
5700 case BlackHSideCastleFR:
5702 currentMoveString[2]--;
5704 default: ; // nothing to do, but suppresses warning of pedantic compilers
5707 hintRequested = FALSE;
5708 lastHint[0] = NULLCHAR;
5709 bookRequested = FALSE;
5710 /* Program may be pondering now */
5711 cps->maybeThinking = TRUE;
5712 if (cps->sendTime == 2) cps->sendTime = 1;
5713 if (cps->offeredDraw) cps->offeredDraw--;
5716 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5718 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5720 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5721 char buf[3*MSG_SIZ];
5723 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5724 programStats.score / 100.,
5726 programStats.time / 100.,
5727 (unsigned int)programStats.nodes,
5728 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5729 programStats.movelist);
5731 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5735 /* currentMoveString is set as a side-effect of ParseOneMove */
5736 strcpy(machineMove, currentMoveString);
5737 strcat(machineMove, "\n");
5738 strcpy(moveList[forwardMostMove], machineMove);
5740 /* [AS] Save move info and clear stats for next move */
5741 pvInfoList[ forwardMostMove ].score = programStats.score;
5742 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5743 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5744 ClearProgramStats();
5745 thinkOutput[0] = NULLCHAR;
5746 hiddenThinkOutputState = 0;
5748 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5750 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5751 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5754 while( count < adjudicateLossPlies ) {
5755 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5758 score = -score; /* Flip score for winning side */
5761 if( score > adjudicateLossThreshold ) {
5768 if( count >= adjudicateLossPlies ) {
5769 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5771 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5772 "Xboard adjudication",
5779 if( gameMode == TwoMachinesPlay ) {
5780 // [HGM] some adjudications useful with buggy engines
5781 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5782 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5785 if( appData.testLegality )
5786 { /* [HGM] Some more adjudications for obstinate engines */
5787 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5788 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5789 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5790 static int moveCount = 6;
5792 char *reason = NULL;
5794 /* Count what is on board. */
5795 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5796 { ChessSquare p = boards[forwardMostMove][i][j];
5800 { /* count B,N,R and other of each side */
5803 NrK++; break; // [HGM] atomic: count Kings
5807 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5808 bishopsColor |= 1 << ((i^j)&1);
5813 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5814 bishopsColor |= 1 << ((i^j)&1);
5829 PawnAdvance += m; NrPawns++;
5831 NrPieces += (p != EmptySquare);
5832 NrW += ((int)p < (int)BlackPawn);
5833 if(gameInfo.variant == VariantXiangqi &&
5834 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5835 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5836 NrW -= ((int)p < (int)BlackPawn);
5840 /* Some material-based adjudications that have to be made before stalemate test */
5841 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5842 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5843 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5844 if(appData.checkMates) {
5845 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5846 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5848 "Xboard adjudication: King destroyed", GE_XBOARD );
5853 /* Bare King in Shatranj (loses) or Losers (wins) */
5854 if( NrW == 1 || NrPieces - NrW == 1) {
5855 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5856 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5857 if(appData.checkMates) {
5858 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5859 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5860 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5861 "Xboard adjudication: Bare king", GE_XBOARD );
5865 if( gameInfo.variant == VariantShatranj && --bare < 0)
5867 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5868 if(appData.checkMates) {
5869 /* but only adjudicate if adjudication enabled */
5870 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5871 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5872 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5873 "Xboard adjudication: Bare king", GE_XBOARD );
5880 // don't wait for engine to announce game end if we can judge ourselves
5881 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5882 castlingRights[forwardMostMove]) ) {
5884 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5885 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5886 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5887 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5890 reason = "Xboard adjudication: 3rd check";
5891 epStatus[forwardMostMove] = EP_CHECKMATE;
5901 reason = "Xboard adjudication: Stalemate";
5902 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5903 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5904 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5905 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5906 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5907 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5908 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5909 EP_CHECKMATE : EP_WINS);
5910 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5911 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5915 reason = "Xboard adjudication: Checkmate";
5916 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5920 switch(i = epStatus[forwardMostMove]) {
5922 result = GameIsDrawn; break;
5924 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5926 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5928 result = (ChessMove) 0;
5930 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5931 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5932 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5933 GameEnds( result, reason, GE_XBOARD );
5937 /* Next absolutely insufficient mating material. */
5938 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5939 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5940 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5941 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5942 { /* KBK, KNK, KK of KBKB with like Bishops */
5944 /* always flag draws, for judging claims */
5945 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5947 if(appData.materialDraws) {
5948 /* but only adjudicate them if adjudication enabled */
5949 SendToProgram("force\n", cps->other); // suppress reply
5950 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5951 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5952 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5957 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5959 ( NrWR == 1 && NrBR == 1 /* KRKR */
5960 || NrWQ==1 && NrBQ==1 /* KQKQ */
5961 || NrWN==2 || NrBN==2 /* KNNK */
5962 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5964 if(--moveCount < 0 && appData.trivialDraws)
5965 { /* if the first 3 moves do not show a tactical win, declare draw */
5966 SendToProgram("force\n", cps->other); // suppress reply
5967 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5968 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5969 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5972 } else moveCount = 6;
5976 if (appData.debugMode) { int i;
5977 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5978 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5979 appData.drawRepeats);
5980 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5981 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5985 /* Check for rep-draws */
5987 for(k = forwardMostMove-2;
5988 k>=backwardMostMove && k>=forwardMostMove-100 &&
5989 epStatus[k] < EP_UNKNOWN &&
5990 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5994 if (appData.debugMode) {
5995 fprintf(debugFP, " loop\n");
5998 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6000 if (appData.debugMode) {
6001 fprintf(debugFP, "match\n");
6004 /* compare castling rights */
6005 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6006 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6007 rights++; /* King lost rights, while rook still had them */
6008 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6009 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6010 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6011 rights++; /* but at least one rook lost them */
6013 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6014 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6016 if( castlingRights[forwardMostMove][5] >= 0 ) {
6017 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6018 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6022 if (appData.debugMode) {
6023 for(i=0; i<nrCastlingRights; i++)
6024 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6027 if (appData.debugMode) {
6028 fprintf(debugFP, " %d %d\n", rights, k);
6031 if( rights == 0 && ++count > appData.drawRepeats-2
6032 && appData.drawRepeats > 1) {
6033 /* adjudicate after user-specified nr of repeats */
6034 SendToProgram("force\n", cps->other); // suppress reply
6035 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6036 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6037 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6038 // [HGM] xiangqi: check for forbidden perpetuals
6039 int m, ourPerpetual = 1, hisPerpetual = 1;
6040 for(m=forwardMostMove; m>k; m-=2) {
6041 if(MateTest(boards[m], PosFlags(m),
6042 EP_NONE, castlingRights[m]) != MT_CHECK)
6043 ourPerpetual = 0; // the current mover did not always check
6044 if(MateTest(boards[m-1], PosFlags(m-1),
6045 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6046 hisPerpetual = 0; // the opponent did not always check
6048 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6049 ourPerpetual, hisPerpetual);
6050 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6051 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6052 "Xboard adjudication: perpetual checking", GE_XBOARD );
6055 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6056 break; // (or we would have caught him before). Abort repetition-checking loop.
6057 // Now check for perpetual chases
6058 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6059 hisPerpetual = PerpetualChase(k, forwardMostMove);
6060 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6061 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6062 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6063 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6066 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6067 break; // Abort repetition-checking loop.
6069 // if neither of us is checking or chasing all the time, or both are, it is draw
6071 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6074 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6075 epStatus[forwardMostMove] = EP_REP_DRAW;
6079 /* Now we test for 50-move draws. Determine ply count */
6080 count = forwardMostMove;
6081 /* look for last irreversble move */
6082 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6084 /* if we hit starting position, add initial plies */
6085 if( count == backwardMostMove )
6086 count -= initialRulePlies;
6087 count = forwardMostMove - count;
6089 epStatus[forwardMostMove] = EP_RULE_DRAW;
6090 /* this is used to judge if draw claims are legal */
6091 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6092 SendToProgram("force\n", cps->other); // suppress reply
6093 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6094 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6095 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6099 /* if draw offer is pending, treat it as a draw claim
6100 * when draw condition present, to allow engines a way to
6101 * claim draws before making their move to avoid a race
6102 * condition occurring after their move
6104 if( cps->other->offeredDraw || cps->offeredDraw ) {
6106 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6107 p = "Draw claim: 50-move rule";
6108 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6109 p = "Draw claim: 3-fold repetition";
6110 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6111 p = "Draw claim: insufficient mating material";
6113 SendToProgram("force\n", cps->other); // suppress reply
6114 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6115 GameEnds( GameIsDrawn, p, GE_XBOARD );
6116 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6122 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6123 SendToProgram("force\n", cps->other); // suppress reply
6124 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6125 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6127 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6134 if (gameMode == TwoMachinesPlay) {
6135 /* [HGM] relaying draw offers moved to after reception of move */
6136 /* and interpreting offer as claim if it brings draw condition */
6137 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6138 SendToProgram("draw\n", cps->other);
6140 if (cps->other->sendTime) {
6141 SendTimeRemaining(cps->other,
6142 cps->other->twoMachinesColor[0] == 'w');
6144 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6145 if (firstMove && !bookHit) {
6147 if (cps->other->useColors) {
6148 SendToProgram(cps->other->twoMachinesColor, cps->other);
6150 SendToProgram("go\n", cps->other);
6152 cps->other->maybeThinking = TRUE;
6155 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6157 if (!pausing && appData.ringBellAfterMoves) {
6162 * Reenable menu items that were disabled while
6163 * machine was thinking
6165 if (gameMode != TwoMachinesPlay)
6166 SetUserThinkingEnables();
6168 // [HGM] book: after book hit opponent has received move and is now in force mode
6169 // force the book reply into it, and then fake that it outputted this move by jumping
6170 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6172 static char bookMove[MSG_SIZ]; // a bit generous?
6174 strcpy(bookMove, "move ");
6175 strcat(bookMove, bookHit);
6178 programStats.nodes = programStats.depth = programStats.time =
6179 programStats.score = programStats.got_only_move = 0;
6180 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6182 if(cps->lastPing != cps->lastPong) {
6183 savedMessage = message; // args for deferred call
6185 ScheduleDelayedEvent(DeferredBookMove, 10);
6194 /* Set special modes for chess engines. Later something general
6195 * could be added here; for now there is just one kludge feature,
6196 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6197 * when "xboard" is given as an interactive command.
6199 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6200 cps->useSigint = FALSE;
6201 cps->useSigterm = FALSE;
6203 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6204 ParseFeatures(message+8, cps);
6205 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6208 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6209 * want this, I was asked to put it in, and obliged.
6211 if (!strncmp(message, "setboard ", 9)) {
6212 Board initial_position; int i;
6214 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6216 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6217 DisplayError(_("Bad FEN received from engine"), 0);
6220 Reset(FALSE, FALSE);
6221 CopyBoard(boards[0], initial_position);
6222 initialRulePlies = FENrulePlies;
6223 epStatus[0] = FENepStatus;
6224 for( i=0; i<nrCastlingRights; i++ )
6225 castlingRights[0][i] = FENcastlingRights[i];
6226 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6227 else gameMode = MachinePlaysBlack;
6228 DrawPosition(FALSE, boards[currentMove]);
6234 * Look for communication commands
6236 if (!strncmp(message, "telluser ", 9)) {
6237 DisplayNote(message + 9);
6240 if (!strncmp(message, "tellusererror ", 14)) {
6241 DisplayError(message + 14, 0);
6244 if (!strncmp(message, "tellopponent ", 13)) {
6245 if (appData.icsActive) {
6247 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6251 DisplayNote(message + 13);
6255 if (!strncmp(message, "tellothers ", 11)) {
6256 if (appData.icsActive) {
6258 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6264 if (!strncmp(message, "tellall ", 8)) {
6265 if (appData.icsActive) {
6267 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6271 DisplayNote(message + 8);
6275 if (strncmp(message, "warning", 7) == 0) {
6276 /* Undocumented feature, use tellusererror in new code */
6277 DisplayError(message, 0);
6280 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6281 strcpy(realname, cps->tidy);
6282 strcat(realname, " query");
6283 AskQuestion(realname, buf2, buf1, cps->pr);
6286 /* Commands from the engine directly to ICS. We don't allow these to be
6287 * sent until we are logged on. Crafty kibitzes have been known to
6288 * interfere with the login process.
6291 if (!strncmp(message, "tellics ", 8)) {
6292 SendToICS(message + 8);
6296 if (!strncmp(message, "tellicsnoalias ", 15)) {
6297 SendToICS(ics_prefix);
6298 SendToICS(message + 15);
6302 /* The following are for backward compatibility only */
6303 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6304 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6305 SendToICS(ics_prefix);
6311 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6315 * If the move is illegal, cancel it and redraw the board.
6316 * Also deal with other error cases. Matching is rather loose
6317 * here to accommodate engines written before the spec.
6319 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6320 strncmp(message, "Error", 5) == 0) {
6321 if (StrStr(message, "name") ||
6322 StrStr(message, "rating") || StrStr(message, "?") ||
6323 StrStr(message, "result") || StrStr(message, "board") ||
6324 StrStr(message, "bk") || StrStr(message, "computer") ||
6325 StrStr(message, "variant") || StrStr(message, "hint") ||
6326 StrStr(message, "random") || StrStr(message, "depth") ||
6327 StrStr(message, "accepted")) {
6330 if (StrStr(message, "protover")) {
6331 /* Program is responding to input, so it's apparently done
6332 initializing, and this error message indicates it is
6333 protocol version 1. So we don't need to wait any longer
6334 for it to initialize and send feature commands. */
6335 FeatureDone(cps, 1);
6336 cps->protocolVersion = 1;
6339 cps->maybeThinking = FALSE;
6341 if (StrStr(message, "draw")) {
6342 /* Program doesn't have "draw" command */
6343 cps->sendDrawOffers = 0;
6346 if (cps->sendTime != 1 &&
6347 (StrStr(message, "time") || StrStr(message, "otim"))) {
6348 /* Program apparently doesn't have "time" or "otim" command */
6352 if (StrStr(message, "analyze")) {
6353 cps->analysisSupport = FALSE;
6354 cps->analyzing = FALSE;
6356 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6357 DisplayError(buf2, 0);
6360 if (StrStr(message, "(no matching move)st")) {
6361 /* Special kludge for GNU Chess 4 only */
6362 cps->stKludge = TRUE;
6363 SendTimeControl(cps, movesPerSession, timeControl,
6364 timeIncrement, appData.searchDepth,
6368 if (StrStr(message, "(no matching move)sd")) {
6369 /* Special kludge for GNU Chess 4 only */
6370 cps->sdKludge = TRUE;
6371 SendTimeControl(cps, movesPerSession, timeControl,
6372 timeIncrement, appData.searchDepth,
6376 if (!StrStr(message, "llegal")) {
6379 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6380 gameMode == IcsIdle) return;
6381 if (forwardMostMove <= backwardMostMove) return;
6383 /* Following removed: it caused a bug where a real illegal move
6384 message in analyze mored would be ignored. */
6385 if (cps == &first && programStats.ok_to_send == 0) {
6386 /* Bogus message from Crafty responding to "." This filtering
6387 can miss some of the bad messages, but fortunately the bug
6388 is fixed in current Crafty versions, so it doesn't matter. */
6392 if (pausing) PauseEvent();
6393 if (gameMode == PlayFromGameFile) {
6394 /* Stop reading this game file */
6395 gameMode = EditGame;
6398 currentMove = --forwardMostMove;
6399 DisplayMove(currentMove-1); /* before DisplayMoveError */
6401 DisplayBothClocks();
6402 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6403 parseList[currentMove], cps->which);
6404 DisplayMoveError(buf1);
6405 DrawPosition(FALSE, boards[currentMove]);
6407 /* [HGM] illegal-move claim should forfeit game when Xboard */
6408 /* only passes fully legal moves */
6409 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6410 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6411 "False illegal-move claim", GE_XBOARD );
6415 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6416 /* Program has a broken "time" command that
6417 outputs a string not ending in newline.
6423 * If chess program startup fails, exit with an error message.
6424 * Attempts to recover here are futile.
6426 if ((StrStr(message, "unknown host") != NULL)
6427 || (StrStr(message, "No remote directory") != NULL)
6428 || (StrStr(message, "not found") != NULL)
6429 || (StrStr(message, "No such file") != NULL)
6430 || (StrStr(message, "can't alloc") != NULL)
6431 || (StrStr(message, "Permission denied") != NULL)) {
6433 cps->maybeThinking = FALSE;
6434 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6435 cps->which, cps->program, cps->host, message);
6436 RemoveInputSource(cps->isr);
6437 DisplayFatalError(buf1, 0, 1);
6442 * Look for hint output
6444 if (sscanf(message, "Hint: %s", buf1) == 1) {
6445 if (cps == &first && hintRequested) {
6446 hintRequested = FALSE;
6447 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6448 &fromX, &fromY, &toX, &toY, &promoChar)) {
6449 (void) CoordsToAlgebraic(boards[forwardMostMove],
6450 PosFlags(forwardMostMove), EP_UNKNOWN,
6451 fromY, fromX, toY, toX, promoChar, buf1);
6452 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6453 DisplayInformation(buf2);
6455 /* Hint move could not be parsed!? */
6456 snprintf(buf2, sizeof(buf2),
6457 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6459 DisplayError(buf2, 0);
6462 strcpy(lastHint, buf1);
6468 * Ignore other messages if game is not in progress
6470 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6471 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6474 * look for win, lose, draw, or draw offer
6476 if (strncmp(message, "1-0", 3) == 0) {
6477 char *p, *q, *r = "";
6478 p = strchr(message, '{');
6486 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6488 } else if (strncmp(message, "0-1", 3) == 0) {
6489 char *p, *q, *r = "";
6490 p = strchr(message, '{');
6498 /* Kludge for Arasan 4.1 bug */
6499 if (strcmp(r, "Black resigns") == 0) {
6500 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6503 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6505 } else if (strncmp(message, "1/2", 3) == 0) {
6506 char *p, *q, *r = "";
6507 p = strchr(message, '{');
6516 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6519 } else if (strncmp(message, "White resign", 12) == 0) {
6520 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6522 } else if (strncmp(message, "Black resign", 12) == 0) {
6523 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6525 } else if (strncmp(message, "White matches", 13) == 0 ||
6526 strncmp(message, "Black matches", 13) == 0 ) {
6527 /* [HGM] ignore GNUShogi noises */
6529 } else if (strncmp(message, "White", 5) == 0 &&
6530 message[5] != '(' &&
6531 StrStr(message, "Black") == NULL) {
6532 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6534 } else if (strncmp(message, "Black", 5) == 0 &&
6535 message[5] != '(') {
6536 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6538 } else if (strcmp(message, "resign") == 0 ||
6539 strcmp(message, "computer resigns") == 0) {
6541 case MachinePlaysBlack:
6542 case IcsPlayingBlack:
6543 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6545 case MachinePlaysWhite:
6546 case IcsPlayingWhite:
6547 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6549 case TwoMachinesPlay:
6550 if (cps->twoMachinesColor[0] == 'w')
6551 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6553 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6560 } else if (strncmp(message, "opponent mates", 14) == 0) {
6562 case MachinePlaysBlack:
6563 case IcsPlayingBlack:
6564 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6566 case MachinePlaysWhite:
6567 case IcsPlayingWhite:
6568 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6570 case TwoMachinesPlay:
6571 if (cps->twoMachinesColor[0] == 'w')
6572 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6574 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581 } else if (strncmp(message, "computer mates", 14) == 0) {
6583 case MachinePlaysBlack:
6584 case IcsPlayingBlack:
6585 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6587 case MachinePlaysWhite:
6588 case IcsPlayingWhite:
6589 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6591 case TwoMachinesPlay:
6592 if (cps->twoMachinesColor[0] == 'w')
6593 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6595 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6602 } else if (strncmp(message, "checkmate", 9) == 0) {
6603 if (WhiteOnMove(forwardMostMove)) {
6604 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6606 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6609 } else if (strstr(message, "Draw") != NULL ||
6610 strstr(message, "game is a draw") != NULL) {
6611 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6613 } else if (strstr(message, "offer") != NULL &&
6614 strstr(message, "draw") != NULL) {
6616 if (appData.zippyPlay && first.initDone) {
6617 /* Relay offer to ICS */
6618 SendToICS(ics_prefix);
6619 SendToICS("draw\n");
6622 cps->offeredDraw = 2; /* valid until this engine moves twice */
6623 if (gameMode == TwoMachinesPlay) {
6624 if (cps->other->offeredDraw) {
6625 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6626 /* [HGM] in two-machine mode we delay relaying draw offer */
6627 /* until after we also have move, to see if it is really claim */
6631 if (cps->other->sendDrawOffers) {
6632 SendToProgram("draw\n", cps->other);
6636 } else if (gameMode == MachinePlaysWhite ||
6637 gameMode == MachinePlaysBlack) {
6638 if (userOfferedDraw) {
6639 DisplayInformation(_("Machine accepts your draw offer"));
6640 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6642 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6649 * Look for thinking output
6651 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6652 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6654 int plylev, mvleft, mvtot, curscore, time;
6655 char mvname[MOVE_LEN];
6659 int prefixHint = FALSE;
6660 mvname[0] = NULLCHAR;
6663 case MachinePlaysBlack:
6664 case IcsPlayingBlack:
6665 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6667 case MachinePlaysWhite:
6668 case IcsPlayingWhite:
6669 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6674 case IcsObserving: /* [DM] icsEngineAnalyze */
6675 if (!appData.icsEngineAnalyze) ignore = TRUE;
6677 case TwoMachinesPlay:
6678 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6689 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6690 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6692 if (plyext != ' ' && plyext != '\t') {
6696 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6697 if( cps->scoreIsAbsolute &&
6698 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6700 curscore = -curscore;
6704 programStats.depth = plylev;
6705 programStats.nodes = nodes;
6706 programStats.time = time;
6707 programStats.score = curscore;
6708 programStats.got_only_move = 0;
6710 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6713 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6714 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6715 if(WhiteOnMove(forwardMostMove))
6716 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6717 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6720 /* Buffer overflow protection */
6721 if (buf1[0] != NULLCHAR) {
6722 if (strlen(buf1) >= sizeof(programStats.movelist)
6723 && appData.debugMode) {
6725 "PV is too long; using the first %d bytes.\n",
6726 sizeof(programStats.movelist) - 1);
6729 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6731 sprintf(programStats.movelist, " no PV\n");
6734 if (programStats.seen_stat) {
6735 programStats.ok_to_send = 1;
6738 if (strchr(programStats.movelist, '(') != NULL) {
6739 programStats.line_is_book = 1;
6740 programStats.nr_moves = 0;
6741 programStats.moves_left = 0;
6743 programStats.line_is_book = 0;
6746 SendProgramStatsToFrontend( cps, &programStats );
6749 [AS] Protect the thinkOutput buffer from overflow... this
6750 is only useful if buf1 hasn't overflowed first!
6752 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6754 (gameMode == TwoMachinesPlay ?
6755 ToUpper(cps->twoMachinesColor[0]) : ' '),
6756 ((double) curscore) / 100.0,
6757 prefixHint ? lastHint : "",
6758 prefixHint ? " " : "" );
6760 if( buf1[0] != NULLCHAR ) {
6761 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6763 if( strlen(buf1) > max_len ) {
6764 if( appData.debugMode) {
6765 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6767 buf1[max_len+1] = '\0';
6770 strcat( thinkOutput, buf1 );
6773 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6774 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6775 DisplayMove(currentMove - 1);
6780 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6781 /* crafty (9.25+) says "(only move) <move>"
6782 * if there is only 1 legal move
6784 sscanf(p, "(only move) %s", buf1);
6785 sprintf(thinkOutput, "%s (only move)", buf1);
6786 sprintf(programStats.movelist, "%s (only move)", buf1);
6787 programStats.depth = 1;
6788 programStats.nr_moves = 1;
6789 programStats.moves_left = 1;
6790 programStats.nodes = 1;
6791 programStats.time = 1;
6792 programStats.got_only_move = 1;
6794 /* Not really, but we also use this member to
6795 mean "line isn't going to change" (Crafty
6796 isn't searching, so stats won't change) */
6797 programStats.line_is_book = 1;
6799 SendProgramStatsToFrontend( cps, &programStats );
6801 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6802 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6803 DisplayMove(currentMove - 1);
6807 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6808 &time, &nodes, &plylev, &mvleft,
6809 &mvtot, mvname) >= 5) {
6810 /* The stat01: line is from Crafty (9.29+) in response
6811 to the "." command */
6812 programStats.seen_stat = 1;
6813 cps->maybeThinking = TRUE;
6815 if (programStats.got_only_move || !appData.periodicUpdates)
6818 programStats.depth = plylev;
6819 programStats.time = time;
6820 programStats.nodes = nodes;
6821 programStats.moves_left = mvleft;
6822 programStats.nr_moves = mvtot;
6823 strcpy(programStats.move_name, mvname);
6824 programStats.ok_to_send = 1;
6825 programStats.movelist[0] = '\0';
6827 SendProgramStatsToFrontend( cps, &programStats );
6832 } else if (strncmp(message,"++",2) == 0) {
6833 /* Crafty 9.29+ outputs this */
6834 programStats.got_fail = 2;
6837 } else if (strncmp(message,"--",2) == 0) {
6838 /* Crafty 9.29+ outputs this */
6839 programStats.got_fail = 1;
6842 } else if (thinkOutput[0] != NULLCHAR &&
6843 strncmp(message, " ", 4) == 0) {
6844 unsigned message_len;
6847 while (*p && *p == ' ') p++;
6849 message_len = strlen( p );
6851 /* [AS] Avoid buffer overflow */
6852 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6853 strcat(thinkOutput, " ");
6854 strcat(thinkOutput, p);
6857 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6858 strcat(programStats.movelist, " ");
6859 strcat(programStats.movelist, p);
6862 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6863 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6864 DisplayMove(currentMove - 1);
6873 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6874 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6876 ChessProgramStats cpstats;
6878 if (plyext != ' ' && plyext != '\t') {
6882 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6883 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6884 curscore = -curscore;
6887 cpstats.depth = plylev;
6888 cpstats.nodes = nodes;
6889 cpstats.time = time;
6890 cpstats.score = curscore;
6891 cpstats.got_only_move = 0;
6892 cpstats.movelist[0] = '\0';
6894 if (buf1[0] != NULLCHAR) {
6895 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6898 cpstats.ok_to_send = 0;
6899 cpstats.line_is_book = 0;
6900 cpstats.nr_moves = 0;
6901 cpstats.moves_left = 0;
6903 SendProgramStatsToFrontend( cps, &cpstats );
6910 /* Parse a game score from the character string "game", and
6911 record it as the history of the current game. The game
6912 score is NOT assumed to start from the standard position.
6913 The display is not updated in any way.
6916 ParseGameHistory(game)
6920 int fromX, fromY, toX, toY, boardIndex;
6925 if (appData.debugMode)
6926 fprintf(debugFP, "Parsing game history: %s\n", game);
6928 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6929 gameInfo.site = StrSave(appData.icsHost);
6930 gameInfo.date = PGNDate();
6931 gameInfo.round = StrSave("-");
6933 /* Parse out names of players */
6934 while (*game == ' ') game++;
6936 while (*game != ' ') *p++ = *game++;
6938 gameInfo.white = StrSave(buf);
6939 while (*game == ' ') game++;
6941 while (*game != ' ' && *game != '\n') *p++ = *game++;
6943 gameInfo.black = StrSave(buf);
6946 boardIndex = blackPlaysFirst ? 1 : 0;
6949 yyboardindex = boardIndex;
6950 moveType = (ChessMove) yylex();
6952 case IllegalMove: /* maybe suicide chess, etc. */
6953 if (appData.debugMode) {
6954 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6955 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6956 setbuf(debugFP, NULL);
6958 case WhitePromotionChancellor:
6959 case BlackPromotionChancellor:
6960 case WhitePromotionArchbishop:
6961 case BlackPromotionArchbishop:
6962 case WhitePromotionQueen:
6963 case BlackPromotionQueen:
6964 case WhitePromotionRook:
6965 case BlackPromotionRook:
6966 case WhitePromotionBishop:
6967 case BlackPromotionBishop:
6968 case WhitePromotionKnight:
6969 case BlackPromotionKnight:
6970 case WhitePromotionKing:
6971 case BlackPromotionKing:
6973 case WhiteCapturesEnPassant:
6974 case BlackCapturesEnPassant:
6975 case WhiteKingSideCastle:
6976 case WhiteQueenSideCastle:
6977 case BlackKingSideCastle:
6978 case BlackQueenSideCastle:
6979 case WhiteKingSideCastleWild:
6980 case WhiteQueenSideCastleWild:
6981 case BlackKingSideCastleWild:
6982 case BlackQueenSideCastleWild:
6984 case WhiteHSideCastleFR:
6985 case WhiteASideCastleFR:
6986 case BlackHSideCastleFR:
6987 case BlackASideCastleFR:
6989 fromX = currentMoveString[0] - AAA;
6990 fromY = currentMoveString[1] - ONE;
6991 toX = currentMoveString[2] - AAA;
6992 toY = currentMoveString[3] - ONE;
6993 promoChar = currentMoveString[4];
6997 fromX = moveType == WhiteDrop ?
6998 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6999 (int) CharToPiece(ToLower(currentMoveString[0]));
7001 toX = currentMoveString[2] - AAA;
7002 toY = currentMoveString[3] - ONE;
7003 promoChar = NULLCHAR;
7007 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7008 if (appData.debugMode) {
7009 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7010 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7011 setbuf(debugFP, NULL);
7013 DisplayError(buf, 0);
7015 case ImpossibleMove:
7017 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7018 if (appData.debugMode) {
7019 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7020 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7021 setbuf(debugFP, NULL);
7023 DisplayError(buf, 0);
7025 case (ChessMove) 0: /* end of file */
7026 if (boardIndex < backwardMostMove) {
7027 /* Oops, gap. How did that happen? */
7028 DisplayError(_("Gap in move list"), 0);
7031 backwardMostMove = blackPlaysFirst ? 1 : 0;
7032 if (boardIndex > forwardMostMove) {
7033 forwardMostMove = boardIndex;
7037 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7038 strcat(parseList[boardIndex-1], " ");
7039 strcat(parseList[boardIndex-1], yy_text);
7051 case GameUnfinished:
7052 if (gameMode == IcsExamining) {
7053 if (boardIndex < backwardMostMove) {
7054 /* Oops, gap. How did that happen? */
7057 backwardMostMove = blackPlaysFirst ? 1 : 0;
7060 gameInfo.result = moveType;
7061 p = strchr(yy_text, '{');
7062 if (p == NULL) p = strchr(yy_text, '(');
7065 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7067 q = strchr(p, *p == '{' ? '}' : ')');
7068 if (q != NULL) *q = NULLCHAR;
7071 gameInfo.resultDetails = StrSave(p);
7074 if (boardIndex >= forwardMostMove &&
7075 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7076 backwardMostMove = blackPlaysFirst ? 1 : 0;
7079 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7080 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7081 parseList[boardIndex]);
7082 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7083 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7084 /* currentMoveString is set as a side-effect of yylex */
7085 strcpy(moveList[boardIndex], currentMoveString);
7086 strcat(moveList[boardIndex], "\n");
7088 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7089 castlingRights[boardIndex], &epStatus[boardIndex]);
7090 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7091 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7097 if(gameInfo.variant != VariantShogi)
7098 strcat(parseList[boardIndex - 1], "+");
7102 strcat(parseList[boardIndex - 1], "#");
7109 /* Apply a move to the given board */
7111 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7112 int fromX, fromY, toX, toY;
7118 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7120 /* [HGM] compute & store e.p. status and castling rights for new position */
7121 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7124 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7128 if( board[toY][toX] != EmptySquare )
7131 if( board[fromY][fromX] == WhitePawn ) {
7132 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7135 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7136 gameInfo.variant != VariantBerolina || toX < fromX)
7137 *ep = toX | berolina;
7138 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7139 gameInfo.variant != VariantBerolina || toX > fromX)
7143 if( board[fromY][fromX] == BlackPawn ) {
7144 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7146 if( toY-fromY== -2) {
7147 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7148 gameInfo.variant != VariantBerolina || toX < fromX)
7149 *ep = toX | berolina;
7150 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7151 gameInfo.variant != VariantBerolina || toX > fromX)
7156 for(i=0; i<nrCastlingRights; i++) {
7157 if(castling[i] == fromX && castlingRank[i] == fromY ||
7158 castling[i] == toX && castlingRank[i] == toY
7159 ) castling[i] = -1; // revoke for moved or captured piece
7164 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7165 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7166 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7168 if (fromX == toX && fromY == toY) return;
7170 if (fromY == DROP_RANK) {
7172 piece = board[toY][toX] = (ChessSquare) fromX;
7174 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7175 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7176 if(gameInfo.variant == VariantKnightmate)
7177 king += (int) WhiteUnicorn - (int) WhiteKing;
7179 /* Code added by Tord: */
7180 /* FRC castling assumed when king captures friendly rook. */
7181 if (board[fromY][fromX] == WhiteKing &&
7182 board[toY][toX] == WhiteRook) {
7183 board[fromY][fromX] = EmptySquare;
7184 board[toY][toX] = EmptySquare;
7186 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7188 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7190 } else if (board[fromY][fromX] == BlackKing &&
7191 board[toY][toX] == BlackRook) {
7192 board[fromY][fromX] = EmptySquare;
7193 board[toY][toX] = EmptySquare;
7195 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7197 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7199 /* End of code added by Tord */
7201 } else if (board[fromY][fromX] == king
7202 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7203 && toY == fromY && toX > fromX+1) {
7204 board[fromY][fromX] = EmptySquare;
7205 board[toY][toX] = king;
7206 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7207 board[fromY][BOARD_RGHT-1] = EmptySquare;
7208 } else if (board[fromY][fromX] == king
7209 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7210 && toY == fromY && toX < fromX-1) {
7211 board[fromY][fromX] = EmptySquare;
7212 board[toY][toX] = king;
7213 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7214 board[fromY][BOARD_LEFT] = EmptySquare;
7215 } else if (board[fromY][fromX] == WhitePawn
7216 && toY == BOARD_HEIGHT-1
7217 && gameInfo.variant != VariantXiangqi
7219 /* white pawn promotion */
7220 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7221 if (board[toY][toX] == EmptySquare) {
7222 board[toY][toX] = WhiteQueen;
7224 if(gameInfo.variant==VariantBughouse ||
7225 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7226 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7227 board[fromY][fromX] = EmptySquare;
7228 } else if ((fromY == BOARD_HEIGHT-4)
7230 && gameInfo.variant != VariantXiangqi
7231 && gameInfo.variant != VariantBerolina
7232 && (board[fromY][fromX] == WhitePawn)
7233 && (board[toY][toX] == EmptySquare)) {
7234 board[fromY][fromX] = EmptySquare;
7235 board[toY][toX] = WhitePawn;
7236 captured = board[toY - 1][toX];
7237 board[toY - 1][toX] = EmptySquare;
7238 } else if ((fromY == BOARD_HEIGHT-4)
7240 && gameInfo.variant == VariantBerolina
7241 && (board[fromY][fromX] == WhitePawn)
7242 && (board[toY][toX] == EmptySquare)) {
7243 board[fromY][fromX] = EmptySquare;
7244 board[toY][toX] = WhitePawn;
7245 if(oldEP & EP_BEROLIN_A) {
7246 captured = board[fromY][fromX-1];
7247 board[fromY][fromX-1] = EmptySquare;
7248 }else{ captured = board[fromY][fromX+1];
7249 board[fromY][fromX+1] = EmptySquare;
7251 } else if (board[fromY][fromX] == king
7252 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7253 && toY == fromY && toX > fromX+1) {
7254 board[fromY][fromX] = EmptySquare;
7255 board[toY][toX] = king;
7256 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7257 board[fromY][BOARD_RGHT-1] = EmptySquare;
7258 } else if (board[fromY][fromX] == king
7259 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7260 && toY == fromY && toX < fromX-1) {
7261 board[fromY][fromX] = EmptySquare;
7262 board[toY][toX] = king;
7263 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7264 board[fromY][BOARD_LEFT] = EmptySquare;
7265 } else if (fromY == 7 && fromX == 3
7266 && board[fromY][fromX] == BlackKing
7267 && toY == 7 && toX == 5) {
7268 board[fromY][fromX] = EmptySquare;
7269 board[toY][toX] = BlackKing;
7270 board[fromY][7] = EmptySquare;
7271 board[toY][4] = BlackRook;
7272 } else if (fromY == 7 && fromX == 3
7273 && board[fromY][fromX] == BlackKing
7274 && toY == 7 && toX == 1) {
7275 board[fromY][fromX] = EmptySquare;
7276 board[toY][toX] = BlackKing;
7277 board[fromY][0] = EmptySquare;
7278 board[toY][2] = BlackRook;
7279 } else if (board[fromY][fromX] == BlackPawn
7281 && gameInfo.variant != VariantXiangqi
7283 /* black pawn promotion */
7284 board[0][toX] = CharToPiece(ToLower(promoChar));
7285 if (board[0][toX] == EmptySquare) {
7286 board[0][toX] = BlackQueen;
7288 if(gameInfo.variant==VariantBughouse ||
7289 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7290 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7291 board[fromY][fromX] = EmptySquare;
7292 } else if ((fromY == 3)
7294 && gameInfo.variant != VariantXiangqi
7295 && gameInfo.variant != VariantBerolina
7296 && (board[fromY][fromX] == BlackPawn)
7297 && (board[toY][toX] == EmptySquare)) {
7298 board[fromY][fromX] = EmptySquare;
7299 board[toY][toX] = BlackPawn;
7300 captured = board[toY + 1][toX];
7301 board[toY + 1][toX] = EmptySquare;
7302 } else if ((fromY == 3)
7304 && gameInfo.variant == VariantBerolina
7305 && (board[fromY][fromX] == BlackPawn)
7306 && (board[toY][toX] == EmptySquare)) {
7307 board[fromY][fromX] = EmptySquare;
7308 board[toY][toX] = BlackPawn;
7309 if(oldEP & EP_BEROLIN_A) {
7310 captured = board[fromY][fromX-1];
7311 board[fromY][fromX-1] = EmptySquare;
7312 }else{ captured = board[fromY][fromX+1];
7313 board[fromY][fromX+1] = EmptySquare;
7316 board[toY][toX] = board[fromY][fromX];
7317 board[fromY][fromX] = EmptySquare;
7320 /* [HGM] now we promote for Shogi, if needed */
7321 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7322 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7325 if (gameInfo.holdingsWidth != 0) {
7327 /* !!A lot more code needs to be written to support holdings */
7328 /* [HGM] OK, so I have written it. Holdings are stored in the */
7329 /* penultimate board files, so they are automaticlly stored */
7330 /* in the game history. */
7331 if (fromY == DROP_RANK) {
7332 /* Delete from holdings, by decreasing count */
7333 /* and erasing image if necessary */
7335 if(p < (int) BlackPawn) { /* white drop */
7336 p -= (int)WhitePawn;
7337 if(p >= gameInfo.holdingsSize) p = 0;
7338 if(--board[p][BOARD_WIDTH-2] == 0)
7339 board[p][BOARD_WIDTH-1] = EmptySquare;
7340 } else { /* black drop */
7341 p -= (int)BlackPawn;
7342 if(p >= gameInfo.holdingsSize) p = 0;
7343 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7344 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7347 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7348 && gameInfo.variant != VariantBughouse ) {
7349 /* [HGM] holdings: Add to holdings, if holdings exist */
7350 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7351 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7352 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7355 if (p >= (int) BlackPawn) {
7356 p -= (int)BlackPawn;
7357 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7358 /* in Shogi restore piece to its original first */
7359 captured = (ChessSquare) (DEMOTED captured);
7362 p = PieceToNumber((ChessSquare)p);
7363 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7364 board[p][BOARD_WIDTH-2]++;
7365 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7367 p -= (int)WhitePawn;
7368 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7369 captured = (ChessSquare) (DEMOTED captured);
7372 p = PieceToNumber((ChessSquare)p);
7373 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7374 board[BOARD_HEIGHT-1-p][1]++;
7375 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7379 } else if (gameInfo.variant == VariantAtomic) {
7380 if (captured != EmptySquare) {
7382 for (y = toY-1; y <= toY+1; y++) {
7383 for (x = toX-1; x <= toX+1; x++) {
7384 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7385 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7386 board[y][x] = EmptySquare;
7390 board[toY][toX] = EmptySquare;
7393 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7394 /* [HGM] Shogi promotions */
7395 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7398 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7399 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7400 // [HGM] superchess: take promotion piece out of holdings
7401 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7402 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7403 if(!--board[k][BOARD_WIDTH-2])
7404 board[k][BOARD_WIDTH-1] = EmptySquare;
7406 if(!--board[BOARD_HEIGHT-1-k][1])
7407 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7413 /* Updates forwardMostMove */
7415 MakeMove(fromX, fromY, toX, toY, promoChar)
7416 int fromX, fromY, toX, toY;
7419 // forwardMostMove++; // [HGM] bare: moved downstream
7421 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7422 int timeLeft; static int lastLoadFlag=0; int king, piece;
7423 piece = boards[forwardMostMove][fromY][fromX];
7424 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7425 if(gameInfo.variant == VariantKnightmate)
7426 king += (int) WhiteUnicorn - (int) WhiteKing;
7427 if(forwardMostMove == 0) {
7429 fprintf(serverMoves, "%s;", second.tidy);
7430 fprintf(serverMoves, "%s;", first.tidy);
7431 if(!blackPlaysFirst)
7432 fprintf(serverMoves, "%s;", second.tidy);
7433 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7434 lastLoadFlag = loadFlag;
7436 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7437 // print castling suffix
7438 if( toY == fromY && piece == king ) {
7440 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7442 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7445 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7446 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7447 boards[forwardMostMove][toY][toX] == EmptySquare
7449 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7451 if(promoChar != NULLCHAR)
7452 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7454 fprintf(serverMoves, "/%d/%d",
7455 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7456 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7457 else timeLeft = blackTimeRemaining/1000;
7458 fprintf(serverMoves, "/%d", timeLeft);
7460 fflush(serverMoves);
7463 if (forwardMostMove+1 >= MAX_MOVES) {
7464 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7469 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7470 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7471 if (commentList[forwardMostMove+1] != NULL) {
7472 free(commentList[forwardMostMove+1]);
7473 commentList[forwardMostMove+1] = NULL;
7475 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7476 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7477 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7478 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7479 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7480 gameInfo.result = GameUnfinished;
7481 if (gameInfo.resultDetails != NULL) {
7482 free(gameInfo.resultDetails);
7483 gameInfo.resultDetails = NULL;
7485 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7486 moveList[forwardMostMove - 1]);
7487 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7488 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7489 fromY, fromX, toY, toX, promoChar,
7490 parseList[forwardMostMove - 1]);
7491 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7492 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7493 castlingRights[forwardMostMove]) ) {
7499 if(gameInfo.variant != VariantShogi)
7500 strcat(parseList[forwardMostMove - 1], "+");
7504 strcat(parseList[forwardMostMove - 1], "#");
7507 if (appData.debugMode) {
7508 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7513 /* Updates currentMove if not pausing */
7515 ShowMove(fromX, fromY, toX, toY)
7517 int instant = (gameMode == PlayFromGameFile) ?
7518 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7519 if(appData.noGUI) return;
7520 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7522 if (forwardMostMove == currentMove + 1) {
7523 AnimateMove(boards[forwardMostMove - 1],
7524 fromX, fromY, toX, toY);
7526 if (appData.highlightLastMove) {
7527 SetHighlights(fromX, fromY, toX, toY);
7530 currentMove = forwardMostMove;
7533 if (instant) return;
7535 DisplayMove(currentMove - 1);
7536 DrawPosition(FALSE, boards[currentMove]);
7537 DisplayBothClocks();
7538 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7541 void SendEgtPath(ChessProgramState *cps)
7542 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7543 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7545 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7548 char c, *q = name+1, *r, *s;
7550 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7551 while(*p && *p != ',') *q++ = *p++;
7553 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7554 strcmp(name, ",nalimov:") == 0 ) {
7555 // take nalimov path from the menu-changeable option first, if it is defined
7556 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7557 SendToProgram(buf,cps); // send egtbpath command for nalimov
7559 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7560 (s = StrStr(appData.egtFormats, name)) != NULL) {
7561 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7562 s = r = StrStr(s, ":") + 1; // beginning of path info
7563 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7564 c = *r; *r = 0; // temporarily null-terminate path info
7565 *--q = 0; // strip of trailig ':' from name
7566 sprintf(buf, "egtpath %s %s\n", name+1, s);
7568 SendToProgram(buf,cps); // send egtbpath command for this format
7570 if(*p == ',') p++; // read away comma to position for next format name
7575 InitChessProgram(cps, setup)
7576 ChessProgramState *cps;
7577 int setup; /* [HGM] needed to setup FRC opening position */
7579 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7580 if (appData.noChessProgram) return;
7581 hintRequested = FALSE;
7582 bookRequested = FALSE;
7584 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7585 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7586 if(cps->memSize) { /* [HGM] memory */
7587 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7588 SendToProgram(buf, cps);
7590 SendEgtPath(cps); /* [HGM] EGT */
7591 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7592 sprintf(buf, "cores %d\n", appData.smpCores);
7593 SendToProgram(buf, cps);
7596 SendToProgram(cps->initString, cps);
7597 if (gameInfo.variant != VariantNormal &&
7598 gameInfo.variant != VariantLoadable
7599 /* [HGM] also send variant if board size non-standard */
7600 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7602 char *v = VariantName(gameInfo.variant);
7603 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7604 /* [HGM] in protocol 1 we have to assume all variants valid */
7605 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7606 DisplayFatalError(buf, 0, 1);
7610 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7611 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7612 if( gameInfo.variant == VariantXiangqi )
7613 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7614 if( gameInfo.variant == VariantShogi )
7615 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7616 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7617 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7618 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7619 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7620 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7621 if( gameInfo.variant == VariantCourier )
7622 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7623 if( gameInfo.variant == VariantSuper )
7624 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7625 if( gameInfo.variant == VariantGreat )
7626 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7629 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7630 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7631 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7632 if(StrStr(cps->variants, b) == NULL) {
7633 // specific sized variant not known, check if general sizing allowed
7634 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7635 if(StrStr(cps->variants, "boardsize") == NULL) {
7636 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7637 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7638 DisplayFatalError(buf, 0, 1);
7641 /* [HGM] here we really should compare with the maximum supported board size */
7644 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7645 sprintf(buf, "variant %s\n", b);
7646 SendToProgram(buf, cps);
7648 currentlyInitializedVariant = gameInfo.variant;
7650 /* [HGM] send opening position in FRC to first engine */
7652 SendToProgram("force\n", cps);
7654 /* engine is now in force mode! Set flag to wake it up after first move. */
7655 setboardSpoiledMachineBlack = 1;
7659 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7660 SendToProgram(buf, cps);
7662 cps->maybeThinking = FALSE;
7663 cps->offeredDraw = 0;
7664 if (!appData.icsActive) {
7665 SendTimeControl(cps, movesPerSession, timeControl,
7666 timeIncrement, appData.searchDepth,
7669 if (appData.showThinking
7670 // [HGM] thinking: four options require thinking output to be sent
7671 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7673 SendToProgram("post\n", cps);
7675 SendToProgram("hard\n", cps);
7676 if (!appData.ponderNextMove) {
7677 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7678 it without being sure what state we are in first. "hard"
7679 is not a toggle, so that one is OK.
7681 SendToProgram("easy\n", cps);
7684 sprintf(buf, "ping %d\n", ++cps->lastPing);
7685 SendToProgram(buf, cps);
7687 cps->initDone = TRUE;
7692 StartChessProgram(cps)
7693 ChessProgramState *cps;
7698 if (appData.noChessProgram) return;
7699 cps->initDone = FALSE;
7701 if (strcmp(cps->host, "localhost") == 0) {
7702 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7703 } else if (*appData.remoteShell == NULLCHAR) {
7704 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7706 if (*appData.remoteUser == NULLCHAR) {
7707 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7710 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7711 cps->host, appData.remoteUser, cps->program);
7713 err = StartChildProcess(buf, "", &cps->pr);
7717 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7718 DisplayFatalError(buf, err, 1);
7724 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7725 if (cps->protocolVersion > 1) {
7726 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7727 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7728 cps->comboCnt = 0; // and values of combo boxes
7729 SendToProgram(buf, cps);
7731 SendToProgram("xboard\n", cps);
7737 TwoMachinesEventIfReady P((void))
7739 if (first.lastPing != first.lastPong) {
7740 DisplayMessage("", _("Waiting for first chess program"));
7741 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7744 if (second.lastPing != second.lastPong) {
7745 DisplayMessage("", _("Waiting for second chess program"));
7746 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7754 NextMatchGame P((void))
7756 int index; /* [HGM] autoinc: step lod index during match */
7758 if (*appData.loadGameFile != NULLCHAR) {
7759 index = appData.loadGameIndex;
7760 if(index < 0) { // [HGM] autoinc
7761 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7762 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7764 LoadGameFromFile(appData.loadGameFile,
7766 appData.loadGameFile, FALSE);
7767 } else if (*appData.loadPositionFile != NULLCHAR) {
7768 index = appData.loadPositionIndex;
7769 if(index < 0) { // [HGM] autoinc
7770 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7771 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7773 LoadPositionFromFile(appData.loadPositionFile,
7775 appData.loadPositionFile);
7777 TwoMachinesEventIfReady();
7780 void UserAdjudicationEvent( int result )
7782 ChessMove gameResult = GameIsDrawn;
7785 gameResult = WhiteWins;
7787 else if( result < 0 ) {
7788 gameResult = BlackWins;
7791 if( gameMode == TwoMachinesPlay ) {
7792 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7797 // [HGM] save: calculate checksum of game to make games easily identifiable
7798 int StringCheckSum(char *s)
7801 if(s==NULL) return 0;
7802 while(*s) i = i*259 + *s++;
7809 for(i=backwardMostMove; i<forwardMostMove; i++) {
7810 sum += pvInfoList[i].depth;
7811 sum += StringCheckSum(parseList[i]);
7812 sum += StringCheckSum(commentList[i]);
7815 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7816 return sum + StringCheckSum(commentList[i]);
7817 } // end of save patch
7820 GameEnds(result, resultDetails, whosays)
7822 char *resultDetails;
7825 GameMode nextGameMode;
7829 if(endingGame) return; /* [HGM] crash: forbid recursion */
7832 if (appData.debugMode) {
7833 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7834 result, resultDetails ? resultDetails : "(null)", whosays);
7837 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7838 /* If we are playing on ICS, the server decides when the
7839 game is over, but the engine can offer to draw, claim
7843 if (appData.zippyPlay && first.initDone) {
7844 if (result == GameIsDrawn) {
7845 /* In case draw still needs to be claimed */
7846 SendToICS(ics_prefix);
7847 SendToICS("draw\n");
7848 } else if (StrCaseStr(resultDetails, "resign")) {
7849 SendToICS(ics_prefix);
7850 SendToICS("resign\n");
7854 endingGame = 0; /* [HGM] crash */
7858 /* If we're loading the game from a file, stop */
7859 if (whosays == GE_FILE) {
7860 (void) StopLoadGameTimer();
7864 /* Cancel draw offers */
7865 first.offeredDraw = second.offeredDraw = 0;
7867 /* If this is an ICS game, only ICS can really say it's done;
7868 if not, anyone can. */
7869 isIcsGame = (gameMode == IcsPlayingWhite ||
7870 gameMode == IcsPlayingBlack ||
7871 gameMode == IcsObserving ||
7872 gameMode == IcsExamining);
7874 if (!isIcsGame || whosays == GE_ICS) {
7875 /* OK -- not an ICS game, or ICS said it was done */
7877 if (!isIcsGame && !appData.noChessProgram)
7878 SetUserThinkingEnables();
7880 /* [HGM] if a machine claims the game end we verify this claim */
7881 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7882 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7884 ChessMove trueResult = (ChessMove) -1;
7886 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7887 first.twoMachinesColor[0] :
7888 second.twoMachinesColor[0] ;
7890 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7891 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7892 /* [HGM] verify: engine mate claims accepted if they were flagged */
7893 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7895 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7896 /* [HGM] verify: engine mate claims accepted if they were flagged */
7897 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7899 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7900 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7903 // now verify win claims, but not in drop games, as we don't understand those yet
7904 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7905 || gameInfo.variant == VariantGreat) &&
7906 (result == WhiteWins && claimer == 'w' ||
7907 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7908 if (appData.debugMode) {
7909 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7910 result, epStatus[forwardMostMove], forwardMostMove);
7912 if(result != trueResult) {
7913 sprintf(buf, "False win claim: '%s'", resultDetails);
7914 result = claimer == 'w' ? BlackWins : WhiteWins;
7915 resultDetails = buf;
7918 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7919 && (forwardMostMove <= backwardMostMove ||
7920 epStatus[forwardMostMove-1] > EP_DRAWS ||
7921 (claimer=='b')==(forwardMostMove&1))
7923 /* [HGM] verify: draws that were not flagged are false claims */
7924 sprintf(buf, "False draw claim: '%s'", resultDetails);
7925 result = claimer == 'w' ? BlackWins : WhiteWins;
7926 resultDetails = buf;
7928 /* (Claiming a loss is accepted no questions asked!) */
7930 /* [HGM] bare: don't allow bare King to win */
7931 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7932 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7933 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7934 && result != GameIsDrawn)
7935 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7936 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7937 int p = (int)boards[forwardMostMove][i][j] - color;
7938 if(p >= 0 && p <= (int)WhiteKing) k++;
7940 if (appData.debugMode) {
7941 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7942 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7945 result = GameIsDrawn;
7946 sprintf(buf, "%s but bare king", resultDetails);
7947 resultDetails = buf;
7953 if(serverMoves != NULL && !loadFlag) { char c = '=';
7954 if(result==WhiteWins) c = '+';
7955 if(result==BlackWins) c = '-';
7956 if(resultDetails != NULL)
7957 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7959 if (resultDetails != NULL) {
7960 gameInfo.result = result;
7961 gameInfo.resultDetails = StrSave(resultDetails);
7963 /* display last move only if game was not loaded from file */
7964 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7965 DisplayMove(currentMove - 1);
7967 if (forwardMostMove != 0) {
7968 if (gameMode != PlayFromGameFile && gameMode != EditGame
7969 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7971 if (*appData.saveGameFile != NULLCHAR) {
7972 SaveGameToFile(appData.saveGameFile, TRUE);
7973 } else if (appData.autoSaveGames) {
7976 if (*appData.savePositionFile != NULLCHAR) {
7977 SavePositionToFile(appData.savePositionFile);
7982 /* Tell program how game ended in case it is learning */
7983 /* [HGM] Moved this to after saving the PGN, just in case */
7984 /* engine died and we got here through time loss. In that */
7985 /* case we will get a fatal error writing the pipe, which */
7986 /* would otherwise lose us the PGN. */
7987 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7988 /* output during GameEnds should never be fatal anymore */
7989 if (gameMode == MachinePlaysWhite ||
7990 gameMode == MachinePlaysBlack ||
7991 gameMode == TwoMachinesPlay ||
7992 gameMode == IcsPlayingWhite ||
7993 gameMode == IcsPlayingBlack ||
7994 gameMode == BeginningOfGame) {
7996 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7998 if (first.pr != NoProc) {
7999 SendToProgram(buf, &first);
8001 if (second.pr != NoProc &&
8002 gameMode == TwoMachinesPlay) {
8003 SendToProgram(buf, &second);
8008 if (appData.icsActive) {
8009 if (appData.quietPlay &&
8010 (gameMode == IcsPlayingWhite ||
8011 gameMode == IcsPlayingBlack)) {
8012 SendToICS(ics_prefix);
8013 SendToICS("set shout 1\n");
8015 nextGameMode = IcsIdle;
8016 ics_user_moved = FALSE;
8017 /* clean up premove. It's ugly when the game has ended and the
8018 * premove highlights are still on the board.
8022 ClearPremoveHighlights();
8023 DrawPosition(FALSE, boards[currentMove]);
8025 if (whosays == GE_ICS) {
8028 if (gameMode == IcsPlayingWhite)
8030 else if(gameMode == IcsPlayingBlack)
8034 if (gameMode == IcsPlayingBlack)
8036 else if(gameMode == IcsPlayingWhite)
8043 PlayIcsUnfinishedSound();
8046 } else if (gameMode == EditGame ||
8047 gameMode == PlayFromGameFile ||
8048 gameMode == AnalyzeMode ||
8049 gameMode == AnalyzeFile) {
8050 nextGameMode = gameMode;
8052 nextGameMode = EndOfGame;
8057 nextGameMode = gameMode;
8060 if (appData.noChessProgram) {
8061 gameMode = nextGameMode;
8063 endingGame = 0; /* [HGM] crash */
8068 /* Put first chess program into idle state */
8069 if (first.pr != NoProc &&
8070 (gameMode == MachinePlaysWhite ||
8071 gameMode == MachinePlaysBlack ||
8072 gameMode == TwoMachinesPlay ||
8073 gameMode == IcsPlayingWhite ||
8074 gameMode == IcsPlayingBlack ||
8075 gameMode == BeginningOfGame)) {
8076 SendToProgram("force\n", &first);
8077 if (first.usePing) {
8079 sprintf(buf, "ping %d\n", ++first.lastPing);
8080 SendToProgram(buf, &first);
8083 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8084 /* Kill off first chess program */
8085 if (first.isr != NULL)
8086 RemoveInputSource(first.isr);
8089 if (first.pr != NoProc) {
8091 DoSleep( appData.delayBeforeQuit );
8092 SendToProgram("quit\n", &first);
8093 DoSleep( appData.delayAfterQuit );
8094 DestroyChildProcess(first.pr, first.useSigterm);
8099 /* Put second chess program into idle state */
8100 if (second.pr != NoProc &&
8101 gameMode == TwoMachinesPlay) {
8102 SendToProgram("force\n", &second);
8103 if (second.usePing) {
8105 sprintf(buf, "ping %d\n", ++second.lastPing);
8106 SendToProgram(buf, &second);
8109 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8110 /* Kill off second chess program */
8111 if (second.isr != NULL)
8112 RemoveInputSource(second.isr);
8115 if (second.pr != NoProc) {
8116 DoSleep( appData.delayBeforeQuit );
8117 SendToProgram("quit\n", &second);
8118 DoSleep( appData.delayAfterQuit );
8119 DestroyChildProcess(second.pr, second.useSigterm);
8124 if (matchMode && gameMode == TwoMachinesPlay) {
8127 if (first.twoMachinesColor[0] == 'w') {
8134 if (first.twoMachinesColor[0] == 'b') {
8143 if (matchGame < appData.matchGames) {
8145 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8146 tmp = first.twoMachinesColor;
8147 first.twoMachinesColor = second.twoMachinesColor;
8148 second.twoMachinesColor = tmp;
8150 gameMode = nextGameMode;
8152 if(appData.matchPause>10000 || appData.matchPause<10)
8153 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8154 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8155 endingGame = 0; /* [HGM] crash */
8159 gameMode = nextGameMode;
8160 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8161 first.tidy, second.tidy,
8162 first.matchWins, second.matchWins,
8163 appData.matchGames - (first.matchWins + second.matchWins));
8164 DisplayFatalError(buf, 0, 0);
8167 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8168 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8170 gameMode = nextGameMode;
8172 endingGame = 0; /* [HGM] crash */
8175 /* Assumes program was just initialized (initString sent).
8176 Leaves program in force mode. */
8178 FeedMovesToProgram(cps, upto)
8179 ChessProgramState *cps;
8184 if (appData.debugMode)
8185 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8186 startedFromSetupPosition ? "position and " : "",
8187 backwardMostMove, upto, cps->which);
8188 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8189 // [HGM] variantswitch: make engine aware of new variant
8190 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8191 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8192 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8193 SendToProgram(buf, cps);
8194 currentlyInitializedVariant = gameInfo.variant;
8196 SendToProgram("force\n", cps);
8197 if (startedFromSetupPosition) {
8198 SendBoard(cps, backwardMostMove);
8199 if (appData.debugMode) {
8200 fprintf(debugFP, "feedMoves\n");
8203 for (i = backwardMostMove; i < upto; i++) {
8204 SendMoveToProgram(i, cps);
8210 ResurrectChessProgram()
8212 /* The chess program may have exited.
8213 If so, restart it and feed it all the moves made so far. */
8215 if (appData.noChessProgram || first.pr != NoProc) return;
8217 StartChessProgram(&first);
8218 InitChessProgram(&first, FALSE);
8219 FeedMovesToProgram(&first, currentMove);
8221 if (!first.sendTime) {
8222 /* can't tell gnuchess what its clock should read,
8223 so we bow to its notion. */
8225 timeRemaining[0][currentMove] = whiteTimeRemaining;
8226 timeRemaining[1][currentMove] = blackTimeRemaining;
8229 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8230 appData.icsEngineAnalyze) && first.analysisSupport) {
8231 SendToProgram("analyze\n", &first);
8232 first.analyzing = TRUE;
8245 if (appData.debugMode) {
8246 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8247 redraw, init, gameMode);
8249 pausing = pauseExamInvalid = FALSE;
8250 startedFromSetupPosition = blackPlaysFirst = FALSE;
8252 whiteFlag = blackFlag = FALSE;
8253 userOfferedDraw = FALSE;
8254 hintRequested = bookRequested = FALSE;
8255 first.maybeThinking = FALSE;
8256 second.maybeThinking = FALSE;
8257 first.bookSuspend = FALSE; // [HGM] book
8258 second.bookSuspend = FALSE;
8259 thinkOutput[0] = NULLCHAR;
8260 lastHint[0] = NULLCHAR;
8261 ClearGameInfo(&gameInfo);
8262 gameInfo.variant = StringToVariant(appData.variant);
8263 ics_user_moved = ics_clock_paused = FALSE;
8264 ics_getting_history = H_FALSE;
8266 white_holding[0] = black_holding[0] = NULLCHAR;
8267 ClearProgramStats();
8268 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8272 flipView = appData.flipView;
8273 ClearPremoveHighlights();
8275 alarmSounded = FALSE;
8277 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8278 if(appData.serverMovesName != NULL) {
8279 /* [HGM] prepare to make moves file for broadcasting */
8280 clock_t t = clock();
8281 if(serverMoves != NULL) fclose(serverMoves);
8282 serverMoves = fopen(appData.serverMovesName, "r");
8283 if(serverMoves != NULL) {
8284 fclose(serverMoves);
8285 /* delay 15 sec before overwriting, so all clients can see end */
8286 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8288 serverMoves = fopen(appData.serverMovesName, "w");
8292 gameMode = BeginningOfGame;
8294 if(appData.icsActive) gameInfo.variant = VariantNormal;
8295 currentMove = forwardMostMove = backwardMostMove = 0;
8296 InitPosition(redraw);
8297 for (i = 0; i < MAX_MOVES; i++) {
8298 if (commentList[i] != NULL) {
8299 free(commentList[i]);
8300 commentList[i] = NULL;
8304 timeRemaining[0][0] = whiteTimeRemaining;
8305 timeRemaining[1][0] = blackTimeRemaining;
8306 if (first.pr == NULL) {
8307 StartChessProgram(&first);
8310 InitChessProgram(&first, startedFromSetupPosition);
8313 DisplayMessage("", "");
8314 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8315 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8322 if (!AutoPlayOneMove())
8324 if (matchMode || appData.timeDelay == 0)
8326 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8328 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8337 int fromX, fromY, toX, toY;
8339 if (appData.debugMode) {
8340 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8343 if (gameMode != PlayFromGameFile)
8346 if (currentMove >= forwardMostMove) {
8347 gameMode = EditGame;
8350 /* [AS] Clear current move marker at the end of a game */
8351 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8356 toX = moveList[currentMove][2] - AAA;
8357 toY = moveList[currentMove][3] - ONE;
8359 if (moveList[currentMove][1] == '@') {
8360 if (appData.highlightLastMove) {
8361 SetHighlights(-1, -1, toX, toY);
8364 fromX = moveList[currentMove][0] - AAA;
8365 fromY = moveList[currentMove][1] - ONE;
8367 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8369 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8371 if (appData.highlightLastMove) {
8372 SetHighlights(fromX, fromY, toX, toY);
8375 DisplayMove(currentMove);
8376 SendMoveToProgram(currentMove++, &first);
8377 DisplayBothClocks();
8378 DrawPosition(FALSE, boards[currentMove]);
8379 // [HGM] PV info: always display, routine tests if empty
8380 DisplayComment(currentMove - 1, commentList[currentMove]);
8386 LoadGameOneMove(readAhead)
8387 ChessMove readAhead;
8389 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8390 char promoChar = NULLCHAR;
8395 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8396 gameMode != AnalyzeMode && gameMode != Training) {
8401 yyboardindex = forwardMostMove;
8402 if (readAhead != (ChessMove)0) {
8403 moveType = readAhead;
8405 if (gameFileFP == NULL)
8407 moveType = (ChessMove) yylex();
8413 if (appData.debugMode)
8414 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8416 if (*p == '{' || *p == '[' || *p == '(') {
8417 p[strlen(p) - 1] = NULLCHAR;
8421 /* append the comment but don't display it */
8422 while (*p == '\n') p++;
8423 AppendComment(currentMove, p);
8426 case WhiteCapturesEnPassant:
8427 case BlackCapturesEnPassant:
8428 case WhitePromotionChancellor:
8429 case BlackPromotionChancellor:
8430 case WhitePromotionArchbishop:
8431 case BlackPromotionArchbishop:
8432 case WhitePromotionCentaur:
8433 case BlackPromotionCentaur:
8434 case WhitePromotionQueen:
8435 case BlackPromotionQueen:
8436 case WhitePromotionRook:
8437 case BlackPromotionRook:
8438 case WhitePromotionBishop:
8439 case BlackPromotionBishop:
8440 case WhitePromotionKnight:
8441 case BlackPromotionKnight:
8442 case WhitePromotionKing:
8443 case BlackPromotionKing:
8445 case WhiteKingSideCastle:
8446 case WhiteQueenSideCastle:
8447 case BlackKingSideCastle:
8448 case BlackQueenSideCastle:
8449 case WhiteKingSideCastleWild:
8450 case WhiteQueenSideCastleWild:
8451 case BlackKingSideCastleWild:
8452 case BlackQueenSideCastleWild:
8454 case WhiteHSideCastleFR:
8455 case WhiteASideCastleFR:
8456 case BlackHSideCastleFR:
8457 case BlackASideCastleFR:
8459 if (appData.debugMode)
8460 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8461 fromX = currentMoveString[0] - AAA;
8462 fromY = currentMoveString[1] - ONE;
8463 toX = currentMoveString[2] - AAA;
8464 toY = currentMoveString[3] - ONE;
8465 promoChar = currentMoveString[4];
8470 if (appData.debugMode)
8471 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8472 fromX = moveType == WhiteDrop ?
8473 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8474 (int) CharToPiece(ToLower(currentMoveString[0]));
8476 toX = currentMoveString[2] - AAA;
8477 toY = currentMoveString[3] - ONE;
8483 case GameUnfinished:
8484 if (appData.debugMode)
8485 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8486 p = strchr(yy_text, '{');
8487 if (p == NULL) p = strchr(yy_text, '(');
8490 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8492 q = strchr(p, *p == '{' ? '}' : ')');
8493 if (q != NULL) *q = NULLCHAR;
8496 GameEnds(moveType, p, GE_FILE);
8498 if (cmailMsgLoaded) {
8500 flipView = WhiteOnMove(currentMove);
8501 if (moveType == GameUnfinished) flipView = !flipView;
8502 if (appData.debugMode)
8503 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8507 case (ChessMove) 0: /* end of file */
8508 if (appData.debugMode)
8509 fprintf(debugFP, "Parser hit end of file\n");
8510 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8511 EP_UNKNOWN, castlingRights[currentMove]) ) {
8517 if (WhiteOnMove(currentMove)) {
8518 GameEnds(BlackWins, "Black mates", GE_FILE);
8520 GameEnds(WhiteWins, "White mates", GE_FILE);
8524 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8531 if (lastLoadGameStart == GNUChessGame) {
8532 /* GNUChessGames have numbers, but they aren't move numbers */
8533 if (appData.debugMode)
8534 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8535 yy_text, (int) moveType);
8536 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8538 /* else fall thru */
8543 /* Reached start of next game in file */
8544 if (appData.debugMode)
8545 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8546 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8547 EP_UNKNOWN, castlingRights[currentMove]) ) {
8553 if (WhiteOnMove(currentMove)) {
8554 GameEnds(BlackWins, "Black mates", GE_FILE);
8556 GameEnds(WhiteWins, "White mates", GE_FILE);
8560 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8566 case PositionDiagram: /* should not happen; ignore */
8567 case ElapsedTime: /* ignore */
8568 case NAG: /* ignore */
8569 if (appData.debugMode)
8570 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8571 yy_text, (int) moveType);
8572 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8575 if (appData.testLegality) {
8576 if (appData.debugMode)
8577 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8578 sprintf(move, _("Illegal move: %d.%s%s"),
8579 (forwardMostMove / 2) + 1,
8580 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8581 DisplayError(move, 0);
8584 if (appData.debugMode)
8585 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8586 yy_text, currentMoveString);
8587 fromX = currentMoveString[0] - AAA;
8588 fromY = currentMoveString[1] - ONE;
8589 toX = currentMoveString[2] - AAA;
8590 toY = currentMoveString[3] - ONE;
8591 promoChar = currentMoveString[4];
8596 if (appData.debugMode)
8597 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8598 sprintf(move, _("Ambiguous move: %d.%s%s"),
8599 (forwardMostMove / 2) + 1,
8600 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8601 DisplayError(move, 0);
8606 case ImpossibleMove:
8607 if (appData.debugMode)
8608 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8609 sprintf(move, _("Illegal move: %d.%s%s"),
8610 (forwardMostMove / 2) + 1,
8611 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8612 DisplayError(move, 0);
8618 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8619 DrawPosition(FALSE, boards[currentMove]);
8620 DisplayBothClocks();
8621 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8622 DisplayComment(currentMove - 1, commentList[currentMove]);
8624 (void) StopLoadGameTimer();
8626 cmailOldMove = forwardMostMove;
8629 /* currentMoveString is set as a side-effect of yylex */
8630 strcat(currentMoveString, "\n");
8631 strcpy(moveList[forwardMostMove], currentMoveString);
8633 thinkOutput[0] = NULLCHAR;
8634 MakeMove(fromX, fromY, toX, toY, promoChar);
8635 currentMove = forwardMostMove;
8640 /* Load the nth game from the given file */
8642 LoadGameFromFile(filename, n, title, useList)
8646 /*Boolean*/ int useList;
8651 if (strcmp(filename, "-") == 0) {
8655 f = fopen(filename, "rb");
8657 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8658 DisplayError(buf, errno);
8662 if (fseek(f, 0, 0) == -1) {
8663 /* f is not seekable; probably a pipe */
8666 if (useList && n == 0) {
8667 int error = GameListBuild(f);
8669 DisplayError(_("Cannot build game list"), error);
8670 } else if (!ListEmpty(&gameList) &&
8671 ((ListGame *) gameList.tailPred)->number > 1) {
8672 GameListPopUp(f, title);
8679 return LoadGame(f, n, title, FALSE);
8684 MakeRegisteredMove()
8686 int fromX, fromY, toX, toY;
8688 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8689 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8692 if (appData.debugMode)
8693 fprintf(debugFP, "Restoring %s for game %d\n",
8694 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8696 thinkOutput[0] = NULLCHAR;
8697 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8698 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8699 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8700 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8701 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8702 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8703 MakeMove(fromX, fromY, toX, toY, promoChar);
8704 ShowMove(fromX, fromY, toX, toY);
8706 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8707 EP_UNKNOWN, castlingRights[currentMove]) ) {
8714 if (WhiteOnMove(currentMove)) {
8715 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8717 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8722 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8729 if (WhiteOnMove(currentMove)) {
8730 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8732 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8737 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8748 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8750 CmailLoadGame(f, gameNumber, title, useList)
8758 if (gameNumber > nCmailGames) {
8759 DisplayError(_("No more games in this message"), 0);
8762 if (f == lastLoadGameFP) {
8763 int offset = gameNumber - lastLoadGameNumber;
8765 cmailMsg[0] = NULLCHAR;
8766 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8767 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8768 nCmailMovesRegistered--;
8770 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8771 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8772 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8775 if (! RegisterMove()) return FALSE;
8779 retVal = LoadGame(f, gameNumber, title, useList);
8781 /* Make move registered during previous look at this game, if any */
8782 MakeRegisteredMove();
8784 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8785 commentList[currentMove]
8786 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8787 DisplayComment(currentMove - 1, commentList[currentMove]);
8793 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8798 int gameNumber = lastLoadGameNumber + offset;
8799 if (lastLoadGameFP == NULL) {
8800 DisplayError(_("No game has been loaded yet"), 0);
8803 if (gameNumber <= 0) {
8804 DisplayError(_("Can't back up any further"), 0);
8807 if (cmailMsgLoaded) {
8808 return CmailLoadGame(lastLoadGameFP, gameNumber,
8809 lastLoadGameTitle, lastLoadGameUseList);
8811 return LoadGame(lastLoadGameFP, gameNumber,
8812 lastLoadGameTitle, lastLoadGameUseList);
8818 /* Load the nth game from open file f */
8820 LoadGame(f, gameNumber, title, useList)
8828 int gn = gameNumber;
8829 ListGame *lg = NULL;
8832 GameMode oldGameMode;
8833 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8835 if (appData.debugMode)
8836 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8838 if (gameMode == Training )
8839 SetTrainingModeOff();
8841 oldGameMode = gameMode;
8842 if (gameMode != BeginningOfGame) {
8847 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8848 fclose(lastLoadGameFP);
8852 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8855 fseek(f, lg->offset, 0);
8856 GameListHighlight(gameNumber);
8860 DisplayError(_("Game number out of range"), 0);
8865 if (fseek(f, 0, 0) == -1) {
8866 if (f == lastLoadGameFP ?
8867 gameNumber == lastLoadGameNumber + 1 :
8871 DisplayError(_("Can't seek on game file"), 0);
8877 lastLoadGameNumber = gameNumber;
8878 strcpy(lastLoadGameTitle, title);
8879 lastLoadGameUseList = useList;
8883 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8884 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8885 lg->gameInfo.black);
8887 } else if (*title != NULLCHAR) {
8888 if (gameNumber > 1) {
8889 sprintf(buf, "%s %d", title, gameNumber);
8892 DisplayTitle(title);
8896 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8897 gameMode = PlayFromGameFile;
8901 currentMove = forwardMostMove = backwardMostMove = 0;
8902 CopyBoard(boards[0], initialPosition);
8906 * Skip the first gn-1 games in the file.
8907 * Also skip over anything that precedes an identifiable
8908 * start of game marker, to avoid being confused by
8909 * garbage at the start of the file. Currently
8910 * recognized start of game markers are the move number "1",
8911 * the pattern "gnuchess .* game", the pattern
8912 * "^[#;%] [^ ]* game file", and a PGN tag block.
8913 * A game that starts with one of the latter two patterns
8914 * will also have a move number 1, possibly
8915 * following a position diagram.
8916 * 5-4-02: Let's try being more lenient and allowing a game to
8917 * start with an unnumbered move. Does that break anything?
8919 cm = lastLoadGameStart = (ChessMove) 0;
8921 yyboardindex = forwardMostMove;
8922 cm = (ChessMove) yylex();
8925 if (cmailMsgLoaded) {
8926 nCmailGames = CMAIL_MAX_GAMES - gn;
8929 DisplayError(_("Game not found in file"), 0);
8936 lastLoadGameStart = cm;
8940 switch (lastLoadGameStart) {
8947 gn--; /* count this game */
8948 lastLoadGameStart = cm;
8957 switch (lastLoadGameStart) {
8962 gn--; /* count this game */
8963 lastLoadGameStart = cm;
8966 lastLoadGameStart = cm; /* game counted already */
8974 yyboardindex = forwardMostMove;
8975 cm = (ChessMove) yylex();
8976 } while (cm == PGNTag || cm == Comment);
8983 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8984 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8985 != CMAIL_OLD_RESULT) {
8987 cmailResult[ CMAIL_MAX_GAMES
8988 - gn - 1] = CMAIL_OLD_RESULT;
8994 /* Only a NormalMove can be at the start of a game
8995 * without a position diagram. */
8996 if (lastLoadGameStart == (ChessMove) 0) {
8998 lastLoadGameStart = MoveNumberOne;
9007 if (appData.debugMode)
9008 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9010 if (cm == XBoardGame) {
9011 /* Skip any header junk before position diagram and/or move 1 */
9013 yyboardindex = forwardMostMove;
9014 cm = (ChessMove) yylex();
9016 if (cm == (ChessMove) 0 ||
9017 cm == GNUChessGame || cm == XBoardGame) {
9018 /* Empty game; pretend end-of-file and handle later */
9023 if (cm == MoveNumberOne || cm == PositionDiagram ||
9024 cm == PGNTag || cm == Comment)
9027 } else if (cm == GNUChessGame) {
9028 if (gameInfo.event != NULL) {
9029 free(gameInfo.event);
9031 gameInfo.event = StrSave(yy_text);
9034 startedFromSetupPosition = FALSE;
9035 while (cm == PGNTag) {
9036 if (appData.debugMode)
9037 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9038 err = ParsePGNTag(yy_text, &gameInfo);
9039 if (!err) numPGNTags++;
9041 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9042 if(gameInfo.variant != oldVariant) {
9043 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9045 oldVariant = gameInfo.variant;
9046 if (appData.debugMode)
9047 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9051 if (gameInfo.fen != NULL) {
9052 Board initial_position;
9053 startedFromSetupPosition = TRUE;
9054 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9056 DisplayError(_("Bad FEN position in file"), 0);
9059 CopyBoard(boards[0], initial_position);
9060 if (blackPlaysFirst) {
9061 currentMove = forwardMostMove = backwardMostMove = 1;
9062 CopyBoard(boards[1], initial_position);
9063 strcpy(moveList[0], "");
9064 strcpy(parseList[0], "");
9065 timeRemaining[0][1] = whiteTimeRemaining;
9066 timeRemaining[1][1] = blackTimeRemaining;
9067 if (commentList[0] != NULL) {
9068 commentList[1] = commentList[0];
9069 commentList[0] = NULL;
9072 currentMove = forwardMostMove = backwardMostMove = 0;
9074 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9076 initialRulePlies = FENrulePlies;
9077 epStatus[forwardMostMove] = FENepStatus;
9078 for( i=0; i< nrCastlingRights; i++ )
9079 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9081 yyboardindex = forwardMostMove;
9083 gameInfo.fen = NULL;
9086 yyboardindex = forwardMostMove;
9087 cm = (ChessMove) yylex();
9089 /* Handle comments interspersed among the tags */
9090 while (cm == Comment) {
9092 if (appData.debugMode)
9093 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9095 if (*p == '{' || *p == '[' || *p == '(') {
9096 p[strlen(p) - 1] = NULLCHAR;
9099 while (*p == '\n') p++;
9100 AppendComment(currentMove, p);
9101 yyboardindex = forwardMostMove;
9102 cm = (ChessMove) yylex();
9106 /* don't rely on existence of Event tag since if game was
9107 * pasted from clipboard the Event tag may not exist
9109 if (numPGNTags > 0){
9111 if (gameInfo.variant == VariantNormal) {
9112 gameInfo.variant = StringToVariant(gameInfo.event);
9115 if( appData.autoDisplayTags ) {
9116 tags = PGNTags(&gameInfo);
9117 TagsPopUp(tags, CmailMsg());
9122 /* Make something up, but don't display it now */
9127 if (cm == PositionDiagram) {
9130 Board initial_position;
9132 if (appData.debugMode)
9133 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9135 if (!startedFromSetupPosition) {
9137 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9138 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9148 initial_position[i][j++] = CharToPiece(*p);
9151 while (*p == ' ' || *p == '\t' ||
9152 *p == '\n' || *p == '\r') p++;
9154 if (strncmp(p, "black", strlen("black"))==0)
9155 blackPlaysFirst = TRUE;
9157 blackPlaysFirst = FALSE;
9158 startedFromSetupPosition = TRUE;
9160 CopyBoard(boards[0], initial_position);
9161 if (blackPlaysFirst) {
9162 currentMove = forwardMostMove = backwardMostMove = 1;
9163 CopyBoard(boards[1], initial_position);
9164 strcpy(moveList[0], "");
9165 strcpy(parseList[0], "");
9166 timeRemaining[0][1] = whiteTimeRemaining;
9167 timeRemaining[1][1] = blackTimeRemaining;
9168 if (commentList[0] != NULL) {
9169 commentList[1] = commentList[0];
9170 commentList[0] = NULL;
9173 currentMove = forwardMostMove = backwardMostMove = 0;
9176 yyboardindex = forwardMostMove;
9177 cm = (ChessMove) yylex();
9180 if (first.pr == NoProc) {
9181 StartChessProgram(&first);
9183 InitChessProgram(&first, FALSE);
9184 SendToProgram("force\n", &first);
9185 if (startedFromSetupPosition) {
9186 SendBoard(&first, forwardMostMove);
9187 if (appData.debugMode) {
9188 fprintf(debugFP, "Load Game\n");
9190 DisplayBothClocks();
9193 /* [HGM] server: flag to write setup moves in broadcast file as one */
9194 loadFlag = appData.suppressLoadMoves;
9196 while (cm == Comment) {
9198 if (appData.debugMode)
9199 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9201 if (*p == '{' || *p == '[' || *p == '(') {
9202 p[strlen(p) - 1] = NULLCHAR;
9205 while (*p == '\n') p++;
9206 AppendComment(currentMove, p);
9207 yyboardindex = forwardMostMove;
9208 cm = (ChessMove) yylex();
9211 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9212 cm == WhiteWins || cm == BlackWins ||
9213 cm == GameIsDrawn || cm == GameUnfinished) {
9214 DisplayMessage("", _("No moves in game"));
9215 if (cmailMsgLoaded) {
9216 if (appData.debugMode)
9217 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9221 DrawPosition(FALSE, boards[currentMove]);
9222 DisplayBothClocks();
9223 gameMode = EditGame;
9230 // [HGM] PV info: routine tests if comment empty
9231 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9232 DisplayComment(currentMove - 1, commentList[currentMove]);
9234 if (!matchMode && appData.timeDelay != 0)
9235 DrawPosition(FALSE, boards[currentMove]);
9237 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9238 programStats.ok_to_send = 1;
9241 /* if the first token after the PGN tags is a move
9242 * and not move number 1, retrieve it from the parser
9244 if (cm != MoveNumberOne)
9245 LoadGameOneMove(cm);
9247 /* load the remaining moves from the file */
9248 while (LoadGameOneMove((ChessMove)0)) {
9249 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9250 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9253 /* rewind to the start of the game */
9254 currentMove = backwardMostMove;
9256 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9258 if (oldGameMode == AnalyzeFile ||
9259 oldGameMode == AnalyzeMode) {
9263 if (matchMode || appData.timeDelay == 0) {
9265 gameMode = EditGame;
9267 } else if (appData.timeDelay > 0) {
9271 if (appData.debugMode)
9272 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9274 loadFlag = 0; /* [HGM] true game starts */
9278 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9280 ReloadPosition(offset)
9283 int positionNumber = lastLoadPositionNumber + offset;
9284 if (lastLoadPositionFP == NULL) {
9285 DisplayError(_("No position has been loaded yet"), 0);
9288 if (positionNumber <= 0) {
9289 DisplayError(_("Can't back up any further"), 0);
9292 return LoadPosition(lastLoadPositionFP, positionNumber,
9293 lastLoadPositionTitle);
9296 /* Load the nth position from the given file */
9298 LoadPositionFromFile(filename, n, title)
9306 if (strcmp(filename, "-") == 0) {
9307 return LoadPosition(stdin, n, "stdin");
9309 f = fopen(filename, "rb");
9311 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9312 DisplayError(buf, errno);
9315 return LoadPosition(f, n, title);
9320 /* Load the nth position from the given open file, and close it */
9322 LoadPosition(f, positionNumber, title)
9327 char *p, line[MSG_SIZ];
9328 Board initial_position;
9329 int i, j, fenMode, pn;
9331 if (gameMode == Training )
9332 SetTrainingModeOff();
9334 if (gameMode != BeginningOfGame) {
9337 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9338 fclose(lastLoadPositionFP);
9340 if (positionNumber == 0) positionNumber = 1;
9341 lastLoadPositionFP = f;
9342 lastLoadPositionNumber = positionNumber;
9343 strcpy(lastLoadPositionTitle, title);
9344 if (first.pr == NoProc) {
9345 StartChessProgram(&first);
9346 InitChessProgram(&first, FALSE);
9348 pn = positionNumber;
9349 if (positionNumber < 0) {
9350 /* Negative position number means to seek to that byte offset */
9351 if (fseek(f, -positionNumber, 0) == -1) {
9352 DisplayError(_("Can't seek on position file"), 0);
9357 if (fseek(f, 0, 0) == -1) {
9358 if (f == lastLoadPositionFP ?
9359 positionNumber == lastLoadPositionNumber + 1 :
9360 positionNumber == 1) {
9363 DisplayError(_("Can't seek on position file"), 0);
9368 /* See if this file is FEN or old-style xboard */
9369 if (fgets(line, MSG_SIZ, f) == NULL) {
9370 DisplayError(_("Position not found in file"), 0);
9379 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9380 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9381 case '1': case '2': case '3': case '4': case '5': case '6':
9382 case '7': case '8': case '9':
9383 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9384 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9385 case 'C': case 'W': case 'c': case 'w':
9390 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9391 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9395 if (fenMode || line[0] == '#') pn--;
9397 /* skip positions before number pn */
9398 if (fgets(line, MSG_SIZ, f) == NULL) {
9400 DisplayError(_("Position not found in file"), 0);
9403 if (fenMode || line[0] == '#') pn--;
9408 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9409 DisplayError(_("Bad FEN position in file"), 0);
9413 (void) fgets(line, MSG_SIZ, f);
9414 (void) fgets(line, MSG_SIZ, f);
9416 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9417 (void) fgets(line, MSG_SIZ, f);
9418 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9421 initial_position[i][j++] = CharToPiece(*p);
9425 blackPlaysFirst = FALSE;
9427 (void) fgets(line, MSG_SIZ, f);
9428 if (strncmp(line, "black", strlen("black"))==0)
9429 blackPlaysFirst = TRUE;
9432 startedFromSetupPosition = TRUE;
9434 SendToProgram("force\n", &first);
9435 CopyBoard(boards[0], initial_position);
9436 if (blackPlaysFirst) {
9437 currentMove = forwardMostMove = backwardMostMove = 1;
9438 strcpy(moveList[0], "");
9439 strcpy(parseList[0], "");
9440 CopyBoard(boards[1], initial_position);
9441 DisplayMessage("", _("Black to play"));
9443 currentMove = forwardMostMove = backwardMostMove = 0;
9444 DisplayMessage("", _("White to play"));
9446 /* [HGM] copy FEN attributes as well */
9448 initialRulePlies = FENrulePlies;
9449 epStatus[forwardMostMove] = FENepStatus;
9450 for( i=0; i< nrCastlingRights; i++ )
9451 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9453 SendBoard(&first, forwardMostMove);
9454 if (appData.debugMode) {
9456 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9457 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9458 fprintf(debugFP, "Load Position\n");
9461 if (positionNumber > 1) {
9462 sprintf(line, "%s %d", title, positionNumber);
9465 DisplayTitle(title);
9467 gameMode = EditGame;
9470 timeRemaining[0][1] = whiteTimeRemaining;
9471 timeRemaining[1][1] = blackTimeRemaining;
9472 DrawPosition(FALSE, boards[currentMove]);
9479 CopyPlayerNameIntoFileName(dest, src)
9482 while (*src != NULLCHAR && *src != ',') {
9487 *(*dest)++ = *src++;
9492 char *DefaultFileName(ext)
9495 static char def[MSG_SIZ];
9498 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9500 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9502 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9511 /* Save the current game to the given file */
9513 SaveGameToFile(filename, append)
9520 if (strcmp(filename, "-") == 0) {
9521 return SaveGame(stdout, 0, NULL);
9523 f = fopen(filename, append ? "a" : "w");
9525 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9526 DisplayError(buf, errno);
9529 return SaveGame(f, 0, NULL);
9538 static char buf[MSG_SIZ];
9541 p = strchr(str, ' ');
9542 if (p == NULL) return str;
9543 strncpy(buf, str, p - str);
9544 buf[p - str] = NULLCHAR;
9548 #define PGN_MAX_LINE 75
9550 #define PGN_SIDE_WHITE 0
9551 #define PGN_SIDE_BLACK 1
9554 static int FindFirstMoveOutOfBook( int side )
9558 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9559 int index = backwardMostMove;
9560 int has_book_hit = 0;
9562 if( (index % 2) != side ) {
9566 while( index < forwardMostMove ) {
9567 /* Check to see if engine is in book */
9568 int depth = pvInfoList[index].depth;
9569 int score = pvInfoList[index].score;
9575 else if( score == 0 && depth == 63 ) {
9576 in_book = 1; /* Zappa */
9578 else if( score == 2 && depth == 99 ) {
9579 in_book = 1; /* Abrok */
9582 has_book_hit += in_book;
9598 void GetOutOfBookInfo( char * buf )
9602 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9604 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9605 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9609 if( oob[0] >= 0 || oob[1] >= 0 ) {
9610 for( i=0; i<2; i++ ) {
9614 if( i > 0 && oob[0] >= 0 ) {
9618 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9619 sprintf( buf+strlen(buf), "%s%.2f",
9620 pvInfoList[idx].score >= 0 ? "+" : "",
9621 pvInfoList[idx].score / 100.0 );
9627 /* Save game in PGN style and close the file */
9632 int i, offset, linelen, newblock;
9636 int movelen, numlen, blank;
9637 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9639 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9641 tm = time((time_t *) NULL);
9643 PrintPGNTags(f, &gameInfo);
9645 if (backwardMostMove > 0 || startedFromSetupPosition) {
9646 char *fen = PositionToFEN(backwardMostMove, NULL);
9647 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9648 fprintf(f, "\n{--------------\n");
9649 PrintPosition(f, backwardMostMove);
9650 fprintf(f, "--------------}\n");
9654 /* [AS] Out of book annotation */
9655 if( appData.saveOutOfBookInfo ) {
9658 GetOutOfBookInfo( buf );
9660 if( buf[0] != '\0' ) {
9661 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9668 i = backwardMostMove;
9672 while (i < forwardMostMove) {
9673 /* Print comments preceding this move */
9674 if (commentList[i] != NULL) {
9675 if (linelen > 0) fprintf(f, "\n");
9676 fprintf(f, "{\n%s}\n", commentList[i]);
9681 /* Format move number */
9683 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9686 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9688 numtext[0] = NULLCHAR;
9691 numlen = strlen(numtext);
9694 /* Print move number */
9695 blank = linelen > 0 && numlen > 0;
9696 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9705 fprintf(f, numtext);
9709 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9710 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9712 // SavePart already does this!
9713 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9714 int p = movelen - 1;
9715 if(move_buffer[p] == ' ') p--;
9716 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9717 while(p && move_buffer[--p] != '(');
9718 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9723 blank = linelen > 0 && movelen > 0;
9724 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9733 fprintf(f, move_buffer);
9736 /* [AS] Add PV info if present */
9737 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9738 /* [HGM] add time */
9739 char buf[MSG_SIZ]; int seconds = 0;
9742 if(i >= backwardMostMove) {
9744 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9745 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9747 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9748 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9750 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9752 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9755 if( seconds <= 0) buf[0] = 0; else
9756 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9757 seconds = (seconds + 4)/10; // round to full seconds
9758 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9759 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9762 sprintf( move_buffer, "{%s%.2f/%d%s}",
9763 pvInfoList[i].score >= 0 ? "+" : "",
9764 pvInfoList[i].score / 100.0,
9765 pvInfoList[i].depth,
9768 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9770 /* Print score/depth */
9771 blank = linelen > 0 && movelen > 0;
9772 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9781 fprintf(f, move_buffer);
9788 /* Start a new line */
9789 if (linelen > 0) fprintf(f, "\n");
9791 /* Print comments after last move */
9792 if (commentList[i] != NULL) {
9793 fprintf(f, "{\n%s}\n", commentList[i]);
9797 if (gameInfo.resultDetails != NULL &&
9798 gameInfo.resultDetails[0] != NULLCHAR) {
9799 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9800 PGNResult(gameInfo.result));
9802 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9806 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9810 /* Save game in old style and close the file */
9818 tm = time((time_t *) NULL);
9820 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9823 if (backwardMostMove > 0 || startedFromSetupPosition) {
9824 fprintf(f, "\n[--------------\n");
9825 PrintPosition(f, backwardMostMove);
9826 fprintf(f, "--------------]\n");
9831 i = backwardMostMove;
9832 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9834 while (i < forwardMostMove) {
9835 if (commentList[i] != NULL) {
9836 fprintf(f, "[%s]\n", commentList[i]);
9840 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9843 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9845 if (commentList[i] != NULL) {
9849 if (i >= forwardMostMove) {
9853 fprintf(f, "%s\n", parseList[i]);
9858 if (commentList[i] != NULL) {
9859 fprintf(f, "[%s]\n", commentList[i]);
9862 /* This isn't really the old style, but it's close enough */
9863 if (gameInfo.resultDetails != NULL &&
9864 gameInfo.resultDetails[0] != NULLCHAR) {
9865 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9866 gameInfo.resultDetails);
9868 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9875 /* Save the current game to open file f and close the file */
9877 SaveGame(f, dummy, dummy2)
9882 if (gameMode == EditPosition) EditPositionDone();
9883 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9884 if (appData.oldSaveStyle)
9885 return SaveGameOldStyle(f);
9887 return SaveGamePGN(f);
9890 /* Save the current position to the given file */
9892 SavePositionToFile(filename)
9898 if (strcmp(filename, "-") == 0) {
9899 return SavePosition(stdout, 0, NULL);
9901 f = fopen(filename, "a");
9903 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9904 DisplayError(buf, errno);
9907 SavePosition(f, 0, NULL);
9913 /* Save the current position to the given open file and close the file */
9915 SavePosition(f, dummy, dummy2)
9923 if (appData.oldSaveStyle) {
9924 tm = time((time_t *) NULL);
9926 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9928 fprintf(f, "[--------------\n");
9929 PrintPosition(f, currentMove);
9930 fprintf(f, "--------------]\n");
9932 fen = PositionToFEN(currentMove, NULL);
9933 fprintf(f, "%s\n", fen);
9941 ReloadCmailMsgEvent(unregister)
9945 static char *inFilename = NULL;
9946 static char *outFilename;
9948 struct stat inbuf, outbuf;
9951 /* Any registered moves are unregistered if unregister is set, */
9952 /* i.e. invoked by the signal handler */
9954 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9955 cmailMoveRegistered[i] = FALSE;
9956 if (cmailCommentList[i] != NULL) {
9957 free(cmailCommentList[i]);
9958 cmailCommentList[i] = NULL;
9961 nCmailMovesRegistered = 0;
9964 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9965 cmailResult[i] = CMAIL_NOT_RESULT;
9969 if (inFilename == NULL) {
9970 /* Because the filenames are static they only get malloced once */
9971 /* and they never get freed */
9972 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9973 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9975 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9976 sprintf(outFilename, "%s.out", appData.cmailGameName);
9979 status = stat(outFilename, &outbuf);
9981 cmailMailedMove = FALSE;
9983 status = stat(inFilename, &inbuf);
9984 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9987 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9988 counts the games, notes how each one terminated, etc.
9990 It would be nice to remove this kludge and instead gather all
9991 the information while building the game list. (And to keep it
9992 in the game list nodes instead of having a bunch of fixed-size
9993 parallel arrays.) Note this will require getting each game's
9994 termination from the PGN tags, as the game list builder does
9995 not process the game moves. --mann
9997 cmailMsgLoaded = TRUE;
9998 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10000 /* Load first game in the file or popup game menu */
10001 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10003 #endif /* !WIN32 */
10011 char string[MSG_SIZ];
10013 if ( cmailMailedMove
10014 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10015 return TRUE; /* Allow free viewing */
10018 /* Unregister move to ensure that we don't leave RegisterMove */
10019 /* with the move registered when the conditions for registering no */
10021 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10022 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10023 nCmailMovesRegistered --;
10025 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10027 free(cmailCommentList[lastLoadGameNumber - 1]);
10028 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10032 if (cmailOldMove == -1) {
10033 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10037 if (currentMove > cmailOldMove + 1) {
10038 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10042 if (currentMove < cmailOldMove) {
10043 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10047 if (forwardMostMove > currentMove) {
10048 /* Silently truncate extra moves */
10052 if ( (currentMove == cmailOldMove + 1)
10053 || ( (currentMove == cmailOldMove)
10054 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10055 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10056 if (gameInfo.result != GameUnfinished) {
10057 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10060 if (commentList[currentMove] != NULL) {
10061 cmailCommentList[lastLoadGameNumber - 1]
10062 = StrSave(commentList[currentMove]);
10064 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10066 if (appData.debugMode)
10067 fprintf(debugFP, "Saving %s for game %d\n",
10068 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10071 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10073 f = fopen(string, "w");
10074 if (appData.oldSaveStyle) {
10075 SaveGameOldStyle(f); /* also closes the file */
10077 sprintf(string, "%s.pos.out", appData.cmailGameName);
10078 f = fopen(string, "w");
10079 SavePosition(f, 0, NULL); /* also closes the file */
10081 fprintf(f, "{--------------\n");
10082 PrintPosition(f, currentMove);
10083 fprintf(f, "--------------}\n\n");
10085 SaveGame(f, 0, NULL); /* also closes the file*/
10088 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10089 nCmailMovesRegistered ++;
10090 } else if (nCmailGames == 1) {
10091 DisplayError(_("You have not made a move yet"), 0);
10102 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10103 FILE *commandOutput;
10104 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10105 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10111 if (! cmailMsgLoaded) {
10112 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10116 if (nCmailGames == nCmailResults) {
10117 DisplayError(_("No unfinished games"), 0);
10121 #if CMAIL_PROHIBIT_REMAIL
10122 if (cmailMailedMove) {
10123 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);
10124 DisplayError(msg, 0);
10129 if (! (cmailMailedMove || RegisterMove())) return;
10131 if ( cmailMailedMove
10132 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10133 sprintf(string, partCommandString,
10134 appData.debugMode ? " -v" : "", appData.cmailGameName);
10135 commandOutput = popen(string, "r");
10137 if (commandOutput == NULL) {
10138 DisplayError(_("Failed to invoke cmail"), 0);
10140 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10141 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10143 if (nBuffers > 1) {
10144 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10145 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10146 nBytes = MSG_SIZ - 1;
10148 (void) memcpy(msg, buffer, nBytes);
10150 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10152 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10153 cmailMailedMove = TRUE; /* Prevent >1 moves */
10156 for (i = 0; i < nCmailGames; i ++) {
10157 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10162 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10164 sprintf(buffer, "%s/%s.%s.archive",
10166 appData.cmailGameName,
10168 LoadGameFromFile(buffer, 1, buffer, FALSE);
10169 cmailMsgLoaded = FALSE;
10173 DisplayInformation(msg);
10174 pclose(commandOutput);
10177 if ((*cmailMsg) != '\0') {
10178 DisplayInformation(cmailMsg);
10183 #endif /* !WIN32 */
10192 int prependComma = 0;
10194 char string[MSG_SIZ]; /* Space for game-list */
10197 if (!cmailMsgLoaded) return "";
10199 if (cmailMailedMove) {
10200 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10202 /* Create a list of games left */
10203 sprintf(string, "[");
10204 for (i = 0; i < nCmailGames; i ++) {
10205 if (! ( cmailMoveRegistered[i]
10206 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10207 if (prependComma) {
10208 sprintf(number, ",%d", i + 1);
10210 sprintf(number, "%d", i + 1);
10214 strcat(string, number);
10217 strcat(string, "]");
10219 if (nCmailMovesRegistered + nCmailResults == 0) {
10220 switch (nCmailGames) {
10223 _("Still need to make move for game\n"));
10228 _("Still need to make moves for both games\n"));
10233 _("Still need to make moves for all %d games\n"),
10238 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10241 _("Still need to make a move for game %s\n"),
10246 if (nCmailResults == nCmailGames) {
10247 sprintf(cmailMsg, _("No unfinished games\n"));
10249 sprintf(cmailMsg, _("Ready to send mail\n"));
10255 _("Still need to make moves for games %s\n"),
10267 if (gameMode == Training)
10268 SetTrainingModeOff();
10271 cmailMsgLoaded = FALSE;
10272 if (appData.icsActive) {
10273 SendToICS(ics_prefix);
10274 SendToICS("refresh\n");
10284 /* Give up on clean exit */
10288 /* Keep trying for clean exit */
10292 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10294 if (telnetISR != NULL) {
10295 RemoveInputSource(telnetISR);
10297 if (icsPR != NoProc) {
10298 DestroyChildProcess(icsPR, TRUE);
10301 /* Save game if resource set and not already saved by GameEnds() */
10302 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10303 && forwardMostMove > 0) {
10304 if (*appData.saveGameFile != NULLCHAR) {
10305 SaveGameToFile(appData.saveGameFile, TRUE);
10306 } else if (appData.autoSaveGames) {
10309 if (*appData.savePositionFile != NULLCHAR) {
10310 SavePositionToFile(appData.savePositionFile);
10313 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10315 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10316 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10318 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10319 /* make sure this other one finishes before killing it! */
10320 if(endingGame) { int count = 0;
10321 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10322 while(endingGame && count++ < 10) DoSleep(1);
10323 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10326 /* Kill off chess programs */
10327 if (first.pr != NoProc) {
10330 DoSleep( appData.delayBeforeQuit );
10331 SendToProgram("quit\n", &first);
10332 DoSleep( appData.delayAfterQuit );
10333 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10335 if (second.pr != NoProc) {
10336 DoSleep( appData.delayBeforeQuit );
10337 SendToProgram("quit\n", &second);
10338 DoSleep( appData.delayAfterQuit );
10339 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10341 if (first.isr != NULL) {
10342 RemoveInputSource(first.isr);
10344 if (second.isr != NULL) {
10345 RemoveInputSource(second.isr);
10348 ShutDownFrontEnd();
10355 if (appData.debugMode)
10356 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10360 if (gameMode == MachinePlaysWhite ||
10361 gameMode == MachinePlaysBlack) {
10364 DisplayBothClocks();
10366 if (gameMode == PlayFromGameFile) {
10367 if (appData.timeDelay >= 0)
10368 AutoPlayGameLoop();
10369 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10370 Reset(FALSE, TRUE);
10371 SendToICS(ics_prefix);
10372 SendToICS("refresh\n");
10373 } else if (currentMove < forwardMostMove) {
10374 ForwardInner(forwardMostMove);
10376 pauseExamInvalid = FALSE;
10378 switch (gameMode) {
10382 pauseExamForwardMostMove = forwardMostMove;
10383 pauseExamInvalid = FALSE;
10386 case IcsPlayingWhite:
10387 case IcsPlayingBlack:
10391 case PlayFromGameFile:
10392 (void) StopLoadGameTimer();
10396 case BeginningOfGame:
10397 if (appData.icsActive) return;
10398 /* else fall through */
10399 case MachinePlaysWhite:
10400 case MachinePlaysBlack:
10401 case TwoMachinesPlay:
10402 if (forwardMostMove == 0)
10403 return; /* don't pause if no one has moved */
10404 if ((gameMode == MachinePlaysWhite &&
10405 !WhiteOnMove(forwardMostMove)) ||
10406 (gameMode == MachinePlaysBlack &&
10407 WhiteOnMove(forwardMostMove))) {
10420 char title[MSG_SIZ];
10422 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10423 strcpy(title, _("Edit comment"));
10425 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10426 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10427 parseList[currentMove - 1]);
10430 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10437 char *tags = PGNTags(&gameInfo);
10438 EditTagsPopUp(tags);
10445 if (appData.noChessProgram || gameMode == AnalyzeMode)
10448 if (gameMode != AnalyzeFile) {
10449 if (!appData.icsEngineAnalyze) {
10451 if (gameMode != EditGame) return;
10453 ResurrectChessProgram();
10454 SendToProgram("analyze\n", &first);
10455 first.analyzing = TRUE;
10456 /*first.maybeThinking = TRUE;*/
10457 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10458 AnalysisPopUp(_("Analysis"),
10459 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10461 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10466 StartAnalysisClock();
10467 GetTimeMark(&lastNodeCountTime);
10474 if (appData.noChessProgram || gameMode == AnalyzeFile)
10477 if (gameMode != AnalyzeMode) {
10479 if (gameMode != EditGame) return;
10480 ResurrectChessProgram();
10481 SendToProgram("analyze\n", &first);
10482 first.analyzing = TRUE;
10483 /*first.maybeThinking = TRUE;*/
10484 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10485 AnalysisPopUp(_("Analysis"),
10486 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10488 gameMode = AnalyzeFile;
10493 StartAnalysisClock();
10494 GetTimeMark(&lastNodeCountTime);
10499 MachineWhiteEvent()
10502 char *bookHit = NULL;
10504 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10508 if (gameMode == PlayFromGameFile ||
10509 gameMode == TwoMachinesPlay ||
10510 gameMode == Training ||
10511 gameMode == AnalyzeMode ||
10512 gameMode == EndOfGame)
10515 if (gameMode == EditPosition)
10516 EditPositionDone();
10518 if (!WhiteOnMove(currentMove)) {
10519 DisplayError(_("It is not White's turn"), 0);
10523 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10526 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10527 gameMode == AnalyzeFile)
10530 ResurrectChessProgram(); /* in case it isn't running */
10531 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10532 gameMode = MachinePlaysWhite;
10535 gameMode = MachinePlaysWhite;
10539 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10541 if (first.sendName) {
10542 sprintf(buf, "name %s\n", gameInfo.black);
10543 SendToProgram(buf, &first);
10545 if (first.sendTime) {
10546 if (first.useColors) {
10547 SendToProgram("black\n", &first); /*gnu kludge*/
10549 SendTimeRemaining(&first, TRUE);
10551 if (first.useColors) {
10552 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10554 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10555 SetMachineThinkingEnables();
10556 first.maybeThinking = TRUE;
10560 if (appData.autoFlipView && !flipView) {
10561 flipView = !flipView;
10562 DrawPosition(FALSE, NULL);
10563 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10566 if(bookHit) { // [HGM] book: simulate book reply
10567 static char bookMove[MSG_SIZ]; // a bit generous?
10569 programStats.nodes = programStats.depth = programStats.time =
10570 programStats.score = programStats.got_only_move = 0;
10571 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10573 strcpy(bookMove, "move ");
10574 strcat(bookMove, bookHit);
10575 HandleMachineMove(bookMove, &first);
10580 MachineBlackEvent()
10583 char *bookHit = NULL;
10585 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10589 if (gameMode == PlayFromGameFile ||
10590 gameMode == TwoMachinesPlay ||
10591 gameMode == Training ||
10592 gameMode == AnalyzeMode ||
10593 gameMode == EndOfGame)
10596 if (gameMode == EditPosition)
10597 EditPositionDone();
10599 if (WhiteOnMove(currentMove)) {
10600 DisplayError(_("It is not Black's turn"), 0);
10604 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10607 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10608 gameMode == AnalyzeFile)
10611 ResurrectChessProgram(); /* in case it isn't running */
10612 gameMode = MachinePlaysBlack;
10616 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10618 if (first.sendName) {
10619 sprintf(buf, "name %s\n", gameInfo.white);
10620 SendToProgram(buf, &first);
10622 if (first.sendTime) {
10623 if (first.useColors) {
10624 SendToProgram("white\n", &first); /*gnu kludge*/
10626 SendTimeRemaining(&first, FALSE);
10628 if (first.useColors) {
10629 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10631 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10632 SetMachineThinkingEnables();
10633 first.maybeThinking = TRUE;
10636 if (appData.autoFlipView && flipView) {
10637 flipView = !flipView;
10638 DrawPosition(FALSE, NULL);
10639 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10641 if(bookHit) { // [HGM] book: simulate book reply
10642 static char bookMove[MSG_SIZ]; // a bit generous?
10644 programStats.nodes = programStats.depth = programStats.time =
10645 programStats.score = programStats.got_only_move = 0;
10646 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10648 strcpy(bookMove, "move ");
10649 strcat(bookMove, bookHit);
10650 HandleMachineMove(bookMove, &first);
10656 DisplayTwoMachinesTitle()
10659 if (appData.matchGames > 0) {
10660 if (first.twoMachinesColor[0] == 'w') {
10661 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10662 gameInfo.white, gameInfo.black,
10663 first.matchWins, second.matchWins,
10664 matchGame - 1 - (first.matchWins + second.matchWins));
10666 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10667 gameInfo.white, gameInfo.black,
10668 second.matchWins, first.matchWins,
10669 matchGame - 1 - (first.matchWins + second.matchWins));
10672 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10678 TwoMachinesEvent P((void))
10682 ChessProgramState *onmove;
10683 char *bookHit = NULL;
10685 if (appData.noChessProgram) return;
10687 switch (gameMode) {
10688 case TwoMachinesPlay:
10690 case MachinePlaysWhite:
10691 case MachinePlaysBlack:
10692 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10693 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10697 case BeginningOfGame:
10698 case PlayFromGameFile:
10701 if (gameMode != EditGame) return;
10704 EditPositionDone();
10715 forwardMostMove = currentMove;
10716 ResurrectChessProgram(); /* in case first program isn't running */
10718 if (second.pr == NULL) {
10719 StartChessProgram(&second);
10720 if (second.protocolVersion == 1) {
10721 TwoMachinesEventIfReady();
10723 /* kludge: allow timeout for initial "feature" command */
10725 DisplayMessage("", _("Starting second chess program"));
10726 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10730 DisplayMessage("", "");
10731 InitChessProgram(&second, FALSE);
10732 SendToProgram("force\n", &second);
10733 if (startedFromSetupPosition) {
10734 SendBoard(&second, backwardMostMove);
10735 if (appData.debugMode) {
10736 fprintf(debugFP, "Two Machines\n");
10739 for (i = backwardMostMove; i < forwardMostMove; i++) {
10740 SendMoveToProgram(i, &second);
10743 gameMode = TwoMachinesPlay;
10747 DisplayTwoMachinesTitle();
10749 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10755 SendToProgram(first.computerString, &first);
10756 if (first.sendName) {
10757 sprintf(buf, "name %s\n", second.tidy);
10758 SendToProgram(buf, &first);
10760 SendToProgram(second.computerString, &second);
10761 if (second.sendName) {
10762 sprintf(buf, "name %s\n", first.tidy);
10763 SendToProgram(buf, &second);
10767 if (!first.sendTime || !second.sendTime) {
10768 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10769 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10771 if (onmove->sendTime) {
10772 if (onmove->useColors) {
10773 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10775 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10777 if (onmove->useColors) {
10778 SendToProgram(onmove->twoMachinesColor, onmove);
10780 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10781 // SendToProgram("go\n", onmove);
10782 onmove->maybeThinking = TRUE;
10783 SetMachineThinkingEnables();
10787 if(bookHit) { // [HGM] book: simulate book reply
10788 static char bookMove[MSG_SIZ]; // a bit generous?
10790 programStats.nodes = programStats.depth = programStats.time =
10791 programStats.score = programStats.got_only_move = 0;
10792 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10794 strcpy(bookMove, "move ");
10795 strcat(bookMove, bookHit);
10796 HandleMachineMove(bookMove, &first);
10803 if (gameMode == Training) {
10804 SetTrainingModeOff();
10805 gameMode = PlayFromGameFile;
10806 DisplayMessage("", _("Training mode off"));
10808 gameMode = Training;
10809 animateTraining = appData.animate;
10811 /* make sure we are not already at the end of the game */
10812 if (currentMove < forwardMostMove) {
10813 SetTrainingModeOn();
10814 DisplayMessage("", _("Training mode on"));
10816 gameMode = PlayFromGameFile;
10817 DisplayError(_("Already at end of game"), 0);
10826 if (!appData.icsActive) return;
10827 switch (gameMode) {
10828 case IcsPlayingWhite:
10829 case IcsPlayingBlack:
10832 case BeginningOfGame:
10840 EditPositionDone();
10853 gameMode = IcsIdle;
10864 switch (gameMode) {
10866 SetTrainingModeOff();
10868 case MachinePlaysWhite:
10869 case MachinePlaysBlack:
10870 case BeginningOfGame:
10871 SendToProgram("force\n", &first);
10872 SetUserThinkingEnables();
10874 case PlayFromGameFile:
10875 (void) StopLoadGameTimer();
10876 if (gameFileFP != NULL) {
10881 EditPositionDone();
10886 SendToProgram("force\n", &first);
10888 case TwoMachinesPlay:
10889 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10890 ResurrectChessProgram();
10891 SetUserThinkingEnables();
10894 ResurrectChessProgram();
10896 case IcsPlayingBlack:
10897 case IcsPlayingWhite:
10898 DisplayError(_("Warning: You are still playing a game"), 0);
10901 DisplayError(_("Warning: You are still observing a game"), 0);
10904 DisplayError(_("Warning: You are still examining a game"), 0);
10915 first.offeredDraw = second.offeredDraw = 0;
10917 if (gameMode == PlayFromGameFile) {
10918 whiteTimeRemaining = timeRemaining[0][currentMove];
10919 blackTimeRemaining = timeRemaining[1][currentMove];
10923 if (gameMode == MachinePlaysWhite ||
10924 gameMode == MachinePlaysBlack ||
10925 gameMode == TwoMachinesPlay ||
10926 gameMode == EndOfGame) {
10927 i = forwardMostMove;
10928 while (i > currentMove) {
10929 SendToProgram("undo\n", &first);
10932 whiteTimeRemaining = timeRemaining[0][currentMove];
10933 blackTimeRemaining = timeRemaining[1][currentMove];
10934 DisplayBothClocks();
10935 if (whiteFlag || blackFlag) {
10936 whiteFlag = blackFlag = 0;
10941 gameMode = EditGame;
10948 EditPositionEvent()
10950 if (gameMode == EditPosition) {
10956 if (gameMode != EditGame) return;
10958 gameMode = EditPosition;
10961 if (currentMove > 0)
10962 CopyBoard(boards[0], boards[currentMove]);
10964 blackPlaysFirst = !WhiteOnMove(currentMove);
10966 currentMove = forwardMostMove = backwardMostMove = 0;
10967 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10974 /* [DM] icsEngineAnalyze - possible call from other functions */
10975 if (appData.icsEngineAnalyze) {
10976 appData.icsEngineAnalyze = FALSE;
10978 DisplayMessage("",_("Close ICS engine analyze..."));
10980 if (first.analysisSupport && first.analyzing) {
10981 SendToProgram("exit\n", &first);
10982 first.analyzing = FALSE;
10985 thinkOutput[0] = NULLCHAR;
10991 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10993 startedFromSetupPosition = TRUE;
10994 InitChessProgram(&first, FALSE);
10995 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10996 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10997 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10998 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10999 } else castlingRights[0][2] = -1;
11000 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11001 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11002 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11003 } else castlingRights[0][5] = -1;
11004 SendToProgram("force\n", &first);
11005 if (blackPlaysFirst) {
11006 strcpy(moveList[0], "");
11007 strcpy(parseList[0], "");
11008 currentMove = forwardMostMove = backwardMostMove = 1;
11009 CopyBoard(boards[1], boards[0]);
11010 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11012 epStatus[1] = epStatus[0];
11013 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11016 currentMove = forwardMostMove = backwardMostMove = 0;
11018 SendBoard(&first, forwardMostMove);
11019 if (appData.debugMode) {
11020 fprintf(debugFP, "EditPosDone\n");
11023 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11024 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11025 gameMode = EditGame;
11027 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11028 ClearHighlights(); /* [AS] */
11031 /* Pause for `ms' milliseconds */
11032 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11042 } while (SubtractTimeMarks(&m2, &m1) < ms);
11045 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11047 SendMultiLineToICS(buf)
11050 char temp[MSG_SIZ+1], *p;
11057 strncpy(temp, buf, len);
11062 if (*p == '\n' || *p == '\r')
11067 strcat(temp, "\n");
11069 SendToPlayer(temp, strlen(temp));
11073 SetWhiteToPlayEvent()
11075 if (gameMode == EditPosition) {
11076 blackPlaysFirst = FALSE;
11077 DisplayBothClocks(); /* works because currentMove is 0 */
11078 } else if (gameMode == IcsExamining) {
11079 SendToICS(ics_prefix);
11080 SendToICS("tomove white\n");
11085 SetBlackToPlayEvent()
11087 if (gameMode == EditPosition) {
11088 blackPlaysFirst = TRUE;
11089 currentMove = 1; /* kludge */
11090 DisplayBothClocks();
11092 } else if (gameMode == IcsExamining) {
11093 SendToICS(ics_prefix);
11094 SendToICS("tomove black\n");
11099 EditPositionMenuEvent(selection, x, y)
11100 ChessSquare selection;
11104 ChessSquare piece = boards[0][y][x];
11106 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11108 switch (selection) {
11110 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11111 SendToICS(ics_prefix);
11112 SendToICS("bsetup clear\n");
11113 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11114 SendToICS(ics_prefix);
11115 SendToICS("clearboard\n");
11117 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11118 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11119 for (y = 0; y < BOARD_HEIGHT; y++) {
11120 if (gameMode == IcsExamining) {
11121 if (boards[currentMove][y][x] != EmptySquare) {
11122 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11127 boards[0][y][x] = p;
11132 if (gameMode == EditPosition) {
11133 DrawPosition(FALSE, boards[0]);
11138 SetWhiteToPlayEvent();
11142 SetBlackToPlayEvent();
11146 if (gameMode == IcsExamining) {
11147 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11150 boards[0][y][x] = EmptySquare;
11151 DrawPosition(FALSE, boards[0]);
11156 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11157 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11158 selection = (ChessSquare) (PROMOTED piece);
11159 } else if(piece == EmptySquare) selection = WhiteSilver;
11160 else selection = (ChessSquare)((int)piece - 1);
11164 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11165 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11166 selection = (ChessSquare) (DEMOTED piece);
11167 } else if(piece == EmptySquare) selection = BlackSilver;
11168 else selection = (ChessSquare)((int)piece + 1);
11173 if(gameInfo.variant == VariantShatranj ||
11174 gameInfo.variant == VariantXiangqi ||
11175 gameInfo.variant == VariantCourier )
11176 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11181 if(gameInfo.variant == VariantXiangqi)
11182 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11183 if(gameInfo.variant == VariantKnightmate)
11184 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11187 if (gameMode == IcsExamining) {
11188 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11189 PieceToChar(selection), AAA + x, ONE + y);
11192 boards[0][y][x] = selection;
11193 DrawPosition(FALSE, boards[0]);
11201 DropMenuEvent(selection, x, y)
11202 ChessSquare selection;
11205 ChessMove moveType;
11207 switch (gameMode) {
11208 case IcsPlayingWhite:
11209 case MachinePlaysBlack:
11210 if (!WhiteOnMove(currentMove)) {
11211 DisplayMoveError(_("It is Black's turn"));
11214 moveType = WhiteDrop;
11216 case IcsPlayingBlack:
11217 case MachinePlaysWhite:
11218 if (WhiteOnMove(currentMove)) {
11219 DisplayMoveError(_("It is White's turn"));
11222 moveType = BlackDrop;
11225 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11231 if (moveType == BlackDrop && selection < BlackPawn) {
11232 selection = (ChessSquare) ((int) selection
11233 + (int) BlackPawn - (int) WhitePawn);
11235 if (boards[currentMove][y][x] != EmptySquare) {
11236 DisplayMoveError(_("That square is occupied"));
11240 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11246 /* Accept a pending offer of any kind from opponent */
11248 if (appData.icsActive) {
11249 SendToICS(ics_prefix);
11250 SendToICS("accept\n");
11251 } else if (cmailMsgLoaded) {
11252 if (currentMove == cmailOldMove &&
11253 commentList[cmailOldMove] != NULL &&
11254 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11255 "Black offers a draw" : "White offers a draw")) {
11257 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11258 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11260 DisplayError(_("There is no pending offer on this move"), 0);
11261 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11264 /* Not used for offers from chess program */
11271 /* Decline a pending offer of any kind from opponent */
11273 if (appData.icsActive) {
11274 SendToICS(ics_prefix);
11275 SendToICS("decline\n");
11276 } else if (cmailMsgLoaded) {
11277 if (currentMove == cmailOldMove &&
11278 commentList[cmailOldMove] != NULL &&
11279 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11280 "Black offers a draw" : "White offers a draw")) {
11282 AppendComment(cmailOldMove, "Draw declined");
11283 DisplayComment(cmailOldMove - 1, "Draw declined");
11286 DisplayError(_("There is no pending offer on this move"), 0);
11289 /* Not used for offers from chess program */
11296 /* Issue ICS rematch command */
11297 if (appData.icsActive) {
11298 SendToICS(ics_prefix);
11299 SendToICS("rematch\n");
11306 /* Call your opponent's flag (claim a win on time) */
11307 if (appData.icsActive) {
11308 SendToICS(ics_prefix);
11309 SendToICS("flag\n");
11311 switch (gameMode) {
11314 case MachinePlaysWhite:
11317 GameEnds(GameIsDrawn, "Both players ran out of time",
11320 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11322 DisplayError(_("Your opponent is not out of time"), 0);
11325 case MachinePlaysBlack:
11328 GameEnds(GameIsDrawn, "Both players ran out of time",
11331 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11333 DisplayError(_("Your opponent is not out of time"), 0);
11343 /* Offer draw or accept pending draw offer from opponent */
11345 if (appData.icsActive) {
11346 /* Note: tournament rules require draw offers to be
11347 made after you make your move but before you punch
11348 your clock. Currently ICS doesn't let you do that;
11349 instead, you immediately punch your clock after making
11350 a move, but you can offer a draw at any time. */
11352 SendToICS(ics_prefix);
11353 SendToICS("draw\n");
11354 } else if (cmailMsgLoaded) {
11355 if (currentMove == cmailOldMove &&
11356 commentList[cmailOldMove] != NULL &&
11357 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11358 "Black offers a draw" : "White offers a draw")) {
11359 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11360 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11361 } else if (currentMove == cmailOldMove + 1) {
11362 char *offer = WhiteOnMove(cmailOldMove) ?
11363 "White offers a draw" : "Black offers a draw";
11364 AppendComment(currentMove, offer);
11365 DisplayComment(currentMove - 1, offer);
11366 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11368 DisplayError(_("You must make your move before offering a draw"), 0);
11369 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11371 } else if (first.offeredDraw) {
11372 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11374 if (first.sendDrawOffers) {
11375 SendToProgram("draw\n", &first);
11376 userOfferedDraw = TRUE;
11384 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11386 if (appData.icsActive) {
11387 SendToICS(ics_prefix);
11388 SendToICS("adjourn\n");
11390 /* Currently GNU Chess doesn't offer or accept Adjourns */
11398 /* Offer Abort or accept pending Abort offer from opponent */
11400 if (appData.icsActive) {
11401 SendToICS(ics_prefix);
11402 SendToICS("abort\n");
11404 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11411 /* Resign. You can do this even if it's not your turn. */
11413 if (appData.icsActive) {
11414 SendToICS(ics_prefix);
11415 SendToICS("resign\n");
11417 switch (gameMode) {
11418 case MachinePlaysWhite:
11419 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11421 case MachinePlaysBlack:
11422 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11425 if (cmailMsgLoaded) {
11427 if (WhiteOnMove(cmailOldMove)) {
11428 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11430 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11432 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11443 StopObservingEvent()
11445 /* Stop observing current games */
11446 SendToICS(ics_prefix);
11447 SendToICS("unobserve\n");
11451 StopExaminingEvent()
11453 /* Stop observing current game */
11454 SendToICS(ics_prefix);
11455 SendToICS("unexamine\n");
11459 ForwardInner(target)
11464 if (appData.debugMode)
11465 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11466 target, currentMove, forwardMostMove);
11468 if (gameMode == EditPosition)
11471 if (gameMode == PlayFromGameFile && !pausing)
11474 if (gameMode == IcsExamining && pausing)
11475 limit = pauseExamForwardMostMove;
11477 limit = forwardMostMove;
11479 if (target > limit) target = limit;
11481 if (target > 0 && moveList[target - 1][0]) {
11482 int fromX, fromY, toX, toY;
11483 toX = moveList[target - 1][2] - AAA;
11484 toY = moveList[target - 1][3] - ONE;
11485 if (moveList[target - 1][1] == '@') {
11486 if (appData.highlightLastMove) {
11487 SetHighlights(-1, -1, toX, toY);
11490 fromX = moveList[target - 1][0] - AAA;
11491 fromY = moveList[target - 1][1] - ONE;
11492 if (target == currentMove + 1) {
11493 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11495 if (appData.highlightLastMove) {
11496 SetHighlights(fromX, fromY, toX, toY);
11500 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11501 gameMode == Training || gameMode == PlayFromGameFile ||
11502 gameMode == AnalyzeFile) {
11503 while (currentMove < target) {
11504 SendMoveToProgram(currentMove++, &first);
11507 currentMove = target;
11510 if (gameMode == EditGame || gameMode == EndOfGame) {
11511 whiteTimeRemaining = timeRemaining[0][currentMove];
11512 blackTimeRemaining = timeRemaining[1][currentMove];
11514 DisplayBothClocks();
11515 DisplayMove(currentMove - 1);
11516 DrawPosition(FALSE, boards[currentMove]);
11517 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11518 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11519 DisplayComment(currentMove - 1, commentList[currentMove]);
11527 if (gameMode == IcsExamining && !pausing) {
11528 SendToICS(ics_prefix);
11529 SendToICS("forward\n");
11531 ForwardInner(currentMove + 1);
11538 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11539 /* to optimze, we temporarily turn off analysis mode while we feed
11540 * the remaining moves to the engine. Otherwise we get analysis output
11543 if (first.analysisSupport) {
11544 SendToProgram("exit\nforce\n", &first);
11545 first.analyzing = FALSE;
11549 if (gameMode == IcsExamining && !pausing) {
11550 SendToICS(ics_prefix);
11551 SendToICS("forward 999999\n");
11553 ForwardInner(forwardMostMove);
11556 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11557 /* we have fed all the moves, so reactivate analysis mode */
11558 SendToProgram("analyze\n", &first);
11559 first.analyzing = TRUE;
11560 /*first.maybeThinking = TRUE;*/
11561 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11566 BackwardInner(target)
11569 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11571 if (appData.debugMode)
11572 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11573 target, currentMove, forwardMostMove);
11575 if (gameMode == EditPosition) return;
11576 if (currentMove <= backwardMostMove) {
11578 DrawPosition(full_redraw, boards[currentMove]);
11581 if (gameMode == PlayFromGameFile && !pausing)
11584 if (moveList[target][0]) {
11585 int fromX, fromY, toX, toY;
11586 toX = moveList[target][2] - AAA;
11587 toY = moveList[target][3] - ONE;
11588 if (moveList[target][1] == '@') {
11589 if (appData.highlightLastMove) {
11590 SetHighlights(-1, -1, toX, toY);
11593 fromX = moveList[target][0] - AAA;
11594 fromY = moveList[target][1] - ONE;
11595 if (target == currentMove - 1) {
11596 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11598 if (appData.highlightLastMove) {
11599 SetHighlights(fromX, fromY, toX, toY);
11603 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11604 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11605 while (currentMove > target) {
11606 SendToProgram("undo\n", &first);
11610 currentMove = target;
11613 if (gameMode == EditGame || gameMode == EndOfGame) {
11614 whiteTimeRemaining = timeRemaining[0][currentMove];
11615 blackTimeRemaining = timeRemaining[1][currentMove];
11617 DisplayBothClocks();
11618 DisplayMove(currentMove - 1);
11619 DrawPosition(full_redraw, boards[currentMove]);
11620 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11621 // [HGM] PV info: routine tests if comment empty
11622 DisplayComment(currentMove - 1, commentList[currentMove]);
11628 if (gameMode == IcsExamining && !pausing) {
11629 SendToICS(ics_prefix);
11630 SendToICS("backward\n");
11632 BackwardInner(currentMove - 1);
11639 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11640 /* to optimze, we temporarily turn off analysis mode while we undo
11641 * all the moves. Otherwise we get analysis output after each undo.
11643 if (first.analysisSupport) {
11644 SendToProgram("exit\nforce\n", &first);
11645 first.analyzing = FALSE;
11649 if (gameMode == IcsExamining && !pausing) {
11650 SendToICS(ics_prefix);
11651 SendToICS("backward 999999\n");
11653 BackwardInner(backwardMostMove);
11656 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11657 /* we have fed all the moves, so reactivate analysis mode */
11658 SendToProgram("analyze\n", &first);
11659 first.analyzing = TRUE;
11660 /*first.maybeThinking = TRUE;*/
11661 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11668 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11669 if (to >= forwardMostMove) to = forwardMostMove;
11670 if (to <= backwardMostMove) to = backwardMostMove;
11671 if (to < currentMove) {
11681 if (gameMode != IcsExamining) {
11682 DisplayError(_("You are not examining a game"), 0);
11686 DisplayError(_("You can't revert while pausing"), 0);
11689 SendToICS(ics_prefix);
11690 SendToICS("revert\n");
11696 switch (gameMode) {
11697 case MachinePlaysWhite:
11698 case MachinePlaysBlack:
11699 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11700 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11703 if (forwardMostMove < 2) return;
11704 currentMove = forwardMostMove = forwardMostMove - 2;
11705 whiteTimeRemaining = timeRemaining[0][currentMove];
11706 blackTimeRemaining = timeRemaining[1][currentMove];
11707 DisplayBothClocks();
11708 DisplayMove(currentMove - 1);
11709 ClearHighlights();/*!! could figure this out*/
11710 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11711 SendToProgram("remove\n", &first);
11712 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11715 case BeginningOfGame:
11719 case IcsPlayingWhite:
11720 case IcsPlayingBlack:
11721 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11722 SendToICS(ics_prefix);
11723 SendToICS("takeback 2\n");
11725 SendToICS(ics_prefix);
11726 SendToICS("takeback 1\n");
11735 ChessProgramState *cps;
11737 switch (gameMode) {
11738 case MachinePlaysWhite:
11739 if (!WhiteOnMove(forwardMostMove)) {
11740 DisplayError(_("It is your turn"), 0);
11745 case MachinePlaysBlack:
11746 if (WhiteOnMove(forwardMostMove)) {
11747 DisplayError(_("It is your turn"), 0);
11752 case TwoMachinesPlay:
11753 if (WhiteOnMove(forwardMostMove) ==
11754 (first.twoMachinesColor[0] == 'w')) {
11760 case BeginningOfGame:
11764 SendToProgram("?\n", cps);
11768 TruncateGameEvent()
11771 if (gameMode != EditGame) return;
11778 if (forwardMostMove > currentMove) {
11779 if (gameInfo.resultDetails != NULL) {
11780 free(gameInfo.resultDetails);
11781 gameInfo.resultDetails = NULL;
11782 gameInfo.result = GameUnfinished;
11784 forwardMostMove = currentMove;
11785 HistorySet(parseList, backwardMostMove, forwardMostMove,
11793 if (appData.noChessProgram) return;
11794 switch (gameMode) {
11795 case MachinePlaysWhite:
11796 if (WhiteOnMove(forwardMostMove)) {
11797 DisplayError(_("Wait until your turn"), 0);
11801 case BeginningOfGame:
11802 case MachinePlaysBlack:
11803 if (!WhiteOnMove(forwardMostMove)) {
11804 DisplayError(_("Wait until your turn"), 0);
11809 DisplayError(_("No hint available"), 0);
11812 SendToProgram("hint\n", &first);
11813 hintRequested = TRUE;
11819 if (appData.noChessProgram) return;
11820 switch (gameMode) {
11821 case MachinePlaysWhite:
11822 if (WhiteOnMove(forwardMostMove)) {
11823 DisplayError(_("Wait until your turn"), 0);
11827 case BeginningOfGame:
11828 case MachinePlaysBlack:
11829 if (!WhiteOnMove(forwardMostMove)) {
11830 DisplayError(_("Wait until your turn"), 0);
11835 EditPositionDone();
11837 case TwoMachinesPlay:
11842 SendToProgram("bk\n", &first);
11843 bookOutput[0] = NULLCHAR;
11844 bookRequested = TRUE;
11850 char *tags = PGNTags(&gameInfo);
11851 TagsPopUp(tags, CmailMsg());
11855 /* end button procedures */
11858 PrintPosition(fp, move)
11864 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11865 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11866 char c = PieceToChar(boards[move][i][j]);
11867 fputc(c == 'x' ? '.' : c, fp);
11868 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11871 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11872 fprintf(fp, "white to play\n");
11874 fprintf(fp, "black to play\n");
11881 if (gameInfo.white != NULL) {
11882 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11888 /* Find last component of program's own name, using some heuristics */
11890 TidyProgramName(prog, host, buf)
11891 char *prog, *host, buf[MSG_SIZ];
11894 int local = (strcmp(host, "localhost") == 0);
11895 while (!local && (p = strchr(prog, ';')) != NULL) {
11897 while (*p == ' ') p++;
11900 if (*prog == '"' || *prog == '\'') {
11901 q = strchr(prog + 1, *prog);
11903 q = strchr(prog, ' ');
11905 if (q == NULL) q = prog + strlen(prog);
11907 while (p >= prog && *p != '/' && *p != '\\') p--;
11909 if(p == prog && *p == '"') p++;
11910 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11911 memcpy(buf, p, q - p);
11912 buf[q - p] = NULLCHAR;
11920 TimeControlTagValue()
11923 if (!appData.clockMode) {
11925 } else if (movesPerSession > 0) {
11926 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11927 } else if (timeIncrement == 0) {
11928 sprintf(buf, "%ld", timeControl/1000);
11930 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11932 return StrSave(buf);
11938 /* This routine is used only for certain modes */
11939 VariantClass v = gameInfo.variant;
11940 ClearGameInfo(&gameInfo);
11941 gameInfo.variant = v;
11943 switch (gameMode) {
11944 case MachinePlaysWhite:
11945 gameInfo.event = StrSave( appData.pgnEventHeader );
11946 gameInfo.site = StrSave(HostName());
11947 gameInfo.date = PGNDate();
11948 gameInfo.round = StrSave("-");
11949 gameInfo.white = StrSave(first.tidy);
11950 gameInfo.black = StrSave(UserName());
11951 gameInfo.timeControl = TimeControlTagValue();
11954 case MachinePlaysBlack:
11955 gameInfo.event = StrSave( appData.pgnEventHeader );
11956 gameInfo.site = StrSave(HostName());
11957 gameInfo.date = PGNDate();
11958 gameInfo.round = StrSave("-");
11959 gameInfo.white = StrSave(UserName());
11960 gameInfo.black = StrSave(first.tidy);
11961 gameInfo.timeControl = TimeControlTagValue();
11964 case TwoMachinesPlay:
11965 gameInfo.event = StrSave( appData.pgnEventHeader );
11966 gameInfo.site = StrSave(HostName());
11967 gameInfo.date = PGNDate();
11968 if (matchGame > 0) {
11970 sprintf(buf, "%d", matchGame);
11971 gameInfo.round = StrSave(buf);
11973 gameInfo.round = StrSave("-");
11975 if (first.twoMachinesColor[0] == 'w') {
11976 gameInfo.white = StrSave(first.tidy);
11977 gameInfo.black = StrSave(second.tidy);
11979 gameInfo.white = StrSave(second.tidy);
11980 gameInfo.black = StrSave(first.tidy);
11982 gameInfo.timeControl = TimeControlTagValue();
11986 gameInfo.event = StrSave("Edited game");
11987 gameInfo.site = StrSave(HostName());
11988 gameInfo.date = PGNDate();
11989 gameInfo.round = StrSave("-");
11990 gameInfo.white = StrSave("-");
11991 gameInfo.black = StrSave("-");
11995 gameInfo.event = StrSave("Edited position");
11996 gameInfo.site = StrSave(HostName());
11997 gameInfo.date = PGNDate();
11998 gameInfo.round = StrSave("-");
11999 gameInfo.white = StrSave("-");
12000 gameInfo.black = StrSave("-");
12003 case IcsPlayingWhite:
12004 case IcsPlayingBlack:
12009 case PlayFromGameFile:
12010 gameInfo.event = StrSave("Game from non-PGN file");
12011 gameInfo.site = StrSave(HostName());
12012 gameInfo.date = PGNDate();
12013 gameInfo.round = StrSave("-");
12014 gameInfo.white = StrSave("?");
12015 gameInfo.black = StrSave("?");
12024 ReplaceComment(index, text)
12030 while (*text == '\n') text++;
12031 len = strlen(text);
12032 while (len > 0 && text[len - 1] == '\n') len--;
12034 if (commentList[index] != NULL)
12035 free(commentList[index]);
12038 commentList[index] = NULL;
12041 commentList[index] = (char *) malloc(len + 2);
12042 strncpy(commentList[index], text, len);
12043 commentList[index][len] = '\n';
12044 commentList[index][len + 1] = NULLCHAR;
12057 if (ch == '\r') continue;
12059 } while (ch != '\0');
12063 AppendComment(index, text)
12070 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12073 while (*text == '\n') text++;
12074 len = strlen(text);
12075 while (len > 0 && text[len - 1] == '\n') len--;
12077 if (len == 0) return;
12079 if (commentList[index] != NULL) {
12080 old = commentList[index];
12081 oldlen = strlen(old);
12082 commentList[index] = (char *) malloc(oldlen + len + 2);
12083 strcpy(commentList[index], old);
12085 strncpy(&commentList[index][oldlen], text, len);
12086 commentList[index][oldlen + len] = '\n';
12087 commentList[index][oldlen + len + 1] = NULLCHAR;
12089 commentList[index] = (char *) malloc(len + 2);
12090 strncpy(commentList[index], text, len);
12091 commentList[index][len] = '\n';
12092 commentList[index][len + 1] = NULLCHAR;
12096 static char * FindStr( char * text, char * sub_text )
12098 char * result = strstr( text, sub_text );
12100 if( result != NULL ) {
12101 result += strlen( sub_text );
12107 /* [AS] Try to extract PV info from PGN comment */
12108 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12109 char *GetInfoFromComment( int index, char * text )
12113 if( text != NULL && index > 0 ) {
12116 int time = -1, sec = 0, deci;
12117 char * s_eval = FindStr( text, "[%eval " );
12118 char * s_emt = FindStr( text, "[%emt " );
12120 if( s_eval != NULL || s_emt != NULL ) {
12124 if( s_eval != NULL ) {
12125 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12129 if( delim != ']' ) {
12134 if( s_emt != NULL ) {
12138 /* We expect something like: [+|-]nnn.nn/dd */
12141 sep = strchr( text, '/' );
12142 if( sep == NULL || sep < (text+4) ) {
12146 time = -1; sec = -1; deci = -1;
12147 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12148 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12149 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12150 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12154 if( score_lo < 0 || score_lo >= 100 ) {
12158 if(sec >= 0) time = 600*time + 10*sec; else
12159 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12161 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12163 /* [HGM] PV time: now locate end of PV info */
12164 while( *++sep >= '0' && *sep <= '9'); // strip depth
12166 while( *++sep >= '0' && *sep <= '9'); // strip time
12168 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12170 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12171 while(*sep == ' ') sep++;
12182 pvInfoList[index-1].depth = depth;
12183 pvInfoList[index-1].score = score;
12184 pvInfoList[index-1].time = 10*time; // centi-sec
12190 SendToProgram(message, cps)
12192 ChessProgramState *cps;
12194 int count, outCount, error;
12197 if (cps->pr == NULL) return;
12200 if (appData.debugMode) {
12203 fprintf(debugFP, "%ld >%-6s: %s",
12204 SubtractTimeMarks(&now, &programStartTime),
12205 cps->which, message);
12208 count = strlen(message);
12209 outCount = OutputToProcess(cps->pr, message, count, &error);
12210 if (outCount < count && !exiting
12211 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12212 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12213 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12214 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12215 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12216 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12218 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12220 gameInfo.resultDetails = buf;
12222 DisplayFatalError(buf, error, 1);
12227 ReceiveFromProgram(isr, closure, message, count, error)
12228 InputSourceRef isr;
12236 ChessProgramState *cps = (ChessProgramState *)closure;
12238 if (isr != cps->isr) return; /* Killed intentionally */
12242 _("Error: %s chess program (%s) exited unexpectedly"),
12243 cps->which, cps->program);
12244 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12245 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12246 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12247 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12249 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12251 gameInfo.resultDetails = buf;
12253 RemoveInputSource(cps->isr);
12254 DisplayFatalError(buf, 0, 1);
12257 _("Error reading from %s chess program (%s)"),
12258 cps->which, cps->program);
12259 RemoveInputSource(cps->isr);
12261 /* [AS] Program is misbehaving badly... kill it */
12262 if( count == -2 ) {
12263 DestroyChildProcess( cps->pr, 9 );
12267 DisplayFatalError(buf, error, 1);
12272 if ((end_str = strchr(message, '\r')) != NULL)
12273 *end_str = NULLCHAR;
12274 if ((end_str = strchr(message, '\n')) != NULL)
12275 *end_str = NULLCHAR;
12277 if (appData.debugMode) {
12278 TimeMark now; int print = 1;
12279 char *quote = ""; char c; int i;
12281 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12282 char start = message[0];
12283 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12284 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12285 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12286 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12287 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12288 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12289 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12290 sscanf(message, "pong %c", &c)!=1 && start != '#')
12291 { quote = "# "; print = (appData.engineComments == 2); }
12292 message[0] = start; // restore original message
12296 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12297 SubtractTimeMarks(&now, &programStartTime), cps->which,
12303 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12304 if (appData.icsEngineAnalyze) {
12305 if (strstr(message, "whisper") != NULL ||
12306 strstr(message, "kibitz") != NULL ||
12307 strstr(message, "tellics") != NULL) return;
12310 HandleMachineMove(message, cps);
12315 SendTimeControl(cps, mps, tc, inc, sd, st)
12316 ChessProgramState *cps;
12317 int mps, inc, sd, st;
12323 if( timeControl_2 > 0 ) {
12324 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12325 tc = timeControl_2;
12328 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12329 inc /= cps->timeOdds;
12330 st /= cps->timeOdds;
12332 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12335 /* Set exact time per move, normally using st command */
12336 if (cps->stKludge) {
12337 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12339 if (seconds == 0) {
12340 sprintf(buf, "level 1 %d\n", st/60);
12342 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12345 sprintf(buf, "st %d\n", st);
12348 /* Set conventional or incremental time control, using level command */
12349 if (seconds == 0) {
12350 /* Note old gnuchess bug -- minutes:seconds used to not work.
12351 Fixed in later versions, but still avoid :seconds
12352 when seconds is 0. */
12353 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12355 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12356 seconds, inc/1000);
12359 SendToProgram(buf, cps);
12361 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12362 /* Orthogonally, limit search to given depth */
12364 if (cps->sdKludge) {
12365 sprintf(buf, "depth\n%d\n", sd);
12367 sprintf(buf, "sd %d\n", sd);
12369 SendToProgram(buf, cps);
12372 if(cps->nps > 0) { /* [HGM] nps */
12373 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12375 sprintf(buf, "nps %d\n", cps->nps);
12376 SendToProgram(buf, cps);
12381 ChessProgramState *WhitePlayer()
12382 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12384 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12385 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12391 SendTimeRemaining(cps, machineWhite)
12392 ChessProgramState *cps;
12393 int /*boolean*/ machineWhite;
12395 char message[MSG_SIZ];
12398 /* Note: this routine must be called when the clocks are stopped
12399 or when they have *just* been set or switched; otherwise
12400 it will be off by the time since the current tick started.
12402 if (machineWhite) {
12403 time = whiteTimeRemaining / 10;
12404 otime = blackTimeRemaining / 10;
12406 time = blackTimeRemaining / 10;
12407 otime = whiteTimeRemaining / 10;
12409 /* [HGM] translate opponent's time by time-odds factor */
12410 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12411 if (appData.debugMode) {
12412 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12415 if (time <= 0) time = 1;
12416 if (otime <= 0) otime = 1;
12418 sprintf(message, "time %ld\n", time);
12419 SendToProgram(message, cps);
12421 sprintf(message, "otim %ld\n", otime);
12422 SendToProgram(message, cps);
12426 BoolFeature(p, name, loc, cps)
12430 ChessProgramState *cps;
12433 int len = strlen(name);
12435 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12437 sscanf(*p, "%d", &val);
12439 while (**p && **p != ' ') (*p)++;
12440 sprintf(buf, "accepted %s\n", name);
12441 SendToProgram(buf, cps);
12448 IntFeature(p, name, loc, cps)
12452 ChessProgramState *cps;
12455 int len = strlen(name);
12456 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12458 sscanf(*p, "%d", loc);
12459 while (**p && **p != ' ') (*p)++;
12460 sprintf(buf, "accepted %s\n", name);
12461 SendToProgram(buf, cps);
12468 StringFeature(p, name, loc, cps)
12472 ChessProgramState *cps;
12475 int len = strlen(name);
12476 if (strncmp((*p), name, len) == 0
12477 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12479 sscanf(*p, "%[^\"]", loc);
12480 while (**p && **p != '\"') (*p)++;
12481 if (**p == '\"') (*p)++;
12482 sprintf(buf, "accepted %s\n", name);
12483 SendToProgram(buf, cps);
12490 ParseOption(Option *opt, ChessProgramState *cps)
12491 // [HGM] options: process the string that defines an engine option, and determine
12492 // name, type, default value, and allowed value range
12494 char *p, *q, buf[MSG_SIZ];
12495 int n, min = (-1)<<31, max = 1<<31, def;
12497 if(p = strstr(opt->name, " -spin ")) {
12498 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12499 if(max < min) max = min; // enforce consistency
12500 if(def < min) def = min;
12501 if(def > max) def = max;
12506 } else if((p = strstr(opt->name, " -slider "))) {
12507 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12508 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12509 if(max < min) max = min; // enforce consistency
12510 if(def < min) def = min;
12511 if(def > max) def = max;
12515 opt->type = Spin; // Slider;
12516 } else if((p = strstr(opt->name, " -string "))) {
12517 opt->textValue = p+9;
12518 opt->type = TextBox;
12519 } else if((p = strstr(opt->name, " -file "))) {
12520 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12521 opt->textValue = p+7;
12522 opt->type = TextBox; // FileName;
12523 } else if((p = strstr(opt->name, " -path "))) {
12524 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12525 opt->textValue = p+7;
12526 opt->type = TextBox; // PathName;
12527 } else if(p = strstr(opt->name, " -check ")) {
12528 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12529 opt->value = (def != 0);
12530 opt->type = CheckBox;
12531 } else if(p = strstr(opt->name, " -combo ")) {
12532 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12533 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12534 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12535 opt->value = n = 0;
12536 while(q = StrStr(q, " /// ")) {
12537 n++; *q = 0; // count choices, and null-terminate each of them
12539 if(*q == '*') { // remember default, which is marked with * prefix
12543 cps->comboList[cps->comboCnt++] = q;
12545 cps->comboList[cps->comboCnt++] = NULL;
12547 opt->type = ComboBox;
12548 } else if(p = strstr(opt->name, " -button")) {
12549 opt->type = Button;
12550 } else if(p = strstr(opt->name, " -save")) {
12551 opt->type = SaveButton;
12552 } else return FALSE;
12553 *p = 0; // terminate option name
12554 // now look if the command-line options define a setting for this engine option.
12555 if(cps->optionSettings && cps->optionSettings[0])
12556 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12557 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12558 sprintf(buf, "option %s", p);
12559 if(p = strstr(buf, ",")) *p = 0;
12561 SendToProgram(buf, cps);
12567 FeatureDone(cps, val)
12568 ChessProgramState* cps;
12571 DelayedEventCallback cb = GetDelayedEvent();
12572 if ((cb == InitBackEnd3 && cps == &first) ||
12573 (cb == TwoMachinesEventIfReady && cps == &second)) {
12574 CancelDelayedEvent();
12575 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12577 cps->initDone = val;
12580 /* Parse feature command from engine */
12582 ParseFeatures(args, cps)
12584 ChessProgramState *cps;
12592 while (*p == ' ') p++;
12593 if (*p == NULLCHAR) return;
12595 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12596 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12597 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12598 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12599 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12600 if (BoolFeature(&p, "reuse", &val, cps)) {
12601 /* Engine can disable reuse, but can't enable it if user said no */
12602 if (!val) cps->reuse = FALSE;
12605 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12606 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12607 if (gameMode == TwoMachinesPlay) {
12608 DisplayTwoMachinesTitle();
12614 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12615 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12616 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12617 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12618 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12619 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12620 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12621 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12622 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12623 if (IntFeature(&p, "done", &val, cps)) {
12624 FeatureDone(cps, val);
12627 /* Added by Tord: */
12628 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12629 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12630 /* End of additions by Tord */
12632 /* [HGM] added features: */
12633 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12634 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12635 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12636 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12637 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12638 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12639 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12640 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12641 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12642 SendToProgram(buf, cps);
12645 if(cps->nrOptions >= MAX_OPTIONS) {
12647 sprintf(buf, "%s engine has too many options\n", cps->which);
12648 DisplayError(buf, 0);
12652 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12653 /* End of additions by HGM */
12655 /* unknown feature: complain and skip */
12657 while (*q && *q != '=') q++;
12658 sprintf(buf, "rejected %.*s\n", q-p, p);
12659 SendToProgram(buf, cps);
12665 while (*p && *p != '\"') p++;
12666 if (*p == '\"') p++;
12668 while (*p && *p != ' ') p++;
12676 PeriodicUpdatesEvent(newState)
12679 if (newState == appData.periodicUpdates)
12682 appData.periodicUpdates=newState;
12684 /* Display type changes, so update it now */
12687 /* Get the ball rolling again... */
12689 AnalysisPeriodicEvent(1);
12690 StartAnalysisClock();
12695 PonderNextMoveEvent(newState)
12698 if (newState == appData.ponderNextMove) return;
12699 if (gameMode == EditPosition) EditPositionDone();
12701 SendToProgram("hard\n", &first);
12702 if (gameMode == TwoMachinesPlay) {
12703 SendToProgram("hard\n", &second);
12706 SendToProgram("easy\n", &first);
12707 thinkOutput[0] = NULLCHAR;
12708 if (gameMode == TwoMachinesPlay) {
12709 SendToProgram("easy\n", &second);
12712 appData.ponderNextMove = newState;
12716 NewSettingEvent(option, command, value)
12722 if (gameMode == EditPosition) EditPositionDone();
12723 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12724 SendToProgram(buf, &first);
12725 if (gameMode == TwoMachinesPlay) {
12726 SendToProgram(buf, &second);
12731 ShowThinkingEvent()
12732 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12734 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12735 int newState = appData.showThinking
12736 // [HGM] thinking: other features now need thinking output as well
12737 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12739 if (oldState == newState) return;
12740 oldState = newState;
12741 if (gameMode == EditPosition) EditPositionDone();
12743 SendToProgram("post\n", &first);
12744 if (gameMode == TwoMachinesPlay) {
12745 SendToProgram("post\n", &second);
12748 SendToProgram("nopost\n", &first);
12749 thinkOutput[0] = NULLCHAR;
12750 if (gameMode == TwoMachinesPlay) {
12751 SendToProgram("nopost\n", &second);
12754 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12758 AskQuestionEvent(title, question, replyPrefix, which)
12759 char *title; char *question; char *replyPrefix; char *which;
12761 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12762 if (pr == NoProc) return;
12763 AskQuestion(title, question, replyPrefix, pr);
12767 DisplayMove(moveNumber)
12770 char message[MSG_SIZ];
12772 char cpThinkOutput[MSG_SIZ];
12774 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12776 if (moveNumber == forwardMostMove - 1 ||
12777 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12779 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12781 if (strchr(cpThinkOutput, '\n')) {
12782 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12785 *cpThinkOutput = NULLCHAR;
12788 /* [AS] Hide thinking from human user */
12789 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12790 *cpThinkOutput = NULLCHAR;
12791 if( thinkOutput[0] != NULLCHAR ) {
12794 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12795 cpThinkOutput[i] = '.';
12797 cpThinkOutput[i] = NULLCHAR;
12798 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12802 if (moveNumber == forwardMostMove - 1 &&
12803 gameInfo.resultDetails != NULL) {
12804 if (gameInfo.resultDetails[0] == NULLCHAR) {
12805 sprintf(res, " %s", PGNResult(gameInfo.result));
12807 sprintf(res, " {%s} %s",
12808 gameInfo.resultDetails, PGNResult(gameInfo.result));
12814 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12815 DisplayMessage(res, cpThinkOutput);
12817 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12818 WhiteOnMove(moveNumber) ? " " : ".. ",
12819 parseList[moveNumber], res);
12820 DisplayMessage(message, cpThinkOutput);
12825 DisplayAnalysisText(text)
12830 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12831 || appData.icsEngineAnalyze) {
12832 sprintf(buf, "Analysis (%s)", first.tidy);
12833 AnalysisPopUp(buf, text);
12841 while (*str && isspace(*str)) ++str;
12842 while (*str && !isspace(*str)) ++str;
12843 if (!*str) return 1;
12844 while (*str && isspace(*str)) ++str;
12845 if (!*str) return 1;
12853 char lst[MSG_SIZ / 2];
12855 static char *xtra[] = { "", " (--)", " (++)" };
12858 if (programStats.time == 0) {
12859 programStats.time = 1;
12862 if (programStats.got_only_move) {
12863 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12865 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12867 nps = (u64ToDouble(programStats.nodes) /
12868 ((double)programStats.time /100.0));
12870 cs = programStats.time % 100;
12871 s = programStats.time / 100;
12877 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12878 if (programStats.move_name[0] != NULLCHAR) {
12879 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12880 programStats.depth,
12881 programStats.nr_moves-programStats.moves_left,
12882 programStats.nr_moves, programStats.move_name,
12883 ((float)programStats.score)/100.0, lst,
12884 only_one_move(lst)?
12885 xtra[programStats.got_fail] : "",
12886 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12888 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12889 programStats.depth,
12890 programStats.nr_moves-programStats.moves_left,
12891 programStats.nr_moves, ((float)programStats.score)/100.0,
12893 only_one_move(lst)?
12894 xtra[programStats.got_fail] : "",
12895 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12898 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12899 programStats.depth,
12900 ((float)programStats.score)/100.0,
12902 only_one_move(lst)?
12903 xtra[programStats.got_fail] : "",
12904 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12907 DisplayAnalysisText(buf);
12911 DisplayComment(moveNumber, text)
12915 char title[MSG_SIZ];
12916 char buf[8000]; // comment can be long!
12919 if( appData.autoDisplayComment ) {
12920 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12921 strcpy(title, "Comment");
12923 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12924 WhiteOnMove(moveNumber) ? " " : ".. ",
12925 parseList[moveNumber]);
12927 // [HGM] PV info: display PV info together with (or as) comment
12928 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12929 if(text == NULL) text = "";
12930 score = pvInfoList[moveNumber].score;
12931 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12932 depth, (pvInfoList[moveNumber].time+50)/100, text);
12935 } else title[0] = 0;
12938 CommentPopUp(title, text);
12941 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12942 * might be busy thinking or pondering. It can be omitted if your
12943 * gnuchess is configured to stop thinking immediately on any user
12944 * input. However, that gnuchess feature depends on the FIONREAD
12945 * ioctl, which does not work properly on some flavors of Unix.
12949 ChessProgramState *cps;
12952 if (!cps->useSigint) return;
12953 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12954 switch (gameMode) {
12955 case MachinePlaysWhite:
12956 case MachinePlaysBlack:
12957 case TwoMachinesPlay:
12958 case IcsPlayingWhite:
12959 case IcsPlayingBlack:
12962 /* Skip if we know it isn't thinking */
12963 if (!cps->maybeThinking) return;
12964 if (appData.debugMode)
12965 fprintf(debugFP, "Interrupting %s\n", cps->which);
12966 InterruptChildProcess(cps->pr);
12967 cps->maybeThinking = FALSE;
12972 #endif /*ATTENTION*/
12978 if (whiteTimeRemaining <= 0) {
12981 if (appData.icsActive) {
12982 if (appData.autoCallFlag &&
12983 gameMode == IcsPlayingBlack && !blackFlag) {
12984 SendToICS(ics_prefix);
12985 SendToICS("flag\n");
12989 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12991 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12992 if (appData.autoCallFlag) {
12993 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13000 if (blackTimeRemaining <= 0) {
13003 if (appData.icsActive) {
13004 if (appData.autoCallFlag &&
13005 gameMode == IcsPlayingWhite && !whiteFlag) {
13006 SendToICS(ics_prefix);
13007 SendToICS("flag\n");
13011 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13013 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13014 if (appData.autoCallFlag) {
13015 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13028 if (!appData.clockMode || appData.icsActive ||
13029 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13032 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13034 if ( !WhiteOnMove(forwardMostMove) )
13035 /* White made time control */
13036 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13037 /* [HGM] time odds: correct new time quota for time odds! */
13038 / WhitePlayer()->timeOdds;
13040 /* Black made time control */
13041 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13042 / WhitePlayer()->other->timeOdds;
13046 DisplayBothClocks()
13048 int wom = gameMode == EditPosition ?
13049 !blackPlaysFirst : WhiteOnMove(currentMove);
13050 DisplayWhiteClock(whiteTimeRemaining, wom);
13051 DisplayBlackClock(blackTimeRemaining, !wom);
13055 /* Timekeeping seems to be a portability nightmare. I think everyone
13056 has ftime(), but I'm really not sure, so I'm including some ifdefs
13057 to use other calls if you don't. Clocks will be less accurate if
13058 you have neither ftime nor gettimeofday.
13061 /* VS 2008 requires the #include outside of the function */
13062 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13063 #include <sys/timeb.h>
13066 /* Get the current time as a TimeMark */
13071 #if HAVE_GETTIMEOFDAY
13073 struct timeval timeVal;
13074 struct timezone timeZone;
13076 gettimeofday(&timeVal, &timeZone);
13077 tm->sec = (long) timeVal.tv_sec;
13078 tm->ms = (int) (timeVal.tv_usec / 1000L);
13080 #else /*!HAVE_GETTIMEOFDAY*/
13083 // include <sys/timeb.h> / moved to just above start of function
13084 struct timeb timeB;
13087 tm->sec = (long) timeB.time;
13088 tm->ms = (int) timeB.millitm;
13090 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13091 tm->sec = (long) time(NULL);
13097 /* Return the difference in milliseconds between two
13098 time marks. We assume the difference will fit in a long!
13101 SubtractTimeMarks(tm2, tm1)
13102 TimeMark *tm2, *tm1;
13104 return 1000L*(tm2->sec - tm1->sec) +
13105 (long) (tm2->ms - tm1->ms);
13110 * Code to manage the game clocks.
13112 * In tournament play, black starts the clock and then white makes a move.
13113 * We give the human user a slight advantage if he is playing white---the
13114 * clocks don't run until he makes his first move, so it takes zero time.
13115 * Also, we don't account for network lag, so we could get out of sync
13116 * with GNU Chess's clock -- but then, referees are always right.
13119 static TimeMark tickStartTM;
13120 static long intendedTickLength;
13123 NextTickLength(timeRemaining)
13124 long timeRemaining;
13126 long nominalTickLength, nextTickLength;
13128 if (timeRemaining > 0L && timeRemaining <= 10000L)
13129 nominalTickLength = 100L;
13131 nominalTickLength = 1000L;
13132 nextTickLength = timeRemaining % nominalTickLength;
13133 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13135 return nextTickLength;
13138 /* Adjust clock one minute up or down */
13140 AdjustClock(Boolean which, int dir)
13142 if(which) blackTimeRemaining += 60000*dir;
13143 else whiteTimeRemaining += 60000*dir;
13144 DisplayBothClocks();
13147 /* Stop clocks and reset to a fresh time control */
13151 (void) StopClockTimer();
13152 if (appData.icsActive) {
13153 whiteTimeRemaining = blackTimeRemaining = 0;
13154 } else { /* [HGM] correct new time quote for time odds */
13155 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13156 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13158 if (whiteFlag || blackFlag) {
13160 whiteFlag = blackFlag = FALSE;
13162 DisplayBothClocks();
13165 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13167 /* Decrement running clock by amount of time that has passed */
13171 long timeRemaining;
13172 long lastTickLength, fudge;
13175 if (!appData.clockMode) return;
13176 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13180 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13182 /* Fudge if we woke up a little too soon */
13183 fudge = intendedTickLength - lastTickLength;
13184 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13186 if (WhiteOnMove(forwardMostMove)) {
13187 if(whiteNPS >= 0) lastTickLength = 0;
13188 timeRemaining = whiteTimeRemaining -= lastTickLength;
13189 DisplayWhiteClock(whiteTimeRemaining - fudge,
13190 WhiteOnMove(currentMove));
13192 if(blackNPS >= 0) lastTickLength = 0;
13193 timeRemaining = blackTimeRemaining -= lastTickLength;
13194 DisplayBlackClock(blackTimeRemaining - fudge,
13195 !WhiteOnMove(currentMove));
13198 if (CheckFlags()) return;
13201 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13202 StartClockTimer(intendedTickLength);
13204 /* if the time remaining has fallen below the alarm threshold, sound the
13205 * alarm. if the alarm has sounded and (due to a takeback or time control
13206 * with increment) the time remaining has increased to a level above the
13207 * threshold, reset the alarm so it can sound again.
13210 if (appData.icsActive && appData.icsAlarm) {
13212 /* make sure we are dealing with the user's clock */
13213 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13214 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13217 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13218 alarmSounded = FALSE;
13219 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13221 alarmSounded = TRUE;
13227 /* A player has just moved, so stop the previously running
13228 clock and (if in clock mode) start the other one.
13229 We redisplay both clocks in case we're in ICS mode, because
13230 ICS gives us an update to both clocks after every move.
13231 Note that this routine is called *after* forwardMostMove
13232 is updated, so the last fractional tick must be subtracted
13233 from the color that is *not* on move now.
13238 long lastTickLength;
13240 int flagged = FALSE;
13244 if (StopClockTimer() && appData.clockMode) {
13245 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13246 if (WhiteOnMove(forwardMostMove)) {
13247 if(blackNPS >= 0) lastTickLength = 0;
13248 blackTimeRemaining -= lastTickLength;
13249 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13250 // if(pvInfoList[forwardMostMove-1].time == -1)
13251 pvInfoList[forwardMostMove-1].time = // use GUI time
13252 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13254 if(whiteNPS >= 0) lastTickLength = 0;
13255 whiteTimeRemaining -= lastTickLength;
13256 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13257 // if(pvInfoList[forwardMostMove-1].time == -1)
13258 pvInfoList[forwardMostMove-1].time =
13259 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13261 flagged = CheckFlags();
13263 CheckTimeControl();
13265 if (flagged || !appData.clockMode) return;
13267 switch (gameMode) {
13268 case MachinePlaysBlack:
13269 case MachinePlaysWhite:
13270 case BeginningOfGame:
13271 if (pausing) return;
13275 case PlayFromGameFile:
13284 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13285 whiteTimeRemaining : blackTimeRemaining);
13286 StartClockTimer(intendedTickLength);
13290 /* Stop both clocks */
13294 long lastTickLength;
13297 if (!StopClockTimer()) return;
13298 if (!appData.clockMode) return;
13302 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13303 if (WhiteOnMove(forwardMostMove)) {
13304 if(whiteNPS >= 0) lastTickLength = 0;
13305 whiteTimeRemaining -= lastTickLength;
13306 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13308 if(blackNPS >= 0) lastTickLength = 0;
13309 blackTimeRemaining -= lastTickLength;
13310 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13315 /* Start clock of player on move. Time may have been reset, so
13316 if clock is already running, stop and restart it. */
13320 (void) StopClockTimer(); /* in case it was running already */
13321 DisplayBothClocks();
13322 if (CheckFlags()) return;
13324 if (!appData.clockMode) return;
13325 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13327 GetTimeMark(&tickStartTM);
13328 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13329 whiteTimeRemaining : blackTimeRemaining);
13331 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13332 whiteNPS = blackNPS = -1;
13333 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13334 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13335 whiteNPS = first.nps;
13336 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13337 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13338 blackNPS = first.nps;
13339 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13340 whiteNPS = second.nps;
13341 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13342 blackNPS = second.nps;
13343 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13345 StartClockTimer(intendedTickLength);
13352 long second, minute, hour, day;
13354 static char buf[32];
13356 if (ms > 0 && ms <= 9900) {
13357 /* convert milliseconds to tenths, rounding up */
13358 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13360 sprintf(buf, " %03.1f ", tenths/10.0);
13364 /* convert milliseconds to seconds, rounding up */
13365 /* use floating point to avoid strangeness of integer division
13366 with negative dividends on many machines */
13367 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13374 day = second / (60 * 60 * 24);
13375 second = second % (60 * 60 * 24);
13376 hour = second / (60 * 60);
13377 second = second % (60 * 60);
13378 minute = second / 60;
13379 second = second % 60;
13382 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13383 sign, day, hour, minute, second);
13385 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13387 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13394 * This is necessary because some C libraries aren't ANSI C compliant yet.
13397 StrStr(string, match)
13398 char *string, *match;
13402 length = strlen(match);
13404 for (i = strlen(string) - length; i >= 0; i--, string++)
13405 if (!strncmp(match, string, length))
13412 StrCaseStr(string, match)
13413 char *string, *match;
13417 length = strlen(match);
13419 for (i = strlen(string) - length; i >= 0; i--, string++) {
13420 for (j = 0; j < length; j++) {
13421 if (ToLower(match[j]) != ToLower(string[j]))
13424 if (j == length) return string;
13438 c1 = ToLower(*s1++);
13439 c2 = ToLower(*s2++);
13440 if (c1 > c2) return 1;
13441 if (c1 < c2) return -1;
13442 if (c1 == NULLCHAR) return 0;
13451 return isupper(c) ? tolower(c) : c;
13459 return islower(c) ? toupper(c) : c;
13461 #endif /* !_amigados */
13469 if ((ret = (char *) malloc(strlen(s) + 1))) {
13476 StrSavePtr(s, savePtr)
13477 char *s, **savePtr;
13482 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13483 strcpy(*savePtr, s);
13495 clock = time((time_t *)NULL);
13496 tm = localtime(&clock);
13497 sprintf(buf, "%04d.%02d.%02d",
13498 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13499 return StrSave(buf);
13504 PositionToFEN(move, overrideCastling)
13506 char *overrideCastling;
13508 int i, j, fromX, fromY, toX, toY;
13515 whiteToPlay = (gameMode == EditPosition) ?
13516 !blackPlaysFirst : (move % 2 == 0);
13519 /* Piece placement data */
13520 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13522 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13523 if (boards[move][i][j] == EmptySquare) {
13525 } else { ChessSquare piece = boards[move][i][j];
13526 if (emptycount > 0) {
13527 if(emptycount<10) /* [HGM] can be >= 10 */
13528 *p++ = '0' + emptycount;
13529 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13532 if(PieceToChar(piece) == '+') {
13533 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13535 piece = (ChessSquare)(DEMOTED piece);
13537 *p++ = PieceToChar(piece);
13539 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13540 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13545 if (emptycount > 0) {
13546 if(emptycount<10) /* [HGM] can be >= 10 */
13547 *p++ = '0' + emptycount;
13548 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13555 /* [HGM] print Crazyhouse or Shogi holdings */
13556 if( gameInfo.holdingsWidth ) {
13557 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13559 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13560 piece = boards[move][i][BOARD_WIDTH-1];
13561 if( piece != EmptySquare )
13562 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13563 *p++ = PieceToChar(piece);
13565 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13566 piece = boards[move][BOARD_HEIGHT-i-1][0];
13567 if( piece != EmptySquare )
13568 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13569 *p++ = PieceToChar(piece);
13572 if( q == p ) *p++ = '-';
13578 *p++ = whiteToPlay ? 'w' : 'b';
13581 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13582 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13584 if(nrCastlingRights) {
13586 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13587 /* [HGM] write directly from rights */
13588 if(castlingRights[move][2] >= 0 &&
13589 castlingRights[move][0] >= 0 )
13590 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13591 if(castlingRights[move][2] >= 0 &&
13592 castlingRights[move][1] >= 0 )
13593 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13594 if(castlingRights[move][5] >= 0 &&
13595 castlingRights[move][3] >= 0 )
13596 *p++ = castlingRights[move][3] + AAA;
13597 if(castlingRights[move][5] >= 0 &&
13598 castlingRights[move][4] >= 0 )
13599 *p++ = castlingRights[move][4] + AAA;
13602 /* [HGM] write true castling rights */
13603 if( nrCastlingRights == 6 ) {
13604 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13605 castlingRights[move][2] >= 0 ) *p++ = 'K';
13606 if(castlingRights[move][1] == BOARD_LEFT &&
13607 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13608 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13609 castlingRights[move][5] >= 0 ) *p++ = 'k';
13610 if(castlingRights[move][4] == BOARD_LEFT &&
13611 castlingRights[move][5] >= 0 ) *p++ = 'q';
13614 if (q == p) *p++ = '-'; /* No castling rights */
13618 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13619 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13620 /* En passant target square */
13621 if (move > backwardMostMove) {
13622 fromX = moveList[move - 1][0] - AAA;
13623 fromY = moveList[move - 1][1] - ONE;
13624 toX = moveList[move - 1][2] - AAA;
13625 toY = moveList[move - 1][3] - ONE;
13626 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13627 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13628 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13630 /* 2-square pawn move just happened */
13632 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13643 /* [HGM] find reversible plies */
13644 { int i = 0, j=move;
13646 if (appData.debugMode) { int k;
13647 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13648 for(k=backwardMostMove; k<=forwardMostMove; k++)
13649 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13653 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13654 if( j == backwardMostMove ) i += initialRulePlies;
13655 sprintf(p, "%d ", i);
13656 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13658 /* Fullmove number */
13659 sprintf(p, "%d", (move / 2) + 1);
13661 return StrSave(buf);
13665 ParseFEN(board, blackPlaysFirst, fen)
13667 int *blackPlaysFirst;
13677 /* [HGM] by default clear Crazyhouse holdings, if present */
13678 if(gameInfo.holdingsWidth) {
13679 for(i=0; i<BOARD_HEIGHT; i++) {
13680 board[i][0] = EmptySquare; /* black holdings */
13681 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13682 board[i][1] = (ChessSquare) 0; /* black counts */
13683 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13687 /* Piece placement data */
13688 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13691 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13692 if (*p == '/') p++;
13693 emptycount = gameInfo.boardWidth - j;
13694 while (emptycount--)
13695 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13697 #if(BOARD_SIZE >= 10)
13698 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13699 p++; emptycount=10;
13700 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13701 while (emptycount--)
13702 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13704 } else if (isdigit(*p)) {
13705 emptycount = *p++ - '0';
13706 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13707 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13708 while (emptycount--)
13709 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13710 } else if (*p == '+' || isalpha(*p)) {
13711 if (j >= gameInfo.boardWidth) return FALSE;
13713 piece = CharToPiece(*++p);
13714 if(piece == EmptySquare) return FALSE; /* unknown piece */
13715 piece = (ChessSquare) (PROMOTED piece ); p++;
13716 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13717 } else piece = CharToPiece(*p++);
13719 if(piece==EmptySquare) return FALSE; /* unknown piece */
13720 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13721 piece = (ChessSquare) (PROMOTED piece);
13722 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13725 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13731 while (*p == '/' || *p == ' ') p++;
13733 /* [HGM] look for Crazyhouse holdings here */
13734 while(*p==' ') p++;
13735 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13737 if(*p == '-' ) *p++; /* empty holdings */ else {
13738 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13739 /* if we would allow FEN reading to set board size, we would */
13740 /* have to add holdings and shift the board read so far here */
13741 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13743 if((int) piece >= (int) BlackPawn ) {
13744 i = (int)piece - (int)BlackPawn;
13745 i = PieceToNumber((ChessSquare)i);
13746 if( i >= gameInfo.holdingsSize ) return FALSE;
13747 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13748 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13750 i = (int)piece - (int)WhitePawn;
13751 i = PieceToNumber((ChessSquare)i);
13752 if( i >= gameInfo.holdingsSize ) return FALSE;
13753 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13754 board[i][BOARD_WIDTH-2]++; /* black holdings */
13758 if(*p == ']') *p++;
13761 while(*p == ' ') p++;
13766 *blackPlaysFirst = FALSE;
13769 *blackPlaysFirst = TRUE;
13775 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13776 /* return the extra info in global variiables */
13778 /* set defaults in case FEN is incomplete */
13779 FENepStatus = EP_UNKNOWN;
13780 for(i=0; i<nrCastlingRights; i++ ) {
13781 FENcastlingRights[i] =
13782 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13783 } /* assume possible unless obviously impossible */
13784 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13785 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13786 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13787 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13788 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13789 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13792 while(*p==' ') p++;
13793 if(nrCastlingRights) {
13794 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13795 /* castling indicator present, so default becomes no castlings */
13796 for(i=0; i<nrCastlingRights; i++ ) {
13797 FENcastlingRights[i] = -1;
13800 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13801 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13802 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13803 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13804 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13806 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13807 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13808 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13812 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13813 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13814 FENcastlingRights[2] = whiteKingFile;
13817 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13818 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13819 FENcastlingRights[2] = whiteKingFile;
13822 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13823 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13824 FENcastlingRights[5] = blackKingFile;
13827 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13828 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13829 FENcastlingRights[5] = blackKingFile;
13832 default: /* FRC castlings */
13833 if(c >= 'a') { /* black rights */
13834 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13835 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13836 if(i == BOARD_RGHT) break;
13837 FENcastlingRights[5] = i;
13839 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13840 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13842 FENcastlingRights[3] = c;
13844 FENcastlingRights[4] = c;
13845 } else { /* white rights */
13846 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13847 if(board[0][i] == WhiteKing) break;
13848 if(i == BOARD_RGHT) break;
13849 FENcastlingRights[2] = i;
13850 c -= AAA - 'a' + 'A';
13851 if(board[0][c] >= WhiteKing) break;
13853 FENcastlingRights[0] = c;
13855 FENcastlingRights[1] = c;
13859 if (appData.debugMode) {
13860 fprintf(debugFP, "FEN castling rights:");
13861 for(i=0; i<nrCastlingRights; i++)
13862 fprintf(debugFP, " %d", FENcastlingRights[i]);
13863 fprintf(debugFP, "\n");
13866 while(*p==' ') p++;
13869 /* read e.p. field in games that know e.p. capture */
13870 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13871 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13873 p++; FENepStatus = EP_NONE;
13875 char c = *p++ - AAA;
13877 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13878 if(*p >= '0' && *p <='9') *p++;
13884 if(sscanf(p, "%d", &i) == 1) {
13885 FENrulePlies = i; /* 50-move ply counter */
13886 /* (The move number is still ignored) */
13893 EditPositionPasteFEN(char *fen)
13896 Board initial_position;
13898 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13899 DisplayError(_("Bad FEN position in clipboard"), 0);
13902 int savedBlackPlaysFirst = blackPlaysFirst;
13903 EditPositionEvent();
13904 blackPlaysFirst = savedBlackPlaysFirst;
13905 CopyBoard(boards[0], initial_position);
13906 /* [HGM] copy FEN attributes as well */
13908 initialRulePlies = FENrulePlies;
13909 epStatus[0] = FENepStatus;
13910 for( i=0; i<nrCastlingRights; i++ )
13911 castlingRights[0][i] = FENcastlingRights[i];
13913 EditPositionDone();
13914 DisplayBothClocks();
13915 DrawPosition(FALSE, boards[currentMove]);