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 InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
240 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
246 /* States for ics_getting_history */
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
254 /* whosays values for GameEnds */
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
266 /* Different types of move when calling RegisterMove */
268 #define CMAIL_RESIGN 1
270 #define CMAIL_ACCEPT 3
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
277 /* Telnet protocol constants */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
290 assert( dst != NULL );
291 assert( src != NULL );
294 strncpy( dst, src, count );
295 dst[ count-1 ] = '\0';
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
305 assert( dst != NULL );
306 assert( src != NULL );
309 dst_len = strlen(dst);
311 assert( count > dst_len ); /* Buffer size must be greater than current length */
313 safeStrCpy( dst + dst_len, src, count - dst_len );
319 /* Some compiler can't cast u64 to double
320 * This function do the job for us:
322 * We use the highest bit for cast, this only
323 * works if the highest bit is not
324 * in use (This should not happen)
326 * We used this for all compiler
329 u64ToDouble(u64 value)
332 u64 tmp = value & u64Const(0x7fffffffffffffff);
333 r = (double)(s64)tmp;
334 if (value & u64Const(0x8000000000000000))
335 r += 9.2233720368547758080e18; /* 2^63 */
339 /* Fake up flags for now, as we aren't keeping track of castling
340 availability yet. [HGM] Change of logic: the flag now only
341 indicates the type of castlings allowed by the rule of the game.
342 The actual rights themselves are maintained in the array
343 castlingRights, as part of the game history, and are not probed
349 int flags = F_ALL_CASTLE_OK;
350 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351 switch (gameInfo.variant) {
353 flags &= ~F_ALL_CASTLE_OK;
354 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355 flags |= F_IGNORE_CHECK;
357 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
360 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
362 case VariantKriegspiel:
363 flags |= F_KRIEGSPIEL_CAPTURE;
365 case VariantCapaRandom:
366 case VariantFischeRandom:
367 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368 case VariantNoCastle:
369 case VariantShatranj:
371 flags &= ~F_ALL_CASTLE_OK;
379 FILE *gameFileFP, *debugFP;
382 [AS] Note: sometimes, the sscanf() function is used to parse the input
383 into a fixed-size buffer. Because of this, we must be prepared to
384 receive strings as long as the size of the input buffer, which is currently
385 set to 4K for Windows and 8K for the rest.
386 So, we must either allocate sufficiently large buffers here, or
387 reduce the size of the input buffer in the input reading part.
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
394 ChessProgramState first, second;
396 /* premove variables */
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
446 /* animateTraining preserves the state of appData.animate
447 * when Training mode is activated. This allows the
448 * response to be animated when appData.animate == TRUE and
449 * appData.animateDragging == TRUE.
451 Boolean animateTraining;
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char epStatus[MAX_MOVES];
460 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int initialRulePlies, FENrulePlies;
466 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 ChessSquare FIDEArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<MAX_MOVES; i++ ) {
662 pvInfoList[i].depth = -1;
664 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 appData.clockMode = FALSE;
813 first.sendTime = second.sendTime = 0;
817 /* Override some settings from environment variables, for backward
818 compatibility. Unfortunately it's not feasible to have the env
819 vars just set defaults, at least in xboard. Ugh.
821 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826 if (appData.noChessProgram) {
827 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828 sprintf(programVersion, "%s", PACKAGE_STRING);
833 while (*q != ' ' && *q != NULLCHAR) q++;
835 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837 sprintf(programVersion, "%s + ", PACKAGE_STRING);
838 strncat(programVersion, p, q - p);
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 if (!appData.icsActive) {
848 /* Check for variants that are supported only in ICS mode,
849 or not at all. Some that are accepted here nevertheless
850 have bugs; see comments below.
852 VariantClass variant = StringToVariant(appData.variant);
854 case VariantBughouse: /* need four players and two boards */
855 case VariantKriegspiel: /* need to hide pieces and move details */
856 /* case VariantFischeRandom: (Fabien: moved below) */
857 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
862 case VariantLoadable:
872 sprintf(buf, _("Unknown variant name %s"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
876 case VariantXiangqi: /* [HGM] repetition rules not implemented */
877 case VariantFairy: /* [HGM] TestLegality definitely off! */
878 case VariantGothic: /* [HGM] should work */
879 case VariantCapablanca: /* [HGM] should work */
880 case VariantCourier: /* [HGM] initial forced moves not implemented */
881 case VariantShogi: /* [HGM] drops not tested for legality */
882 case VariantKnightmate: /* [HGM] should work */
883 case VariantCylinder: /* [HGM] untested */
884 case VariantFalcon: /* [HGM] untested */
885 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886 offboard interposition not understood */
887 case VariantNormal: /* definitely works! */
888 case VariantWildCastle: /* pieces not automatically shuffled */
889 case VariantNoCastle: /* pieces not automatically shuffled */
890 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891 case VariantLosers: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantSuicide: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantGiveaway: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantTwoKings: /* should work */
898 case VariantAtomic: /* should work except for win condition */
899 case Variant3Check: /* should work except for win condition */
900 case VariantShatranj: /* should work except for all win conditions */
901 case VariantBerolina: /* might work if TestLegality is off */
902 case VariantCapaRandom: /* should work */
903 case VariantJanus: /* should work */
904 case VariantSuper: /* experimental */
905 case VariantGreat: /* experimental, requires legality testing to be off */
910 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
911 InitEngineUCI( installDir, &second );
914 int NextIntegerFromString( char ** str, long * value )
919 while( *s == ' ' || *s == '\t' ) {
925 if( *s >= '0' && *s <= '9' ) {
926 while( *s >= '0' && *s <= '9' ) {
927 *value = *value * 10 + (*s - '0');
939 int NextTimeControlFromString( char ** str, long * value )
942 int result = NextIntegerFromString( str, &temp );
945 *value = temp * 60; /* Minutes */
948 result = NextIntegerFromString( str, &temp );
949 *value += temp; /* Seconds */
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 { /* [HGM] routine added to read '+moves/time' for secondary time control */
958 int result = -1; long temp, temp2;
960 if(**str != '+') return -1; // old params remain in force!
962 if( NextTimeControlFromString( str, &temp ) ) return -1;
965 /* time only: incremental or sudden-death time control */
966 if(**str == '+') { /* increment follows; read it */
968 if(result = NextIntegerFromString( str, &temp2)) return -1;
971 *moves = 0; *tc = temp * 1000;
973 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
975 (*str)++; /* classical time control */
976 result = NextTimeControlFromString( str, &temp2);
985 int GetTimeQuota(int movenr)
986 { /* [HGM] get time to add from the multi-session time-control string */
987 int moves=1; /* kludge to force reading of first session */
988 long time, increment;
989 char *s = fullTimeControlString;
991 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995 if(movenr == -1) return time; /* last move before new session */
996 if(!moves) return increment; /* current session is incremental */
997 if(movenr >= 0) movenr -= moves; /* we already finished this session */
998 } while(movenr >= -1); /* try again for next session */
1000 return 0; // no new time quota on this move
1004 ParseTimeControl(tc, ti, mps)
1010 int matched, min, sec;
1012 matched = sscanf(tc, "%d:%d", &min, &sec);
1014 timeControl = min * 60 * 1000;
1015 } else if (matched == 2) {
1016 timeControl = (min * 60 + sec) * 1000;
1025 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1028 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029 else sprintf(buf, "+%s+%d", tc, ti);
1032 sprintf(buf, "+%d/%s", mps, tc);
1033 else sprintf(buf, "+%s", tc);
1035 fullTimeControlString = StrSave(buf);
1037 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042 /* Parse second time control */
1045 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1053 timeControl_2 = tc2 * 1000;
1063 timeControl = tc1 * 1000;
1067 timeIncrement = ti * 1000; /* convert to ms */
1068 movesPerSession = 0;
1071 movesPerSession = mps;
1079 if (appData.debugMode) {
1080 fprintf(debugFP, "%s\n", programVersion);
1083 if (appData.matchGames > 0) {
1084 appData.matchMode = TRUE;
1085 } else if (appData.matchMode) {
1086 appData.matchGames = 1;
1088 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089 appData.matchGames = appData.sameColorGames;
1090 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1095 if (appData.noChessProgram || first.protocolVersion == 1) {
1098 /* kludge: allow timeout for initial "feature" commands */
1100 DisplayMessage("", _("Starting chess program"));
1101 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106 InitBackEnd3 P((void))
1108 GameMode initialMode;
1112 InitChessProgram(&first, startedFromSetupPosition);
1115 if (appData.icsActive) {
1117 /* [DM] Make a console window if needed [HGM] merged ifs */
1122 if (*appData.icsCommPort != NULLCHAR) {
1123 sprintf(buf, _("Could not open comm port %s"),
1124 appData.icsCommPort);
1126 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1127 appData.icsHost, appData.icsPort);
1129 DisplayFatalError(buf, err, 1);
1134 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137 } else if (appData.noChessProgram) {
1143 if (*appData.cmailGameName != NULLCHAR) {
1145 OpenLoopback(&cmailPR);
1147 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151 DisplayMessage("", "");
1152 if (StrCaseCmp(appData.initialMode, "") == 0) {
1153 initialMode = BeginningOfGame;
1154 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155 initialMode = TwoMachinesPlay;
1156 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157 initialMode = AnalyzeFile;
1158 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159 initialMode = AnalyzeMode;
1160 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161 initialMode = MachinePlaysWhite;
1162 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163 initialMode = MachinePlaysBlack;
1164 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165 initialMode = EditGame;
1166 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167 initialMode = EditPosition;
1168 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169 initialMode = Training;
1171 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172 DisplayFatalError(buf, 0, 2);
1176 if (appData.matchMode) {
1177 /* Set up machine vs. machine match */
1178 if (appData.noChessProgram) {
1179 DisplayFatalError(_("Can't have a match with no chess programs"),
1185 if (*appData.loadGameFile != NULLCHAR) {
1186 int index = appData.loadGameIndex; // [HGM] autoinc
1187 if(index<0) lastIndex = index = 1;
1188 if (!LoadGameFromFile(appData.loadGameFile,
1190 appData.loadGameFile, FALSE)) {
1191 DisplayFatalError(_("Bad game file"), 0, 1);
1194 } else if (*appData.loadPositionFile != NULLCHAR) {
1195 int index = appData.loadPositionIndex; // [HGM] autoinc
1196 if(index<0) lastIndex = index = 1;
1197 if (!LoadPositionFromFile(appData.loadPositionFile,
1199 appData.loadPositionFile)) {
1200 DisplayFatalError(_("Bad position file"), 0, 1);
1205 } else if (*appData.cmailGameName != NULLCHAR) {
1206 /* Set up cmail mode */
1207 ReloadCmailMsgEvent(TRUE);
1209 /* Set up other modes */
1210 if (initialMode == AnalyzeFile) {
1211 if (*appData.loadGameFile == NULLCHAR) {
1212 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216 if (*appData.loadGameFile != NULLCHAR) {
1217 (void) LoadGameFromFile(appData.loadGameFile,
1218 appData.loadGameIndex,
1219 appData.loadGameFile, TRUE);
1220 } else if (*appData.loadPositionFile != NULLCHAR) {
1221 (void) LoadPositionFromFile(appData.loadPositionFile,
1222 appData.loadPositionIndex,
1223 appData.loadPositionFile);
1224 /* [HGM] try to make self-starting even after FEN load */
1225 /* to allow automatic setup of fairy variants with wtm */
1226 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227 gameMode = BeginningOfGame;
1228 setboardSpoiledMachineBlack = 1;
1230 /* [HGM] loadPos: make that every new game uses the setup */
1231 /* from file as long as we do not switch variant */
1232 if(!blackPlaysFirst) { int i;
1233 startedFromPositionFile = TRUE;
1234 CopyBoard(filePosition, boards[0]);
1235 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1238 if (initialMode == AnalyzeMode) {
1239 if (appData.noChessProgram) {
1240 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1243 if (appData.icsActive) {
1244 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248 } else if (initialMode == AnalyzeFile) {
1249 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250 ShowThinkingEvent();
1252 AnalysisPeriodicEvent(1);
1253 } else if (initialMode == MachinePlaysWhite) {
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259 if (appData.icsActive) {
1260 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264 MachineWhiteEvent();
1265 } else if (initialMode == MachinePlaysBlack) {
1266 if (appData.noChessProgram) {
1267 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271 if (appData.icsActive) {
1272 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276 MachineBlackEvent();
1277 } else if (initialMode == TwoMachinesPlay) {
1278 if (appData.noChessProgram) {
1279 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283 if (appData.icsActive) {
1284 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289 } else if (initialMode == EditGame) {
1291 } else if (initialMode == EditPosition) {
1292 EditPositionEvent();
1293 } else if (initialMode == Training) {
1294 if (*appData.loadGameFile == NULLCHAR) {
1295 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1304 * Establish will establish a contact to a remote host.port.
1305 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306 * used to talk to the host.
1307 * Returns 0 if okay, error code if not.
1314 if (*appData.icsCommPort != NULLCHAR) {
1315 /* Talk to the host through a serial comm port */
1316 return OpenCommPort(appData.icsCommPort, &icsPR);
1318 } else if (*appData.gateway != NULLCHAR) {
1319 if (*appData.remoteShell == NULLCHAR) {
1320 /* Use the rcmd protocol to run telnet program on a gateway host */
1321 snprintf(buf, sizeof(buf), "%s %s %s",
1322 appData.telnetProgram, appData.icsHost, appData.icsPort);
1323 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1326 /* Use the rsh program to run telnet program on a gateway host */
1327 if (*appData.remoteUser == NULLCHAR) {
1328 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329 appData.gateway, appData.telnetProgram,
1330 appData.icsHost, appData.icsPort);
1332 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333 appData.remoteShell, appData.gateway,
1334 appData.remoteUser, appData.telnetProgram,
1335 appData.icsHost, appData.icsPort);
1337 return StartChildProcess(buf, "", &icsPR);
1340 } else if (appData.useTelnet) {
1341 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1344 /* TCP socket interface differs somewhat between
1345 Unix and NT; handle details in the front end.
1347 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352 show_bytes(fp, buf, count)
1358 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359 fprintf(fp, "\\%03o", *buf & 0xff);
1368 /* Returns an errno value */
1370 OutputMaybeTelnet(pr, message, count, outError)
1376 char buf[8192], *p, *q, *buflim;
1377 int left, newcount, outcount;
1379 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380 *appData.gateway != NULLCHAR) {
1381 if (appData.debugMode) {
1382 fprintf(debugFP, ">ICS: ");
1383 show_bytes(debugFP, message, count);
1384 fprintf(debugFP, "\n");
1386 return OutputToProcess(pr, message, count, outError);
1389 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1396 if (appData.debugMode) {
1397 fprintf(debugFP, ">ICS: ");
1398 show_bytes(debugFP, buf, newcount);
1399 fprintf(debugFP, "\n");
1401 outcount = OutputToProcess(pr, buf, newcount, outError);
1402 if (outcount < newcount) return -1; /* to be sure */
1409 } else if (((unsigned char) *p) == TN_IAC) {
1410 *q++ = (char) TN_IAC;
1417 if (appData.debugMode) {
1418 fprintf(debugFP, ">ICS: ");
1419 show_bytes(debugFP, buf, newcount);
1420 fprintf(debugFP, "\n");
1422 outcount = OutputToProcess(pr, buf, newcount, outError);
1423 if (outcount < newcount) return -1; /* to be sure */
1428 read_from_player(isr, closure, message, count, error)
1435 int outError, outCount;
1436 static int gotEof = 0;
1438 /* Pass data read from player on to ICS */
1441 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442 if (outCount < count) {
1443 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445 } else if (count < 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448 } else if (gotEof++ > 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1458 int count, outCount, outError;
1460 if (icsPR == NULL) return;
1463 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464 if (outCount < count) {
1465 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469 /* This is used for sending logon scripts to the ICS. Sending
1470 without a delay causes problems when using timestamp on ICC
1471 (at least on my machine). */
1473 SendToICSDelayed(s,msdelay)
1477 int count, outCount, outError;
1479 if (icsPR == NULL) return;
1482 if (appData.debugMode) {
1483 fprintf(debugFP, ">ICS: ");
1484 show_bytes(debugFP, s, count);
1485 fprintf(debugFP, "\n");
1487 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489 if (outCount < count) {
1490 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1495 /* Remove all highlighting escape sequences in s
1496 Also deletes any suffix starting with '('
1499 StripHighlightAndTitle(s)
1502 static char retbuf[MSG_SIZ];
1505 while (*s != NULLCHAR) {
1506 while (*s == '\033') {
1507 while (*s != NULLCHAR && !isalpha(*s)) s++;
1508 if (*s != NULLCHAR) s++;
1510 while (*s != NULLCHAR && *s != '\033') {
1511 if (*s == '(' || *s == '[') {
1522 /* Remove all highlighting escape sequences in s */
1527 static char retbuf[MSG_SIZ];
1530 while (*s != NULLCHAR) {
1531 while (*s == '\033') {
1532 while (*s != NULLCHAR && !isalpha(*s)) s++;
1533 if (*s != NULLCHAR) s++;
1535 while (*s != NULLCHAR && *s != '\033') {
1543 char *variantNames[] = VARIANT_NAMES;
1548 return variantNames[v];
1552 /* Identify a variant from the strings the chess servers use or the
1553 PGN Variant tag names we use. */
1560 VariantClass v = VariantNormal;
1561 int i, found = FALSE;
1566 /* [HGM] skip over optional board-size prefixes */
1567 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569 while( *e++ != '_');
1572 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573 if (StrCaseStr(e, variantNames[i])) {
1574 v = (VariantClass) i;
1581 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582 || StrCaseStr(e, "wild/fr")
1583 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584 v = VariantFischeRandom;
1585 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586 (i = 1, p = StrCaseStr(e, "w"))) {
1588 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1595 case 0: /* FICS only, actually */
1597 /* Castling legal even if K starts on d-file */
1598 v = VariantWildCastle;
1603 /* Castling illegal even if K & R happen to start in
1604 normal positions. */
1605 v = VariantNoCastle;
1618 /* Castling legal iff K & R start in normal positions */
1624 /* Special wilds for position setup; unclear what to do here */
1625 v = VariantLoadable;
1628 /* Bizarre ICC game */
1629 v = VariantTwoKings;
1632 v = VariantKriegspiel;
1638 v = VariantFischeRandom;
1641 v = VariantCrazyhouse;
1644 v = VariantBughouse;
1650 /* Not quite the same as FICS suicide! */
1651 v = VariantGiveaway;
1657 v = VariantShatranj;
1660 /* Temporary names for future ICC types. The name *will* change in
1661 the next xboard/WinBoard release after ICC defines it. */
1699 v = VariantCapablanca;
1702 v = VariantKnightmate;
1708 v = VariantCylinder;
1714 v = VariantCapaRandom;
1717 v = VariantBerolina;
1729 /* Found "wild" or "w" in the string but no number;
1730 must assume it's normal chess. */
1734 sprintf(buf, _("Unknown wild type %d"), wnum);
1735 DisplayError(buf, 0);
1741 if (appData.debugMode) {
1742 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743 e, wnum, VariantName(v));
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752 advance *index beyond it, and set leftover_start to the new value of
1753 *index; else return FALSE. If pattern contains the character '*', it
1754 matches any sequence of characters not containing '\r', '\n', or the
1755 character following the '*' (if any), and the matched sequence(s) are
1756 copied into star_match.
1759 looking_at(buf, index, pattern)
1764 char *bufp = &buf[*index], *patternp = pattern;
1766 char *matchp = star_match[0];
1769 if (*patternp == NULLCHAR) {
1770 *index = leftover_start = bufp - buf;
1774 if (*bufp == NULLCHAR) return FALSE;
1775 if (*patternp == '*') {
1776 if (*bufp == *(patternp + 1)) {
1778 matchp = star_match[++star_count];
1782 } else if (*bufp == '\n' || *bufp == '\r') {
1784 if (*patternp == NULLCHAR)
1789 *matchp++ = *bufp++;
1793 if (*patternp != *bufp) return FALSE;
1800 SendToPlayer(data, length)
1804 int error, outCount;
1805 outCount = OutputToProcess(NoProc, data, length, &error);
1806 if (outCount < length) {
1807 DisplayFatalError(_("Error writing to display"), error, 1);
1812 PackHolding(packed, holding)
1824 switch (runlength) {
1835 sprintf(q, "%d", runlength);
1847 /* Telnet protocol requests from the front end */
1849 TelnetRequest(ddww, option)
1850 unsigned char ddww, option;
1852 unsigned char msg[3];
1853 int outCount, outError;
1855 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857 if (appData.debugMode) {
1858 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1874 sprintf(buf1, "%d", ddww);
1883 sprintf(buf2, "%d", option);
1886 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1891 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DO, TN_ECHO);
1907 if (!appData.icsActive) return;
1908 TelnetRequest(TN_DONT, TN_ECHO);
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 /* put the holdings sent to us by the server on the board holdings area */
1915 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1919 if(gameInfo.holdingsWidth < 2) return;
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1953 VariantSwitch(Board board, VariantClass newVariant)
1955 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959 startedFromPositionFile = FALSE;
1960 if(gameInfo.variant == newVariant) return;
1962 /* [HGM] This routine is called each time an assignment is made to
1963 * gameInfo.variant during a game, to make sure the board sizes
1964 * are set to match the new variant. If that means adding or deleting
1965 * holdings, we shift the playing board accordingly
1966 * This kludge is needed because in ICS observe mode, we get boards
1967 * of an ongoing game without knowing the variant, and learn about the
1968 * latter only later. This can be because of the move list we requested,
1969 * in which case the game history is refilled from the beginning anyway,
1970 * but also when receiving holdings of a crazyhouse game. In the latter
1971 * case we want to add those holdings to the already received position.
1975 if (appData.debugMode) {
1976 fprintf(debugFP, "Switch board from %s to %s\n",
1977 VariantName(gameInfo.variant), VariantName(newVariant));
1978 setbuf(debugFP, NULL);
1980 shuffleOpenings = 0; /* [HGM] shuffle */
1981 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 switch(newVariant) {
1984 newWidth = 9; newHeight = 9;
1985 gameInfo.holdingsSize = 7;
1986 case VariantBughouse:
1987 case VariantCrazyhouse:
1988 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = gameInfo.holdingsSize = 0;
1993 if(newWidth != gameInfo.boardWidth ||
1994 newHeight != gameInfo.boardHeight ||
1995 newHoldingsWidth != gameInfo.holdingsWidth ) {
1997 /* shift position to new playing area, if needed */
1998 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999 for(i=0; i<BOARD_HEIGHT; i++)
2000 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003 for(i=0; i<newHeight; i++) {
2004 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 gameInfo.boardWidth = newWidth;
2015 gameInfo.boardHeight = newHeight;
2016 gameInfo.holdingsWidth = newHoldingsWidth;
2017 gameInfo.variant = newVariant;
2018 InitDrawingSizes(-2, 0);
2020 /* [HGM] The following should definitely be solved in a better way */
2022 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024 saveEP = epStatus[0];
2026 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2028 epStatus[0] = saveEP;
2029 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030 CopyBoard(tempBoard, board); /* restore position received from ICS */
2032 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034 forwardMostMove = oldForwardMostMove;
2035 backwardMostMove = oldBackwardMostMove;
2036 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2039 static int loggedOn = FALSE;
2041 /*-- Game start info cache: --*/
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2054 read_from_ics(isr, closure, data, count, error)
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2071 static int started = STARTED_NONE;
2072 static char parse[20000];
2073 static int parse_pos = 0;
2074 static char buf[BUF_SIZE + 1];
2075 static int firstTime = TRUE, intfSet = FALSE;
2076 static ColorClass prevColor = ColorNormal;
2077 static int savingComment = FALSE;
2083 int backup; /* [DM] For zippy color lines */
2086 if (appData.debugMode) {
2088 fprintf(debugFP, "<ICS: ");
2089 show_bytes(debugFP, data, count);
2090 fprintf(debugFP, "\n");
2094 if (appData.debugMode) { int f = forwardMostMove;
2095 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* Copy in new characters, removing nulls and \r's */
2107 buf_len = leftover_len;
2108 for (i = 0; i < count; i++) {
2109 if (data[i] != NULLCHAR && data[i] != '\r')
2110 buf[buf_len++] = data[i];
2111 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2112 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
2113 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2117 buf[buf_len] = NULLCHAR;
2118 next_out = leftover_len;
2122 while (i < buf_len) {
2123 /* Deal with part of the TELNET option negotiation
2124 protocol. We refuse to do anything beyond the
2125 defaults, except that we allow the WILL ECHO option,
2126 which ICS uses to turn off password echoing when we are
2127 directly connected to it. We reject this option
2128 if localLineEditing mode is on (always on in xboard)
2129 and we are talking to port 23, which might be a real
2130 telnet server that will try to keep WILL ECHO on permanently.
2132 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2133 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2134 unsigned char option;
2136 switch ((unsigned char) buf[++i]) {
2138 if (appData.debugMode)
2139 fprintf(debugFP, "\n<WILL ");
2140 switch (option = (unsigned char) buf[++i]) {
2142 if (appData.debugMode)
2143 fprintf(debugFP, "ECHO ");
2144 /* Reply only if this is a change, according
2145 to the protocol rules. */
2146 if (remoteEchoOption) break;
2147 if (appData.localLineEditing &&
2148 atoi(appData.icsPort) == TN_PORT) {
2149 TelnetRequest(TN_DONT, TN_ECHO);
2152 TelnetRequest(TN_DO, TN_ECHO);
2153 remoteEchoOption = TRUE;
2157 if (appData.debugMode)
2158 fprintf(debugFP, "%d ", option);
2159 /* Whatever this is, we don't want it. */
2160 TelnetRequest(TN_DONT, option);
2165 if (appData.debugMode)
2166 fprintf(debugFP, "\n<WONT ");
2167 switch (option = (unsigned char) buf[++i]) {
2169 if (appData.debugMode)
2170 fprintf(debugFP, "ECHO ");
2171 /* Reply only if this is a change, according
2172 to the protocol rules. */
2173 if (!remoteEchoOption) break;
2175 TelnetRequest(TN_DONT, TN_ECHO);
2176 remoteEchoOption = FALSE;
2179 if (appData.debugMode)
2180 fprintf(debugFP, "%d ", (unsigned char) option);
2181 /* Whatever this is, it must already be turned
2182 off, because we never agree to turn on
2183 anything non-default, so according to the
2184 protocol rules, we don't reply. */
2189 if (appData.debugMode)
2190 fprintf(debugFP, "\n<DO ");
2191 switch (option = (unsigned char) buf[++i]) {
2193 /* Whatever this is, we refuse to do it. */
2194 if (appData.debugMode)
2195 fprintf(debugFP, "%d ", option);
2196 TelnetRequest(TN_WONT, option);
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<DONT ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 if (appData.debugMode)
2206 fprintf(debugFP, "%d ", option);
2207 /* Whatever this is, we are already not doing
2208 it, because we never agree to do anything
2209 non-default, so according to the protocol
2210 rules, we don't reply. */
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<IAC ");
2217 /* Doubled IAC; pass it through */
2221 if (appData.debugMode)
2222 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2223 /* Drop all other telnet commands on the floor */
2226 if (oldi > next_out)
2227 SendToPlayer(&buf[next_out], oldi - next_out);
2233 /* OK, this at least will *usually* work */
2234 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2238 if (loggedOn && !intfSet) {
2239 if (ics_type == ICS_ICC) {
2241 "/set-quietly interface %s\n/set-quietly style 12\n",
2244 } else if (ics_type == ICS_CHESSNET) {
2245 sprintf(str, "/style 12\n");
2247 strcpy(str, "alias $ @\n$set interface ");
2248 strcat(str, programVersion);
2249 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2251 strcat(str, "$iset nohighlight 1\n");
2253 strcat(str, "$iset lock 1\n$style 12\n");
2259 if (started == STARTED_COMMENT) {
2260 /* Accumulate characters in comment */
2261 parse[parse_pos++] = buf[i];
2262 if (buf[i] == '\n') {
2263 parse[parse_pos] = NULLCHAR;
2264 if(!suppressKibitz) // [HGM] kibitz
2265 AppendComment(forwardMostMove, StripHighlight(parse));
2266 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2267 int nrDigit = 0, nrAlph = 0, i;
2268 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2269 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2270 parse[parse_pos] = NULLCHAR;
2271 // try to be smart: if it does not look like search info, it should go to
2272 // ICS interaction window after all, not to engine-output window.
2273 for(i=0; i<parse_pos; i++) { // count letters and digits
2274 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2275 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2276 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2278 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2279 int depth=0; float score;
2280 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2281 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2282 pvInfoList[forwardMostMove-1].depth = depth;
2283 pvInfoList[forwardMostMove-1].score = 100*score;
2285 OutputKibitz(suppressKibitz, parse);
2288 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2289 SendToPlayer(tmp, strlen(tmp));
2292 started = STARTED_NONE;
2294 /* Don't match patterns against characters in chatter */
2299 if (started == STARTED_CHATTER) {
2300 if (buf[i] != '\n') {
2301 /* Don't match patterns against characters in chatter */
2305 started = STARTED_NONE;
2308 /* Kludge to deal with rcmd protocol */
2309 if (firstTime && looking_at(buf, &i, "\001*")) {
2310 DisplayFatalError(&buf[1], 0, 1);
2316 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2319 if (appData.debugMode)
2320 fprintf(debugFP, "ics_type %d\n", ics_type);
2323 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2324 ics_type = ICS_FICS;
2326 if (appData.debugMode)
2327 fprintf(debugFP, "ics_type %d\n", ics_type);
2330 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2331 ics_type = ICS_CHESSNET;
2333 if (appData.debugMode)
2334 fprintf(debugFP, "ics_type %d\n", ics_type);
2339 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2340 looking_at(buf, &i, "Logging you in as \"*\"") ||
2341 looking_at(buf, &i, "will be \"*\""))) {
2342 strcpy(ics_handle, star_match[0]);
2346 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2348 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2349 DisplayIcsInteractionTitle(buf);
2350 have_set_title = TRUE;
2353 /* skip finger notes */
2354 if (started == STARTED_NONE &&
2355 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2356 (buf[i] == '1' && buf[i+1] == '0')) &&
2357 buf[i+2] == ':' && buf[i+3] == ' ') {
2358 started = STARTED_CHATTER;
2363 /* skip formula vars */
2364 if (started == STARTED_NONE &&
2365 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2366 started = STARTED_CHATTER;
2372 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2373 if (appData.autoKibitz && started == STARTED_NONE &&
2374 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2375 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2376 if(looking_at(buf, &i, "* kibitzes: ") &&
2377 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2378 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2379 suppressKibitz = TRUE;
2380 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2381 && (gameMode == IcsPlayingWhite)) ||
2382 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2383 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2384 started = STARTED_CHATTER; // own kibitz we simply discard
2386 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2387 parse_pos = 0; parse[0] = NULLCHAR;
2388 savingComment = TRUE;
2389 suppressKibitz = gameMode != IcsObserving ? 2 :
2390 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2394 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2395 started = STARTED_CHATTER;
2396 suppressKibitz = TRUE;
2398 } // [HGM] kibitz: end of patch
2400 if (appData.zippyTalk || appData.zippyPlay) {
2401 /* [DM] Backup address for color zippy lines */
2405 if (loggedOn == TRUE)
2406 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2407 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2409 if (ZippyControl(buf, &i) ||
2410 ZippyConverse(buf, &i) ||
2411 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2413 if (!appData.colorize) continue;
2417 } // [DM] 'else { ' deleted
2418 if (/* Don't color "message" or "messages" output */
2419 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2420 looking_at(buf, &i, "*. * at *:*: ") ||
2421 looking_at(buf, &i, "--* (*:*): ") ||
2422 /* Regular tells and says */
2423 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2424 looking_at(buf, &i, "* (your partner) tells you: ") ||
2425 looking_at(buf, &i, "* says: ") ||
2426 /* Message notifications (same color as tells) */
2427 looking_at(buf, &i, "* has left a message ") ||
2428 looking_at(buf, &i, "* just sent you a message:\n") ||
2429 /* Whispers and kibitzes */
2430 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2431 looking_at(buf, &i, "* kibitzes: ") ||
2433 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2435 if (tkind == 1 && strchr(star_match[0], ':')) {
2436 /* Avoid "tells you:" spoofs in channels */
2439 if (star_match[0][0] == NULLCHAR ||
2440 strchr(star_match[0], ' ') ||
2441 (tkind == 3 && strchr(star_match[1], ' '))) {
2442 /* Reject bogus matches */
2445 if (appData.colorize) {
2446 if (oldi > next_out) {
2447 SendToPlayer(&buf[next_out], oldi - next_out);
2452 Colorize(ColorTell, FALSE);
2453 curColor = ColorTell;
2456 Colorize(ColorKibitz, FALSE);
2457 curColor = ColorKibitz;
2460 p = strrchr(star_match[1], '(');
2467 Colorize(ColorChannel1, FALSE);
2468 curColor = ColorChannel1;
2470 Colorize(ColorChannel, FALSE);
2471 curColor = ColorChannel;
2475 curColor = ColorNormal;
2479 if (started == STARTED_NONE && appData.autoComment &&
2480 (gameMode == IcsObserving ||
2481 gameMode == IcsPlayingWhite ||
2482 gameMode == IcsPlayingBlack)) {
2483 parse_pos = i - oldi;
2484 memcpy(parse, &buf[oldi], parse_pos);
2485 parse[parse_pos] = NULLCHAR;
2486 started = STARTED_COMMENT;
2487 savingComment = TRUE;
2489 started = STARTED_CHATTER;
2490 savingComment = FALSE;
2497 if (looking_at(buf, &i, "* s-shouts: ") ||
2498 looking_at(buf, &i, "* c-shouts: ")) {
2499 if (appData.colorize) {
2500 if (oldi > next_out) {
2501 SendToPlayer(&buf[next_out], oldi - next_out);
2504 Colorize(ColorSShout, FALSE);
2505 curColor = ColorSShout;
2508 started = STARTED_CHATTER;
2512 if (looking_at(buf, &i, "--->")) {
2517 if (looking_at(buf, &i, "* shouts: ") ||
2518 looking_at(buf, &i, "--> ")) {
2519 if (appData.colorize) {
2520 if (oldi > next_out) {
2521 SendToPlayer(&buf[next_out], oldi - next_out);
2524 Colorize(ColorShout, FALSE);
2525 curColor = ColorShout;
2528 started = STARTED_CHATTER;
2532 if (looking_at( buf, &i, "Challenge:")) {
2533 if (appData.colorize) {
2534 if (oldi > next_out) {
2535 SendToPlayer(&buf[next_out], oldi - next_out);
2538 Colorize(ColorChallenge, FALSE);
2539 curColor = ColorChallenge;
2545 if (looking_at(buf, &i, "* offers you") ||
2546 looking_at(buf, &i, "* offers to be") ||
2547 looking_at(buf, &i, "* would like to") ||
2548 looking_at(buf, &i, "* requests to") ||
2549 looking_at(buf, &i, "Your opponent offers") ||
2550 looking_at(buf, &i, "Your opponent requests")) {
2552 if (appData.colorize) {
2553 if (oldi > next_out) {
2554 SendToPlayer(&buf[next_out], oldi - next_out);
2557 Colorize(ColorRequest, FALSE);
2558 curColor = ColorRequest;
2563 if (looking_at(buf, &i, "* (*) seeking")) {
2564 if (appData.colorize) {
2565 if (oldi > next_out) {
2566 SendToPlayer(&buf[next_out], oldi - next_out);
2569 Colorize(ColorSeek, FALSE);
2570 curColor = ColorSeek;
2575 if (looking_at(buf, &i, "\\ ")) {
2576 if (prevColor != ColorNormal) {
2577 if (oldi > next_out) {
2578 SendToPlayer(&buf[next_out], oldi - next_out);
2581 Colorize(prevColor, TRUE);
2582 curColor = prevColor;
2584 if (savingComment) {
2585 parse_pos = i - oldi;
2586 memcpy(parse, &buf[oldi], parse_pos);
2587 parse[parse_pos] = NULLCHAR;
2588 started = STARTED_COMMENT;
2590 started = STARTED_CHATTER;
2595 if (looking_at(buf, &i, "Black Strength :") ||
2596 looking_at(buf, &i, "<<< style 10 board >>>") ||
2597 looking_at(buf, &i, "<10>") ||
2598 looking_at(buf, &i, "#@#")) {
2599 /* Wrong board style */
2601 SendToICS(ics_prefix);
2602 SendToICS("set style 12\n");
2603 SendToICS(ics_prefix);
2604 SendToICS("refresh\n");
2608 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2610 have_sent_ICS_logon = 1;
2614 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2615 (looking_at(buf, &i, "\n<12> ") ||
2616 looking_at(buf, &i, "<12> "))) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 started = STARTED_BOARD;
2627 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2628 looking_at(buf, &i, "<b1> ")) {
2629 if (oldi > next_out) {
2630 SendToPlayer(&buf[next_out], oldi - next_out);
2633 started = STARTED_HOLDINGS;
2638 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2640 /* Header for a move list -- first line */
2642 switch (ics_getting_history) {
2646 case BeginningOfGame:
2647 /* User typed "moves" or "oldmoves" while we
2648 were idle. Pretend we asked for these
2649 moves and soak them up so user can step
2650 through them and/or save them.
2653 gameMode = IcsObserving;
2656 ics_getting_history = H_GOT_UNREQ_HEADER;
2658 case EditGame: /*?*/
2659 case EditPosition: /*?*/
2660 /* Should above feature work in these modes too? */
2661 /* For now it doesn't */
2662 ics_getting_history = H_GOT_UNWANTED_HEADER;
2665 ics_getting_history = H_GOT_UNWANTED_HEADER;
2670 /* Is this the right one? */
2671 if (gameInfo.white && gameInfo.black &&
2672 strcmp(gameInfo.white, star_match[0]) == 0 &&
2673 strcmp(gameInfo.black, star_match[2]) == 0) {
2675 ics_getting_history = H_GOT_REQ_HEADER;
2678 case H_GOT_REQ_HEADER:
2679 case H_GOT_UNREQ_HEADER:
2680 case H_GOT_UNWANTED_HEADER:
2681 case H_GETTING_MOVES:
2682 /* Should not happen */
2683 DisplayError(_("Error gathering move list: two headers"), 0);
2684 ics_getting_history = H_FALSE;
2688 /* Save player ratings into gameInfo if needed */
2689 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2690 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2691 (gameInfo.whiteRating == -1 ||
2692 gameInfo.blackRating == -1)) {
2694 gameInfo.whiteRating = string_to_rating(star_match[1]);
2695 gameInfo.blackRating = string_to_rating(star_match[3]);
2696 if (appData.debugMode)
2697 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2698 gameInfo.whiteRating, gameInfo.blackRating);
2703 if (looking_at(buf, &i,
2704 "* * match, initial time: * minute*, increment: * second")) {
2705 /* Header for a move list -- second line */
2706 /* Initial board will follow if this is a wild game */
2707 if (gameInfo.event != NULL) free(gameInfo.event);
2708 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2709 gameInfo.event = StrSave(str);
2710 /* [HGM] we switched variant. Translate boards if needed. */
2711 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2715 if (looking_at(buf, &i, "Move ")) {
2716 /* Beginning of a move list */
2717 switch (ics_getting_history) {
2719 /* Normally should not happen */
2720 /* Maybe user hit reset while we were parsing */
2723 /* Happens if we are ignoring a move list that is not
2724 * the one we just requested. Common if the user
2725 * tries to observe two games without turning off
2728 case H_GETTING_MOVES:
2729 /* Should not happen */
2730 DisplayError(_("Error gathering move list: nested"), 0);
2731 ics_getting_history = H_FALSE;
2733 case H_GOT_REQ_HEADER:
2734 ics_getting_history = H_GETTING_MOVES;
2735 started = STARTED_MOVES;
2737 if (oldi > next_out) {
2738 SendToPlayer(&buf[next_out], oldi - next_out);
2741 case H_GOT_UNREQ_HEADER:
2742 ics_getting_history = H_GETTING_MOVES;
2743 started = STARTED_MOVES_NOHIDE;
2746 case H_GOT_UNWANTED_HEADER:
2747 ics_getting_history = H_FALSE;
2753 if (looking_at(buf, &i, "% ") ||
2754 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2755 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2756 savingComment = FALSE;
2759 case STARTED_MOVES_NOHIDE:
2760 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2761 parse[parse_pos + i - oldi] = NULLCHAR;
2762 ParseGameHistory(parse);
2764 if (appData.zippyPlay && first.initDone) {
2765 FeedMovesToProgram(&first, forwardMostMove);
2766 if (gameMode == IcsPlayingWhite) {
2767 if (WhiteOnMove(forwardMostMove)) {
2768 if (first.sendTime) {
2769 if (first.useColors) {
2770 SendToProgram("black\n", &first);
2772 SendTimeRemaining(&first, TRUE);
2775 if (first.useColors) {
2776 SendToProgram("white\ngo\n", &first);
2778 SendToProgram("go\n", &first);
2781 if (first.useColors) {
2782 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2784 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2786 first.maybeThinking = TRUE;
2788 if (first.usePlayother) {
2789 if (first.sendTime) {
2790 SendTimeRemaining(&first, TRUE);
2792 SendToProgram("playother\n", &first);
2798 } else if (gameMode == IcsPlayingBlack) {
2799 if (!WhiteOnMove(forwardMostMove)) {
2800 if (first.sendTime) {
2801 if (first.useColors) {
2802 SendToProgram("white\n", &first);
2804 SendTimeRemaining(&first, FALSE);
2807 if (first.useColors) {
2808 SendToProgram("black\ngo\n", &first);
2810 SendToProgram("go\n", &first);
2813 if (first.useColors) {
2814 SendToProgram("black\n", &first);
2816 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2818 first.maybeThinking = TRUE;
2820 if (first.usePlayother) {
2821 if (first.sendTime) {
2822 SendTimeRemaining(&first, FALSE);
2824 SendToProgram("playother\n", &first);
2833 if (gameMode == IcsObserving && ics_gamenum == -1) {
2834 /* Moves came from oldmoves or moves command
2835 while we weren't doing anything else.
2837 currentMove = forwardMostMove;
2838 ClearHighlights();/*!!could figure this out*/
2839 flipView = appData.flipView;
2840 DrawPosition(FALSE, boards[currentMove]);
2841 DisplayBothClocks();
2842 sprintf(str, "%s vs. %s",
2843 gameInfo.white, gameInfo.black);
2847 /* Moves were history of an active game */
2848 if (gameInfo.resultDetails != NULL) {
2849 free(gameInfo.resultDetails);
2850 gameInfo.resultDetails = NULL;
2853 HistorySet(parseList, backwardMostMove,
2854 forwardMostMove, currentMove-1);
2855 DisplayMove(currentMove - 1);
2856 if (started == STARTED_MOVES) next_out = i;
2857 started = STARTED_NONE;
2858 ics_getting_history = H_FALSE;
2861 case STARTED_OBSERVE:
2862 started = STARTED_NONE;
2863 SendToICS(ics_prefix);
2864 SendToICS("refresh\n");
2870 if(bookHit) { // [HGM] book: simulate book reply
2871 static char bookMove[MSG_SIZ]; // a bit generous?
2873 programStats.nodes = programStats.depth = programStats.time =
2874 programStats.score = programStats.got_only_move = 0;
2875 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2877 strcpy(bookMove, "move ");
2878 strcat(bookMove, bookHit);
2879 HandleMachineMove(bookMove, &first);
2884 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2885 started == STARTED_HOLDINGS ||
2886 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2887 /* Accumulate characters in move list or board */
2888 parse[parse_pos++] = buf[i];
2891 /* Start of game messages. Mostly we detect start of game
2892 when the first board image arrives. On some versions
2893 of the ICS, though, we need to do a "refresh" after starting
2894 to observe in order to get the current board right away. */
2895 if (looking_at(buf, &i, "Adding game * to observation list")) {
2896 started = STARTED_OBSERVE;
2900 /* Handle auto-observe */
2901 if (appData.autoObserve &&
2902 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2903 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2905 /* Choose the player that was highlighted, if any. */
2906 if (star_match[0][0] == '\033' ||
2907 star_match[1][0] != '\033') {
2908 player = star_match[0];
2910 player = star_match[2];
2912 sprintf(str, "%sobserve %s\n",
2913 ics_prefix, StripHighlightAndTitle(player));
2916 /* Save ratings from notify string */
2917 strcpy(player1Name, star_match[0]);
2918 player1Rating = string_to_rating(star_match[1]);
2919 strcpy(player2Name, star_match[2]);
2920 player2Rating = string_to_rating(star_match[3]);
2922 if (appData.debugMode)
2924 "Ratings from 'Game notification:' %s %d, %s %d\n",
2925 player1Name, player1Rating,
2926 player2Name, player2Rating);
2931 /* Deal with automatic examine mode after a game,
2932 and with IcsObserving -> IcsExamining transition */
2933 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2934 looking_at(buf, &i, "has made you an examiner of game *")) {
2936 int gamenum = atoi(star_match[0]);
2937 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2938 gamenum == ics_gamenum) {
2939 /* We were already playing or observing this game;
2940 no need to refetch history */
2941 gameMode = IcsExamining;
2943 pauseExamForwardMostMove = forwardMostMove;
2944 } else if (currentMove < forwardMostMove) {
2945 ForwardInner(forwardMostMove);
2948 /* I don't think this case really can happen */
2949 SendToICS(ics_prefix);
2950 SendToICS("refresh\n");
2955 /* Error messages */
2956 // if (ics_user_moved) {
2957 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2958 if (looking_at(buf, &i, "Illegal move") ||
2959 looking_at(buf, &i, "Not a legal move") ||
2960 looking_at(buf, &i, "Your king is in check") ||
2961 looking_at(buf, &i, "It isn't your turn") ||
2962 looking_at(buf, &i, "It is not your move")) {
2964 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2965 currentMove = --forwardMostMove;
2966 DisplayMove(currentMove - 1); /* before DMError */
2967 DrawPosition(FALSE, boards[currentMove]);
2969 DisplayBothClocks();
2971 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2977 if (looking_at(buf, &i, "still have time") ||
2978 looking_at(buf, &i, "not out of time") ||
2979 looking_at(buf, &i, "either player is out of time") ||
2980 looking_at(buf, &i, "has timeseal; checking")) {
2981 /* We must have called his flag a little too soon */
2982 whiteFlag = blackFlag = FALSE;
2986 if (looking_at(buf, &i, "added * seconds to") ||
2987 looking_at(buf, &i, "seconds were added to")) {
2988 /* Update the clocks */
2989 SendToICS(ics_prefix);
2990 SendToICS("refresh\n");
2994 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2995 ics_clock_paused = TRUE;
3000 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3001 ics_clock_paused = FALSE;
3006 /* Grab player ratings from the Creating: message.
3007 Note we have to check for the special case when
3008 the ICS inserts things like [white] or [black]. */
3009 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3010 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3012 0 player 1 name (not necessarily white)
3014 2 empty, white, or black (IGNORED)
3015 3 player 2 name (not necessarily black)
3018 The names/ratings are sorted out when the game
3019 actually starts (below).
3021 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3022 player1Rating = string_to_rating(star_match[1]);
3023 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3024 player2Rating = string_to_rating(star_match[4]);
3026 if (appData.debugMode)
3028 "Ratings from 'Creating:' %s %d, %s %d\n",
3029 player1Name, player1Rating,
3030 player2Name, player2Rating);
3035 /* Improved generic start/end-of-game messages */
3036 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3037 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3038 /* If tkind == 0: */
3039 /* star_match[0] is the game number */
3040 /* [1] is the white player's name */
3041 /* [2] is the black player's name */
3042 /* For end-of-game: */
3043 /* [3] is the reason for the game end */
3044 /* [4] is a PGN end game-token, preceded by " " */
3045 /* For start-of-game: */
3046 /* [3] begins with "Creating" or "Continuing" */
3047 /* [4] is " *" or empty (don't care). */
3048 int gamenum = atoi(star_match[0]);
3049 char *whitename, *blackname, *why, *endtoken;
3050 ChessMove endtype = (ChessMove) 0;
3053 whitename = star_match[1];
3054 blackname = star_match[2];
3055 why = star_match[3];
3056 endtoken = star_match[4];
3058 whitename = star_match[1];
3059 blackname = star_match[3];
3060 why = star_match[5];
3061 endtoken = star_match[6];
3064 /* Game start messages */
3065 if (strncmp(why, "Creating ", 9) == 0 ||
3066 strncmp(why, "Continuing ", 11) == 0) {
3067 gs_gamenum = gamenum;
3068 strcpy(gs_kind, strchr(why, ' ') + 1);
3070 if (appData.zippyPlay) {
3071 ZippyGameStart(whitename, blackname);
3077 /* Game end messages */
3078 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3079 ics_gamenum != gamenum) {
3082 while (endtoken[0] == ' ') endtoken++;
3083 switch (endtoken[0]) {
3086 endtype = GameUnfinished;
3089 endtype = BlackWins;
3092 if (endtoken[1] == '/')
3093 endtype = GameIsDrawn;
3095 endtype = WhiteWins;
3098 GameEnds(endtype, why, GE_ICS);
3100 if (appData.zippyPlay && first.initDone) {
3101 ZippyGameEnd(endtype, why);
3102 if (first.pr == NULL) {
3103 /* Start the next process early so that we'll
3104 be ready for the next challenge */
3105 StartChessProgram(&first);
3107 /* Send "new" early, in case this command takes
3108 a long time to finish, so that we'll be ready
3109 for the next challenge. */
3110 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3117 if (looking_at(buf, &i, "Removing game * from observation") ||
3118 looking_at(buf, &i, "no longer observing game *") ||
3119 looking_at(buf, &i, "Game * (*) has no examiners")) {
3120 if (gameMode == IcsObserving &&
3121 atoi(star_match[0]) == ics_gamenum)
3123 /* icsEngineAnalyze */
3124 if (appData.icsEngineAnalyze) {
3131 ics_user_moved = FALSE;
3136 if (looking_at(buf, &i, "no longer examining game *")) {
3137 if (gameMode == IcsExamining &&
3138 atoi(star_match[0]) == ics_gamenum)
3142 ics_user_moved = FALSE;
3147 /* Advance leftover_start past any newlines we find,
3148 so only partial lines can get reparsed */
3149 if (looking_at(buf, &i, "\n")) {
3150 prevColor = curColor;
3151 if (curColor != ColorNormal) {
3152 if (oldi > next_out) {
3153 SendToPlayer(&buf[next_out], oldi - next_out);
3156 Colorize(ColorNormal, FALSE);
3157 curColor = ColorNormal;
3159 if (started == STARTED_BOARD) {
3160 started = STARTED_NONE;
3161 parse[parse_pos] = NULLCHAR;
3162 ParseBoard12(parse);
3165 /* Send premove here */
3166 if (appData.premove) {
3168 if (currentMove == 0 &&
3169 gameMode == IcsPlayingWhite &&
3170 appData.premoveWhite) {
3171 sprintf(str, "%s%s\n", ics_prefix,
3172 appData.premoveWhiteText);
3173 if (appData.debugMode)
3174 fprintf(debugFP, "Sending premove:\n");
3176 } else if (currentMove == 1 &&
3177 gameMode == IcsPlayingBlack &&
3178 appData.premoveBlack) {
3179 sprintf(str, "%s%s\n", ics_prefix,
3180 appData.premoveBlackText);
3181 if (appData.debugMode)
3182 fprintf(debugFP, "Sending premove:\n");
3184 } else if (gotPremove) {
3186 ClearPremoveHighlights();
3187 if (appData.debugMode)
3188 fprintf(debugFP, "Sending premove:\n");
3189 UserMoveEvent(premoveFromX, premoveFromY,
3190 premoveToX, premoveToY,
3195 /* Usually suppress following prompt */
3196 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3197 if (looking_at(buf, &i, "*% ")) {
3198 savingComment = FALSE;
3202 } else if (started == STARTED_HOLDINGS) {
3204 char new_piece[MSG_SIZ];
3205 started = STARTED_NONE;
3206 parse[parse_pos] = NULLCHAR;
3207 if (appData.debugMode)
3208 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3209 parse, currentMove);
3210 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3211 gamenum == ics_gamenum) {
3212 if (gameInfo.variant == VariantNormal) {
3213 /* [HGM] We seem to switch variant during a game!
3214 * Presumably no holdings were displayed, so we have
3215 * to move the position two files to the right to
3216 * create room for them!
3218 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3219 /* Get a move list just to see the header, which
3220 will tell us whether this is really bug or zh */
3221 if (ics_getting_history == H_FALSE) {
3222 ics_getting_history = H_REQUESTED;
3223 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3227 new_piece[0] = NULLCHAR;
3228 sscanf(parse, "game %d white [%s black [%s <- %s",
3229 &gamenum, white_holding, black_holding,
3231 white_holding[strlen(white_holding)-1] = NULLCHAR;
3232 black_holding[strlen(black_holding)-1] = NULLCHAR;
3233 /* [HGM] copy holdings to board holdings area */
3234 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3235 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3237 if (appData.zippyPlay && first.initDone) {
3238 ZippyHoldings(white_holding, black_holding,
3242 if (tinyLayout || smallLayout) {
3243 char wh[16], bh[16];
3244 PackHolding(wh, white_holding);
3245 PackHolding(bh, black_holding);
3246 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3247 gameInfo.white, gameInfo.black);
3249 sprintf(str, "%s [%s] vs. %s [%s]",
3250 gameInfo.white, white_holding,
3251 gameInfo.black, black_holding);
3254 DrawPosition(FALSE, boards[currentMove]);
3257 /* Suppress following prompt */
3258 if (looking_at(buf, &i, "*% ")) {
3259 savingComment = FALSE;
3266 i++; /* skip unparsed character and loop back */
3269 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3270 started != STARTED_HOLDINGS && i > next_out) {
3271 SendToPlayer(&buf[next_out], i - next_out);
3274 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3276 leftover_len = buf_len - leftover_start;
3277 /* if buffer ends with something we couldn't parse,
3278 reparse it after appending the next read */
3280 } else if (count == 0) {
3281 RemoveInputSource(isr);
3282 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3284 DisplayFatalError(_("Error reading from ICS"), error, 1);
3289 /* Board style 12 looks like this:
3291 <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
3293 * The "<12> " is stripped before it gets to this routine. The two
3294 * trailing 0's (flip state and clock ticking) are later addition, and
3295 * some chess servers may not have them, or may have only the first.
3296 * Additional trailing fields may be added in the future.
3299 #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"
3301 #define RELATION_OBSERVING_PLAYED 0
3302 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3303 #define RELATION_PLAYING_MYMOVE 1
3304 #define RELATION_PLAYING_NOTMYMOVE -1
3305 #define RELATION_EXAMINING 2
3306 #define RELATION_ISOLATED_BOARD -3
3307 #define RELATION_STARTING_POSITION -4 /* FICS only */
3310 ParseBoard12(string)
3313 GameMode newGameMode;
3314 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3315 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3316 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3317 char to_play, board_chars[200];
3318 char move_str[500], str[500], elapsed_time[500];
3319 char black[32], white[32];
3321 int prevMove = currentMove;
3324 int fromX, fromY, toX, toY;
3326 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3327 char *bookHit = NULL; // [HGM] book
3329 fromX = fromY = toX = toY = -1;
3333 if (appData.debugMode)
3334 fprintf(debugFP, _("Parsing board: %s\n"), string);
3336 move_str[0] = NULLCHAR;
3337 elapsed_time[0] = NULLCHAR;
3338 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3340 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3341 if(string[i] == ' ') { ranks++; files = 0; }
3345 for(j = 0; j <i; j++) board_chars[j] = string[j];
3346 board_chars[i] = '\0';
3349 n = sscanf(string, PATTERN, &to_play, &double_push,
3350 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3351 &gamenum, white, black, &relation, &basetime, &increment,
3352 &white_stren, &black_stren, &white_time, &black_time,
3353 &moveNum, str, elapsed_time, move_str, &ics_flip,
3357 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3358 DisplayError(str, 0);
3362 /* Convert the move number to internal form */
3363 moveNum = (moveNum - 1) * 2;
3364 if (to_play == 'B') moveNum++;
3365 if (moveNum >= MAX_MOVES) {
3366 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3372 case RELATION_OBSERVING_PLAYED:
3373 case RELATION_OBSERVING_STATIC:
3374 if (gamenum == -1) {
3375 /* Old ICC buglet */
3376 relation = RELATION_OBSERVING_STATIC;
3378 newGameMode = IcsObserving;
3380 case RELATION_PLAYING_MYMOVE:
3381 case RELATION_PLAYING_NOTMYMOVE:
3383 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3384 IcsPlayingWhite : IcsPlayingBlack;
3386 case RELATION_EXAMINING:
3387 newGameMode = IcsExamining;
3389 case RELATION_ISOLATED_BOARD:
3391 /* Just display this board. If user was doing something else,
3392 we will forget about it until the next board comes. */
3393 newGameMode = IcsIdle;
3395 case RELATION_STARTING_POSITION:
3396 newGameMode = gameMode;
3400 /* Modify behavior for initial board display on move listing
3403 switch (ics_getting_history) {
3407 case H_GOT_REQ_HEADER:
3408 case H_GOT_UNREQ_HEADER:
3409 /* This is the initial position of the current game */
3410 gamenum = ics_gamenum;
3411 moveNum = 0; /* old ICS bug workaround */
3412 if (to_play == 'B') {
3413 startedFromSetupPosition = TRUE;
3414 blackPlaysFirst = TRUE;
3416 if (forwardMostMove == 0) forwardMostMove = 1;
3417 if (backwardMostMove == 0) backwardMostMove = 1;
3418 if (currentMove == 0) currentMove = 1;
3420 newGameMode = gameMode;
3421 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3423 case H_GOT_UNWANTED_HEADER:
3424 /* This is an initial board that we don't want */
3426 case H_GETTING_MOVES:
3427 /* Should not happen */
3428 DisplayError(_("Error gathering move list: extra board"), 0);
3429 ics_getting_history = H_FALSE;
3433 /* Take action if this is the first board of a new game, or of a
3434 different game than is currently being displayed. */
3435 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3436 relation == RELATION_ISOLATED_BOARD) {
3438 /* Forget the old game and get the history (if any) of the new one */
3439 if (gameMode != BeginningOfGame) {
3443 if (appData.autoRaiseBoard) BoardToTop();
3445 if (gamenum == -1) {
3446 newGameMode = IcsIdle;
3447 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3448 appData.getMoveList) {
3449 /* Need to get game history */
3450 ics_getting_history = H_REQUESTED;
3451 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3455 /* Initially flip the board to have black on the bottom if playing
3456 black or if the ICS flip flag is set, but let the user change
3457 it with the Flip View button. */
3458 flipView = appData.autoFlipView ?
3459 (newGameMode == IcsPlayingBlack) || ics_flip :
3462 /* Done with values from previous mode; copy in new ones */
3463 gameMode = newGameMode;
3465 ics_gamenum = gamenum;
3466 if (gamenum == gs_gamenum) {
3467 int klen = strlen(gs_kind);
3468 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3469 sprintf(str, "ICS %s", gs_kind);
3470 gameInfo.event = StrSave(str);
3472 gameInfo.event = StrSave("ICS game");
3474 gameInfo.site = StrSave(appData.icsHost);
3475 gameInfo.date = PGNDate();
3476 gameInfo.round = StrSave("-");
3477 gameInfo.white = StrSave(white);
3478 gameInfo.black = StrSave(black);
3479 timeControl = basetime * 60 * 1000;
3481 timeIncrement = increment * 1000;
3482 movesPerSession = 0;
3483 gameInfo.timeControl = TimeControlTagValue();
3484 VariantSwitch(board, StringToVariant(gameInfo.event) );
3485 if (appData.debugMode) {
3486 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3487 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3488 setbuf(debugFP, NULL);
3491 gameInfo.outOfBook = NULL;
3493 /* Do we have the ratings? */
3494 if (strcmp(player1Name, white) == 0 &&
3495 strcmp(player2Name, black) == 0) {
3496 if (appData.debugMode)
3497 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3498 player1Rating, player2Rating);
3499 gameInfo.whiteRating = player1Rating;
3500 gameInfo.blackRating = player2Rating;
3501 } else if (strcmp(player2Name, white) == 0 &&
3502 strcmp(player1Name, black) == 0) {
3503 if (appData.debugMode)
3504 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505 player2Rating, player1Rating);
3506 gameInfo.whiteRating = player2Rating;
3507 gameInfo.blackRating = player1Rating;
3509 player1Name[0] = player2Name[0] = NULLCHAR;
3511 /* Silence shouts if requested */
3512 if (appData.quietPlay &&
3513 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3514 SendToICS(ics_prefix);
3515 SendToICS("set shout 0\n");
3519 /* Deal with midgame name changes */
3521 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3522 if (gameInfo.white) free(gameInfo.white);
3523 gameInfo.white = StrSave(white);
3525 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3526 if (gameInfo.black) free(gameInfo.black);
3527 gameInfo.black = StrSave(black);
3531 /* Throw away game result if anything actually changes in examine mode */
3532 if (gameMode == IcsExamining && !newGame) {
3533 gameInfo.result = GameUnfinished;
3534 if (gameInfo.resultDetails != NULL) {
3535 free(gameInfo.resultDetails);
3536 gameInfo.resultDetails = NULL;
3540 /* In pausing && IcsExamining mode, we ignore boards coming
3541 in if they are in a different variation than we are. */
3542 if (pauseExamInvalid) return;
3543 if (pausing && gameMode == IcsExamining) {
3544 if (moveNum <= pauseExamForwardMostMove) {
3545 pauseExamInvalid = TRUE;
3546 forwardMostMove = pauseExamForwardMostMove;
3551 if (appData.debugMode) {
3552 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3554 /* Parse the board */
3555 for (k = 0; k < ranks; k++) {
3556 for (j = 0; j < files; j++)
3557 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3558 if(gameInfo.holdingsWidth > 1) {
3559 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3560 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3563 CopyBoard(boards[moveNum], board);
3565 startedFromSetupPosition =
3566 !CompareBoards(board, initialPosition);
3567 if(startedFromSetupPosition)
3568 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3571 /* [HGM] Set castling rights. Take the outermost Rooks,
3572 to make it also work for FRC opening positions. Note that board12
3573 is really defective for later FRC positions, as it has no way to
3574 indicate which Rook can castle if they are on the same side of King.
3575 For the initial position we grant rights to the outermost Rooks,
3576 and remember thos rights, and we then copy them on positions
3577 later in an FRC game. This means WB might not recognize castlings with
3578 Rooks that have moved back to their original position as illegal,
3579 but in ICS mode that is not its job anyway.
3581 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3582 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3584 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3585 if(board[0][i] == WhiteRook) j = i;
3586 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3587 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3588 if(board[0][i] == WhiteRook) j = i;
3589 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3590 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3591 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3592 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3593 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3594 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3595 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3598 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3599 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3600 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601 if(board[BOARD_HEIGHT-1][k] == bKing)
3602 initialRights[5] = castlingRights[moveNum][5] = k;
3604 r = castlingRights[moveNum][0] = initialRights[0];
3605 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3606 r = castlingRights[moveNum][1] = initialRights[1];
3607 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3608 r = castlingRights[moveNum][3] = initialRights[3];
3609 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3610 r = castlingRights[moveNum][4] = initialRights[4];
3611 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3612 /* wildcastle kludge: always assume King has rights */
3613 r = castlingRights[moveNum][2] = initialRights[2];
3614 r = castlingRights[moveNum][5] = initialRights[5];
3616 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3617 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3620 if (ics_getting_history == H_GOT_REQ_HEADER ||
3621 ics_getting_history == H_GOT_UNREQ_HEADER) {
3622 /* This was an initial position from a move list, not
3623 the current position */
3627 /* Update currentMove and known move number limits */
3628 newMove = newGame || moveNum > forwardMostMove;
3630 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3631 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3632 takeback = forwardMostMove - moveNum;
3633 for (i = 0; i < takeback; i++) {
3634 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3635 SendToProgram("undo\n", &first);
3640 forwardMostMove = backwardMostMove = currentMove = moveNum;
3641 if (gameMode == IcsExamining && moveNum == 0) {
3642 /* Workaround for ICS limitation: we are not told the wild
3643 type when starting to examine a game. But if we ask for
3644 the move list, the move list header will tell us */
3645 ics_getting_history = H_REQUESTED;
3646 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3649 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3650 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3651 forwardMostMove = moveNum;
3652 if (!pausing || currentMove > forwardMostMove)
3653 currentMove = forwardMostMove;
3655 /* New part of history that is not contiguous with old part */
3656 if (pausing && gameMode == IcsExamining) {
3657 pauseExamInvalid = TRUE;
3658 forwardMostMove = pauseExamForwardMostMove;
3661 forwardMostMove = backwardMostMove = currentMove = moveNum;
3662 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3663 ics_getting_history = H_REQUESTED;
3664 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3669 /* Update the clocks */
3670 if (strchr(elapsed_time, '.')) {
3672 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3673 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3675 /* Time is in seconds */
3676 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3677 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3682 if (appData.zippyPlay && newGame &&
3683 gameMode != IcsObserving && gameMode != IcsIdle &&
3684 gameMode != IcsExamining)
3685 ZippyFirstBoard(moveNum, basetime, increment);
3688 /* Put the move on the move list, first converting
3689 to canonical algebraic form. */
3691 if (appData.debugMode) {
3692 if (appData.debugMode) { int f = forwardMostMove;
3693 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3694 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3696 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3697 fprintf(debugFP, "moveNum = %d\n", moveNum);
3698 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3699 setbuf(debugFP, NULL);
3701 if (moveNum <= backwardMostMove) {
3702 /* We don't know what the board looked like before
3704 strcpy(parseList[moveNum - 1], move_str);
3705 strcat(parseList[moveNum - 1], " ");
3706 strcat(parseList[moveNum - 1], elapsed_time);
3707 moveList[moveNum - 1][0] = NULLCHAR;
3708 } else if (strcmp(move_str, "none") == 0) {
3709 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3710 /* Again, we don't know what the board looked like;
3711 this is really the start of the game. */
3712 parseList[moveNum - 1][0] = NULLCHAR;
3713 moveList[moveNum - 1][0] = NULLCHAR;
3714 backwardMostMove = moveNum;
3715 startedFromSetupPosition = TRUE;
3716 fromX = fromY = toX = toY = -1;
3718 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3719 // So we parse the long-algebraic move string in stead of the SAN move
3720 int valid; char buf[MSG_SIZ], *prom;
3722 // str looks something like "Q/a1-a2"; kill the slash
3724 sprintf(buf, "%c%s", str[0], str+2);
3725 else strcpy(buf, str); // might be castling
3726 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3727 strcat(buf, prom); // long move lacks promo specification!
3728 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3729 if(appData.debugMode)
3730 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3731 strcpy(move_str, buf);
3733 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3734 &fromX, &fromY, &toX, &toY, &promoChar)
3735 || ParseOneMove(buf, moveNum - 1, &moveType,
3736 &fromX, &fromY, &toX, &toY, &promoChar);
3737 // end of long SAN patch
3739 (void) CoordsToAlgebraic(boards[moveNum - 1],
3740 PosFlags(moveNum - 1), EP_UNKNOWN,
3741 fromY, fromX, toY, toX, promoChar,
3742 parseList[moveNum-1]);
3743 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3744 castlingRights[moveNum]) ) {
3750 if(gameInfo.variant != VariantShogi)
3751 strcat(parseList[moveNum - 1], "+");
3754 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3755 strcat(parseList[moveNum - 1], "#");
3758 strcat(parseList[moveNum - 1], " ");
3759 strcat(parseList[moveNum - 1], elapsed_time);
3760 /* currentMoveString is set as a side-effect of ParseOneMove */
3761 strcpy(moveList[moveNum - 1], currentMoveString);
3762 strcat(moveList[moveNum - 1], "\n");
3764 /* Move from ICS was illegal!? Punt. */
3765 if (appData.debugMode) {
3766 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3767 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3770 if (appData.testLegality && appData.debugMode) {
3771 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3772 DisplayError(str, 0);
3775 strcpy(parseList[moveNum - 1], move_str);
3776 strcat(parseList[moveNum - 1], " ");
3777 strcat(parseList[moveNum - 1], elapsed_time);
3778 moveList[moveNum - 1][0] = NULLCHAR;
3779 fromX = fromY = toX = toY = -1;
3782 if (appData.debugMode) {
3783 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3784 setbuf(debugFP, NULL);
3788 /* Send move to chess program (BEFORE animating it). */
3789 if (appData.zippyPlay && !newGame && newMove &&
3790 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3792 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3793 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3794 if (moveList[moveNum - 1][0] == NULLCHAR) {
3795 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3797 DisplayError(str, 0);
3799 if (first.sendTime) {
3800 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3802 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3803 if (firstMove && !bookHit) {
3805 if (first.useColors) {
3806 SendToProgram(gameMode == IcsPlayingWhite ?
3808 "black\ngo\n", &first);
3810 SendToProgram("go\n", &first);
3812 first.maybeThinking = TRUE;
3815 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3816 if (moveList[moveNum - 1][0] == NULLCHAR) {
3817 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3818 DisplayError(str, 0);
3820 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3821 SendMoveToProgram(moveNum - 1, &first);
3828 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3829 /* If move comes from a remote source, animate it. If it
3830 isn't remote, it will have already been animated. */
3831 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3832 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3834 if (!pausing && appData.highlightLastMove) {
3835 SetHighlights(fromX, fromY, toX, toY);
3839 /* Start the clocks */
3840 whiteFlag = blackFlag = FALSE;
3841 appData.clockMode = !(basetime == 0 && increment == 0);
3843 ics_clock_paused = TRUE;
3845 } else if (ticking == 1) {
3846 ics_clock_paused = FALSE;
3848 if (gameMode == IcsIdle ||
3849 relation == RELATION_OBSERVING_STATIC ||
3850 relation == RELATION_EXAMINING ||
3852 DisplayBothClocks();
3856 /* Display opponents and material strengths */
3857 if (gameInfo.variant != VariantBughouse &&
3858 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3859 if (tinyLayout || smallLayout) {
3860 if(gameInfo.variant == VariantNormal)
3861 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3862 gameInfo.white, white_stren, gameInfo.black, black_stren,
3863 basetime, increment);
3865 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3866 gameInfo.white, white_stren, gameInfo.black, black_stren,
3867 basetime, increment, (int) gameInfo.variant);
3869 if(gameInfo.variant == VariantNormal)
3870 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3871 gameInfo.white, white_stren, gameInfo.black, black_stren,
3872 basetime, increment);
3874 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3875 gameInfo.white, white_stren, gameInfo.black, black_stren,
3876 basetime, increment, VariantName(gameInfo.variant));
3879 if (appData.debugMode) {
3880 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3885 /* Display the board */
3886 if (!pausing && !appData.noGUI) {
3888 if (appData.premove)
3890 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3891 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3892 ClearPremoveHighlights();
3894 DrawPosition(FALSE, boards[currentMove]);
3895 DisplayMove(moveNum - 1);
3896 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3897 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3898 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3899 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3903 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3905 if(bookHit) { // [HGM] book: simulate book reply
3906 static char bookMove[MSG_SIZ]; // a bit generous?
3908 programStats.nodes = programStats.depth = programStats.time =
3909 programStats.score = programStats.got_only_move = 0;
3910 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3912 strcpy(bookMove, "move ");
3913 strcat(bookMove, bookHit);
3914 HandleMachineMove(bookMove, &first);
3923 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3924 ics_getting_history = H_REQUESTED;
3925 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3931 AnalysisPeriodicEvent(force)
3934 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3935 && !force) || !appData.periodicUpdates)
3938 /* Send . command to Crafty to collect stats */
3939 SendToProgram(".\n", &first);
3941 /* Don't send another until we get a response (this makes
3942 us stop sending to old Crafty's which don't understand
3943 the "." command (sending illegal cmds resets node count & time,
3944 which looks bad)) */
3945 programStats.ok_to_send = 0;
3949 SendMoveToProgram(moveNum, cps)
3951 ChessProgramState *cps;
3955 if (cps->useUsermove) {
3956 SendToProgram("usermove ", cps);
3960 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3961 int len = space - parseList[moveNum];
3962 memcpy(buf, parseList[moveNum], len);
3964 buf[len] = NULLCHAR;
3966 sprintf(buf, "%s\n", parseList[moveNum]);
3968 SendToProgram(buf, cps);
3970 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3971 AlphaRank(moveList[moveNum], 4);
3972 SendToProgram(moveList[moveNum], cps);
3973 AlphaRank(moveList[moveNum], 4); // and back
3975 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3976 * the engine. It would be nice to have a better way to identify castle
3978 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3979 && cps->useOOCastle) {
3980 int fromX = moveList[moveNum][0] - AAA;
3981 int fromY = moveList[moveNum][1] - ONE;
3982 int toX = moveList[moveNum][2] - AAA;
3983 int toY = moveList[moveNum][3] - ONE;
3984 if((boards[moveNum][fromY][fromX] == WhiteKing
3985 && boards[moveNum][toY][toX] == WhiteRook)
3986 || (boards[moveNum][fromY][fromX] == BlackKing
3987 && boards[moveNum][toY][toX] == BlackRook)) {
3988 if(toX > fromX) SendToProgram("O-O\n", cps);
3989 else SendToProgram("O-O-O\n", cps);
3991 else SendToProgram(moveList[moveNum], cps);
3993 else SendToProgram(moveList[moveNum], cps);
3994 /* End of additions by Tord */
3997 /* [HGM] setting up the opening has brought engine in force mode! */
3998 /* Send 'go' if we are in a mode where machine should play. */
3999 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4000 (gameMode == TwoMachinesPlay ||
4002 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4004 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4005 SendToProgram("go\n", cps);
4006 if (appData.debugMode) {
4007 fprintf(debugFP, "(extra)\n");
4010 setboardSpoiledMachineBlack = 0;
4014 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4016 int fromX, fromY, toX, toY;
4018 char user_move[MSG_SIZ];
4022 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4023 (int)moveType, fromX, fromY, toX, toY);
4024 DisplayError(user_move + strlen("say "), 0);
4026 case WhiteKingSideCastle:
4027 case BlackKingSideCastle:
4028 case WhiteQueenSideCastleWild:
4029 case BlackQueenSideCastleWild:
4031 case WhiteHSideCastleFR:
4032 case BlackHSideCastleFR:
4034 sprintf(user_move, "o-o\n");
4036 case WhiteQueenSideCastle:
4037 case BlackQueenSideCastle:
4038 case WhiteKingSideCastleWild:
4039 case BlackKingSideCastleWild:
4041 case WhiteASideCastleFR:
4042 case BlackASideCastleFR:
4044 sprintf(user_move, "o-o-o\n");
4046 case WhitePromotionQueen:
4047 case BlackPromotionQueen:
4048 case WhitePromotionRook:
4049 case BlackPromotionRook:
4050 case WhitePromotionBishop:
4051 case BlackPromotionBishop:
4052 case WhitePromotionKnight:
4053 case BlackPromotionKnight:
4054 case WhitePromotionKing:
4055 case BlackPromotionKing:
4056 case WhitePromotionChancellor:
4057 case BlackPromotionChancellor:
4058 case WhitePromotionArchbishop:
4059 case BlackPromotionArchbishop:
4060 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4061 sprintf(user_move, "%c%c%c%c=%c\n",
4062 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4063 PieceToChar(WhiteFerz));
4064 else if(gameInfo.variant == VariantGreat)
4065 sprintf(user_move, "%c%c%c%c=%c\n",
4066 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067 PieceToChar(WhiteMan));
4069 sprintf(user_move, "%c%c%c%c=%c\n",
4070 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071 PieceToChar(PromoPiece(moveType)));
4075 sprintf(user_move, "%c@%c%c\n",
4076 ToUpper(PieceToChar((ChessSquare) fromX)),
4077 AAA + toX, ONE + toY);
4080 case WhiteCapturesEnPassant:
4081 case BlackCapturesEnPassant:
4082 case IllegalMove: /* could be a variant we don't quite understand */
4083 sprintf(user_move, "%c%c%c%c\n",
4084 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4087 SendToICS(user_move);
4091 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4096 if (rf == DROP_RANK) {
4097 sprintf(move, "%c@%c%c\n",
4098 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4100 if (promoChar == 'x' || promoChar == NULLCHAR) {
4101 sprintf(move, "%c%c%c%c\n",
4102 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4104 sprintf(move, "%c%c%c%c%c\n",
4105 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4111 ProcessICSInitScript(f)
4116 while (fgets(buf, MSG_SIZ, f)) {
4117 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4124 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4126 AlphaRank(char *move, int n)
4128 // char *p = move, c; int x, y;
4130 if (appData.debugMode) {
4131 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4135 move[2]>='0' && move[2]<='9' &&
4136 move[3]>='a' && move[3]<='x' ) {
4138 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4139 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4141 if(move[0]>='0' && move[0]<='9' &&
4142 move[1]>='a' && move[1]<='x' &&
4143 move[2]>='0' && move[2]<='9' &&
4144 move[3]>='a' && move[3]<='x' ) {
4145 /* input move, Shogi -> normal */
4146 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4147 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4148 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4149 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4152 move[3]>='0' && move[3]<='9' &&
4153 move[2]>='a' && move[2]<='x' ) {
4155 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4156 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4159 move[0]>='a' && move[0]<='x' &&
4160 move[3]>='0' && move[3]<='9' &&
4161 move[2]>='a' && move[2]<='x' ) {
4162 /* output move, normal -> Shogi */
4163 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4164 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4165 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4166 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4167 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4169 if (appData.debugMode) {
4170 fprintf(debugFP, " out = '%s'\n", move);
4174 /* Parser for moves from gnuchess, ICS, or user typein box */
4176 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4179 ChessMove *moveType;
4180 int *fromX, *fromY, *toX, *toY;
4183 if (appData.debugMode) {
4184 fprintf(debugFP, "move to parse: %s\n", move);
4186 *moveType = yylexstr(moveNum, move);
4188 switch (*moveType) {
4189 case WhitePromotionChancellor:
4190 case BlackPromotionChancellor:
4191 case WhitePromotionArchbishop:
4192 case BlackPromotionArchbishop:
4193 case WhitePromotionQueen:
4194 case BlackPromotionQueen:
4195 case WhitePromotionRook:
4196 case BlackPromotionRook:
4197 case WhitePromotionBishop:
4198 case BlackPromotionBishop:
4199 case WhitePromotionKnight:
4200 case BlackPromotionKnight:
4201 case WhitePromotionKing:
4202 case BlackPromotionKing:
4204 case WhiteCapturesEnPassant:
4205 case BlackCapturesEnPassant:
4206 case WhiteKingSideCastle:
4207 case WhiteQueenSideCastle:
4208 case BlackKingSideCastle:
4209 case BlackQueenSideCastle:
4210 case WhiteKingSideCastleWild:
4211 case WhiteQueenSideCastleWild:
4212 case BlackKingSideCastleWild:
4213 case BlackQueenSideCastleWild:
4214 /* Code added by Tord: */
4215 case WhiteHSideCastleFR:
4216 case WhiteASideCastleFR:
4217 case BlackHSideCastleFR:
4218 case BlackASideCastleFR:
4219 /* End of code added by Tord */
4220 case IllegalMove: /* bug or odd chess variant */
4221 *fromX = currentMoveString[0] - AAA;
4222 *fromY = currentMoveString[1] - ONE;
4223 *toX = currentMoveString[2] - AAA;
4224 *toY = currentMoveString[3] - ONE;
4225 *promoChar = currentMoveString[4];
4226 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4227 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4228 if (appData.debugMode) {
4229 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4231 *fromX = *fromY = *toX = *toY = 0;
4234 if (appData.testLegality) {
4235 return (*moveType != IllegalMove);
4237 return !(fromX == fromY && toX == toY);
4242 *fromX = *moveType == WhiteDrop ?
4243 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4244 (int) CharToPiece(ToLower(currentMoveString[0]));
4246 *toX = currentMoveString[2] - AAA;
4247 *toY = currentMoveString[3] - ONE;
4248 *promoChar = NULLCHAR;
4252 case ImpossibleMove:
4253 case (ChessMove) 0: /* end of file */
4262 if (appData.debugMode) {
4263 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4266 *fromX = *fromY = *toX = *toY = 0;
4267 *promoChar = NULLCHAR;
4272 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4273 // All positions will have equal probability, but the current method will not provide a unique
4274 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4280 int piecesLeft[(int)BlackPawn];
4281 int seed, nrOfShuffles;
4283 void GetPositionNumber()
4284 { // sets global variable seed
4287 seed = appData.defaultFrcPosition;
4288 if(seed < 0) { // randomize based on time for negative FRC position numbers
4289 for(i=0; i<50; i++) seed += random();
4290 seed = random() ^ random() >> 8 ^ random() << 8;
4291 if(seed<0) seed = -seed;
4295 int put(Board board, int pieceType, int rank, int n, int shade)
4296 // put the piece on the (n-1)-th empty squares of the given shade
4300 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4301 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4302 board[rank][i] = (ChessSquare) pieceType;
4303 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4305 piecesLeft[pieceType]--;
4313 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4314 // calculate where the next piece goes, (any empty square), and put it there
4318 i = seed % squaresLeft[shade];
4319 nrOfShuffles *= squaresLeft[shade];
4320 seed /= squaresLeft[shade];
4321 put(board, pieceType, rank, i, shade);
4324 void AddTwoPieces(Board board, int pieceType, int rank)
4325 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4327 int i, n=squaresLeft[ANY], j=n-1, k;
4329 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4330 i = seed % k; // pick one
4333 while(i >= j) i -= j--;
4334 j = n - 1 - j; i += j;
4335 put(board, pieceType, rank, j, ANY);
4336 put(board, pieceType, rank, i, ANY);
4339 void SetUpShuffle(Board board, int number)
4343 GetPositionNumber(); nrOfShuffles = 1;
4345 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4346 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4347 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4349 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4351 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4352 p = (int) board[0][i];
4353 if(p < (int) BlackPawn) piecesLeft[p] ++;
4354 board[0][i] = EmptySquare;
4357 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4358 // shuffles restricted to allow normal castling put KRR first
4359 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4360 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4361 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4362 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4363 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4364 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4365 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4366 put(board, WhiteRook, 0, 0, ANY);
4367 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4370 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4371 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4372 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4373 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4374 while(piecesLeft[p] >= 2) {
4375 AddOnePiece(board, p, 0, LITE);
4376 AddOnePiece(board, p, 0, DARK);
4378 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4381 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4382 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4383 // but we leave King and Rooks for last, to possibly obey FRC restriction
4384 if(p == (int)WhiteRook) continue;
4385 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4386 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4389 // now everything is placed, except perhaps King (Unicorn) and Rooks
4391 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4392 // Last King gets castling rights
4393 while(piecesLeft[(int)WhiteUnicorn]) {
4394 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4395 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4398 while(piecesLeft[(int)WhiteKing]) {
4399 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4400 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4405 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4406 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4409 // Only Rooks can be left; simply place them all
4410 while(piecesLeft[(int)WhiteRook]) {
4411 i = put(board, WhiteRook, 0, 0, ANY);
4412 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4415 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4417 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4420 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4421 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4424 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4427 int SetCharTable( char *table, const char * map )
4428 /* [HGM] moved here from winboard.c because of its general usefulness */
4429 /* Basically a safe strcpy that uses the last character as King */
4431 int result = FALSE; int NrPieces;
4433 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4434 && NrPieces >= 12 && !(NrPieces&1)) {
4435 int i; /* [HGM] Accept even length from 12 to 34 */
4437 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4438 for( i=0; i<NrPieces/2-1; i++ ) {
4440 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4442 table[(int) WhiteKing] = map[NrPieces/2-1];
4443 table[(int) BlackKing] = map[NrPieces-1];
4451 void Prelude(Board board)
4452 { // [HGM] superchess: random selection of exo-pieces
4453 int i, j, k; ChessSquare p;
4454 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4456 GetPositionNumber(); // use FRC position number
4458 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4459 SetCharTable(pieceToChar, appData.pieceToCharTable);
4460 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4461 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4464 j = seed%4; seed /= 4;
4465 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4466 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4467 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4468 j = seed%3 + (seed%3 >= j); seed /= 3;
4469 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4470 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4471 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4472 j = seed%3; seed /= 3;
4473 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4474 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4475 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4476 j = seed%2 + (seed%2 >= j); seed /= 2;
4477 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4478 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4479 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4480 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4481 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4482 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4483 put(board, exoPieces[0], 0, 0, ANY);
4484 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4488 InitPosition(redraw)
4491 ChessSquare (* pieces)[BOARD_SIZE];
4492 int i, j, pawnRow, overrule,
4493 oldx = gameInfo.boardWidth,
4494 oldy = gameInfo.boardHeight,
4495 oldh = gameInfo.holdingsWidth,
4496 oldv = gameInfo.variant;
4498 currentMove = forwardMostMove = backwardMostMove = 0;
4499 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4501 /* [AS] Initialize pv info list [HGM] and game status */
4503 for( i=0; i<MAX_MOVES; i++ ) {
4504 pvInfoList[i].depth = 0;
4505 epStatus[i]=EP_NONE;
4506 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4509 initialRulePlies = 0; /* 50-move counter start */
4511 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4512 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4516 /* [HGM] logic here is completely changed. In stead of full positions */
4517 /* the initialized data only consist of the two backranks. The switch */
4518 /* selects which one we will use, which is than copied to the Board */
4519 /* initialPosition, which for the rest is initialized by Pawns and */
4520 /* empty squares. This initial position is then copied to boards[0], */
4521 /* possibly after shuffling, so that it remains available. */
4523 gameInfo.holdingsWidth = 0; /* default board sizes */
4524 gameInfo.boardWidth = 8;
4525 gameInfo.boardHeight = 8;
4526 gameInfo.holdingsSize = 0;
4527 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4528 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4529 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4531 switch (gameInfo.variant) {
4532 case VariantFischeRandom:
4533 shuffleOpenings = TRUE;
4537 case VariantShatranj:
4538 pieces = ShatranjArray;
4539 nrCastlingRights = 0;
4540 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4542 case VariantTwoKings:
4543 pieces = twoKingsArray;
4545 case VariantCapaRandom:
4546 shuffleOpenings = TRUE;
4547 case VariantCapablanca:
4548 pieces = CapablancaArray;
4549 gameInfo.boardWidth = 10;
4550 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4553 pieces = GothicArray;
4554 gameInfo.boardWidth = 10;
4555 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4558 pieces = JanusArray;
4559 gameInfo.boardWidth = 10;
4560 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4561 nrCastlingRights = 6;
4562 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4563 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4564 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4565 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4566 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4567 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4570 pieces = FalconArray;
4571 gameInfo.boardWidth = 10;
4572 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4574 case VariantXiangqi:
4575 pieces = XiangqiArray;
4576 gameInfo.boardWidth = 9;
4577 gameInfo.boardHeight = 10;
4578 nrCastlingRights = 0;
4579 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4582 pieces = ShogiArray;
4583 gameInfo.boardWidth = 9;
4584 gameInfo.boardHeight = 9;
4585 gameInfo.holdingsSize = 7;
4586 nrCastlingRights = 0;
4587 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4589 case VariantCourier:
4590 pieces = CourierArray;
4591 gameInfo.boardWidth = 12;
4592 nrCastlingRights = 0;
4593 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4594 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4596 case VariantKnightmate:
4597 pieces = KnightmateArray;
4598 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4601 pieces = fairyArray;
4602 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4605 pieces = GreatArray;
4606 gameInfo.boardWidth = 10;
4607 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4608 gameInfo.holdingsSize = 8;
4612 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4613 gameInfo.holdingsSize = 8;
4614 startedFromSetupPosition = TRUE;
4616 case VariantCrazyhouse:
4617 case VariantBughouse:
4619 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4620 gameInfo.holdingsSize = 5;
4622 case VariantWildCastle:
4624 /* !!?shuffle with kings guaranteed to be on d or e file */
4625 shuffleOpenings = 1;
4627 case VariantNoCastle:
4629 nrCastlingRights = 0;
4630 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4631 /* !!?unconstrained back-rank shuffle */
4632 shuffleOpenings = 1;
4637 if(appData.NrFiles >= 0) {
4638 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4639 gameInfo.boardWidth = appData.NrFiles;
4641 if(appData.NrRanks >= 0) {
4642 gameInfo.boardHeight = appData.NrRanks;
4644 if(appData.holdingsSize >= 0) {
4645 i = appData.holdingsSize;
4646 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4647 gameInfo.holdingsSize = i;
4649 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4650 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4651 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4653 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4654 if(pawnRow < 1) pawnRow = 1;
4656 /* User pieceToChar list overrules defaults */
4657 if(appData.pieceToCharTable != NULL)
4658 SetCharTable(pieceToChar, appData.pieceToCharTable);
4660 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4662 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4663 s = (ChessSquare) 0; /* account holding counts in guard band */
4664 for( i=0; i<BOARD_HEIGHT; i++ )
4665 initialPosition[i][j] = s;
4667 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4668 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4669 initialPosition[pawnRow][j] = WhitePawn;
4670 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4671 if(gameInfo.variant == VariantXiangqi) {
4673 initialPosition[pawnRow][j] =
4674 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4675 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4676 initialPosition[2][j] = WhiteCannon;
4677 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4681 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4683 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4686 initialPosition[1][j] = WhiteBishop;
4687 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4689 initialPosition[1][j] = WhiteRook;
4690 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4693 if( nrCastlingRights == -1) {
4694 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4695 /* This sets default castling rights from none to normal corners */
4696 /* Variants with other castling rights must set them themselves above */
4697 nrCastlingRights = 6;
4699 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4700 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4701 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4702 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4703 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4704 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4707 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4708 if(gameInfo.variant == VariantGreat) { // promotion commoners
4709 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4710 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4711 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4712 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4715 if(gameInfo.variant == VariantFischeRandom) {
4716 if( appData.defaultFrcPosition < 0 ) {
4717 ShuffleFRC( initialPosition );
4720 SetupFRC( initialPosition, appData.defaultFrcPosition );
4722 startedFromSetupPosition = TRUE;
4725 if (appData.debugMode) {
4726 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4728 if(shuffleOpenings) {
4729 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4730 startedFromSetupPosition = TRUE;
4733 if(startedFromPositionFile) {
4734 /* [HGM] loadPos: use PositionFile for every new game */
4735 CopyBoard(initialPosition, filePosition);
4736 for(i=0; i<nrCastlingRights; i++)
4737 castlingRights[0][i] = initialRights[i] = fileRights[i];
4738 startedFromSetupPosition = TRUE;
4741 CopyBoard(boards[0], initialPosition);
4743 if(oldx != gameInfo.boardWidth ||
4744 oldy != gameInfo.boardHeight ||
4745 oldh != gameInfo.holdingsWidth
4747 || oldv == VariantGothic || // For licensing popups
4748 gameInfo.variant == VariantGothic
4751 || oldv == VariantFalcon ||
4752 gameInfo.variant == VariantFalcon
4755 InitDrawingSizes(-2 ,0);
4758 DrawPosition(TRUE, boards[currentMove]);
4762 SendBoard(cps, moveNum)
4763 ChessProgramState *cps;
4766 char message[MSG_SIZ];
4768 if (cps->useSetboard) {
4769 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4770 sprintf(message, "setboard %s\n", fen);
4771 SendToProgram(message, cps);
4777 /* Kludge to set black to move, avoiding the troublesome and now
4778 * deprecated "black" command.
4780 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782 SendToProgram("edit\n", cps);
4783 SendToProgram("#\n", cps);
4784 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4785 bp = &boards[moveNum][i][BOARD_LEFT];
4786 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4787 if ((int) *bp < (int) BlackPawn) {
4788 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4790 if(message[0] == '+' || message[0] == '~') {
4791 sprintf(message, "%c%c%c+\n",
4792 PieceToChar((ChessSquare)(DEMOTED *bp)),
4795 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4796 message[1] = BOARD_RGHT - 1 - j + '1';
4797 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799 SendToProgram(message, cps);
4804 SendToProgram("c\n", cps);
4805 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4806 bp = &boards[moveNum][i][BOARD_LEFT];
4807 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4808 if (((int) *bp != (int) EmptySquare)
4809 && ((int) *bp >= (int) BlackPawn)) {
4810 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812 if(message[0] == '+' || message[0] == '~') {
4813 sprintf(message, "%c%c%c+\n",
4814 PieceToChar((ChessSquare)(DEMOTED *bp)),
4817 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4818 message[1] = BOARD_RGHT - 1 - j + '1';
4819 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821 SendToProgram(message, cps);
4826 SendToProgram(".\n", cps);
4828 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4832 IsPromotion(fromX, fromY, toX, toY)
4833 int fromX, fromY, toX, toY;
4835 /* [HGM] add Shogi promotions */
4836 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4839 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4840 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4841 /* [HGM] Note to self: line above also weeds out drops */
4842 piece = boards[currentMove][fromY][fromX];
4843 if(gameInfo.variant == VariantShogi) {
4844 promotionZoneSize = 3;
4845 highestPromotingPiece = (int)WhiteKing;
4846 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4847 and if in normal chess we then allow promotion to King, why not
4848 allow promotion of other piece in Shogi? */
4850 if((int)piece >= BlackPawn) {
4851 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4856 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858 return ( (int)piece <= highestPromotingPiece );
4862 InPalace(row, column)
4864 { /* [HGM] for Xiangqi */
4865 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4866 column < (BOARD_WIDTH + 4)/2 &&
4867 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4872 PieceForSquare (x, y)
4876 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4879 return boards[currentMove][y][x];
4883 OKToStartUserMove(x, y)
4886 ChessSquare from_piece;
4889 if (matchMode) return FALSE;
4890 if (gameMode == EditPosition) return TRUE;
4892 if (x >= 0 && y >= 0)
4893 from_piece = boards[currentMove][y][x];
4895 from_piece = EmptySquare;
4897 if (from_piece == EmptySquare) return FALSE;
4899 white_piece = (int)from_piece >= (int)WhitePawn &&
4900 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4903 case PlayFromGameFile:
4905 case TwoMachinesPlay:
4913 case MachinePlaysWhite:
4914 case IcsPlayingBlack:
4915 if (appData.zippyPlay) return FALSE;
4917 DisplayMoveError(_("You are playing Black"));
4922 case MachinePlaysBlack:
4923 case IcsPlayingWhite:
4924 if (appData.zippyPlay) return FALSE;
4926 DisplayMoveError(_("You are playing White"));
4932 if (!white_piece && WhiteOnMove(currentMove)) {
4933 DisplayMoveError(_("It is White's turn"));
4936 if (white_piece && !WhiteOnMove(currentMove)) {
4937 DisplayMoveError(_("It is Black's turn"));
4940 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4941 /* Editing correspondence game history */
4942 /* Could disallow this or prompt for confirmation */
4945 if (currentMove < forwardMostMove) {
4946 /* Discarding moves */
4947 /* Could prompt for confirmation here,
4948 but I don't think that's such a good idea */
4949 forwardMostMove = currentMove;
4953 case BeginningOfGame:
4954 if (appData.icsActive) return FALSE;
4955 if (!appData.noChessProgram) {
4957 DisplayMoveError(_("You are playing White"));
4964 if (!white_piece && WhiteOnMove(currentMove)) {
4965 DisplayMoveError(_("It is White's turn"));
4968 if (white_piece && !WhiteOnMove(currentMove)) {
4969 DisplayMoveError(_("It is Black's turn"));
4978 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4979 && gameMode != AnalyzeFile && gameMode != Training) {
4980 DisplayMoveError(_("Displayed position is not current"));
4986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4988 int lastLoadGameUseList = FALSE;
4989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4990 ChessMove lastLoadGameStart = (ChessMove) 0;
4994 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4995 int fromX, fromY, toX, toY;
4999 ChessSquare pdown, pup;
5001 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5002 if ((fromX == toX) && (fromY == toY)) {
5003 return ImpossibleMove;
5006 /* [HGM] suppress all moves into holdings area and guard band */
5007 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5008 return ImpossibleMove;
5010 /* [HGM] <sameColor> moved to here from winboard.c */
5011 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5012 pdown = boards[currentMove][fromY][fromX];
5013 pup = boards[currentMove][toY][toX];
5014 if ( gameMode != EditPosition &&
5015 (WhitePawn <= pdown && pdown < BlackPawn &&
5016 WhitePawn <= pup && pup < BlackPawn ||
5017 BlackPawn <= pdown && pdown < EmptySquare &&
5018 BlackPawn <= pup && pup < EmptySquare
5019 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5020 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5021 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5023 return ImpossibleMove;
5025 /* Check if the user is playing in turn. This is complicated because we
5026 let the user "pick up" a piece before it is his turn. So the piece he
5027 tried to pick up may have been captured by the time he puts it down!
5028 Therefore we use the color the user is supposed to be playing in this
5029 test, not the color of the piece that is currently on the starting
5030 square---except in EditGame mode, where the user is playing both
5031 sides; fortunately there the capture race can't happen. (It can
5032 now happen in IcsExamining mode, but that's just too bad. The user
5033 will get a somewhat confusing message in that case.)
5037 case PlayFromGameFile:
5039 case TwoMachinesPlay:
5043 /* We switched into a game mode where moves are not accepted,
5044 perhaps while the mouse button was down. */
5045 return ImpossibleMove;
5047 case MachinePlaysWhite:
5048 /* User is moving for Black */
5049 if (WhiteOnMove(currentMove)) {
5050 DisplayMoveError(_("It is White's turn"));
5051 return ImpossibleMove;
5055 case MachinePlaysBlack:
5056 /* User is moving for White */
5057 if (!WhiteOnMove(currentMove)) {
5058 DisplayMoveError(_("It is Black's turn"));
5059 return ImpossibleMove;
5065 case BeginningOfGame:
5068 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5069 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5070 /* User is moving for Black */
5071 if (WhiteOnMove(currentMove)) {
5072 DisplayMoveError(_("It is White's turn"));
5073 return ImpossibleMove;
5076 /* User is moving for White */
5077 if (!WhiteOnMove(currentMove)) {
5078 DisplayMoveError(_("It is Black's turn"));
5079 return ImpossibleMove;
5084 case IcsPlayingBlack:
5085 /* User is moving for Black */
5086 if (WhiteOnMove(currentMove)) {
5087 if (!appData.premove) {
5088 DisplayMoveError(_("It is White's turn"));
5089 } else if (toX >= 0 && toY >= 0) {
5092 premoveFromX = fromX;
5093 premoveFromY = fromY;
5094 premovePromoChar = promoChar;
5096 if (appData.debugMode)
5097 fprintf(debugFP, "Got premove: fromX %d,"
5098 "fromY %d, toX %d, toY %d\n",
5099 fromX, fromY, toX, toY);
5101 return ImpossibleMove;
5105 case IcsPlayingWhite:
5106 /* User is moving for White */
5107 if (!WhiteOnMove(currentMove)) {
5108 if (!appData.premove) {
5109 DisplayMoveError(_("It is Black's turn"));
5110 } else if (toX >= 0 && toY >= 0) {
5113 premoveFromX = fromX;
5114 premoveFromY = fromY;
5115 premovePromoChar = promoChar;
5117 if (appData.debugMode)
5118 fprintf(debugFP, "Got premove: fromX %d,"
5119 "fromY %d, toX %d, toY %d\n",
5120 fromX, fromY, toX, toY);
5122 return ImpossibleMove;
5130 /* EditPosition, empty square, or different color piece;
5131 click-click move is possible */
5132 if (toX == -2 || toY == -2) {
5133 boards[0][fromY][fromX] = EmptySquare;
5134 return AmbiguousMove;
5135 } else if (toX >= 0 && toY >= 0) {
5136 boards[0][toY][toX] = boards[0][fromY][fromX];
5137 boards[0][fromY][fromX] = EmptySquare;
5138 return AmbiguousMove;
5140 return ImpossibleMove;
5143 /* [HGM] If move started in holdings, it means a drop */
5144 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5145 if( pup != EmptySquare ) return ImpossibleMove;
5146 if(appData.testLegality) {
5147 /* it would be more logical if LegalityTest() also figured out
5148 * which drops are legal. For now we forbid pawns on back rank.
5149 * Shogi is on its own here...
5151 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5152 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5153 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155 return WhiteDrop; /* Not needed to specify white or black yet */
5158 userOfferedDraw = FALSE;
5160 /* [HGM] always test for legality, to get promotion info */
5161 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162 epStatus[currentMove], castlingRights[currentMove],
5163 fromY, fromX, toY, toX, promoChar);
5165 /* [HGM] but possibly ignore an IllegalMove result */
5166 if (appData.testLegality) {
5167 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5168 DisplayMoveError(_("Illegal move"));
5169 return ImpossibleMove;
5172 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5174 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5175 function is made into one that returns an OK move type if FinishMove
5176 should be called. This to give the calling driver routine the
5177 opportunity to finish the userMove input with a promotion popup,
5178 without bothering the user with this for invalid or illegal moves */
5180 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5183 /* Common tail of UserMoveEvent and DropMenuEvent */
5185 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5187 int fromX, fromY, toX, toY;
5188 /*char*/int promoChar;
5191 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5192 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5193 // [HGM] superchess: suppress promotions to non-available piece
5194 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5195 if(WhiteOnMove(currentMove)) {
5196 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5198 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5202 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5203 move type in caller when we know the move is a legal promotion */
5204 if(moveType == NormalMove && promoChar)
5205 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5206 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5207 /* [HGM] convert drag-and-drop piece drops to standard form */
5208 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5209 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5210 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5211 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5212 // fromX = boards[currentMove][fromY][fromX];
5213 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5214 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5215 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5216 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5220 /* [HGM] <popupFix> The following if has been moved here from
5221 UserMoveEvent(). Because it seemed to belon here (why not allow
5222 piece drops in training games?), and because it can only be
5223 performed after it is known to what we promote. */
5224 if (gameMode == Training) {
5225 /* compare the move played on the board to the next move in the
5226 * game. If they match, display the move and the opponent's response.
5227 * If they don't match, display an error message.
5230 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5231 CopyBoard(testBoard, boards[currentMove]);
5232 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5234 if (CompareBoards(testBoard, boards[currentMove+1])) {
5235 ForwardInner(currentMove+1);
5237 /* Autoplay the opponent's response.
5238 * if appData.animate was TRUE when Training mode was entered,
5239 * the response will be animated.
5241 saveAnimate = appData.animate;
5242 appData.animate = animateTraining;
5243 ForwardInner(currentMove+1);
5244 appData.animate = saveAnimate;
5246 /* check for the end of the game */
5247 if (currentMove >= forwardMostMove) {
5248 gameMode = PlayFromGameFile;
5250 SetTrainingModeOff();
5251 DisplayInformation(_("End of game"));
5254 DisplayError(_("Incorrect move"), 0);
5259 /* Ok, now we know that the move is good, so we can kill
5260 the previous line in Analysis Mode */
5261 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5262 forwardMostMove = currentMove;
5265 /* If we need the chess program but it's dead, restart it */
5266 ResurrectChessProgram();
5268 /* A user move restarts a paused game*/
5272 thinkOutput[0] = NULLCHAR;
5274 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5276 if (gameMode == BeginningOfGame) {
5277 if (appData.noChessProgram) {
5278 gameMode = EditGame;
5282 gameMode = MachinePlaysBlack;
5285 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5287 if (first.sendName) {
5288 sprintf(buf, "name %s\n", gameInfo.white);
5289 SendToProgram(buf, &first);
5295 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5296 /* Relay move to ICS or chess engine */
5297 if (appData.icsActive) {
5298 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5299 gameMode == IcsExamining) {
5300 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5304 if (first.sendTime && (gameMode == BeginningOfGame ||
5305 gameMode == MachinePlaysWhite ||
5306 gameMode == MachinePlaysBlack)) {
5307 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5309 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5310 // [HGM] book: if program might be playing, let it use book
5311 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5312 first.maybeThinking = TRUE;
5313 } else SendMoveToProgram(forwardMostMove-1, &first);
5314 if (currentMove == cmailOldMove + 1) {
5315 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5319 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5323 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5324 EP_UNKNOWN, castlingRights[currentMove]) ) {
5330 if (WhiteOnMove(currentMove)) {
5331 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5333 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5337 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5342 case MachinePlaysBlack:
5343 case MachinePlaysWhite:
5344 /* disable certain menu options while machine is thinking */
5345 SetMachineThinkingEnables();
5352 if(bookHit) { // [HGM] book: simulate book reply
5353 static char bookMove[MSG_SIZ]; // a bit generous?
5355 programStats.nodes = programStats.depth = programStats.time =
5356 programStats.score = programStats.got_only_move = 0;
5357 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5359 strcpy(bookMove, "move ");
5360 strcat(bookMove, bookHit);
5361 HandleMachineMove(bookMove, &first);
5367 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5368 int fromX, fromY, toX, toY;
5371 /* [HGM] This routine was added to allow calling of its two logical
5372 parts from other modules in the old way. Before, UserMoveEvent()
5373 automatically called FinishMove() if the move was OK, and returned
5374 otherwise. I separated the two, in order to make it possible to
5375 slip a promotion popup in between. But that it always needs two
5376 calls, to the first part, (now called UserMoveTest() ), and to
5377 FinishMove if the first part succeeded. Calls that do not need
5378 to do anything in between, can call this routine the old way.
5380 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5381 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5382 if(moveType != ImpossibleMove)
5383 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5386 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5388 // char * hint = lastHint;
5389 FrontEndProgramStats stats;
5391 stats.which = cps == &first ? 0 : 1;
5392 stats.depth = cpstats->depth;
5393 stats.nodes = cpstats->nodes;
5394 stats.score = cpstats->score;
5395 stats.time = cpstats->time;
5396 stats.pv = cpstats->movelist;
5397 stats.hint = lastHint;
5398 stats.an_move_index = 0;
5399 stats.an_move_count = 0;
5401 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5402 stats.hint = cpstats->move_name;
5403 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5404 stats.an_move_count = cpstats->nr_moves;
5407 SetProgramStats( &stats );
5410 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5411 { // [HGM] book: this routine intercepts moves to simulate book replies
5412 char *bookHit = NULL;
5414 //first determine if the incoming move brings opponent into his book
5415 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5416 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5417 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5418 if(bookHit != NULL && !cps->bookSuspend) {
5419 // make sure opponent is not going to reply after receiving move to book position
5420 SendToProgram("force\n", cps);
5421 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5423 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5424 // now arrange restart after book miss
5426 // after a book hit we never send 'go', and the code after the call to this routine
5427 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5429 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5430 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5431 SendToProgram(buf, cps);
5432 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5433 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5434 SendToProgram("go\n", cps);
5435 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5436 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5437 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5438 SendToProgram("go\n", cps);
5439 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5441 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5445 ChessProgramState *savedState;
5446 void DeferredBookMove(void)
5448 if(savedState->lastPing != savedState->lastPong)
5449 ScheduleDelayedEvent(DeferredBookMove, 10);
5451 HandleMachineMove(savedMessage, savedState);
5455 HandleMachineMove(message, cps)
5457 ChessProgramState *cps;
5459 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5460 char realname[MSG_SIZ];
5461 int fromX, fromY, toX, toY;
5468 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5470 * Kludge to ignore BEL characters
5472 while (*message == '\007') message++;
5475 * [HGM] engine debug message: ignore lines starting with '#' character
5477 if(cps->debug && *message == '#') return;
5480 * Look for book output
5482 if (cps == &first && bookRequested) {
5483 if (message[0] == '\t' || message[0] == ' ') {
5484 /* Part of the book output is here; append it */
5485 strcat(bookOutput, message);
5486 strcat(bookOutput, " \n");
5488 } else if (bookOutput[0] != NULLCHAR) {
5489 /* All of book output has arrived; display it */
5490 char *p = bookOutput;
5491 while (*p != NULLCHAR) {
5492 if (*p == '\t') *p = ' ';
5495 DisplayInformation(bookOutput);
5496 bookRequested = FALSE;
5497 /* Fall through to parse the current output */
5502 * Look for machine move.
5504 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5505 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5507 /* This method is only useful on engines that support ping */
5508 if (cps->lastPing != cps->lastPong) {
5509 if (gameMode == BeginningOfGame) {
5510 /* Extra move from before last new; ignore */
5511 if (appData.debugMode) {
5512 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5515 if (appData.debugMode) {
5516 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5517 cps->which, gameMode);
5520 SendToProgram("undo\n", cps);
5526 case BeginningOfGame:
5527 /* Extra move from before last reset; ignore */
5528 if (appData.debugMode) {
5529 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5536 /* Extra move after we tried to stop. The mode test is
5537 not a reliable way of detecting this problem, but it's
5538 the best we can do on engines that don't support ping.
5540 if (appData.debugMode) {
5541 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5542 cps->which, gameMode);
5544 SendToProgram("undo\n", cps);
5547 case MachinePlaysWhite:
5548 case IcsPlayingWhite:
5549 machineWhite = TRUE;
5552 case MachinePlaysBlack:
5553 case IcsPlayingBlack:
5554 machineWhite = FALSE;
5557 case TwoMachinesPlay:
5558 machineWhite = (cps->twoMachinesColor[0] == 'w');
5561 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5562 if (appData.debugMode) {
5564 "Ignoring move out of turn by %s, gameMode %d"
5565 ", forwardMost %d\n",
5566 cps->which, gameMode, forwardMostMove);
5571 if (appData.debugMode) { int f = forwardMostMove;
5572 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5573 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5575 if(cps->alphaRank) AlphaRank(machineMove, 4);
5576 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5577 &fromX, &fromY, &toX, &toY, &promoChar)) {
5578 /* Machine move could not be parsed; ignore it. */
5579 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5580 machineMove, cps->which);
5581 DisplayError(buf1, 0);
5582 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5583 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5584 if (gameMode == TwoMachinesPlay) {
5585 GameEnds(machineWhite ? BlackWins : WhiteWins,
5591 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5592 /* So we have to redo legality test with true e.p. status here, */
5593 /* to make sure an illegal e.p. capture does not slip through, */
5594 /* to cause a forfeit on a justified illegal-move complaint */
5595 /* of the opponent. */
5596 if( gameMode==TwoMachinesPlay && appData.testLegality
5597 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5600 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5601 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5602 fromY, fromX, toY, toX, promoChar);
5603 if (appData.debugMode) {
5605 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5606 castlingRights[forwardMostMove][i], castlingRank[i]);
5607 fprintf(debugFP, "castling rights\n");
5609 if(moveType == IllegalMove) {
5610 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5611 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5612 GameEnds(machineWhite ? BlackWins : WhiteWins,
5615 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5616 /* [HGM] Kludge to handle engines that send FRC-style castling
5617 when they shouldn't (like TSCP-Gothic) */
5619 case WhiteASideCastleFR:
5620 case BlackASideCastleFR:
5622 currentMoveString[2]++;
5624 case WhiteHSideCastleFR:
5625 case BlackHSideCastleFR:
5627 currentMoveString[2]--;
5629 default: ; // nothing to do, but suppresses warning of pedantic compilers
5632 hintRequested = FALSE;
5633 lastHint[0] = NULLCHAR;
5634 bookRequested = FALSE;
5635 /* Program may be pondering now */
5636 cps->maybeThinking = TRUE;
5637 if (cps->sendTime == 2) cps->sendTime = 1;
5638 if (cps->offeredDraw) cps->offeredDraw--;
5641 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5643 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5645 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5646 char buf[3*MSG_SIZ];
5648 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5649 programStats.score / 100.,
5651 programStats.time / 100.,
5652 (unsigned int)programStats.nodes,
5653 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5654 programStats.movelist);
5659 /* currentMoveString is set as a side-effect of ParseOneMove */
5660 strcpy(machineMove, currentMoveString);
5661 strcat(machineMove, "\n");
5662 strcpy(moveList[forwardMostMove], machineMove);
5664 /* [AS] Save move info and clear stats for next move */
5665 pvInfoList[ forwardMostMove ].score = programStats.score;
5666 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5667 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5668 ClearProgramStats();
5669 thinkOutput[0] = NULLCHAR;
5670 hiddenThinkOutputState = 0;
5672 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5674 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5675 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5678 while( count < adjudicateLossPlies ) {
5679 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5682 score = -score; /* Flip score for winning side */
5685 if( score > adjudicateLossThreshold ) {
5692 if( count >= adjudicateLossPlies ) {
5693 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5695 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5696 "Xboard adjudication",
5703 if( gameMode == TwoMachinesPlay ) {
5704 // [HGM] some adjudications useful with buggy engines
5705 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5706 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5709 if( appData.testLegality )
5710 { /* [HGM] Some more adjudications for obstinate engines */
5711 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5712 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5713 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5714 static int moveCount = 6;
5716 char *reason = NULL;
5718 /* Count what is on board. */
5719 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5720 { ChessSquare p = boards[forwardMostMove][i][j];
5724 { /* count B,N,R and other of each side */
5727 NrK++; break; // [HGM] atomic: count Kings
5731 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5732 bishopsColor |= 1 << ((i^j)&1);
5737 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5738 bishopsColor |= 1 << ((i^j)&1);
5753 PawnAdvance += m; NrPawns++;
5755 NrPieces += (p != EmptySquare);
5756 NrW += ((int)p < (int)BlackPawn);
5757 if(gameInfo.variant == VariantXiangqi &&
5758 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5759 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5760 NrW -= ((int)p < (int)BlackPawn);
5764 /* Some material-based adjudications that have to be made before stalemate test */
5765 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5766 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5767 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5768 if(appData.checkMates) {
5769 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5770 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5771 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5772 "Xboard adjudication: King destroyed", GE_XBOARD );
5777 /* Bare King in Shatranj (loses) or Losers (wins) */
5778 if( NrW == 1 || NrPieces - NrW == 1) {
5779 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5780 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5781 if(appData.checkMates) {
5782 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5783 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5784 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5785 "Xboard adjudication: Bare king", GE_XBOARD );
5789 if( gameInfo.variant == VariantShatranj && --bare < 0)
5791 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5792 if(appData.checkMates) {
5793 /* but only adjudicate if adjudication enabled */
5794 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5795 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5796 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5797 "Xboard adjudication: Bare king", GE_XBOARD );
5804 // don't wait for engine to announce game end if we can judge ourselves
5805 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5806 castlingRights[forwardMostMove]) ) {
5808 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5809 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5810 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5811 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5814 reason = "Xboard adjudication: 3rd check";
5815 epStatus[forwardMostMove] = EP_CHECKMATE;
5825 reason = "Xboard adjudication: Stalemate";
5826 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5827 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5828 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5829 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5830 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5831 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5832 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5833 EP_CHECKMATE : EP_WINS);
5834 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5835 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5839 reason = "Xboard adjudication: Checkmate";
5840 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5844 switch(i = epStatus[forwardMostMove]) {
5846 result = GameIsDrawn; break;
5848 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5850 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5852 result = (ChessMove) 0;
5854 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5855 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5856 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5857 GameEnds( result, reason, GE_XBOARD );
5861 /* Next absolutely insufficient mating material. */
5862 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5863 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5864 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5865 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5866 { /* KBK, KNK, KK of KBKB with like Bishops */
5868 /* always flag draws, for judging claims */
5869 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5871 if(appData.materialDraws) {
5872 /* but only adjudicate them if adjudication enabled */
5873 SendToProgram("force\n", cps->other); // suppress reply
5874 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5875 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5876 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5881 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5883 ( NrWR == 1 && NrBR == 1 /* KRKR */
5884 || NrWQ==1 && NrBQ==1 /* KQKQ */
5885 || NrWN==2 || NrBN==2 /* KNNK */
5886 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5888 if(--moveCount < 0 && appData.trivialDraws)
5889 { /* if the first 3 moves do not show a tactical win, declare draw */
5890 SendToProgram("force\n", cps->other); // suppress reply
5891 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5892 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5896 } else moveCount = 6;
5900 if (appData.debugMode) { int i;
5901 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5902 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5903 appData.drawRepeats);
5904 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5905 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5909 /* Check for rep-draws */
5911 for(k = forwardMostMove-2;
5912 k>=backwardMostMove && k>=forwardMostMove-100 &&
5913 epStatus[k] < EP_UNKNOWN &&
5914 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5918 if (appData.debugMode) {
5919 fprintf(debugFP, " loop\n");
5922 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5924 if (appData.debugMode) {
5925 fprintf(debugFP, "match\n");
5928 /* compare castling rights */
5929 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5930 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5931 rights++; /* King lost rights, while rook still had them */
5932 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5933 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5934 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5935 rights++; /* but at least one rook lost them */
5937 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5938 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5940 if( castlingRights[forwardMostMove][5] >= 0 ) {
5941 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5942 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5946 if (appData.debugMode) {
5947 for(i=0; i<nrCastlingRights; i++)
5948 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5951 if (appData.debugMode) {
5952 fprintf(debugFP, " %d %d\n", rights, k);
5955 if( rights == 0 && ++count > appData.drawRepeats-2
5956 && appData.drawRepeats > 1) {
5957 /* adjudicate after user-specified nr of repeats */
5958 SendToProgram("force\n", cps->other); // suppress reply
5959 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5960 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5961 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5962 // [HGM] xiangqi: check for forbidden perpetuals
5963 int m, ourPerpetual = 1, hisPerpetual = 1;
5964 for(m=forwardMostMove; m>k; m-=2) {
5965 if(MateTest(boards[m], PosFlags(m),
5966 EP_NONE, castlingRights[m]) != MT_CHECK)
5967 ourPerpetual = 0; // the current mover did not always check
5968 if(MateTest(boards[m-1], PosFlags(m-1),
5969 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5970 hisPerpetual = 0; // the opponent did not always check
5972 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5973 ourPerpetual, hisPerpetual);
5974 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5975 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5976 "Xboard adjudication: perpetual checking", GE_XBOARD );
5979 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5980 break; // (or we would have caught him before). Abort repetition-checking loop.
5981 // Now check for perpetual chases
5982 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5983 hisPerpetual = PerpetualChase(k, forwardMostMove);
5984 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5985 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5986 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5987 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5990 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5991 break; // Abort repetition-checking loop.
5993 // if neither of us is checking or chasing all the time, or both are, it is draw
5995 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5998 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5999 epStatus[forwardMostMove] = EP_REP_DRAW;
6003 /* Now we test for 50-move draws. Determine ply count */
6004 count = forwardMostMove;
6005 /* look for last irreversble move */
6006 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6008 /* if we hit starting position, add initial plies */
6009 if( count == backwardMostMove )
6010 count -= initialRulePlies;
6011 count = forwardMostMove - count;
6013 epStatus[forwardMostMove] = EP_RULE_DRAW;
6014 /* this is used to judge if draw claims are legal */
6015 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6016 SendToProgram("force\n", cps->other); // suppress reply
6017 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6018 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6019 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6023 /* if draw offer is pending, treat it as a draw claim
6024 * when draw condition present, to allow engines a way to
6025 * claim draws before making their move to avoid a race
6026 * condition occurring after their move
6028 if( cps->other->offeredDraw || cps->offeredDraw ) {
6030 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6031 p = "Draw claim: 50-move rule";
6032 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6033 p = "Draw claim: 3-fold repetition";
6034 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6035 p = "Draw claim: insufficient mating material";
6037 SendToProgram("force\n", cps->other); // suppress reply
6038 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6039 GameEnds( GameIsDrawn, p, GE_XBOARD );
6040 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6046 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6047 SendToProgram("force\n", cps->other); // suppress reply
6048 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6049 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6051 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6058 if (gameMode == TwoMachinesPlay) {
6059 /* [HGM] relaying draw offers moved to after reception of move */
6060 /* and interpreting offer as claim if it brings draw condition */
6061 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6062 SendToProgram("draw\n", cps->other);
6064 if (cps->other->sendTime) {
6065 SendTimeRemaining(cps->other,
6066 cps->other->twoMachinesColor[0] == 'w');
6068 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6069 if (firstMove && !bookHit) {
6071 if (cps->other->useColors) {
6072 SendToProgram(cps->other->twoMachinesColor, cps->other);
6074 SendToProgram("go\n", cps->other);
6076 cps->other->maybeThinking = TRUE;
6079 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081 if (!pausing && appData.ringBellAfterMoves) {
6086 * Reenable menu items that were disabled while
6087 * machine was thinking
6089 if (gameMode != TwoMachinesPlay)
6090 SetUserThinkingEnables();
6092 // [HGM] book: after book hit opponent has received move and is now in force mode
6093 // force the book reply into it, and then fake that it outputted this move by jumping
6094 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6096 static char bookMove[MSG_SIZ]; // a bit generous?
6098 strcpy(bookMove, "move ");
6099 strcat(bookMove, bookHit);
6102 programStats.nodes = programStats.depth = programStats.time =
6103 programStats.score = programStats.got_only_move = 0;
6104 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6106 if(cps->lastPing != cps->lastPong) {
6107 savedMessage = message; // args for deferred call
6109 ScheduleDelayedEvent(DeferredBookMove, 10);
6118 /* Set special modes for chess engines. Later something general
6119 * could be added here; for now there is just one kludge feature,
6120 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6121 * when "xboard" is given as an interactive command.
6123 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6124 cps->useSigint = FALSE;
6125 cps->useSigterm = FALSE;
6127 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6128 ParseFeatures(message+8, cps);
6129 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6132 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6133 * want this, I was asked to put it in, and obliged.
6135 if (!strncmp(message, "setboard ", 9)) {
6136 Board initial_position; int i;
6138 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6140 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6141 DisplayError(_("Bad FEN received from engine"), 0);
6144 Reset(FALSE, FALSE);
6145 CopyBoard(boards[0], initial_position);
6146 initialRulePlies = FENrulePlies;
6147 epStatus[0] = FENepStatus;
6148 for( i=0; i<nrCastlingRights; i++ )
6149 castlingRights[0][i] = FENcastlingRights[i];
6150 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6151 else gameMode = MachinePlaysBlack;
6152 DrawPosition(FALSE, boards[currentMove]);
6158 * Look for communication commands
6160 if (!strncmp(message, "telluser ", 9)) {
6161 DisplayNote(message + 9);
6164 if (!strncmp(message, "tellusererror ", 14)) {
6165 DisplayError(message + 14, 0);
6168 if (!strncmp(message, "tellopponent ", 13)) {
6169 if (appData.icsActive) {
6171 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6175 DisplayNote(message + 13);
6179 if (!strncmp(message, "tellothers ", 11)) {
6180 if (appData.icsActive) {
6182 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6188 if (!strncmp(message, "tellall ", 8)) {
6189 if (appData.icsActive) {
6191 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6195 DisplayNote(message + 8);
6199 if (strncmp(message, "warning", 7) == 0) {
6200 /* Undocumented feature, use tellusererror in new code */
6201 DisplayError(message, 0);
6204 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6205 strcpy(realname, cps->tidy);
6206 strcat(realname, " query");
6207 AskQuestion(realname, buf2, buf1, cps->pr);
6210 /* Commands from the engine directly to ICS. We don't allow these to be
6211 * sent until we are logged on. Crafty kibitzes have been known to
6212 * interfere with the login process.
6215 if (!strncmp(message, "tellics ", 8)) {
6216 SendToICS(message + 8);
6220 if (!strncmp(message, "tellicsnoalias ", 15)) {
6221 SendToICS(ics_prefix);
6222 SendToICS(message + 15);
6226 /* The following are for backward compatibility only */
6227 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6228 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6229 SendToICS(ics_prefix);
6235 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6239 * If the move is illegal, cancel it and redraw the board.
6240 * Also deal with other error cases. Matching is rather loose
6241 * here to accommodate engines written before the spec.
6243 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6244 strncmp(message, "Error", 5) == 0) {
6245 if (StrStr(message, "name") ||
6246 StrStr(message, "rating") || StrStr(message, "?") ||
6247 StrStr(message, "result") || StrStr(message, "board") ||
6248 StrStr(message, "bk") || StrStr(message, "computer") ||
6249 StrStr(message, "variant") || StrStr(message, "hint") ||
6250 StrStr(message, "random") || StrStr(message, "depth") ||
6251 StrStr(message, "accepted")) {
6254 if (StrStr(message, "protover")) {
6255 /* Program is responding to input, so it's apparently done
6256 initializing, and this error message indicates it is
6257 protocol version 1. So we don't need to wait any longer
6258 for it to initialize and send feature commands. */
6259 FeatureDone(cps, 1);
6260 cps->protocolVersion = 1;
6263 cps->maybeThinking = FALSE;
6265 if (StrStr(message, "draw")) {
6266 /* Program doesn't have "draw" command */
6267 cps->sendDrawOffers = 0;
6270 if (cps->sendTime != 1 &&
6271 (StrStr(message, "time") || StrStr(message, "otim"))) {
6272 /* Program apparently doesn't have "time" or "otim" command */
6276 if (StrStr(message, "analyze")) {
6277 cps->analysisSupport = FALSE;
6278 cps->analyzing = FALSE;
6280 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6281 DisplayError(buf2, 0);
6284 if (StrStr(message, "(no matching move)st")) {
6285 /* Special kludge for GNU Chess 4 only */
6286 cps->stKludge = TRUE;
6287 SendTimeControl(cps, movesPerSession, timeControl,
6288 timeIncrement, appData.searchDepth,
6292 if (StrStr(message, "(no matching move)sd")) {
6293 /* Special kludge for GNU Chess 4 only */
6294 cps->sdKludge = TRUE;
6295 SendTimeControl(cps, movesPerSession, timeControl,
6296 timeIncrement, appData.searchDepth,
6300 if (!StrStr(message, "llegal")) {
6303 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6304 gameMode == IcsIdle) return;
6305 if (forwardMostMove <= backwardMostMove) return;
6307 /* Following removed: it caused a bug where a real illegal move
6308 message in analyze mored would be ignored. */
6309 if (cps == &first && programStats.ok_to_send == 0) {
6310 /* Bogus message from Crafty responding to "." This filtering
6311 can miss some of the bad messages, but fortunately the bug
6312 is fixed in current Crafty versions, so it doesn't matter. */
6316 if (pausing) PauseEvent();
6317 if (gameMode == PlayFromGameFile) {
6318 /* Stop reading this game file */
6319 gameMode = EditGame;
6322 currentMove = --forwardMostMove;
6323 DisplayMove(currentMove-1); /* before DisplayMoveError */
6325 DisplayBothClocks();
6326 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6327 parseList[currentMove], cps->which);
6328 DisplayMoveError(buf1);
6329 DrawPosition(FALSE, boards[currentMove]);
6331 /* [HGM] illegal-move claim should forfeit game when Xboard */
6332 /* only passes fully legal moves */
6333 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6334 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6335 "False illegal-move claim", GE_XBOARD );
6339 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6340 /* Program has a broken "time" command that
6341 outputs a string not ending in newline.
6347 * If chess program startup fails, exit with an error message.
6348 * Attempts to recover here are futile.
6350 if ((StrStr(message, "unknown host") != NULL)
6351 || (StrStr(message, "No remote directory") != NULL)
6352 || (StrStr(message, "not found") != NULL)
6353 || (StrStr(message, "No such file") != NULL)
6354 || (StrStr(message, "can't alloc") != NULL)
6355 || (StrStr(message, "Permission denied") != NULL)) {
6357 cps->maybeThinking = FALSE;
6358 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6359 cps->which, cps->program, cps->host, message);
6360 RemoveInputSource(cps->isr);
6361 DisplayFatalError(buf1, 0, 1);
6366 * Look for hint output
6368 if (sscanf(message, "Hint: %s", buf1) == 1) {
6369 if (cps == &first && hintRequested) {
6370 hintRequested = FALSE;
6371 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6372 &fromX, &fromY, &toX, &toY, &promoChar)) {
6373 (void) CoordsToAlgebraic(boards[forwardMostMove],
6374 PosFlags(forwardMostMove), EP_UNKNOWN,
6375 fromY, fromX, toY, toX, promoChar, buf1);
6376 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6377 DisplayInformation(buf2);
6379 /* Hint move could not be parsed!? */
6380 snprintf(buf2, sizeof(buf2),
6381 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6383 DisplayError(buf2, 0);
6386 strcpy(lastHint, buf1);
6392 * Ignore other messages if game is not in progress
6394 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6395 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6398 * look for win, lose, draw, or draw offer
6400 if (strncmp(message, "1-0", 3) == 0) {
6401 char *p, *q, *r = "";
6402 p = strchr(message, '{');
6410 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6412 } else if (strncmp(message, "0-1", 3) == 0) {
6413 char *p, *q, *r = "";
6414 p = strchr(message, '{');
6422 /* Kludge for Arasan 4.1 bug */
6423 if (strcmp(r, "Black resigns") == 0) {
6424 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6427 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6429 } else if (strncmp(message, "1/2", 3) == 0) {
6430 char *p, *q, *r = "";
6431 p = strchr(message, '{');
6440 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6443 } else if (strncmp(message, "White resign", 12) == 0) {
6444 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6446 } else if (strncmp(message, "Black resign", 12) == 0) {
6447 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6449 } else if (strncmp(message, "White matches", 13) == 0 ||
6450 strncmp(message, "Black matches", 13) == 0 ) {
6451 /* [HGM] ignore GNUShogi noises */
6453 } else if (strncmp(message, "White", 5) == 0 &&
6454 message[5] != '(' &&
6455 StrStr(message, "Black") == NULL) {
6456 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6458 } else if (strncmp(message, "Black", 5) == 0 &&
6459 message[5] != '(') {
6460 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6462 } else if (strcmp(message, "resign") == 0 ||
6463 strcmp(message, "computer resigns") == 0) {
6465 case MachinePlaysBlack:
6466 case IcsPlayingBlack:
6467 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6469 case MachinePlaysWhite:
6470 case IcsPlayingWhite:
6471 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6473 case TwoMachinesPlay:
6474 if (cps->twoMachinesColor[0] == 'w')
6475 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6477 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6484 } else if (strncmp(message, "opponent mates", 14) == 0) {
6486 case MachinePlaysBlack:
6487 case IcsPlayingBlack:
6488 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6490 case MachinePlaysWhite:
6491 case IcsPlayingWhite:
6492 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6494 case TwoMachinesPlay:
6495 if (cps->twoMachinesColor[0] == 'w')
6496 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6498 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6505 } else if (strncmp(message, "computer mates", 14) == 0) {
6507 case MachinePlaysBlack:
6508 case IcsPlayingBlack:
6509 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6511 case MachinePlaysWhite:
6512 case IcsPlayingWhite:
6513 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6515 case TwoMachinesPlay:
6516 if (cps->twoMachinesColor[0] == 'w')
6517 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6519 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6526 } else if (strncmp(message, "checkmate", 9) == 0) {
6527 if (WhiteOnMove(forwardMostMove)) {
6528 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6530 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6533 } else if (strstr(message, "Draw") != NULL ||
6534 strstr(message, "game is a draw") != NULL) {
6535 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6537 } else if (strstr(message, "offer") != NULL &&
6538 strstr(message, "draw") != NULL) {
6540 if (appData.zippyPlay && first.initDone) {
6541 /* Relay offer to ICS */
6542 SendToICS(ics_prefix);
6543 SendToICS("draw\n");
6546 cps->offeredDraw = 2; /* valid until this engine moves twice */
6547 if (gameMode == TwoMachinesPlay) {
6548 if (cps->other->offeredDraw) {
6549 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6550 /* [HGM] in two-machine mode we delay relaying draw offer */
6551 /* until after we also have move, to see if it is really claim */
6555 if (cps->other->sendDrawOffers) {
6556 SendToProgram("draw\n", cps->other);
6560 } else if (gameMode == MachinePlaysWhite ||
6561 gameMode == MachinePlaysBlack) {
6562 if (userOfferedDraw) {
6563 DisplayInformation(_("Machine accepts your draw offer"));
6564 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6566 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6573 * Look for thinking output
6575 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6576 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6578 int plylev, mvleft, mvtot, curscore, time;
6579 char mvname[MOVE_LEN];
6583 int prefixHint = FALSE;
6584 mvname[0] = NULLCHAR;
6587 case MachinePlaysBlack:
6588 case IcsPlayingBlack:
6589 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6591 case MachinePlaysWhite:
6592 case IcsPlayingWhite:
6593 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6598 case IcsObserving: /* [DM] icsEngineAnalyze */
6599 if (!appData.icsEngineAnalyze) ignore = TRUE;
6601 case TwoMachinesPlay:
6602 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6613 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6614 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6616 if (plyext != ' ' && plyext != '\t') {
6620 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6621 if( cps->scoreIsAbsolute &&
6622 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6624 curscore = -curscore;
6628 programStats.depth = plylev;
6629 programStats.nodes = nodes;
6630 programStats.time = time;
6631 programStats.score = curscore;
6632 programStats.got_only_move = 0;
6634 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6637 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6638 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6639 if(WhiteOnMove(forwardMostMove))
6640 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6641 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6644 /* Buffer overflow protection */
6645 if (buf1[0] != NULLCHAR) {
6646 if (strlen(buf1) >= sizeof(programStats.movelist)
6647 && appData.debugMode) {
6649 "PV is too long; using the first %d bytes.\n",
6650 sizeof(programStats.movelist) - 1);
6653 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6655 sprintf(programStats.movelist, " no PV\n");
6658 if (programStats.seen_stat) {
6659 programStats.ok_to_send = 1;
6662 if (strchr(programStats.movelist, '(') != NULL) {
6663 programStats.line_is_book = 1;
6664 programStats.nr_moves = 0;
6665 programStats.moves_left = 0;
6667 programStats.line_is_book = 0;
6670 SendProgramStatsToFrontend( cps, &programStats );
6673 [AS] Protect the thinkOutput buffer from overflow... this
6674 is only useful if buf1 hasn't overflowed first!
6676 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6678 (gameMode == TwoMachinesPlay ?
6679 ToUpper(cps->twoMachinesColor[0]) : ' '),
6680 ((double) curscore) / 100.0,
6681 prefixHint ? lastHint : "",
6682 prefixHint ? " " : "" );
6684 if( buf1[0] != NULLCHAR ) {
6685 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6687 if( strlen(buf1) > max_len ) {
6688 if( appData.debugMode) {
6689 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6691 buf1[max_len+1] = '\0';
6694 strcat( thinkOutput, buf1 );
6697 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6698 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6699 DisplayMove(currentMove - 1);
6704 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6705 /* crafty (9.25+) says "(only move) <move>"
6706 * if there is only 1 legal move
6708 sscanf(p, "(only move) %s", buf1);
6709 sprintf(thinkOutput, "%s (only move)", buf1);
6710 sprintf(programStats.movelist, "%s (only move)", buf1);
6711 programStats.depth = 1;
6712 programStats.nr_moves = 1;
6713 programStats.moves_left = 1;
6714 programStats.nodes = 1;
6715 programStats.time = 1;
6716 programStats.got_only_move = 1;
6718 /* Not really, but we also use this member to
6719 mean "line isn't going to change" (Crafty
6720 isn't searching, so stats won't change) */
6721 programStats.line_is_book = 1;
6723 SendProgramStatsToFrontend( cps, &programStats );
6725 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6726 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6727 DisplayMove(currentMove - 1);
6731 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6732 &time, &nodes, &plylev, &mvleft,
6733 &mvtot, mvname) >= 5) {
6734 /* The stat01: line is from Crafty (9.29+) in response
6735 to the "." command */
6736 programStats.seen_stat = 1;
6737 cps->maybeThinking = TRUE;
6739 if (programStats.got_only_move || !appData.periodicUpdates)
6742 programStats.depth = plylev;
6743 programStats.time = time;
6744 programStats.nodes = nodes;
6745 programStats.moves_left = mvleft;
6746 programStats.nr_moves = mvtot;
6747 strcpy(programStats.move_name, mvname);
6748 programStats.ok_to_send = 1;
6749 programStats.movelist[0] = '\0';
6751 SendProgramStatsToFrontend( cps, &programStats );
6756 } else if (strncmp(message,"++",2) == 0) {
6757 /* Crafty 9.29+ outputs this */
6758 programStats.got_fail = 2;
6761 } else if (strncmp(message,"--",2) == 0) {
6762 /* Crafty 9.29+ outputs this */
6763 programStats.got_fail = 1;
6766 } else if (thinkOutput[0] != NULLCHAR &&
6767 strncmp(message, " ", 4) == 0) {
6768 unsigned message_len;
6771 while (*p && *p == ' ') p++;
6773 message_len = strlen( p );
6775 /* [AS] Avoid buffer overflow */
6776 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6777 strcat(thinkOutput, " ");
6778 strcat(thinkOutput, p);
6781 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6782 strcat(programStats.movelist, " ");
6783 strcat(programStats.movelist, p);
6786 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6787 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6788 DisplayMove(currentMove - 1);
6797 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6798 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6800 ChessProgramStats cpstats;
6802 if (plyext != ' ' && plyext != '\t') {
6806 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6807 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6808 curscore = -curscore;
6811 cpstats.depth = plylev;
6812 cpstats.nodes = nodes;
6813 cpstats.time = time;
6814 cpstats.score = curscore;
6815 cpstats.got_only_move = 0;
6816 cpstats.movelist[0] = '\0';
6818 if (buf1[0] != NULLCHAR) {
6819 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6822 cpstats.ok_to_send = 0;
6823 cpstats.line_is_book = 0;
6824 cpstats.nr_moves = 0;
6825 cpstats.moves_left = 0;
6827 SendProgramStatsToFrontend( cps, &cpstats );
6834 /* Parse a game score from the character string "game", and
6835 record it as the history of the current game. The game
6836 score is NOT assumed to start from the standard position.
6837 The display is not updated in any way.
6840 ParseGameHistory(game)
6844 int fromX, fromY, toX, toY, boardIndex;
6849 if (appData.debugMode)
6850 fprintf(debugFP, "Parsing game history: %s\n", game);
6852 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6853 gameInfo.site = StrSave(appData.icsHost);
6854 gameInfo.date = PGNDate();
6855 gameInfo.round = StrSave("-");
6857 /* Parse out names of players */
6858 while (*game == ' ') game++;
6860 while (*game != ' ') *p++ = *game++;
6862 gameInfo.white = StrSave(buf);
6863 while (*game == ' ') game++;
6865 while (*game != ' ' && *game != '\n') *p++ = *game++;
6867 gameInfo.black = StrSave(buf);
6870 boardIndex = blackPlaysFirst ? 1 : 0;
6873 yyboardindex = boardIndex;
6874 moveType = (ChessMove) yylex();
6876 case IllegalMove: /* maybe suicide chess, etc. */
6877 if (appData.debugMode) {
6878 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6879 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6880 setbuf(debugFP, NULL);
6882 case WhitePromotionChancellor:
6883 case BlackPromotionChancellor:
6884 case WhitePromotionArchbishop:
6885 case BlackPromotionArchbishop:
6886 case WhitePromotionQueen:
6887 case BlackPromotionQueen:
6888 case WhitePromotionRook:
6889 case BlackPromotionRook:
6890 case WhitePromotionBishop:
6891 case BlackPromotionBishop:
6892 case WhitePromotionKnight:
6893 case BlackPromotionKnight:
6894 case WhitePromotionKing:
6895 case BlackPromotionKing:
6897 case WhiteCapturesEnPassant:
6898 case BlackCapturesEnPassant:
6899 case WhiteKingSideCastle:
6900 case WhiteQueenSideCastle:
6901 case BlackKingSideCastle:
6902 case BlackQueenSideCastle:
6903 case WhiteKingSideCastleWild:
6904 case WhiteQueenSideCastleWild:
6905 case BlackKingSideCastleWild:
6906 case BlackQueenSideCastleWild:
6908 case WhiteHSideCastleFR:
6909 case WhiteASideCastleFR:
6910 case BlackHSideCastleFR:
6911 case BlackASideCastleFR:
6913 fromX = currentMoveString[0] - AAA;
6914 fromY = currentMoveString[1] - ONE;
6915 toX = currentMoveString[2] - AAA;
6916 toY = currentMoveString[3] - ONE;
6917 promoChar = currentMoveString[4];
6921 fromX = moveType == WhiteDrop ?
6922 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6923 (int) CharToPiece(ToLower(currentMoveString[0]));
6925 toX = currentMoveString[2] - AAA;
6926 toY = currentMoveString[3] - ONE;
6927 promoChar = NULLCHAR;
6931 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6932 if (appData.debugMode) {
6933 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6934 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6935 setbuf(debugFP, NULL);
6937 DisplayError(buf, 0);
6939 case ImpossibleMove:
6941 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6942 if (appData.debugMode) {
6943 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6944 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6945 setbuf(debugFP, NULL);
6947 DisplayError(buf, 0);
6949 case (ChessMove) 0: /* end of file */
6950 if (boardIndex < backwardMostMove) {
6951 /* Oops, gap. How did that happen? */
6952 DisplayError(_("Gap in move list"), 0);
6955 backwardMostMove = blackPlaysFirst ? 1 : 0;
6956 if (boardIndex > forwardMostMove) {
6957 forwardMostMove = boardIndex;
6961 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6962 strcat(parseList[boardIndex-1], " ");
6963 strcat(parseList[boardIndex-1], yy_text);
6975 case GameUnfinished:
6976 if (gameMode == IcsExamining) {
6977 if (boardIndex < backwardMostMove) {
6978 /* Oops, gap. How did that happen? */
6981 backwardMostMove = blackPlaysFirst ? 1 : 0;
6984 gameInfo.result = moveType;
6985 p = strchr(yy_text, '{');
6986 if (p == NULL) p = strchr(yy_text, '(');
6989 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6991 q = strchr(p, *p == '{' ? '}' : ')');
6992 if (q != NULL) *q = NULLCHAR;
6995 gameInfo.resultDetails = StrSave(p);
6998 if (boardIndex >= forwardMostMove &&
6999 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7000 backwardMostMove = blackPlaysFirst ? 1 : 0;
7003 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7004 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7005 parseList[boardIndex]);
7006 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7007 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7008 /* currentMoveString is set as a side-effect of yylex */
7009 strcpy(moveList[boardIndex], currentMoveString);
7010 strcat(moveList[boardIndex], "\n");
7012 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7013 castlingRights[boardIndex], &epStatus[boardIndex]);
7014 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7015 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7021 if(gameInfo.variant != VariantShogi)
7022 strcat(parseList[boardIndex - 1], "+");
7026 strcat(parseList[boardIndex - 1], "#");
7033 /* Apply a move to the given board */
7035 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7036 int fromX, fromY, toX, toY;
7042 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7044 /* [HGM] compute & store e.p. status and castling rights for new position */
7045 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7048 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7052 if( board[toY][toX] != EmptySquare )
7055 if( board[fromY][fromX] == WhitePawn ) {
7056 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7059 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7060 gameInfo.variant != VariantBerolina || toX < fromX)
7061 *ep = toX | berolina;
7062 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7063 gameInfo.variant != VariantBerolina || toX > fromX)
7067 if( board[fromY][fromX] == BlackPawn ) {
7068 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7070 if( toY-fromY== -2) {
7071 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7072 gameInfo.variant != VariantBerolina || toX < fromX)
7073 *ep = toX | berolina;
7074 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7075 gameInfo.variant != VariantBerolina || toX > fromX)
7080 for(i=0; i<nrCastlingRights; i++) {
7081 if(castling[i] == fromX && castlingRank[i] == fromY ||
7082 castling[i] == toX && castlingRank[i] == toY
7083 ) castling[i] = -1; // revoke for moved or captured piece
7088 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7089 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7090 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7092 if (fromX == toX && fromY == toY) return;
7094 if (fromY == DROP_RANK) {
7096 piece = board[toY][toX] = (ChessSquare) fromX;
7098 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7099 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7100 if(gameInfo.variant == VariantKnightmate)
7101 king += (int) WhiteUnicorn - (int) WhiteKing;
7103 /* Code added by Tord: */
7104 /* FRC castling assumed when king captures friendly rook. */
7105 if (board[fromY][fromX] == WhiteKing &&
7106 board[toY][toX] == WhiteRook) {
7107 board[fromY][fromX] = EmptySquare;
7108 board[toY][toX] = EmptySquare;
7110 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7112 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7114 } else if (board[fromY][fromX] == BlackKing &&
7115 board[toY][toX] == BlackRook) {
7116 board[fromY][fromX] = EmptySquare;
7117 board[toY][toX] = EmptySquare;
7119 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7121 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7123 /* End of code added by Tord */
7125 } else if (board[fromY][fromX] == king
7126 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7127 && toY == fromY && toX > fromX+1) {
7128 board[fromY][fromX] = EmptySquare;
7129 board[toY][toX] = king;
7130 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7131 board[fromY][BOARD_RGHT-1] = EmptySquare;
7132 } else if (board[fromY][fromX] == king
7133 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7134 && toY == fromY && toX < fromX-1) {
7135 board[fromY][fromX] = EmptySquare;
7136 board[toY][toX] = king;
7137 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7138 board[fromY][BOARD_LEFT] = EmptySquare;
7139 } else if (board[fromY][fromX] == WhitePawn
7140 && toY == BOARD_HEIGHT-1
7141 && gameInfo.variant != VariantXiangqi
7143 /* white pawn promotion */
7144 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7145 if (board[toY][toX] == EmptySquare) {
7146 board[toY][toX] = WhiteQueen;
7148 if(gameInfo.variant==VariantBughouse ||
7149 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7150 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7151 board[fromY][fromX] = EmptySquare;
7152 } else if ((fromY == BOARD_HEIGHT-4)
7154 && gameInfo.variant != VariantXiangqi
7155 && gameInfo.variant != VariantBerolina
7156 && (board[fromY][fromX] == WhitePawn)
7157 && (board[toY][toX] == EmptySquare)) {
7158 board[fromY][fromX] = EmptySquare;
7159 board[toY][toX] = WhitePawn;
7160 captured = board[toY - 1][toX];
7161 board[toY - 1][toX] = EmptySquare;
7162 } else if ((fromY == BOARD_HEIGHT-4)
7164 && gameInfo.variant == VariantBerolina
7165 && (board[fromY][fromX] == WhitePawn)
7166 && (board[toY][toX] == EmptySquare)) {
7167 board[fromY][fromX] = EmptySquare;
7168 board[toY][toX] = WhitePawn;
7169 if(oldEP & EP_BEROLIN_A) {
7170 captured = board[fromY][fromX-1];
7171 board[fromY][fromX-1] = EmptySquare;
7172 }else{ captured = board[fromY][fromX+1];
7173 board[fromY][fromX+1] = EmptySquare;
7175 } else if (board[fromY][fromX] == king
7176 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177 && toY == fromY && toX > fromX+1) {
7178 board[fromY][fromX] = EmptySquare;
7179 board[toY][toX] = king;
7180 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7181 board[fromY][BOARD_RGHT-1] = EmptySquare;
7182 } else if (board[fromY][fromX] == king
7183 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7184 && toY == fromY && toX < fromX-1) {
7185 board[fromY][fromX] = EmptySquare;
7186 board[toY][toX] = king;
7187 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7188 board[fromY][BOARD_LEFT] = EmptySquare;
7189 } else if (fromY == 7 && fromX == 3
7190 && board[fromY][fromX] == BlackKing
7191 && toY == 7 && toX == 5) {
7192 board[fromY][fromX] = EmptySquare;
7193 board[toY][toX] = BlackKing;
7194 board[fromY][7] = EmptySquare;
7195 board[toY][4] = BlackRook;
7196 } else if (fromY == 7 && fromX == 3
7197 && board[fromY][fromX] == BlackKing
7198 && toY == 7 && toX == 1) {
7199 board[fromY][fromX] = EmptySquare;
7200 board[toY][toX] = BlackKing;
7201 board[fromY][0] = EmptySquare;
7202 board[toY][2] = BlackRook;
7203 } else if (board[fromY][fromX] == BlackPawn
7205 && gameInfo.variant != VariantXiangqi
7207 /* black pawn promotion */
7208 board[0][toX] = CharToPiece(ToLower(promoChar));
7209 if (board[0][toX] == EmptySquare) {
7210 board[0][toX] = BlackQueen;
7212 if(gameInfo.variant==VariantBughouse ||
7213 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7214 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7215 board[fromY][fromX] = EmptySquare;
7216 } else if ((fromY == 3)
7218 && gameInfo.variant != VariantXiangqi
7219 && gameInfo.variant != VariantBerolina
7220 && (board[fromY][fromX] == BlackPawn)
7221 && (board[toY][toX] == EmptySquare)) {
7222 board[fromY][fromX] = EmptySquare;
7223 board[toY][toX] = BlackPawn;
7224 captured = board[toY + 1][toX];
7225 board[toY + 1][toX] = EmptySquare;
7226 } else if ((fromY == 3)
7228 && gameInfo.variant == VariantBerolina
7229 && (board[fromY][fromX] == BlackPawn)
7230 && (board[toY][toX] == EmptySquare)) {
7231 board[fromY][fromX] = EmptySquare;
7232 board[toY][toX] = BlackPawn;
7233 if(oldEP & EP_BEROLIN_A) {
7234 captured = board[fromY][fromX-1];
7235 board[fromY][fromX-1] = EmptySquare;
7236 }else{ captured = board[fromY][fromX+1];
7237 board[fromY][fromX+1] = EmptySquare;
7240 board[toY][toX] = board[fromY][fromX];
7241 board[fromY][fromX] = EmptySquare;
7244 /* [HGM] now we promote for Shogi, if needed */
7245 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7246 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7249 if (gameInfo.holdingsWidth != 0) {
7251 /* !!A lot more code needs to be written to support holdings */
7252 /* [HGM] OK, so I have written it. Holdings are stored in the */
7253 /* penultimate board files, so they are automaticlly stored */
7254 /* in the game history. */
7255 if (fromY == DROP_RANK) {
7256 /* Delete from holdings, by decreasing count */
7257 /* and erasing image if necessary */
7259 if(p < (int) BlackPawn) { /* white drop */
7260 p -= (int)WhitePawn;
7261 if(p >= gameInfo.holdingsSize) p = 0;
7262 if(--board[p][BOARD_WIDTH-2] == 0)
7263 board[p][BOARD_WIDTH-1] = EmptySquare;
7264 } else { /* black drop */
7265 p -= (int)BlackPawn;
7266 if(p >= gameInfo.holdingsSize) p = 0;
7267 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7268 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7271 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7272 && gameInfo.variant != VariantBughouse ) {
7273 /* [HGM] holdings: Add to holdings, if holdings exist */
7274 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7275 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7276 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7279 if (p >= (int) BlackPawn) {
7280 p -= (int)BlackPawn;
7281 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7282 /* in Shogi restore piece to its original first */
7283 captured = (ChessSquare) (DEMOTED captured);
7286 p = PieceToNumber((ChessSquare)p);
7287 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7288 board[p][BOARD_WIDTH-2]++;
7289 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7291 p -= (int)WhitePawn;
7292 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7293 captured = (ChessSquare) (DEMOTED captured);
7296 p = PieceToNumber((ChessSquare)p);
7297 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7298 board[BOARD_HEIGHT-1-p][1]++;
7299 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7303 } else if (gameInfo.variant == VariantAtomic) {
7304 if (captured != EmptySquare) {
7306 for (y = toY-1; y <= toY+1; y++) {
7307 for (x = toX-1; x <= toX+1; x++) {
7308 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7309 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7310 board[y][x] = EmptySquare;
7314 board[toY][toX] = EmptySquare;
7317 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7318 /* [HGM] Shogi promotions */
7319 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7322 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7323 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7324 // [HGM] superchess: take promotion piece out of holdings
7325 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7326 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7327 if(!--board[k][BOARD_WIDTH-2])
7328 board[k][BOARD_WIDTH-1] = EmptySquare;
7330 if(!--board[BOARD_HEIGHT-1-k][1])
7331 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7337 /* Updates forwardMostMove */
7339 MakeMove(fromX, fromY, toX, toY, promoChar)
7340 int fromX, fromY, toX, toY;
7343 // forwardMostMove++; // [HGM] bare: moved downstream
7345 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7346 int timeLeft; static int lastLoadFlag=0; int king, piece;
7347 piece = boards[forwardMostMove][fromY][fromX];
7348 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7349 if(gameInfo.variant == VariantKnightmate)
7350 king += (int) WhiteUnicorn - (int) WhiteKing;
7351 if(forwardMostMove == 0) {
7353 fprintf(serverMoves, "%s;", second.tidy);
7354 fprintf(serverMoves, "%s;", first.tidy);
7355 if(!blackPlaysFirst)
7356 fprintf(serverMoves, "%s;", second.tidy);
7357 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7358 lastLoadFlag = loadFlag;
7360 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7361 // print castling suffix
7362 if( toY == fromY && piece == king ) {
7364 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7366 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7369 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7370 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7371 boards[forwardMostMove][toY][toX] == EmptySquare
7373 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7375 if(promoChar != NULLCHAR)
7376 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7378 fprintf(serverMoves, "/%d/%d",
7379 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7380 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7381 else timeLeft = blackTimeRemaining/1000;
7382 fprintf(serverMoves, "/%d", timeLeft);
7384 fflush(serverMoves);
7387 if (forwardMostMove+1 >= MAX_MOVES) {
7388 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7393 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7394 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7395 if (commentList[forwardMostMove+1] != NULL) {
7396 free(commentList[forwardMostMove+1]);
7397 commentList[forwardMostMove+1] = NULL;
7399 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7400 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7401 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7402 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7403 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7404 gameInfo.result = GameUnfinished;
7405 if (gameInfo.resultDetails != NULL) {
7406 free(gameInfo.resultDetails);
7407 gameInfo.resultDetails = NULL;
7409 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7410 moveList[forwardMostMove - 1]);
7411 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7412 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7413 fromY, fromX, toY, toX, promoChar,
7414 parseList[forwardMostMove - 1]);
7415 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7416 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7417 castlingRights[forwardMostMove]) ) {
7423 if(gameInfo.variant != VariantShogi)
7424 strcat(parseList[forwardMostMove - 1], "+");
7428 strcat(parseList[forwardMostMove - 1], "#");
7431 if (appData.debugMode) {
7432 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7437 /* Updates currentMove if not pausing */
7439 ShowMove(fromX, fromY, toX, toY)
7441 int instant = (gameMode == PlayFromGameFile) ?
7442 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7443 if(appData.noGUI) return;
7444 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7446 if (forwardMostMove == currentMove + 1) {
7447 AnimateMove(boards[forwardMostMove - 1],
7448 fromX, fromY, toX, toY);
7450 if (appData.highlightLastMove) {
7451 SetHighlights(fromX, fromY, toX, toY);
7454 currentMove = forwardMostMove;
7457 if (instant) return;
7459 DisplayMove(currentMove - 1);
7460 DrawPosition(FALSE, boards[currentMove]);
7461 DisplayBothClocks();
7462 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7465 void SendEgtPath(ChessProgramState *cps)
7466 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7467 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7469 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7472 char c, *q = name+1, *r, *s;
7474 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7475 while(*p && *p != ',') *q++ = *p++;
7477 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7478 strcmp(name, ",nalimov:") == 0 ) {
7479 // take nalimov path from the menu-changeable option first, if it is defined
7480 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7481 SendToProgram(buf,cps); // send egtbpath command for nalimov
7483 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7484 (s = StrStr(appData.egtFormats, name)) != NULL) {
7485 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7486 s = r = StrStr(s, ":") + 1; // beginning of path info
7487 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7488 c = *r; *r = 0; // temporarily null-terminate path info
7489 *--q = 0; // strip of trailig ':' from name
7490 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7492 SendToProgram(buf,cps); // send egtbpath command for this format
7494 if(*p == ',') p++; // read away comma to position for next format name
7499 InitChessProgram(cps, setup)
7500 ChessProgramState *cps;
7501 int setup; /* [HGM] needed to setup FRC opening position */
7503 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7504 if (appData.noChessProgram) return;
7505 hintRequested = FALSE;
7506 bookRequested = FALSE;
7508 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7509 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7510 if(cps->memSize) { /* [HGM] memory */
7511 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7512 SendToProgram(buf, cps);
7514 SendEgtPath(cps); /* [HGM] EGT */
7515 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7516 sprintf(buf, "cores %d\n", appData.smpCores);
7517 SendToProgram(buf, cps);
7520 SendToProgram(cps->initString, cps);
7521 if (gameInfo.variant != VariantNormal &&
7522 gameInfo.variant != VariantLoadable
7523 /* [HGM] also send variant if board size non-standard */
7524 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7526 char *v = VariantName(gameInfo.variant);
7527 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7528 /* [HGM] in protocol 1 we have to assume all variants valid */
7529 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7530 DisplayFatalError(buf, 0, 1);
7534 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7535 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7536 if( gameInfo.variant == VariantXiangqi )
7537 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7538 if( gameInfo.variant == VariantShogi )
7539 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7540 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7541 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7542 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7543 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7544 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7545 if( gameInfo.variant == VariantCourier )
7546 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7547 if( gameInfo.variant == VariantSuper )
7548 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7549 if( gameInfo.variant == VariantGreat )
7550 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7553 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7554 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7555 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7556 if(StrStr(cps->variants, b) == NULL) {
7557 // specific sized variant not known, check if general sizing allowed
7558 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7559 if(StrStr(cps->variants, "boardsize") == NULL) {
7560 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7561 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7562 DisplayFatalError(buf, 0, 1);
7565 /* [HGM] here we really should compare with the maximum supported board size */
7568 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7569 sprintf(buf, "variant %s\n", b);
7570 SendToProgram(buf, cps);
7572 currentlyInitializedVariant = gameInfo.variant;
7574 /* [HGM] send opening position in FRC to first engine */
7576 SendToProgram("force\n", cps);
7578 /* engine is now in force mode! Set flag to wake it up after first move. */
7579 setboardSpoiledMachineBlack = 1;
7583 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7584 SendToProgram(buf, cps);
7586 cps->maybeThinking = FALSE;
7587 cps->offeredDraw = 0;
7588 if (!appData.icsActive) {
7589 SendTimeControl(cps, movesPerSession, timeControl,
7590 timeIncrement, appData.searchDepth,
7593 if (appData.showThinking
7594 // [HGM] thinking: four options require thinking output to be sent
7595 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7597 SendToProgram("post\n", cps);
7599 SendToProgram("hard\n", cps);
7600 if (!appData.ponderNextMove) {
7601 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7602 it without being sure what state we are in first. "hard"
7603 is not a toggle, so that one is OK.
7605 SendToProgram("easy\n", cps);
7608 sprintf(buf, "ping %d\n", ++cps->lastPing);
7609 SendToProgram(buf, cps);
7611 cps->initDone = TRUE;
7616 StartChessProgram(cps)
7617 ChessProgramState *cps;
7622 if (appData.noChessProgram) return;
7623 cps->initDone = FALSE;
7625 if (strcmp(cps->host, "localhost") == 0) {
7626 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7627 } else if (*appData.remoteShell == NULLCHAR) {
7628 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7630 if (*appData.remoteUser == NULLCHAR) {
7631 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7634 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7635 cps->host, appData.remoteUser, cps->program);
7637 err = StartChildProcess(buf, "", &cps->pr);
7641 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7642 DisplayFatalError(buf, err, 1);
7648 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7649 if (cps->protocolVersion > 1) {
7650 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7651 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7652 cps->comboCnt = 0; // and values of combo boxes
7653 SendToProgram(buf, cps);
7655 SendToProgram("xboard\n", cps);
7661 TwoMachinesEventIfReady P((void))
7663 if (first.lastPing != first.lastPong) {
7664 DisplayMessage("", _("Waiting for first chess program"));
7665 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7668 if (second.lastPing != second.lastPong) {
7669 DisplayMessage("", _("Waiting for second chess program"));
7670 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7678 NextMatchGame P((void))
7680 int index; /* [HGM] autoinc: step lod index during match */
7682 if (*appData.loadGameFile != NULLCHAR) {
7683 index = appData.loadGameIndex;
7684 if(index < 0) { // [HGM] autoinc
7685 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7686 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7688 LoadGameFromFile(appData.loadGameFile,
7690 appData.loadGameFile, FALSE);
7691 } else if (*appData.loadPositionFile != NULLCHAR) {
7692 index = appData.loadPositionIndex;
7693 if(index < 0) { // [HGM] autoinc
7694 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7695 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7697 LoadPositionFromFile(appData.loadPositionFile,
7699 appData.loadPositionFile);
7701 TwoMachinesEventIfReady();
7704 void UserAdjudicationEvent( int result )
7706 ChessMove gameResult = GameIsDrawn;
7709 gameResult = WhiteWins;
7711 else if( result < 0 ) {
7712 gameResult = BlackWins;
7715 if( gameMode == TwoMachinesPlay ) {
7716 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7722 GameEnds(result, resultDetails, whosays)
7724 char *resultDetails;
7727 GameMode nextGameMode;
7731 if(endingGame) return; /* [HGM] crash: forbid recursion */
7734 if (appData.debugMode) {
7735 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7736 result, resultDetails ? resultDetails : "(null)", whosays);
7739 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7740 /* If we are playing on ICS, the server decides when the
7741 game is over, but the engine can offer to draw, claim
7745 if (appData.zippyPlay && first.initDone) {
7746 if (result == GameIsDrawn) {
7747 /* In case draw still needs to be claimed */
7748 SendToICS(ics_prefix);
7749 SendToICS("draw\n");
7750 } else if (StrCaseStr(resultDetails, "resign")) {
7751 SendToICS(ics_prefix);
7752 SendToICS("resign\n");
7756 endingGame = 0; /* [HGM] crash */
7760 /* If we're loading the game from a file, stop */
7761 if (whosays == GE_FILE) {
7762 (void) StopLoadGameTimer();
7766 /* Cancel draw offers */
7767 first.offeredDraw = second.offeredDraw = 0;
7769 /* If this is an ICS game, only ICS can really say it's done;
7770 if not, anyone can. */
7771 isIcsGame = (gameMode == IcsPlayingWhite ||
7772 gameMode == IcsPlayingBlack ||
7773 gameMode == IcsObserving ||
7774 gameMode == IcsExamining);
7776 if (!isIcsGame || whosays == GE_ICS) {
7777 /* OK -- not an ICS game, or ICS said it was done */
7779 if (!isIcsGame && !appData.noChessProgram)
7780 SetUserThinkingEnables();
7782 /* [HGM] if a machine claims the game end we verify this claim */
7783 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7784 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7786 ChessMove trueResult = (ChessMove) -1;
7788 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7789 first.twoMachinesColor[0] :
7790 second.twoMachinesColor[0] ;
7792 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7793 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7794 /* [HGM] verify: engine mate claims accepted if they were flagged */
7795 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7797 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7798 /* [HGM] verify: engine mate claims accepted if they were flagged */
7799 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7801 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7802 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7805 // now verify win claims, but not in drop games, as we don't understand those yet
7806 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7807 || gameInfo.variant == VariantGreat) &&
7808 (result == WhiteWins && claimer == 'w' ||
7809 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7810 if (appData.debugMode) {
7811 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7812 result, epStatus[forwardMostMove], forwardMostMove);
7814 if(result != trueResult) {
7815 sprintf(buf, "False win claim: '%s'", resultDetails);
7816 result = claimer == 'w' ? BlackWins : WhiteWins;
7817 resultDetails = buf;
7820 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7821 && (forwardMostMove <= backwardMostMove ||
7822 epStatus[forwardMostMove-1] > EP_DRAWS ||
7823 (claimer=='b')==(forwardMostMove&1))
7825 /* [HGM] verify: draws that were not flagged are false claims */
7826 sprintf(buf, "False draw claim: '%s'", resultDetails);
7827 result = claimer == 'w' ? BlackWins : WhiteWins;
7828 resultDetails = buf;
7830 /* (Claiming a loss is accepted no questions asked!) */
7832 /* [HGM] bare: don't allow bare King to win */
7833 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7834 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7835 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7836 && result != GameIsDrawn)
7837 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7838 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7839 int p = (int)boards[forwardMostMove][i][j] - color;
7840 if(p >= 0 && p <= (int)WhiteKing) k++;
7842 if (appData.debugMode) {
7843 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7844 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7847 result = GameIsDrawn;
7848 sprintf(buf, "%s but bare king", resultDetails);
7849 resultDetails = buf;
7855 if(serverMoves != NULL && !loadFlag) { char c = '=';
7856 if(result==WhiteWins) c = '+';
7857 if(result==BlackWins) c = '-';
7858 if(resultDetails != NULL)
7859 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7861 if (resultDetails != NULL) {
7862 gameInfo.result = result;
7863 gameInfo.resultDetails = StrSave(resultDetails);
7865 /* display last move only if game was not loaded from file */
7866 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7867 DisplayMove(currentMove - 1);
7869 if (forwardMostMove != 0) {
7870 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7871 if (*appData.saveGameFile != NULLCHAR) {
7872 SaveGameToFile(appData.saveGameFile, TRUE);
7873 } else if (appData.autoSaveGames) {
7876 if (*appData.savePositionFile != NULLCHAR) {
7877 SavePositionToFile(appData.savePositionFile);
7882 /* Tell program how game ended in case it is learning */
7883 /* [HGM] Moved this to after saving the PGN, just in case */
7884 /* engine died and we got here through time loss. In that */
7885 /* case we will get a fatal error writing the pipe, which */
7886 /* would otherwise lose us the PGN. */
7887 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7888 /* output during GameEnds should never be fatal anymore */
7889 if (gameMode == MachinePlaysWhite ||
7890 gameMode == MachinePlaysBlack ||
7891 gameMode == TwoMachinesPlay ||
7892 gameMode == IcsPlayingWhite ||
7893 gameMode == IcsPlayingBlack ||
7894 gameMode == BeginningOfGame) {
7896 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7898 if (first.pr != NoProc) {
7899 SendToProgram(buf, &first);
7901 if (second.pr != NoProc &&
7902 gameMode == TwoMachinesPlay) {
7903 SendToProgram(buf, &second);
7908 if (appData.icsActive) {
7909 if (appData.quietPlay &&
7910 (gameMode == IcsPlayingWhite ||
7911 gameMode == IcsPlayingBlack)) {
7912 SendToICS(ics_prefix);
7913 SendToICS("set shout 1\n");
7915 nextGameMode = IcsIdle;
7916 ics_user_moved = FALSE;
7917 /* clean up premove. It's ugly when the game has ended and the
7918 * premove highlights are still on the board.
7922 ClearPremoveHighlights();
7923 DrawPosition(FALSE, boards[currentMove]);
7925 if (whosays == GE_ICS) {
7928 if (gameMode == IcsPlayingWhite)
7930 else if(gameMode == IcsPlayingBlack)
7934 if (gameMode == IcsPlayingBlack)
7936 else if(gameMode == IcsPlayingWhite)
7943 PlayIcsUnfinishedSound();
7946 } else if (gameMode == EditGame ||
7947 gameMode == PlayFromGameFile ||
7948 gameMode == AnalyzeMode ||
7949 gameMode == AnalyzeFile) {
7950 nextGameMode = gameMode;
7952 nextGameMode = EndOfGame;
7957 nextGameMode = gameMode;
7960 if (appData.noChessProgram) {
7961 gameMode = nextGameMode;
7963 endingGame = 0; /* [HGM] crash */
7968 /* Put first chess program into idle state */
7969 if (first.pr != NoProc &&
7970 (gameMode == MachinePlaysWhite ||
7971 gameMode == MachinePlaysBlack ||
7972 gameMode == TwoMachinesPlay ||
7973 gameMode == IcsPlayingWhite ||
7974 gameMode == IcsPlayingBlack ||
7975 gameMode == BeginningOfGame)) {
7976 SendToProgram("force\n", &first);
7977 if (first.usePing) {
7979 sprintf(buf, "ping %d\n", ++first.lastPing);
7980 SendToProgram(buf, &first);
7983 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7984 /* Kill off first chess program */
7985 if (first.isr != NULL)
7986 RemoveInputSource(first.isr);
7989 if (first.pr != NoProc) {
7991 DoSleep( appData.delayBeforeQuit );
7992 SendToProgram("quit\n", &first);
7993 DoSleep( appData.delayAfterQuit );
7994 DestroyChildProcess(first.pr, first.useSigterm);
7999 /* Put second chess program into idle state */
8000 if (second.pr != NoProc &&
8001 gameMode == TwoMachinesPlay) {
8002 SendToProgram("force\n", &second);
8003 if (second.usePing) {
8005 sprintf(buf, "ping %d\n", ++second.lastPing);
8006 SendToProgram(buf, &second);
8009 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8010 /* Kill off second chess program */
8011 if (second.isr != NULL)
8012 RemoveInputSource(second.isr);
8015 if (second.pr != NoProc) {
8016 DoSleep( appData.delayBeforeQuit );
8017 SendToProgram("quit\n", &second);
8018 DoSleep( appData.delayAfterQuit );
8019 DestroyChildProcess(second.pr, second.useSigterm);
8024 if (matchMode && gameMode == TwoMachinesPlay) {
8027 if (first.twoMachinesColor[0] == 'w') {
8034 if (first.twoMachinesColor[0] == 'b') {
8043 if (matchGame < appData.matchGames) {
8045 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8046 tmp = first.twoMachinesColor;
8047 first.twoMachinesColor = second.twoMachinesColor;
8048 second.twoMachinesColor = tmp;
8050 gameMode = nextGameMode;
8052 if(appData.matchPause>10000 || appData.matchPause<10)
8053 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8054 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8055 endingGame = 0; /* [HGM] crash */
8059 gameMode = nextGameMode;
8060 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8061 first.tidy, second.tidy,
8062 first.matchWins, second.matchWins,
8063 appData.matchGames - (first.matchWins + second.matchWins));
8064 DisplayFatalError(buf, 0, 0);
8067 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8068 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8070 gameMode = nextGameMode;
8072 endingGame = 0; /* [HGM] crash */
8075 /* Assumes program was just initialized (initString sent).
8076 Leaves program in force mode. */
8078 FeedMovesToProgram(cps, upto)
8079 ChessProgramState *cps;
8084 if (appData.debugMode)
8085 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8086 startedFromSetupPosition ? "position and " : "",
8087 backwardMostMove, upto, cps->which);
8088 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8089 // [HGM] variantswitch: make engine aware of new variant
8090 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8091 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8092 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8093 SendToProgram(buf, cps);
8094 currentlyInitializedVariant = gameInfo.variant;
8096 SendToProgram("force\n", cps);
8097 if (startedFromSetupPosition) {
8098 SendBoard(cps, backwardMostMove);
8099 if (appData.debugMode) {
8100 fprintf(debugFP, "feedMoves\n");
8103 for (i = backwardMostMove; i < upto; i++) {
8104 SendMoveToProgram(i, cps);
8110 ResurrectChessProgram()
8112 /* The chess program may have exited.
8113 If so, restart it and feed it all the moves made so far. */
8115 if (appData.noChessProgram || first.pr != NoProc) return;
8117 StartChessProgram(&first);
8118 InitChessProgram(&first, FALSE);
8119 FeedMovesToProgram(&first, currentMove);
8121 if (!first.sendTime) {
8122 /* can't tell gnuchess what its clock should read,
8123 so we bow to its notion. */
8125 timeRemaining[0][currentMove] = whiteTimeRemaining;
8126 timeRemaining[1][currentMove] = blackTimeRemaining;
8129 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8130 appData.icsEngineAnalyze) && first.analysisSupport) {
8131 SendToProgram("analyze\n", &first);
8132 first.analyzing = TRUE;
8145 if (appData.debugMode) {
8146 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8147 redraw, init, gameMode);
8149 pausing = pauseExamInvalid = FALSE;
8150 startedFromSetupPosition = blackPlaysFirst = FALSE;
8152 whiteFlag = blackFlag = FALSE;
8153 userOfferedDraw = FALSE;
8154 hintRequested = bookRequested = FALSE;
8155 first.maybeThinking = FALSE;
8156 second.maybeThinking = FALSE;
8157 first.bookSuspend = FALSE; // [HGM] book
8158 second.bookSuspend = FALSE;
8159 thinkOutput[0] = NULLCHAR;
8160 lastHint[0] = NULLCHAR;
8161 ClearGameInfo(&gameInfo);
8162 gameInfo.variant = StringToVariant(appData.variant);
8163 ics_user_moved = ics_clock_paused = FALSE;
8164 ics_getting_history = H_FALSE;
8166 white_holding[0] = black_holding[0] = NULLCHAR;
8167 ClearProgramStats();
8168 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8172 flipView = appData.flipView;
8173 ClearPremoveHighlights();
8175 alarmSounded = FALSE;
8177 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8178 if(appData.serverMovesName != NULL) {
8179 /* [HGM] prepare to make moves file for broadcasting */
8180 clock_t t = clock();
8181 if(serverMoves != NULL) fclose(serverMoves);
8182 serverMoves = fopen(appData.serverMovesName, "r");
8183 if(serverMoves != NULL) {
8184 fclose(serverMoves);
8185 /* delay 15 sec before overwriting, so all clients can see end */
8186 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8188 serverMoves = fopen(appData.serverMovesName, "w");
8192 gameMode = BeginningOfGame;
8194 if(appData.icsActive) gameInfo.variant = VariantNormal;
8195 InitPosition(redraw);
8196 for (i = 0; i < MAX_MOVES; i++) {
8197 if (commentList[i] != NULL) {
8198 free(commentList[i]);
8199 commentList[i] = NULL;
8203 timeRemaining[0][0] = whiteTimeRemaining;
8204 timeRemaining[1][0] = blackTimeRemaining;
8205 if (first.pr == NULL) {
8206 StartChessProgram(&first);
8209 InitChessProgram(&first, startedFromSetupPosition);
8212 DisplayMessage("", "");
8213 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8220 if (!AutoPlayOneMove())
8222 if (matchMode || appData.timeDelay == 0)
8224 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8226 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8235 int fromX, fromY, toX, toY;
8237 if (appData.debugMode) {
8238 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8241 if (gameMode != PlayFromGameFile)
8244 if (currentMove >= forwardMostMove) {
8245 gameMode = EditGame;
8248 /* [AS] Clear current move marker at the end of a game */
8249 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8254 toX = moveList[currentMove][2] - AAA;
8255 toY = moveList[currentMove][3] - ONE;
8257 if (moveList[currentMove][1] == '@') {
8258 if (appData.highlightLastMove) {
8259 SetHighlights(-1, -1, toX, toY);
8262 fromX = moveList[currentMove][0] - AAA;
8263 fromY = moveList[currentMove][1] - ONE;
8265 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8267 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8269 if (appData.highlightLastMove) {
8270 SetHighlights(fromX, fromY, toX, toY);
8273 DisplayMove(currentMove);
8274 SendMoveToProgram(currentMove++, &first);
8275 DisplayBothClocks();
8276 DrawPosition(FALSE, boards[currentMove]);
8277 // [HGM] PV info: always display, routine tests if empty
8278 DisplayComment(currentMove - 1, commentList[currentMove]);
8284 LoadGameOneMove(readAhead)
8285 ChessMove readAhead;
8287 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8288 char promoChar = NULLCHAR;
8293 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8294 gameMode != AnalyzeMode && gameMode != Training) {
8299 yyboardindex = forwardMostMove;
8300 if (readAhead != (ChessMove)0) {
8301 moveType = readAhead;
8303 if (gameFileFP == NULL)
8305 moveType = (ChessMove) yylex();
8311 if (appData.debugMode)
8312 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8314 if (*p == '{' || *p == '[' || *p == '(') {
8315 p[strlen(p) - 1] = NULLCHAR;
8319 /* append the comment but don't display it */
8320 while (*p == '\n') p++;
8321 AppendComment(currentMove, p);
8324 case WhiteCapturesEnPassant:
8325 case BlackCapturesEnPassant:
8326 case WhitePromotionChancellor:
8327 case BlackPromotionChancellor:
8328 case WhitePromotionArchbishop:
8329 case BlackPromotionArchbishop:
8330 case WhitePromotionCentaur:
8331 case BlackPromotionCentaur:
8332 case WhitePromotionQueen:
8333 case BlackPromotionQueen:
8334 case WhitePromotionRook:
8335 case BlackPromotionRook:
8336 case WhitePromotionBishop:
8337 case BlackPromotionBishop:
8338 case WhitePromotionKnight:
8339 case BlackPromotionKnight:
8340 case WhitePromotionKing:
8341 case BlackPromotionKing:
8343 case WhiteKingSideCastle:
8344 case WhiteQueenSideCastle:
8345 case BlackKingSideCastle:
8346 case BlackQueenSideCastle:
8347 case WhiteKingSideCastleWild:
8348 case WhiteQueenSideCastleWild:
8349 case BlackKingSideCastleWild:
8350 case BlackQueenSideCastleWild:
8352 case WhiteHSideCastleFR:
8353 case WhiteASideCastleFR:
8354 case BlackHSideCastleFR:
8355 case BlackASideCastleFR:
8357 if (appData.debugMode)
8358 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8359 fromX = currentMoveString[0] - AAA;
8360 fromY = currentMoveString[1] - ONE;
8361 toX = currentMoveString[2] - AAA;
8362 toY = currentMoveString[3] - ONE;
8363 promoChar = currentMoveString[4];
8368 if (appData.debugMode)
8369 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8370 fromX = moveType == WhiteDrop ?
8371 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8372 (int) CharToPiece(ToLower(currentMoveString[0]));
8374 toX = currentMoveString[2] - AAA;
8375 toY = currentMoveString[3] - ONE;
8381 case GameUnfinished:
8382 if (appData.debugMode)
8383 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8384 p = strchr(yy_text, '{');
8385 if (p == NULL) p = strchr(yy_text, '(');
8388 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8390 q = strchr(p, *p == '{' ? '}' : ')');
8391 if (q != NULL) *q = NULLCHAR;
8394 GameEnds(moveType, p, GE_FILE);
8396 if (cmailMsgLoaded) {
8398 flipView = WhiteOnMove(currentMove);
8399 if (moveType == GameUnfinished) flipView = !flipView;
8400 if (appData.debugMode)
8401 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8405 case (ChessMove) 0: /* end of file */
8406 if (appData.debugMode)
8407 fprintf(debugFP, "Parser hit end of file\n");
8408 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8409 EP_UNKNOWN, castlingRights[currentMove]) ) {
8415 if (WhiteOnMove(currentMove)) {
8416 GameEnds(BlackWins, "Black mates", GE_FILE);
8418 GameEnds(WhiteWins, "White mates", GE_FILE);
8422 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8429 if (lastLoadGameStart == GNUChessGame) {
8430 /* GNUChessGames have numbers, but they aren't move numbers */
8431 if (appData.debugMode)
8432 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8433 yy_text, (int) moveType);
8434 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8436 /* else fall thru */
8441 /* Reached start of next game in file */
8442 if (appData.debugMode)
8443 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8444 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8445 EP_UNKNOWN, castlingRights[currentMove]) ) {
8451 if (WhiteOnMove(currentMove)) {
8452 GameEnds(BlackWins, "Black mates", GE_FILE);
8454 GameEnds(WhiteWins, "White mates", GE_FILE);
8458 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8464 case PositionDiagram: /* should not happen; ignore */
8465 case ElapsedTime: /* ignore */
8466 case NAG: /* ignore */
8467 if (appData.debugMode)
8468 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8469 yy_text, (int) moveType);
8470 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8473 if (appData.testLegality) {
8474 if (appData.debugMode)
8475 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8476 sprintf(move, _("Illegal move: %d.%s%s"),
8477 (forwardMostMove / 2) + 1,
8478 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8479 DisplayError(move, 0);
8482 if (appData.debugMode)
8483 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8484 yy_text, currentMoveString);
8485 fromX = currentMoveString[0] - AAA;
8486 fromY = currentMoveString[1] - ONE;
8487 toX = currentMoveString[2] - AAA;
8488 toY = currentMoveString[3] - ONE;
8489 promoChar = currentMoveString[4];
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8496 sprintf(move, _("Ambiguous move: %d.%s%s"),
8497 (forwardMostMove / 2) + 1,
8498 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8499 DisplayError(move, 0);
8504 case ImpossibleMove:
8505 if (appData.debugMode)
8506 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8507 sprintf(move, _("Illegal move: %d.%s%s"),
8508 (forwardMostMove / 2) + 1,
8509 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8510 DisplayError(move, 0);
8516 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8517 DrawPosition(FALSE, boards[currentMove]);
8518 DisplayBothClocks();
8519 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8520 DisplayComment(currentMove - 1, commentList[currentMove]);
8522 (void) StopLoadGameTimer();
8524 cmailOldMove = forwardMostMove;
8527 /* currentMoveString is set as a side-effect of yylex */
8528 strcat(currentMoveString, "\n");
8529 strcpy(moveList[forwardMostMove], currentMoveString);
8531 thinkOutput[0] = NULLCHAR;
8532 MakeMove(fromX, fromY, toX, toY, promoChar);
8533 currentMove = forwardMostMove;
8538 /* Load the nth game from the given file */
8540 LoadGameFromFile(filename, n, title, useList)
8544 /*Boolean*/ int useList;
8549 if (strcmp(filename, "-") == 0) {
8553 f = fopen(filename, "rb");
8555 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8556 DisplayError(buf, errno);
8560 if (fseek(f, 0, 0) == -1) {
8561 /* f is not seekable; probably a pipe */
8564 if (useList && n == 0) {
8565 int error = GameListBuild(f);
8567 DisplayError(_("Cannot build game list"), error);
8568 } else if (!ListEmpty(&gameList) &&
8569 ((ListGame *) gameList.tailPred)->number > 1) {
8570 GameListPopUp(f, title);
8577 return LoadGame(f, n, title, FALSE);
8582 MakeRegisteredMove()
8584 int fromX, fromY, toX, toY;
8586 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8587 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8590 if (appData.debugMode)
8591 fprintf(debugFP, "Restoring %s for game %d\n",
8592 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8594 thinkOutput[0] = NULLCHAR;
8595 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8596 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8597 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8598 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8599 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8600 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8601 MakeMove(fromX, fromY, toX, toY, promoChar);
8602 ShowMove(fromX, fromY, toX, toY);
8604 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8605 EP_UNKNOWN, castlingRights[currentMove]) ) {
8612 if (WhiteOnMove(currentMove)) {
8613 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8615 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8620 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8627 if (WhiteOnMove(currentMove)) {
8628 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8630 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8635 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8646 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8648 CmailLoadGame(f, gameNumber, title, useList)
8656 if (gameNumber > nCmailGames) {
8657 DisplayError(_("No more games in this message"), 0);
8660 if (f == lastLoadGameFP) {
8661 int offset = gameNumber - lastLoadGameNumber;
8663 cmailMsg[0] = NULLCHAR;
8664 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8665 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8666 nCmailMovesRegistered--;
8668 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8669 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8670 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8673 if (! RegisterMove()) return FALSE;
8677 retVal = LoadGame(f, gameNumber, title, useList);
8679 /* Make move registered during previous look at this game, if any */
8680 MakeRegisteredMove();
8682 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8683 commentList[currentMove]
8684 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8685 DisplayComment(currentMove - 1, commentList[currentMove]);
8691 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8696 int gameNumber = lastLoadGameNumber + offset;
8697 if (lastLoadGameFP == NULL) {
8698 DisplayError(_("No game has been loaded yet"), 0);
8701 if (gameNumber <= 0) {
8702 DisplayError(_("Can't back up any further"), 0);
8705 if (cmailMsgLoaded) {
8706 return CmailLoadGame(lastLoadGameFP, gameNumber,
8707 lastLoadGameTitle, lastLoadGameUseList);
8709 return LoadGame(lastLoadGameFP, gameNumber,
8710 lastLoadGameTitle, lastLoadGameUseList);
8716 /* Load the nth game from open file f */
8718 LoadGame(f, gameNumber, title, useList)
8726 int gn = gameNumber;
8727 ListGame *lg = NULL;
8730 GameMode oldGameMode;
8731 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8733 if (appData.debugMode)
8734 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8736 if (gameMode == Training )
8737 SetTrainingModeOff();
8739 oldGameMode = gameMode;
8740 if (gameMode != BeginningOfGame) {
8745 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8746 fclose(lastLoadGameFP);
8750 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8753 fseek(f, lg->offset, 0);
8754 GameListHighlight(gameNumber);
8758 DisplayError(_("Game number out of range"), 0);
8763 if (fseek(f, 0, 0) == -1) {
8764 if (f == lastLoadGameFP ?
8765 gameNumber == lastLoadGameNumber + 1 :
8769 DisplayError(_("Can't seek on game file"), 0);
8775 lastLoadGameNumber = gameNumber;
8776 strcpy(lastLoadGameTitle, title);
8777 lastLoadGameUseList = useList;
8781 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8782 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8783 lg->gameInfo.black);
8785 } else if (*title != NULLCHAR) {
8786 if (gameNumber > 1) {
8787 sprintf(buf, "%s %d", title, gameNumber);
8790 DisplayTitle(title);
8794 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8795 gameMode = PlayFromGameFile;
8799 currentMove = forwardMostMove = backwardMostMove = 0;
8800 CopyBoard(boards[0], initialPosition);
8804 * Skip the first gn-1 games in the file.
8805 * Also skip over anything that precedes an identifiable
8806 * start of game marker, to avoid being confused by
8807 * garbage at the start of the file. Currently
8808 * recognized start of game markers are the move number "1",
8809 * the pattern "gnuchess .* game", the pattern
8810 * "^[#;%] [^ ]* game file", and a PGN tag block.
8811 * A game that starts with one of the latter two patterns
8812 * will also have a move number 1, possibly
8813 * following a position diagram.
8814 * 5-4-02: Let's try being more lenient and allowing a game to
8815 * start with an unnumbered move. Does that break anything?
8817 cm = lastLoadGameStart = (ChessMove) 0;
8819 yyboardindex = forwardMostMove;
8820 cm = (ChessMove) yylex();
8823 if (cmailMsgLoaded) {
8824 nCmailGames = CMAIL_MAX_GAMES - gn;
8827 DisplayError(_("Game not found in file"), 0);
8834 lastLoadGameStart = cm;
8838 switch (lastLoadGameStart) {
8845 gn--; /* count this game */
8846 lastLoadGameStart = cm;
8855 switch (lastLoadGameStart) {
8860 gn--; /* count this game */
8861 lastLoadGameStart = cm;
8864 lastLoadGameStart = cm; /* game counted already */
8872 yyboardindex = forwardMostMove;
8873 cm = (ChessMove) yylex();
8874 } while (cm == PGNTag || cm == Comment);
8881 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8882 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8883 != CMAIL_OLD_RESULT) {
8885 cmailResult[ CMAIL_MAX_GAMES
8886 - gn - 1] = CMAIL_OLD_RESULT;
8892 /* Only a NormalMove can be at the start of a game
8893 * without a position diagram. */
8894 if (lastLoadGameStart == (ChessMove) 0) {
8896 lastLoadGameStart = MoveNumberOne;
8905 if (appData.debugMode)
8906 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8908 if (cm == XBoardGame) {
8909 /* Skip any header junk before position diagram and/or move 1 */
8911 yyboardindex = forwardMostMove;
8912 cm = (ChessMove) yylex();
8914 if (cm == (ChessMove) 0 ||
8915 cm == GNUChessGame || cm == XBoardGame) {
8916 /* Empty game; pretend end-of-file and handle later */
8921 if (cm == MoveNumberOne || cm == PositionDiagram ||
8922 cm == PGNTag || cm == Comment)
8925 } else if (cm == GNUChessGame) {
8926 if (gameInfo.event != NULL) {
8927 free(gameInfo.event);
8929 gameInfo.event = StrSave(yy_text);
8932 startedFromSetupPosition = FALSE;
8933 while (cm == PGNTag) {
8934 if (appData.debugMode)
8935 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8936 err = ParsePGNTag(yy_text, &gameInfo);
8937 if (!err) numPGNTags++;
8939 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8940 if(gameInfo.variant != oldVariant) {
8941 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8943 oldVariant = gameInfo.variant;
8944 if (appData.debugMode)
8945 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8949 if (gameInfo.fen != NULL) {
8950 Board initial_position;
8951 startedFromSetupPosition = TRUE;
8952 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8954 DisplayError(_("Bad FEN position in file"), 0);
8957 CopyBoard(boards[0], initial_position);
8958 if (blackPlaysFirst) {
8959 currentMove = forwardMostMove = backwardMostMove = 1;
8960 CopyBoard(boards[1], initial_position);
8961 strcpy(moveList[0], "");
8962 strcpy(parseList[0], "");
8963 timeRemaining[0][1] = whiteTimeRemaining;
8964 timeRemaining[1][1] = blackTimeRemaining;
8965 if (commentList[0] != NULL) {
8966 commentList[1] = commentList[0];
8967 commentList[0] = NULL;
8970 currentMove = forwardMostMove = backwardMostMove = 0;
8972 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8974 initialRulePlies = FENrulePlies;
8975 epStatus[forwardMostMove] = FENepStatus;
8976 for( i=0; i< nrCastlingRights; i++ )
8977 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8979 yyboardindex = forwardMostMove;
8981 gameInfo.fen = NULL;
8984 yyboardindex = forwardMostMove;
8985 cm = (ChessMove) yylex();
8987 /* Handle comments interspersed among the tags */
8988 while (cm == Comment) {
8990 if (appData.debugMode)
8991 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8993 if (*p == '{' || *p == '[' || *p == '(') {
8994 p[strlen(p) - 1] = NULLCHAR;
8997 while (*p == '\n') p++;
8998 AppendComment(currentMove, p);
8999 yyboardindex = forwardMostMove;
9000 cm = (ChessMove) yylex();
9004 /* don't rely on existence of Event tag since if game was
9005 * pasted from clipboard the Event tag may not exist
9007 if (numPGNTags > 0){
9009 if (gameInfo.variant == VariantNormal) {
9010 gameInfo.variant = StringToVariant(gameInfo.event);
9013 if( appData.autoDisplayTags ) {
9014 tags = PGNTags(&gameInfo);
9015 TagsPopUp(tags, CmailMsg());
9020 /* Make something up, but don't display it now */
9025 if (cm == PositionDiagram) {
9028 Board initial_position;
9030 if (appData.debugMode)
9031 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9033 if (!startedFromSetupPosition) {
9035 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9036 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9046 initial_position[i][j++] = CharToPiece(*p);
9049 while (*p == ' ' || *p == '\t' ||
9050 *p == '\n' || *p == '\r') p++;
9052 if (strncmp(p, "black", strlen("black"))==0)
9053 blackPlaysFirst = TRUE;
9055 blackPlaysFirst = FALSE;
9056 startedFromSetupPosition = TRUE;
9058 CopyBoard(boards[0], initial_position);
9059 if (blackPlaysFirst) {
9060 currentMove = forwardMostMove = backwardMostMove = 1;
9061 CopyBoard(boards[1], initial_position);
9062 strcpy(moveList[0], "");
9063 strcpy(parseList[0], "");
9064 timeRemaining[0][1] = whiteTimeRemaining;
9065 timeRemaining[1][1] = blackTimeRemaining;
9066 if (commentList[0] != NULL) {
9067 commentList[1] = commentList[0];
9068 commentList[0] = NULL;
9071 currentMove = forwardMostMove = backwardMostMove = 0;
9074 yyboardindex = forwardMostMove;
9075 cm = (ChessMove) yylex();
9078 if (first.pr == NoProc) {
9079 StartChessProgram(&first);
9081 InitChessProgram(&first, FALSE);
9082 SendToProgram("force\n", &first);
9083 if (startedFromSetupPosition) {
9084 SendBoard(&first, forwardMostMove);
9085 if (appData.debugMode) {
9086 fprintf(debugFP, "Load Game\n");
9088 DisplayBothClocks();
9091 /* [HGM] server: flag to write setup moves in broadcast file as one */
9092 loadFlag = appData.suppressLoadMoves;
9094 while (cm == Comment) {
9096 if (appData.debugMode)
9097 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9099 if (*p == '{' || *p == '[' || *p == '(') {
9100 p[strlen(p) - 1] = NULLCHAR;
9103 while (*p == '\n') p++;
9104 AppendComment(currentMove, p);
9105 yyboardindex = forwardMostMove;
9106 cm = (ChessMove) yylex();
9109 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9110 cm == WhiteWins || cm == BlackWins ||
9111 cm == GameIsDrawn || cm == GameUnfinished) {
9112 DisplayMessage("", _("No moves in game"));
9113 if (cmailMsgLoaded) {
9114 if (appData.debugMode)
9115 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9119 DrawPosition(FALSE, boards[currentMove]);
9120 DisplayBothClocks();
9121 gameMode = EditGame;
9128 // [HGM] PV info: routine tests if comment empty
9129 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9130 DisplayComment(currentMove - 1, commentList[currentMove]);
9132 if (!matchMode && appData.timeDelay != 0)
9133 DrawPosition(FALSE, boards[currentMove]);
9135 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9136 programStats.ok_to_send = 1;
9139 /* if the first token after the PGN tags is a move
9140 * and not move number 1, retrieve it from the parser
9142 if (cm != MoveNumberOne)
9143 LoadGameOneMove(cm);
9145 /* load the remaining moves from the file */
9146 while (LoadGameOneMove((ChessMove)0)) {
9147 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9148 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9151 /* rewind to the start of the game */
9152 currentMove = backwardMostMove;
9154 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9156 if (oldGameMode == AnalyzeFile ||
9157 oldGameMode == AnalyzeMode) {
9161 if (matchMode || appData.timeDelay == 0) {
9163 gameMode = EditGame;
9165 } else if (appData.timeDelay > 0) {
9169 if (appData.debugMode)
9170 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9172 loadFlag = 0; /* [HGM] true game starts */
9176 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9178 ReloadPosition(offset)
9181 int positionNumber = lastLoadPositionNumber + offset;
9182 if (lastLoadPositionFP == NULL) {
9183 DisplayError(_("No position has been loaded yet"), 0);
9186 if (positionNumber <= 0) {
9187 DisplayError(_("Can't back up any further"), 0);
9190 return LoadPosition(lastLoadPositionFP, positionNumber,
9191 lastLoadPositionTitle);
9194 /* Load the nth position from the given file */
9196 LoadPositionFromFile(filename, n, title)
9204 if (strcmp(filename, "-") == 0) {
9205 return LoadPosition(stdin, n, "stdin");
9207 f = fopen(filename, "rb");
9209 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9210 DisplayError(buf, errno);
9213 return LoadPosition(f, n, title);
9218 /* Load the nth position from the given open file, and close it */
9220 LoadPosition(f, positionNumber, title)
9225 char *p, line[MSG_SIZ];
9226 Board initial_position;
9227 int i, j, fenMode, pn;
9229 if (gameMode == Training )
9230 SetTrainingModeOff();
9232 if (gameMode != BeginningOfGame) {
9235 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9236 fclose(lastLoadPositionFP);
9238 if (positionNumber == 0) positionNumber = 1;
9239 lastLoadPositionFP = f;
9240 lastLoadPositionNumber = positionNumber;
9241 strcpy(lastLoadPositionTitle, title);
9242 if (first.pr == NoProc) {
9243 StartChessProgram(&first);
9244 InitChessProgram(&first, FALSE);
9246 pn = positionNumber;
9247 if (positionNumber < 0) {
9248 /* Negative position number means to seek to that byte offset */
9249 if (fseek(f, -positionNumber, 0) == -1) {
9250 DisplayError(_("Can't seek on position file"), 0);
9255 if (fseek(f, 0, 0) == -1) {
9256 if (f == lastLoadPositionFP ?
9257 positionNumber == lastLoadPositionNumber + 1 :
9258 positionNumber == 1) {
9261 DisplayError(_("Can't seek on position file"), 0);
9266 /* See if this file is FEN or old-style xboard */
9267 if (fgets(line, MSG_SIZ, f) == NULL) {
9268 DisplayError(_("Position not found in file"), 0);
9277 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9278 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9279 case '1': case '2': case '3': case '4': case '5': case '6':
9280 case '7': case '8': case '9':
9281 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9282 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9283 case 'C': case 'W': case 'c': case 'w':
9288 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9289 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9293 if (fenMode || line[0] == '#') pn--;
9295 /* skip positions before number pn */
9296 if (fgets(line, MSG_SIZ, f) == NULL) {
9298 DisplayError(_("Position not found in file"), 0);
9301 if (fenMode || line[0] == '#') pn--;
9306 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9307 DisplayError(_("Bad FEN position in file"), 0);
9311 (void) fgets(line, MSG_SIZ, f);
9312 (void) fgets(line, MSG_SIZ, f);
9314 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9315 (void) fgets(line, MSG_SIZ, f);
9316 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9319 initial_position[i][j++] = CharToPiece(*p);
9323 blackPlaysFirst = FALSE;
9325 (void) fgets(line, MSG_SIZ, f);
9326 if (strncmp(line, "black", strlen("black"))==0)
9327 blackPlaysFirst = TRUE;
9330 startedFromSetupPosition = TRUE;
9332 SendToProgram("force\n", &first);
9333 CopyBoard(boards[0], initial_position);
9334 if (blackPlaysFirst) {
9335 currentMove = forwardMostMove = backwardMostMove = 1;
9336 strcpy(moveList[0], "");
9337 strcpy(parseList[0], "");
9338 CopyBoard(boards[1], initial_position);
9339 DisplayMessage("", _("Black to play"));
9341 currentMove = forwardMostMove = backwardMostMove = 0;
9342 DisplayMessage("", _("White to play"));
9344 /* [HGM] copy FEN attributes as well */
9346 initialRulePlies = FENrulePlies;
9347 epStatus[forwardMostMove] = FENepStatus;
9348 for( i=0; i< nrCastlingRights; i++ )
9349 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9351 SendBoard(&first, forwardMostMove);
9352 if (appData.debugMode) {
9354 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9355 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9356 fprintf(debugFP, "Load Position\n");
9359 if (positionNumber > 1) {
9360 sprintf(line, "%s %d", title, positionNumber);
9363 DisplayTitle(title);
9365 gameMode = EditGame;
9368 timeRemaining[0][1] = whiteTimeRemaining;
9369 timeRemaining[1][1] = blackTimeRemaining;
9370 DrawPosition(FALSE, boards[currentMove]);
9377 CopyPlayerNameIntoFileName(dest, src)
9380 while (*src != NULLCHAR && *src != ',') {
9385 *(*dest)++ = *src++;
9390 char *DefaultFileName(ext)
9393 static char def[MSG_SIZ];
9396 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9398 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9400 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9409 /* Save the current game to the given file */
9411 SaveGameToFile(filename, append)
9418 if (strcmp(filename, "-") == 0) {
9419 return SaveGame(stdout, 0, NULL);
9421 f = fopen(filename, append ? "a" : "w");
9423 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9424 DisplayError(buf, errno);
9427 return SaveGame(f, 0, NULL);
9436 static char buf[MSG_SIZ];
9439 p = strchr(str, ' ');
9440 if (p == NULL) return str;
9441 strncpy(buf, str, p - str);
9442 buf[p - str] = NULLCHAR;
9446 #define PGN_MAX_LINE 75
9448 #define PGN_SIDE_WHITE 0
9449 #define PGN_SIDE_BLACK 1
9452 static int FindFirstMoveOutOfBook( int side )
9456 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9457 int index = backwardMostMove;
9458 int has_book_hit = 0;
9460 if( (index % 2) != side ) {
9464 while( index < forwardMostMove ) {
9465 /* Check to see if engine is in book */
9466 int depth = pvInfoList[index].depth;
9467 int score = pvInfoList[index].score;
9473 else if( score == 0 && depth == 63 ) {
9474 in_book = 1; /* Zappa */
9476 else if( score == 2 && depth == 99 ) {
9477 in_book = 1; /* Abrok */
9480 has_book_hit += in_book;
9496 void GetOutOfBookInfo( char * buf )
9500 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9502 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9503 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9507 if( oob[0] >= 0 || oob[1] >= 0 ) {
9508 for( i=0; i<2; i++ ) {
9512 if( i > 0 && oob[0] >= 0 ) {
9516 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9517 sprintf( buf+strlen(buf), "%s%.2f",
9518 pvInfoList[idx].score >= 0 ? "+" : "",
9519 pvInfoList[idx].score / 100.0 );
9525 /* Save game in PGN style and close the file */
9530 int i, offset, linelen, newblock;
9534 int movelen, numlen, blank;
9535 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9537 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9539 tm = time((time_t *) NULL);
9541 PrintPGNTags(f, &gameInfo);
9543 if (backwardMostMove > 0 || startedFromSetupPosition) {
9544 char *fen = PositionToFEN(backwardMostMove, NULL);
9545 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9546 fprintf(f, "\n{--------------\n");
9547 PrintPosition(f, backwardMostMove);
9548 fprintf(f, "--------------}\n");
9552 /* [AS] Out of book annotation */
9553 if( appData.saveOutOfBookInfo ) {
9556 GetOutOfBookInfo( buf );
9558 if( buf[0] != '\0' ) {
9559 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9566 i = backwardMostMove;
9570 while (i < forwardMostMove) {
9571 /* Print comments preceding this move */
9572 if (commentList[i] != NULL) {
9573 if (linelen > 0) fprintf(f, "\n");
9574 fprintf(f, "{\n%s}\n", commentList[i]);
9579 /* Format move number */
9581 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9584 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9586 numtext[0] = NULLCHAR;
9589 numlen = strlen(numtext);
9592 /* Print move number */
9593 blank = linelen > 0 && numlen > 0;
9594 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9603 fprintf(f, numtext);
9607 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9608 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9610 // SavePart already does this!
9611 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9612 int p = movelen - 1;
9613 if(move_buffer[p] == ' ') p--;
9614 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9615 while(p && move_buffer[--p] != '(');
9616 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9621 blank = linelen > 0 && movelen > 0;
9622 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9631 fprintf(f, move_buffer);
9634 /* [AS] Add PV info if present */
9635 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9636 /* [HGM] add time */
9637 char buf[MSG_SIZ]; int seconds = 0;
9640 if(i >= backwardMostMove) {
9642 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9643 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9645 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9646 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9648 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9650 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9653 if( seconds <= 0) buf[0] = 0; else
9654 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9655 seconds = (seconds + 4)/10; // round to full seconds
9656 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9657 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9660 sprintf( move_buffer, "{%s%.2f/%d%s}",
9661 pvInfoList[i].score >= 0 ? "+" : "",
9662 pvInfoList[i].score / 100.0,
9663 pvInfoList[i].depth,
9666 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9668 /* Print score/depth */
9669 blank = linelen > 0 && movelen > 0;
9670 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9679 fprintf(f, move_buffer);
9686 /* Start a new line */
9687 if (linelen > 0) fprintf(f, "\n");
9689 /* Print comments after last move */
9690 if (commentList[i] != NULL) {
9691 fprintf(f, "{\n%s}\n", commentList[i]);
9695 if (gameInfo.resultDetails != NULL &&
9696 gameInfo.resultDetails[0] != NULLCHAR) {
9697 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9698 PGNResult(gameInfo.result));
9700 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9707 /* Save game in old style and close the file */
9715 tm = time((time_t *) NULL);
9717 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9720 if (backwardMostMove > 0 || startedFromSetupPosition) {
9721 fprintf(f, "\n[--------------\n");
9722 PrintPosition(f, backwardMostMove);
9723 fprintf(f, "--------------]\n");
9728 i = backwardMostMove;
9729 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9731 while (i < forwardMostMove) {
9732 if (commentList[i] != NULL) {
9733 fprintf(f, "[%s]\n", commentList[i]);
9737 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9740 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9742 if (commentList[i] != NULL) {
9746 if (i >= forwardMostMove) {
9750 fprintf(f, "%s\n", parseList[i]);
9755 if (commentList[i] != NULL) {
9756 fprintf(f, "[%s]\n", commentList[i]);
9759 /* This isn't really the old style, but it's close enough */
9760 if (gameInfo.resultDetails != NULL &&
9761 gameInfo.resultDetails[0] != NULLCHAR) {
9762 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9763 gameInfo.resultDetails);
9765 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9772 /* Save the current game to open file f and close the file */
9774 SaveGame(f, dummy, dummy2)
9779 if (gameMode == EditPosition) EditPositionDone();
9780 if (appData.oldSaveStyle)
9781 return SaveGameOldStyle(f);
9783 return SaveGamePGN(f);
9786 /* Save the current position to the given file */
9788 SavePositionToFile(filename)
9794 if (strcmp(filename, "-") == 0) {
9795 return SavePosition(stdout, 0, NULL);
9797 f = fopen(filename, "a");
9799 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9800 DisplayError(buf, errno);
9803 SavePosition(f, 0, NULL);
9809 /* Save the current position to the given open file and close the file */
9811 SavePosition(f, dummy, dummy2)
9819 if (appData.oldSaveStyle) {
9820 tm = time((time_t *) NULL);
9822 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9824 fprintf(f, "[--------------\n");
9825 PrintPosition(f, currentMove);
9826 fprintf(f, "--------------]\n");
9828 fen = PositionToFEN(currentMove, NULL);
9829 fprintf(f, "%s\n", fen);
9837 ReloadCmailMsgEvent(unregister)
9841 static char *inFilename = NULL;
9842 static char *outFilename;
9844 struct stat inbuf, outbuf;
9847 /* Any registered moves are unregistered if unregister is set, */
9848 /* i.e. invoked by the signal handler */
9850 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9851 cmailMoveRegistered[i] = FALSE;
9852 if (cmailCommentList[i] != NULL) {
9853 free(cmailCommentList[i]);
9854 cmailCommentList[i] = NULL;
9857 nCmailMovesRegistered = 0;
9860 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9861 cmailResult[i] = CMAIL_NOT_RESULT;
9865 if (inFilename == NULL) {
9866 /* Because the filenames are static they only get malloced once */
9867 /* and they never get freed */
9868 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9869 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9871 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9872 sprintf(outFilename, "%s.out", appData.cmailGameName);
9875 status = stat(outFilename, &outbuf);
9877 cmailMailedMove = FALSE;
9879 status = stat(inFilename, &inbuf);
9880 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9883 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9884 counts the games, notes how each one terminated, etc.
9886 It would be nice to remove this kludge and instead gather all
9887 the information while building the game list. (And to keep it
9888 in the game list nodes instead of having a bunch of fixed-size
9889 parallel arrays.) Note this will require getting each game's
9890 termination from the PGN tags, as the game list builder does
9891 not process the game moves. --mann
9893 cmailMsgLoaded = TRUE;
9894 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9896 /* Load first game in the file or popup game menu */
9897 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9907 char string[MSG_SIZ];
9909 if ( cmailMailedMove
9910 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9911 return TRUE; /* Allow free viewing */
9914 /* Unregister move to ensure that we don't leave RegisterMove */
9915 /* with the move registered when the conditions for registering no */
9917 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9918 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9919 nCmailMovesRegistered --;
9921 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9923 free(cmailCommentList[lastLoadGameNumber - 1]);
9924 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9928 if (cmailOldMove == -1) {
9929 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9933 if (currentMove > cmailOldMove + 1) {
9934 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9938 if (currentMove < cmailOldMove) {
9939 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9943 if (forwardMostMove > currentMove) {
9944 /* Silently truncate extra moves */
9948 if ( (currentMove == cmailOldMove + 1)
9949 || ( (currentMove == cmailOldMove)
9950 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9951 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9952 if (gameInfo.result != GameUnfinished) {
9953 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9956 if (commentList[currentMove] != NULL) {
9957 cmailCommentList[lastLoadGameNumber - 1]
9958 = StrSave(commentList[currentMove]);
9960 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9962 if (appData.debugMode)
9963 fprintf(debugFP, "Saving %s for game %d\n",
9964 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9967 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9969 f = fopen(string, "w");
9970 if (appData.oldSaveStyle) {
9971 SaveGameOldStyle(f); /* also closes the file */
9973 sprintf(string, "%s.pos.out", appData.cmailGameName);
9974 f = fopen(string, "w");
9975 SavePosition(f, 0, NULL); /* also closes the file */
9977 fprintf(f, "{--------------\n");
9978 PrintPosition(f, currentMove);
9979 fprintf(f, "--------------}\n\n");
9981 SaveGame(f, 0, NULL); /* also closes the file*/
9984 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9985 nCmailMovesRegistered ++;
9986 } else if (nCmailGames == 1) {
9987 DisplayError(_("You have not made a move yet"), 0);
9998 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9999 FILE *commandOutput;
10000 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10001 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10007 if (! cmailMsgLoaded) {
10008 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10012 if (nCmailGames == nCmailResults) {
10013 DisplayError(_("No unfinished games"), 0);
10017 #if CMAIL_PROHIBIT_REMAIL
10018 if (cmailMailedMove) {
10019 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);
10020 DisplayError(msg, 0);
10025 if (! (cmailMailedMove || RegisterMove())) return;
10027 if ( cmailMailedMove
10028 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10029 sprintf(string, partCommandString,
10030 appData.debugMode ? " -v" : "", appData.cmailGameName);
10031 commandOutput = popen(string, "r");
10033 if (commandOutput == NULL) {
10034 DisplayError(_("Failed to invoke cmail"), 0);
10036 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10037 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10039 if (nBuffers > 1) {
10040 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10041 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10042 nBytes = MSG_SIZ - 1;
10044 (void) memcpy(msg, buffer, nBytes);
10046 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10048 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10049 cmailMailedMove = TRUE; /* Prevent >1 moves */
10052 for (i = 0; i < nCmailGames; i ++) {
10053 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10058 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10060 sprintf(buffer, "%s/%s.%s.archive",
10062 appData.cmailGameName,
10064 LoadGameFromFile(buffer, 1, buffer, FALSE);
10065 cmailMsgLoaded = FALSE;
10069 DisplayInformation(msg);
10070 pclose(commandOutput);
10073 if ((*cmailMsg) != '\0') {
10074 DisplayInformation(cmailMsg);
10079 #endif /* !WIN32 */
10088 int prependComma = 0;
10090 char string[MSG_SIZ]; /* Space for game-list */
10093 if (!cmailMsgLoaded) return "";
10095 if (cmailMailedMove) {
10096 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10098 /* Create a list of games left */
10099 sprintf(string, "[");
10100 for (i = 0; i < nCmailGames; i ++) {
10101 if (! ( cmailMoveRegistered[i]
10102 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10103 if (prependComma) {
10104 sprintf(number, ",%d", i + 1);
10106 sprintf(number, "%d", i + 1);
10110 strcat(string, number);
10113 strcat(string, "]");
10115 if (nCmailMovesRegistered + nCmailResults == 0) {
10116 switch (nCmailGames) {
10119 _("Still need to make move for game\n"));
10124 _("Still need to make moves for both games\n"));
10129 _("Still need to make moves for all %d games\n"),
10134 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10137 _("Still need to make a move for game %s\n"),
10142 if (nCmailResults == nCmailGames) {
10143 sprintf(cmailMsg, _("No unfinished games\n"));
10145 sprintf(cmailMsg, _("Ready to send mail\n"));
10151 _("Still need to make moves for games %s\n"),
10163 if (gameMode == Training)
10164 SetTrainingModeOff();
10167 cmailMsgLoaded = FALSE;
10168 if (appData.icsActive) {
10169 SendToICS(ics_prefix);
10170 SendToICS("refresh\n");
10180 /* Give up on clean exit */
10184 /* Keep trying for clean exit */
10188 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10190 if (telnetISR != NULL) {
10191 RemoveInputSource(telnetISR);
10193 if (icsPR != NoProc) {
10194 DestroyChildProcess(icsPR, TRUE);
10197 /* Save game if resource set and not already saved by GameEnds() */
10198 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10199 && forwardMostMove > 0) {
10200 if (*appData.saveGameFile != NULLCHAR) {
10201 SaveGameToFile(appData.saveGameFile, TRUE);
10202 } else if (appData.autoSaveGames) {
10205 if (*appData.savePositionFile != NULLCHAR) {
10206 SavePositionToFile(appData.savePositionFile);
10209 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10211 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10212 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10214 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10215 /* make sure this other one finishes before killing it! */
10216 if(endingGame) { int count = 0;
10217 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10218 while(endingGame && count++ < 10) DoSleep(1);
10219 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10222 /* Kill off chess programs */
10223 if (first.pr != NoProc) {
10226 DoSleep( appData.delayBeforeQuit );
10227 SendToProgram("quit\n", &first);
10228 DoSleep( appData.delayAfterQuit );
10229 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10231 if (second.pr != NoProc) {
10232 DoSleep( appData.delayBeforeQuit );
10233 SendToProgram("quit\n", &second);
10234 DoSleep( appData.delayAfterQuit );
10235 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10237 if (first.isr != NULL) {
10238 RemoveInputSource(first.isr);
10240 if (second.isr != NULL) {
10241 RemoveInputSource(second.isr);
10244 ShutDownFrontEnd();
10251 if (appData.debugMode)
10252 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10256 if (gameMode == MachinePlaysWhite ||
10257 gameMode == MachinePlaysBlack) {
10260 DisplayBothClocks();
10262 if (gameMode == PlayFromGameFile) {
10263 if (appData.timeDelay >= 0)
10264 AutoPlayGameLoop();
10265 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10266 Reset(FALSE, TRUE);
10267 SendToICS(ics_prefix);
10268 SendToICS("refresh\n");
10269 } else if (currentMove < forwardMostMove) {
10270 ForwardInner(forwardMostMove);
10272 pauseExamInvalid = FALSE;
10274 switch (gameMode) {
10278 pauseExamForwardMostMove = forwardMostMove;
10279 pauseExamInvalid = FALSE;
10282 case IcsPlayingWhite:
10283 case IcsPlayingBlack:
10287 case PlayFromGameFile:
10288 (void) StopLoadGameTimer();
10292 case BeginningOfGame:
10293 if (appData.icsActive) return;
10294 /* else fall through */
10295 case MachinePlaysWhite:
10296 case MachinePlaysBlack:
10297 case TwoMachinesPlay:
10298 if (forwardMostMove == 0)
10299 return; /* don't pause if no one has moved */
10300 if ((gameMode == MachinePlaysWhite &&
10301 !WhiteOnMove(forwardMostMove)) ||
10302 (gameMode == MachinePlaysBlack &&
10303 WhiteOnMove(forwardMostMove))) {
10316 char title[MSG_SIZ];
10318 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10319 strcpy(title, _("Edit comment"));
10321 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10322 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10323 parseList[currentMove - 1]);
10326 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10333 char *tags = PGNTags(&gameInfo);
10334 EditTagsPopUp(tags);
10341 if (appData.noChessProgram || gameMode == AnalyzeMode)
10344 if (gameMode != AnalyzeFile) {
10345 if (!appData.icsEngineAnalyze) {
10347 if (gameMode != EditGame) return;
10349 ResurrectChessProgram();
10350 SendToProgram("analyze\n", &first);
10351 first.analyzing = TRUE;
10352 /*first.maybeThinking = TRUE;*/
10353 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10354 AnalysisPopUp(_("Analysis"),
10355 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10357 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10362 StartAnalysisClock();
10363 GetTimeMark(&lastNodeCountTime);
10370 if (appData.noChessProgram || gameMode == AnalyzeFile)
10373 if (gameMode != AnalyzeMode) {
10375 if (gameMode != EditGame) return;
10376 ResurrectChessProgram();
10377 SendToProgram("analyze\n", &first);
10378 first.analyzing = TRUE;
10379 /*first.maybeThinking = TRUE;*/
10380 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10381 AnalysisPopUp(_("Analysis"),
10382 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10384 gameMode = AnalyzeFile;
10389 StartAnalysisClock();
10390 GetTimeMark(&lastNodeCountTime);
10395 MachineWhiteEvent()
10398 char *bookHit = NULL;
10400 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10404 if (gameMode == PlayFromGameFile ||
10405 gameMode == TwoMachinesPlay ||
10406 gameMode == Training ||
10407 gameMode == AnalyzeMode ||
10408 gameMode == EndOfGame)
10411 if (gameMode == EditPosition)
10412 EditPositionDone();
10414 if (!WhiteOnMove(currentMove)) {
10415 DisplayError(_("It is not White's turn"), 0);
10419 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10422 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10423 gameMode == AnalyzeFile)
10426 ResurrectChessProgram(); /* in case it isn't running */
10427 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10428 gameMode = MachinePlaysWhite;
10431 gameMode = MachinePlaysWhite;
10435 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10437 if (first.sendName) {
10438 sprintf(buf, "name %s\n", gameInfo.black);
10439 SendToProgram(buf, &first);
10441 if (first.sendTime) {
10442 if (first.useColors) {
10443 SendToProgram("black\n", &first); /*gnu kludge*/
10445 SendTimeRemaining(&first, TRUE);
10447 if (first.useColors) {
10448 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10450 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10451 SetMachineThinkingEnables();
10452 first.maybeThinking = TRUE;
10455 if (appData.autoFlipView && !flipView) {
10456 flipView = !flipView;
10457 DrawPosition(FALSE, NULL);
10458 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10461 if(bookHit) { // [HGM] book: simulate book reply
10462 static char bookMove[MSG_SIZ]; // a bit generous?
10464 programStats.nodes = programStats.depth = programStats.time =
10465 programStats.score = programStats.got_only_move = 0;
10466 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10468 strcpy(bookMove, "move ");
10469 strcat(bookMove, bookHit);
10470 HandleMachineMove(bookMove, &first);
10475 MachineBlackEvent()
10478 char *bookHit = NULL;
10480 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10484 if (gameMode == PlayFromGameFile ||
10485 gameMode == TwoMachinesPlay ||
10486 gameMode == Training ||
10487 gameMode == AnalyzeMode ||
10488 gameMode == EndOfGame)
10491 if (gameMode == EditPosition)
10492 EditPositionDone();
10494 if (WhiteOnMove(currentMove)) {
10495 DisplayError(_("It is not Black's turn"), 0);
10499 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10502 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10503 gameMode == AnalyzeFile)
10506 ResurrectChessProgram(); /* in case it isn't running */
10507 gameMode = MachinePlaysBlack;
10511 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10513 if (first.sendName) {
10514 sprintf(buf, "name %s\n", gameInfo.white);
10515 SendToProgram(buf, &first);
10517 if (first.sendTime) {
10518 if (first.useColors) {
10519 SendToProgram("white\n", &first); /*gnu kludge*/
10521 SendTimeRemaining(&first, FALSE);
10523 if (first.useColors) {
10524 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10526 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10527 SetMachineThinkingEnables();
10528 first.maybeThinking = TRUE;
10531 if (appData.autoFlipView && flipView) {
10532 flipView = !flipView;
10533 DrawPosition(FALSE, NULL);
10534 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10536 if(bookHit) { // [HGM] book: simulate book reply
10537 static char bookMove[MSG_SIZ]; // a bit generous?
10539 programStats.nodes = programStats.depth = programStats.time =
10540 programStats.score = programStats.got_only_move = 0;
10541 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10543 strcpy(bookMove, "move ");
10544 strcat(bookMove, bookHit);
10545 HandleMachineMove(bookMove, &first);
10551 DisplayTwoMachinesTitle()
10554 if (appData.matchGames > 0) {
10555 if (first.twoMachinesColor[0] == 'w') {
10556 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10557 gameInfo.white, gameInfo.black,
10558 first.matchWins, second.matchWins,
10559 matchGame - 1 - (first.matchWins + second.matchWins));
10561 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10562 gameInfo.white, gameInfo.black,
10563 second.matchWins, first.matchWins,
10564 matchGame - 1 - (first.matchWins + second.matchWins));
10567 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10573 TwoMachinesEvent P((void))
10577 ChessProgramState *onmove;
10578 char *bookHit = NULL;
10580 if (appData.noChessProgram) return;
10582 switch (gameMode) {
10583 case TwoMachinesPlay:
10585 case MachinePlaysWhite:
10586 case MachinePlaysBlack:
10587 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10588 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10592 case BeginningOfGame:
10593 case PlayFromGameFile:
10596 if (gameMode != EditGame) return;
10599 EditPositionDone();
10610 forwardMostMove = currentMove;
10611 ResurrectChessProgram(); /* in case first program isn't running */
10613 if (second.pr == NULL) {
10614 StartChessProgram(&second);
10615 if (second.protocolVersion == 1) {
10616 TwoMachinesEventIfReady();
10618 /* kludge: allow timeout for initial "feature" command */
10620 DisplayMessage("", _("Starting second chess program"));
10621 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10625 DisplayMessage("", "");
10626 InitChessProgram(&second, FALSE);
10627 SendToProgram("force\n", &second);
10628 if (startedFromSetupPosition) {
10629 SendBoard(&second, backwardMostMove);
10630 if (appData.debugMode) {
10631 fprintf(debugFP, "Two Machines\n");
10634 for (i = backwardMostMove; i < forwardMostMove; i++) {
10635 SendMoveToProgram(i, &second);
10638 gameMode = TwoMachinesPlay;
10642 DisplayTwoMachinesTitle();
10644 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10650 SendToProgram(first.computerString, &first);
10651 if (first.sendName) {
10652 sprintf(buf, "name %s\n", second.tidy);
10653 SendToProgram(buf, &first);
10655 SendToProgram(second.computerString, &second);
10656 if (second.sendName) {
10657 sprintf(buf, "name %s\n", first.tidy);
10658 SendToProgram(buf, &second);
10662 if (!first.sendTime || !second.sendTime) {
10663 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10664 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10666 if (onmove->sendTime) {
10667 if (onmove->useColors) {
10668 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10670 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10672 if (onmove->useColors) {
10673 SendToProgram(onmove->twoMachinesColor, onmove);
10675 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10676 // SendToProgram("go\n", onmove);
10677 onmove->maybeThinking = TRUE;
10678 SetMachineThinkingEnables();
10682 if(bookHit) { // [HGM] book: simulate book reply
10683 static char bookMove[MSG_SIZ]; // a bit generous?
10685 programStats.nodes = programStats.depth = programStats.time =
10686 programStats.score = programStats.got_only_move = 0;
10687 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10689 strcpy(bookMove, "move ");
10690 strcat(bookMove, bookHit);
10691 HandleMachineMove(bookMove, &first);
10698 if (gameMode == Training) {
10699 SetTrainingModeOff();
10700 gameMode = PlayFromGameFile;
10701 DisplayMessage("", _("Training mode off"));
10703 gameMode = Training;
10704 animateTraining = appData.animate;
10706 /* make sure we are not already at the end of the game */
10707 if (currentMove < forwardMostMove) {
10708 SetTrainingModeOn();
10709 DisplayMessage("", _("Training mode on"));
10711 gameMode = PlayFromGameFile;
10712 DisplayError(_("Already at end of game"), 0);
10721 if (!appData.icsActive) return;
10722 switch (gameMode) {
10723 case IcsPlayingWhite:
10724 case IcsPlayingBlack:
10727 case BeginningOfGame:
10735 EditPositionDone();
10748 gameMode = IcsIdle;
10759 switch (gameMode) {
10761 SetTrainingModeOff();
10763 case MachinePlaysWhite:
10764 case MachinePlaysBlack:
10765 case BeginningOfGame:
10766 SendToProgram("force\n", &first);
10767 SetUserThinkingEnables();
10769 case PlayFromGameFile:
10770 (void) StopLoadGameTimer();
10771 if (gameFileFP != NULL) {
10776 EditPositionDone();
10781 SendToProgram("force\n", &first);
10783 case TwoMachinesPlay:
10784 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10785 ResurrectChessProgram();
10786 SetUserThinkingEnables();
10789 ResurrectChessProgram();
10791 case IcsPlayingBlack:
10792 case IcsPlayingWhite:
10793 DisplayError(_("Warning: You are still playing a game"), 0);
10796 DisplayError(_("Warning: You are still observing a game"), 0);
10799 DisplayError(_("Warning: You are still examining a game"), 0);
10810 first.offeredDraw = second.offeredDraw = 0;
10812 if (gameMode == PlayFromGameFile) {
10813 whiteTimeRemaining = timeRemaining[0][currentMove];
10814 blackTimeRemaining = timeRemaining[1][currentMove];
10818 if (gameMode == MachinePlaysWhite ||
10819 gameMode == MachinePlaysBlack ||
10820 gameMode == TwoMachinesPlay ||
10821 gameMode == EndOfGame) {
10822 i = forwardMostMove;
10823 while (i > currentMove) {
10824 SendToProgram("undo\n", &first);
10827 whiteTimeRemaining = timeRemaining[0][currentMove];
10828 blackTimeRemaining = timeRemaining[1][currentMove];
10829 DisplayBothClocks();
10830 if (whiteFlag || blackFlag) {
10831 whiteFlag = blackFlag = 0;
10836 gameMode = EditGame;
10843 EditPositionEvent()
10845 if (gameMode == EditPosition) {
10851 if (gameMode != EditGame) return;
10853 gameMode = EditPosition;
10856 if (currentMove > 0)
10857 CopyBoard(boards[0], boards[currentMove]);
10859 blackPlaysFirst = !WhiteOnMove(currentMove);
10861 currentMove = forwardMostMove = backwardMostMove = 0;
10862 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10869 /* [DM] icsEngineAnalyze - possible call from other functions */
10870 if (appData.icsEngineAnalyze) {
10871 appData.icsEngineAnalyze = FALSE;
10873 DisplayMessage("",_("Close ICS engine analyze..."));
10875 if (first.analysisSupport && first.analyzing) {
10876 SendToProgram("exit\n", &first);
10877 first.analyzing = FALSE;
10880 thinkOutput[0] = NULLCHAR;
10886 startedFromSetupPosition = TRUE;
10887 InitChessProgram(&first, FALSE);
10888 SendToProgram("force\n", &first);
10889 if (blackPlaysFirst) {
10890 strcpy(moveList[0], "");
10891 strcpy(parseList[0], "");
10892 currentMove = forwardMostMove = backwardMostMove = 1;
10893 CopyBoard(boards[1], boards[0]);
10894 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10896 epStatus[1] = epStatus[0];
10897 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10900 currentMove = forwardMostMove = backwardMostMove = 0;
10902 SendBoard(&first, forwardMostMove);
10903 if (appData.debugMode) {
10904 fprintf(debugFP, "EditPosDone\n");
10907 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10908 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10909 gameMode = EditGame;
10911 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10912 ClearHighlights(); /* [AS] */
10915 /* Pause for `ms' milliseconds */
10916 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10926 } while (SubtractTimeMarks(&m2, &m1) < ms);
10929 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10931 SendMultiLineToICS(buf)
10934 char temp[MSG_SIZ+1], *p;
10941 strncpy(temp, buf, len);
10946 if (*p == '\n' || *p == '\r')
10951 strcat(temp, "\n");
10953 SendToPlayer(temp, strlen(temp));
10957 SetWhiteToPlayEvent()
10959 if (gameMode == EditPosition) {
10960 blackPlaysFirst = FALSE;
10961 DisplayBothClocks(); /* works because currentMove is 0 */
10962 } else if (gameMode == IcsExamining) {
10963 SendToICS(ics_prefix);
10964 SendToICS("tomove white\n");
10969 SetBlackToPlayEvent()
10971 if (gameMode == EditPosition) {
10972 blackPlaysFirst = TRUE;
10973 currentMove = 1; /* kludge */
10974 DisplayBothClocks();
10976 } else if (gameMode == IcsExamining) {
10977 SendToICS(ics_prefix);
10978 SendToICS("tomove black\n");
10983 EditPositionMenuEvent(selection, x, y)
10984 ChessSquare selection;
10988 ChessSquare piece = boards[0][y][x];
10990 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10992 switch (selection) {
10994 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10995 SendToICS(ics_prefix);
10996 SendToICS("bsetup clear\n");
10997 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10998 SendToICS(ics_prefix);
10999 SendToICS("clearboard\n");
11001 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11002 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11003 for (y = 0; y < BOARD_HEIGHT; y++) {
11004 if (gameMode == IcsExamining) {
11005 if (boards[currentMove][y][x] != EmptySquare) {
11006 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11011 boards[0][y][x] = p;
11016 if (gameMode == EditPosition) {
11017 DrawPosition(FALSE, boards[0]);
11022 SetWhiteToPlayEvent();
11026 SetBlackToPlayEvent();
11030 if (gameMode == IcsExamining) {
11031 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11034 boards[0][y][x] = EmptySquare;
11035 DrawPosition(FALSE, boards[0]);
11040 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11041 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11042 selection = (ChessSquare) (PROMOTED piece);
11043 } else if(piece == EmptySquare) selection = WhiteSilver;
11044 else selection = (ChessSquare)((int)piece - 1);
11048 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11049 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11050 selection = (ChessSquare) (DEMOTED piece);
11051 } else if(piece == EmptySquare) selection = BlackSilver;
11052 else selection = (ChessSquare)((int)piece + 1);
11057 if(gameInfo.variant == VariantShatranj ||
11058 gameInfo.variant == VariantXiangqi ||
11059 gameInfo.variant == VariantCourier )
11060 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11065 if(gameInfo.variant == VariantXiangqi)
11066 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11067 if(gameInfo.variant == VariantKnightmate)
11068 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11071 if (gameMode == IcsExamining) {
11072 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11073 PieceToChar(selection), AAA + x, ONE + y);
11076 boards[0][y][x] = selection;
11077 DrawPosition(FALSE, boards[0]);
11085 DropMenuEvent(selection, x, y)
11086 ChessSquare selection;
11089 ChessMove moveType;
11091 switch (gameMode) {
11092 case IcsPlayingWhite:
11093 case MachinePlaysBlack:
11094 if (!WhiteOnMove(currentMove)) {
11095 DisplayMoveError(_("It is Black's turn"));
11098 moveType = WhiteDrop;
11100 case IcsPlayingBlack:
11101 case MachinePlaysWhite:
11102 if (WhiteOnMove(currentMove)) {
11103 DisplayMoveError(_("It is White's turn"));
11106 moveType = BlackDrop;
11109 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11115 if (moveType == BlackDrop && selection < BlackPawn) {
11116 selection = (ChessSquare) ((int) selection
11117 + (int) BlackPawn - (int) WhitePawn);
11119 if (boards[currentMove][y][x] != EmptySquare) {
11120 DisplayMoveError(_("That square is occupied"));
11124 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11130 /* Accept a pending offer of any kind from opponent */
11132 if (appData.icsActive) {
11133 SendToICS(ics_prefix);
11134 SendToICS("accept\n");
11135 } else if (cmailMsgLoaded) {
11136 if (currentMove == cmailOldMove &&
11137 commentList[cmailOldMove] != NULL &&
11138 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11139 "Black offers a draw" : "White offers a draw")) {
11141 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11142 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11144 DisplayError(_("There is no pending offer on this move"), 0);
11145 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11148 /* Not used for offers from chess program */
11155 /* Decline a pending offer of any kind from opponent */
11157 if (appData.icsActive) {
11158 SendToICS(ics_prefix);
11159 SendToICS("decline\n");
11160 } else if (cmailMsgLoaded) {
11161 if (currentMove == cmailOldMove &&
11162 commentList[cmailOldMove] != NULL &&
11163 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11164 "Black offers a draw" : "White offers a draw")) {
11166 AppendComment(cmailOldMove, "Draw declined");
11167 DisplayComment(cmailOldMove - 1, "Draw declined");
11170 DisplayError(_("There is no pending offer on this move"), 0);
11173 /* Not used for offers from chess program */
11180 /* Issue ICS rematch command */
11181 if (appData.icsActive) {
11182 SendToICS(ics_prefix);
11183 SendToICS("rematch\n");
11190 /* Call your opponent's flag (claim a win on time) */
11191 if (appData.icsActive) {
11192 SendToICS(ics_prefix);
11193 SendToICS("flag\n");
11195 switch (gameMode) {
11198 case MachinePlaysWhite:
11201 GameEnds(GameIsDrawn, "Both players ran out of time",
11204 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11206 DisplayError(_("Your opponent is not out of time"), 0);
11209 case MachinePlaysBlack:
11212 GameEnds(GameIsDrawn, "Both players ran out of time",
11215 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11217 DisplayError(_("Your opponent is not out of time"), 0);
11227 /* Offer draw or accept pending draw offer from opponent */
11229 if (appData.icsActive) {
11230 /* Note: tournament rules require draw offers to be
11231 made after you make your move but before you punch
11232 your clock. Currently ICS doesn't let you do that;
11233 instead, you immediately punch your clock after making
11234 a move, but you can offer a draw at any time. */
11236 SendToICS(ics_prefix);
11237 SendToICS("draw\n");
11238 } else if (cmailMsgLoaded) {
11239 if (currentMove == cmailOldMove &&
11240 commentList[cmailOldMove] != NULL &&
11241 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11242 "Black offers a draw" : "White offers a draw")) {
11243 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11244 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11245 } else if (currentMove == cmailOldMove + 1) {
11246 char *offer = WhiteOnMove(cmailOldMove) ?
11247 "White offers a draw" : "Black offers a draw";
11248 AppendComment(currentMove, offer);
11249 DisplayComment(currentMove - 1, offer);
11250 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11252 DisplayError(_("You must make your move before offering a draw"), 0);
11253 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11255 } else if (first.offeredDraw) {
11256 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11258 if (first.sendDrawOffers) {
11259 SendToProgram("draw\n", &first);
11260 userOfferedDraw = TRUE;
11268 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11270 if (appData.icsActive) {
11271 SendToICS(ics_prefix);
11272 SendToICS("adjourn\n");
11274 /* Currently GNU Chess doesn't offer or accept Adjourns */
11282 /* Offer Abort or accept pending Abort offer from opponent */
11284 if (appData.icsActive) {
11285 SendToICS(ics_prefix);
11286 SendToICS("abort\n");
11288 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11295 /* Resign. You can do this even if it's not your turn. */
11297 if (appData.icsActive) {
11298 SendToICS(ics_prefix);
11299 SendToICS("resign\n");
11301 switch (gameMode) {
11302 case MachinePlaysWhite:
11303 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11305 case MachinePlaysBlack:
11306 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11309 if (cmailMsgLoaded) {
11311 if (WhiteOnMove(cmailOldMove)) {
11312 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11314 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11316 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11327 StopObservingEvent()
11329 /* Stop observing current games */
11330 SendToICS(ics_prefix);
11331 SendToICS("unobserve\n");
11335 StopExaminingEvent()
11337 /* Stop observing current game */
11338 SendToICS(ics_prefix);
11339 SendToICS("unexamine\n");
11343 ForwardInner(target)
11348 if (appData.debugMode)
11349 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11350 target, currentMove, forwardMostMove);
11352 if (gameMode == EditPosition)
11355 if (gameMode == PlayFromGameFile && !pausing)
11358 if (gameMode == IcsExamining && pausing)
11359 limit = pauseExamForwardMostMove;
11361 limit = forwardMostMove;
11363 if (target > limit) target = limit;
11365 if (target > 0 && moveList[target - 1][0]) {
11366 int fromX, fromY, toX, toY;
11367 toX = moveList[target - 1][2] - AAA;
11368 toY = moveList[target - 1][3] - ONE;
11369 if (moveList[target - 1][1] == '@') {
11370 if (appData.highlightLastMove) {
11371 SetHighlights(-1, -1, toX, toY);
11374 fromX = moveList[target - 1][0] - AAA;
11375 fromY = moveList[target - 1][1] - ONE;
11376 if (target == currentMove + 1) {
11377 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11379 if (appData.highlightLastMove) {
11380 SetHighlights(fromX, fromY, toX, toY);
11384 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11385 gameMode == Training || gameMode == PlayFromGameFile ||
11386 gameMode == AnalyzeFile) {
11387 while (currentMove < target) {
11388 SendMoveToProgram(currentMove++, &first);
11391 currentMove = target;
11394 if (gameMode == EditGame || gameMode == EndOfGame) {
11395 whiteTimeRemaining = timeRemaining[0][currentMove];
11396 blackTimeRemaining = timeRemaining[1][currentMove];
11398 DisplayBothClocks();
11399 DisplayMove(currentMove - 1);
11400 DrawPosition(FALSE, boards[currentMove]);
11401 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11402 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11403 DisplayComment(currentMove - 1, commentList[currentMove]);
11411 if (gameMode == IcsExamining && !pausing) {
11412 SendToICS(ics_prefix);
11413 SendToICS("forward\n");
11415 ForwardInner(currentMove + 1);
11422 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11423 /* to optimze, we temporarily turn off analysis mode while we feed
11424 * the remaining moves to the engine. Otherwise we get analysis output
11427 if (first.analysisSupport) {
11428 SendToProgram("exit\nforce\n", &first);
11429 first.analyzing = FALSE;
11433 if (gameMode == IcsExamining && !pausing) {
11434 SendToICS(ics_prefix);
11435 SendToICS("forward 999999\n");
11437 ForwardInner(forwardMostMove);
11440 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11441 /* we have fed all the moves, so reactivate analysis mode */
11442 SendToProgram("analyze\n", &first);
11443 first.analyzing = TRUE;
11444 /*first.maybeThinking = TRUE;*/
11445 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11450 BackwardInner(target)
11453 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11455 if (appData.debugMode)
11456 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11457 target, currentMove, forwardMostMove);
11459 if (gameMode == EditPosition) return;
11460 if (currentMove <= backwardMostMove) {
11462 DrawPosition(full_redraw, boards[currentMove]);
11465 if (gameMode == PlayFromGameFile && !pausing)
11468 if (moveList[target][0]) {
11469 int fromX, fromY, toX, toY;
11470 toX = moveList[target][2] - AAA;
11471 toY = moveList[target][3] - ONE;
11472 if (moveList[target][1] == '@') {
11473 if (appData.highlightLastMove) {
11474 SetHighlights(-1, -1, toX, toY);
11477 fromX = moveList[target][0] - AAA;
11478 fromY = moveList[target][1] - ONE;
11479 if (target == currentMove - 1) {
11480 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11482 if (appData.highlightLastMove) {
11483 SetHighlights(fromX, fromY, toX, toY);
11487 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11488 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11489 while (currentMove > target) {
11490 SendToProgram("undo\n", &first);
11494 currentMove = target;
11497 if (gameMode == EditGame || gameMode == EndOfGame) {
11498 whiteTimeRemaining = timeRemaining[0][currentMove];
11499 blackTimeRemaining = timeRemaining[1][currentMove];
11501 DisplayBothClocks();
11502 DisplayMove(currentMove - 1);
11503 DrawPosition(full_redraw, boards[currentMove]);
11504 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11505 // [HGM] PV info: routine tests if comment empty
11506 DisplayComment(currentMove - 1, commentList[currentMove]);
11512 if (gameMode == IcsExamining && !pausing) {
11513 SendToICS(ics_prefix);
11514 SendToICS("backward\n");
11516 BackwardInner(currentMove - 1);
11523 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11524 /* to optimze, we temporarily turn off analysis mode while we undo
11525 * all the moves. Otherwise we get analysis output after each undo.
11527 if (first.analysisSupport) {
11528 SendToProgram("exit\nforce\n", &first);
11529 first.analyzing = FALSE;
11533 if (gameMode == IcsExamining && !pausing) {
11534 SendToICS(ics_prefix);
11535 SendToICS("backward 999999\n");
11537 BackwardInner(backwardMostMove);
11540 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11541 /* we have fed all the moves, so reactivate analysis mode */
11542 SendToProgram("analyze\n", &first);
11543 first.analyzing = TRUE;
11544 /*first.maybeThinking = TRUE;*/
11545 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11552 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11553 if (to >= forwardMostMove) to = forwardMostMove;
11554 if (to <= backwardMostMove) to = backwardMostMove;
11555 if (to < currentMove) {
11565 if (gameMode != IcsExamining) {
11566 DisplayError(_("You are not examining a game"), 0);
11570 DisplayError(_("You can't revert while pausing"), 0);
11573 SendToICS(ics_prefix);
11574 SendToICS("revert\n");
11580 switch (gameMode) {
11581 case MachinePlaysWhite:
11582 case MachinePlaysBlack:
11583 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11584 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11587 if (forwardMostMove < 2) return;
11588 currentMove = forwardMostMove = forwardMostMove - 2;
11589 whiteTimeRemaining = timeRemaining[0][currentMove];
11590 blackTimeRemaining = timeRemaining[1][currentMove];
11591 DisplayBothClocks();
11592 DisplayMove(currentMove - 1);
11593 ClearHighlights();/*!! could figure this out*/
11594 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11595 SendToProgram("remove\n", &first);
11596 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11599 case BeginningOfGame:
11603 case IcsPlayingWhite:
11604 case IcsPlayingBlack:
11605 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11606 SendToICS(ics_prefix);
11607 SendToICS("takeback 2\n");
11609 SendToICS(ics_prefix);
11610 SendToICS("takeback 1\n");
11619 ChessProgramState *cps;
11621 switch (gameMode) {
11622 case MachinePlaysWhite:
11623 if (!WhiteOnMove(forwardMostMove)) {
11624 DisplayError(_("It is your turn"), 0);
11629 case MachinePlaysBlack:
11630 if (WhiteOnMove(forwardMostMove)) {
11631 DisplayError(_("It is your turn"), 0);
11636 case TwoMachinesPlay:
11637 if (WhiteOnMove(forwardMostMove) ==
11638 (first.twoMachinesColor[0] == 'w')) {
11644 case BeginningOfGame:
11648 SendToProgram("?\n", cps);
11652 TruncateGameEvent()
11655 if (gameMode != EditGame) return;
11662 if (forwardMostMove > currentMove) {
11663 if (gameInfo.resultDetails != NULL) {
11664 free(gameInfo.resultDetails);
11665 gameInfo.resultDetails = NULL;
11666 gameInfo.result = GameUnfinished;
11668 forwardMostMove = currentMove;
11669 HistorySet(parseList, backwardMostMove, forwardMostMove,
11677 if (appData.noChessProgram) return;
11678 switch (gameMode) {
11679 case MachinePlaysWhite:
11680 if (WhiteOnMove(forwardMostMove)) {
11681 DisplayError(_("Wait until your turn"), 0);
11685 case BeginningOfGame:
11686 case MachinePlaysBlack:
11687 if (!WhiteOnMove(forwardMostMove)) {
11688 DisplayError(_("Wait until your turn"), 0);
11693 DisplayError(_("No hint available"), 0);
11696 SendToProgram("hint\n", &first);
11697 hintRequested = TRUE;
11703 if (appData.noChessProgram) return;
11704 switch (gameMode) {
11705 case MachinePlaysWhite:
11706 if (WhiteOnMove(forwardMostMove)) {
11707 DisplayError(_("Wait until your turn"), 0);
11711 case BeginningOfGame:
11712 case MachinePlaysBlack:
11713 if (!WhiteOnMove(forwardMostMove)) {
11714 DisplayError(_("Wait until your turn"), 0);
11719 EditPositionDone();
11721 case TwoMachinesPlay:
11726 SendToProgram("bk\n", &first);
11727 bookOutput[0] = NULLCHAR;
11728 bookRequested = TRUE;
11734 char *tags = PGNTags(&gameInfo);
11735 TagsPopUp(tags, CmailMsg());
11739 /* end button procedures */
11742 PrintPosition(fp, move)
11748 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11749 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11750 char c = PieceToChar(boards[move][i][j]);
11751 fputc(c == 'x' ? '.' : c, fp);
11752 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11755 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11756 fprintf(fp, "white to play\n");
11758 fprintf(fp, "black to play\n");
11765 if (gameInfo.white != NULL) {
11766 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11772 /* Find last component of program's own name, using some heuristics */
11774 TidyProgramName(prog, host, buf)
11775 char *prog, *host, buf[MSG_SIZ];
11778 int local = (strcmp(host, "localhost") == 0);
11779 while (!local && (p = strchr(prog, ';')) != NULL) {
11781 while (*p == ' ') p++;
11784 if (*prog == '"' || *prog == '\'') {
11785 q = strchr(prog + 1, *prog);
11787 q = strchr(prog, ' ');
11789 if (q == NULL) q = prog + strlen(prog);
11791 while (p >= prog && *p != '/' && *p != '\\') p--;
11793 if(p == prog && *p == '"') p++;
11794 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11795 memcpy(buf, p, q - p);
11796 buf[q - p] = NULLCHAR;
11804 TimeControlTagValue()
11807 if (!appData.clockMode) {
11809 } else if (movesPerSession > 0) {
11810 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11811 } else if (timeIncrement == 0) {
11812 sprintf(buf, "%ld", timeControl/1000);
11814 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11816 return StrSave(buf);
11822 /* This routine is used only for certain modes */
11823 VariantClass v = gameInfo.variant;
11824 ClearGameInfo(&gameInfo);
11825 gameInfo.variant = v;
11827 switch (gameMode) {
11828 case MachinePlaysWhite:
11829 gameInfo.event = StrSave( appData.pgnEventHeader );
11830 gameInfo.site = StrSave(HostName());
11831 gameInfo.date = PGNDate();
11832 gameInfo.round = StrSave("-");
11833 gameInfo.white = StrSave(first.tidy);
11834 gameInfo.black = StrSave(UserName());
11835 gameInfo.timeControl = TimeControlTagValue();
11838 case MachinePlaysBlack:
11839 gameInfo.event = StrSave( appData.pgnEventHeader );
11840 gameInfo.site = StrSave(HostName());
11841 gameInfo.date = PGNDate();
11842 gameInfo.round = StrSave("-");
11843 gameInfo.white = StrSave(UserName());
11844 gameInfo.black = StrSave(first.tidy);
11845 gameInfo.timeControl = TimeControlTagValue();
11848 case TwoMachinesPlay:
11849 gameInfo.event = StrSave( appData.pgnEventHeader );
11850 gameInfo.site = StrSave(HostName());
11851 gameInfo.date = PGNDate();
11852 if (matchGame > 0) {
11854 sprintf(buf, "%d", matchGame);
11855 gameInfo.round = StrSave(buf);
11857 gameInfo.round = StrSave("-");
11859 if (first.twoMachinesColor[0] == 'w') {
11860 gameInfo.white = StrSave(first.tidy);
11861 gameInfo.black = StrSave(second.tidy);
11863 gameInfo.white = StrSave(second.tidy);
11864 gameInfo.black = StrSave(first.tidy);
11866 gameInfo.timeControl = TimeControlTagValue();
11870 gameInfo.event = StrSave("Edited game");
11871 gameInfo.site = StrSave(HostName());
11872 gameInfo.date = PGNDate();
11873 gameInfo.round = StrSave("-");
11874 gameInfo.white = StrSave("-");
11875 gameInfo.black = StrSave("-");
11879 gameInfo.event = StrSave("Edited position");
11880 gameInfo.site = StrSave(HostName());
11881 gameInfo.date = PGNDate();
11882 gameInfo.round = StrSave("-");
11883 gameInfo.white = StrSave("-");
11884 gameInfo.black = StrSave("-");
11887 case IcsPlayingWhite:
11888 case IcsPlayingBlack:
11893 case PlayFromGameFile:
11894 gameInfo.event = StrSave("Game from non-PGN file");
11895 gameInfo.site = StrSave(HostName());
11896 gameInfo.date = PGNDate();
11897 gameInfo.round = StrSave("-");
11898 gameInfo.white = StrSave("?");
11899 gameInfo.black = StrSave("?");
11908 ReplaceComment(index, text)
11914 while (*text == '\n') text++;
11915 len = strlen(text);
11916 while (len > 0 && text[len - 1] == '\n') len--;
11918 if (commentList[index] != NULL)
11919 free(commentList[index]);
11922 commentList[index] = NULL;
11925 commentList[index] = (char *) malloc(len + 2);
11926 strncpy(commentList[index], text, len);
11927 commentList[index][len] = '\n';
11928 commentList[index][len + 1] = NULLCHAR;
11941 if (ch == '\r') continue;
11943 } while (ch != '\0');
11947 AppendComment(index, text)
11954 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11957 while (*text == '\n') text++;
11958 len = strlen(text);
11959 while (len > 0 && text[len - 1] == '\n') len--;
11961 if (len == 0) return;
11963 if (commentList[index] != NULL) {
11964 old = commentList[index];
11965 oldlen = strlen(old);
11966 commentList[index] = (char *) malloc(oldlen + len + 2);
11967 strcpy(commentList[index], old);
11969 strncpy(&commentList[index][oldlen], text, len);
11970 commentList[index][oldlen + len] = '\n';
11971 commentList[index][oldlen + len + 1] = NULLCHAR;
11973 commentList[index] = (char *) malloc(len + 2);
11974 strncpy(commentList[index], text, len);
11975 commentList[index][len] = '\n';
11976 commentList[index][len + 1] = NULLCHAR;
11980 static char * FindStr( char * text, char * sub_text )
11982 char * result = strstr( text, sub_text );
11984 if( result != NULL ) {
11985 result += strlen( sub_text );
11991 /* [AS] Try to extract PV info from PGN comment */
11992 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11993 char *GetInfoFromComment( int index, char * text )
11997 if( text != NULL && index > 0 ) {
12000 int time = -1, sec = 0, deci;
12001 char * s_eval = FindStr( text, "[%eval " );
12002 char * s_emt = FindStr( text, "[%emt " );
12004 if( s_eval != NULL || s_emt != NULL ) {
12008 if( s_eval != NULL ) {
12009 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12013 if( delim != ']' ) {
12018 if( s_emt != NULL ) {
12022 /* We expect something like: [+|-]nnn.nn/dd */
12025 sep = strchr( text, '/' );
12026 if( sep == NULL || sep < (text+4) ) {
12030 time = -1; sec = -1; deci = -1;
12031 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12032 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12033 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12034 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12038 if( score_lo < 0 || score_lo >= 100 ) {
12042 if(sec >= 0) time = 600*time + 10*sec; else
12043 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12045 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12047 /* [HGM] PV time: now locate end of PV info */
12048 while( *++sep >= '0' && *sep <= '9'); // strip depth
12050 while( *++sep >= '0' && *sep <= '9'); // strip time
12052 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12054 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12055 while(*sep == ' ') sep++;
12066 pvInfoList[index-1].depth = depth;
12067 pvInfoList[index-1].score = score;
12068 pvInfoList[index-1].time = 10*time; // centi-sec
12074 SendToProgram(message, cps)
12076 ChessProgramState *cps;
12078 int count, outCount, error;
12081 if (cps->pr == NULL) return;
12084 if (appData.debugMode) {
12087 fprintf(debugFP, "%ld >%-6s: %s",
12088 SubtractTimeMarks(&now, &programStartTime),
12089 cps->which, message);
12092 count = strlen(message);
12093 outCount = OutputToProcess(cps->pr, message, count, &error);
12094 if (outCount < count && !exiting
12095 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12096 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12097 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12098 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12099 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12100 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12102 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12104 gameInfo.resultDetails = buf;
12106 DisplayFatalError(buf, error, 1);
12111 ReceiveFromProgram(isr, closure, message, count, error)
12112 InputSourceRef isr;
12120 ChessProgramState *cps = (ChessProgramState *)closure;
12122 if (isr != cps->isr) return; /* Killed intentionally */
12126 _("Error: %s chess program (%s) exited unexpectedly"),
12127 cps->which, cps->program);
12128 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12129 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12130 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12131 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12133 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12135 gameInfo.resultDetails = buf;
12137 RemoveInputSource(cps->isr);
12138 DisplayFatalError(buf, 0, 1);
12141 _("Error reading from %s chess program (%s)"),
12142 cps->which, cps->program);
12143 RemoveInputSource(cps->isr);
12145 /* [AS] Program is misbehaving badly... kill it */
12146 if( count == -2 ) {
12147 DestroyChildProcess( cps->pr, 9 );
12151 DisplayFatalError(buf, error, 1);
12156 if ((end_str = strchr(message, '\r')) != NULL)
12157 *end_str = NULLCHAR;
12158 if ((end_str = strchr(message, '\n')) != NULL)
12159 *end_str = NULLCHAR;
12161 if (appData.debugMode) {
12162 TimeMark now; int print = 1;
12163 char *quote = ""; char c; int i;
12165 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12166 char start = message[0];
12167 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12168 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12169 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12170 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12171 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12172 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12173 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12174 { quote = "# "; print = (appData.engineComments == 2); }
12175 message[0] = start; // restore original message
12179 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12180 SubtractTimeMarks(&now, &programStartTime), cps->which,
12186 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12187 if (appData.icsEngineAnalyze) {
12188 if (strstr(message, "whisper") != NULL ||
12189 strstr(message, "kibitz") != NULL ||
12190 strstr(message, "tellics") != NULL) return;
12193 HandleMachineMove(message, cps);
12198 SendTimeControl(cps, mps, tc, inc, sd, st)
12199 ChessProgramState *cps;
12200 int mps, inc, sd, st;
12206 if( timeControl_2 > 0 ) {
12207 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12208 tc = timeControl_2;
12211 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12212 inc /= cps->timeOdds;
12213 st /= cps->timeOdds;
12215 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12218 /* Set exact time per move, normally using st command */
12219 if (cps->stKludge) {
12220 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12222 if (seconds == 0) {
12223 sprintf(buf, "level 1 %d\n", st/60);
12225 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12228 sprintf(buf, "st %d\n", st);
12231 /* Set conventional or incremental time control, using level command */
12232 if (seconds == 0) {
12233 /* Note old gnuchess bug -- minutes:seconds used to not work.
12234 Fixed in later versions, but still avoid :seconds
12235 when seconds is 0. */
12236 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12238 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12239 seconds, inc/1000);
12242 SendToProgram(buf, cps);
12244 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12245 /* Orthogonally, limit search to given depth */
12247 if (cps->sdKludge) {
12248 sprintf(buf, "depth\n%d\n", sd);
12250 sprintf(buf, "sd %d\n", sd);
12252 SendToProgram(buf, cps);
12255 if(cps->nps > 0) { /* [HGM] nps */
12256 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12258 sprintf(buf, "nps %d\n", cps->nps);
12259 SendToProgram(buf, cps);
12264 ChessProgramState *WhitePlayer()
12265 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12267 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12268 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12274 SendTimeRemaining(cps, machineWhite)
12275 ChessProgramState *cps;
12276 int /*boolean*/ machineWhite;
12278 char message[MSG_SIZ];
12281 /* Note: this routine must be called when the clocks are stopped
12282 or when they have *just* been set or switched; otherwise
12283 it will be off by the time since the current tick started.
12285 if (machineWhite) {
12286 time = whiteTimeRemaining / 10;
12287 otime = blackTimeRemaining / 10;
12289 time = blackTimeRemaining / 10;
12290 otime = whiteTimeRemaining / 10;
12292 /* [HGM] translate opponent's time by time-odds factor */
12293 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12294 if (appData.debugMode) {
12295 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12298 if (time <= 0) time = 1;
12299 if (otime <= 0) otime = 1;
12301 sprintf(message, "time %ld\n", time);
12302 SendToProgram(message, cps);
12304 sprintf(message, "otim %ld\n", otime);
12305 SendToProgram(message, cps);
12309 BoolFeature(p, name, loc, cps)
12313 ChessProgramState *cps;
12316 int len = strlen(name);
12318 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12320 sscanf(*p, "%d", &val);
12322 while (**p && **p != ' ') (*p)++;
12323 sprintf(buf, "accepted %s\n", name);
12324 SendToProgram(buf, cps);
12331 IntFeature(p, name, loc, cps)
12335 ChessProgramState *cps;
12338 int len = strlen(name);
12339 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12341 sscanf(*p, "%d", loc);
12342 while (**p && **p != ' ') (*p)++;
12343 sprintf(buf, "accepted %s\n", name);
12344 SendToProgram(buf, cps);
12351 StringFeature(p, name, loc, cps)
12355 ChessProgramState *cps;
12358 int len = strlen(name);
12359 if (strncmp((*p), name, len) == 0
12360 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12362 sscanf(*p, "%[^\"]", loc);
12363 while (**p && **p != '\"') (*p)++;
12364 if (**p == '\"') (*p)++;
12365 sprintf(buf, "accepted %s\n", name);
12366 SendToProgram(buf, cps);
12373 ParseOption(Option *opt, ChessProgramState *cps)
12374 // [HGM] options: process the string that defines an engine option, and determine
12375 // name, type, default value, and allowed value range
12377 char *p, *q, buf[MSG_SIZ];
12378 int n, min = (-1)<<31, max = 1<<31, def;
12380 if(p = strstr(opt->name, " -spin ")) {
12381 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12382 if(max < min) max = min; // enforce consistency
12383 if(def < min) def = min;
12384 if(def > max) def = max;
12389 } else if((p = strstr(opt->name, " -slider "))) {
12390 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12391 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12392 if(max < min) max = min; // enforce consistency
12393 if(def < min) def = min;
12394 if(def > max) def = max;
12398 opt->type = Spin; // Slider;
12399 } else if((p = strstr(opt->name, " -string "))) {
12400 opt->textValue = p+9;
12401 opt->type = TextBox;
12402 } else if((p = strstr(opt->name, " -file "))) {
12403 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12404 opt->textValue = p+7;
12405 opt->type = TextBox; // FileName;
12406 } else if((p = strstr(opt->name, " -path "))) {
12407 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12408 opt->textValue = p+7;
12409 opt->type = TextBox; // PathName;
12410 } else if(p = strstr(opt->name, " -check ")) {
12411 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12412 opt->value = (def != 0);
12413 opt->type = CheckBox;
12414 } else if(p = strstr(opt->name, " -combo ")) {
12415 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12416 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12417 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12418 opt->value = n = 0;
12419 while(q = StrStr(q, " /// ")) {
12420 n++; *q = 0; // count choices, and null-terminate each of them
12422 if(*q == '*') { // remember default, which is marked with * prefix
12426 cps->comboList[cps->comboCnt++] = q;
12428 cps->comboList[cps->comboCnt++] = NULL;
12430 opt->type = ComboBox;
12431 } else if(p = strstr(opt->name, " -button")) {
12432 opt->type = Button;
12433 } else if(p = strstr(opt->name, " -save")) {
12434 opt->type = SaveButton;
12435 } else return FALSE;
12436 *p = 0; // terminate option name
12437 // now look if the command-line options define a setting for this engine option.
12438 if(cps->optionSettings && cps->optionSettings[0])
12439 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12440 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12441 sprintf(buf, "option %s", p);
12442 if(p = strstr(buf, ",")) *p = 0;
12444 SendToProgram(buf, cps);
12450 FeatureDone(cps, val)
12451 ChessProgramState* cps;
12454 DelayedEventCallback cb = GetDelayedEvent();
12455 if ((cb == InitBackEnd3 && cps == &first) ||
12456 (cb == TwoMachinesEventIfReady && cps == &second)) {
12457 CancelDelayedEvent();
12458 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12460 cps->initDone = val;
12463 /* Parse feature command from engine */
12465 ParseFeatures(args, cps)
12467 ChessProgramState *cps;
12475 while (*p == ' ') p++;
12476 if (*p == NULLCHAR) return;
12478 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12479 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12480 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12481 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12482 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12483 if (BoolFeature(&p, "reuse", &val, cps)) {
12484 /* Engine can disable reuse, but can't enable it if user said no */
12485 if (!val) cps->reuse = FALSE;
12488 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12489 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12490 if (gameMode == TwoMachinesPlay) {
12491 DisplayTwoMachinesTitle();
12497 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12498 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12499 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12500 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12501 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12502 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12503 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12504 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12505 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12506 if (IntFeature(&p, "done", &val, cps)) {
12507 FeatureDone(cps, val);
12510 /* Added by Tord: */
12511 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12512 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12513 /* End of additions by Tord */
12515 /* [HGM] added features: */
12516 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12517 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12518 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12519 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12520 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12521 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12522 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12523 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12524 if(cps->nrOptions >= MAX_OPTIONS) {
12526 sprintf(buf, "%s engine has too many options\n", cps->which);
12527 DisplayError(buf, 0);
12531 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12532 /* End of additions by HGM */
12534 /* unknown feature: complain and skip */
12536 while (*q && *q != '=') q++;
12537 sprintf(buf, "rejected %.*s\n", q-p, p);
12538 SendToProgram(buf, cps);
12544 while (*p && *p != '\"') p++;
12545 if (*p == '\"') p++;
12547 while (*p && *p != ' ') p++;
12555 PeriodicUpdatesEvent(newState)
12558 if (newState == appData.periodicUpdates)
12561 appData.periodicUpdates=newState;
12563 /* Display type changes, so update it now */
12566 /* Get the ball rolling again... */
12568 AnalysisPeriodicEvent(1);
12569 StartAnalysisClock();
12574 PonderNextMoveEvent(newState)
12577 if (newState == appData.ponderNextMove) return;
12578 if (gameMode == EditPosition) EditPositionDone();
12580 SendToProgram("hard\n", &first);
12581 if (gameMode == TwoMachinesPlay) {
12582 SendToProgram("hard\n", &second);
12585 SendToProgram("easy\n", &first);
12586 thinkOutput[0] = NULLCHAR;
12587 if (gameMode == TwoMachinesPlay) {
12588 SendToProgram("easy\n", &second);
12591 appData.ponderNextMove = newState;
12595 NewSettingEvent(option, command, value)
12601 if (gameMode == EditPosition) EditPositionDone();
12602 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12603 SendToProgram(buf, &first);
12604 if (gameMode == TwoMachinesPlay) {
12605 SendToProgram(buf, &second);
12610 ShowThinkingEvent()
12611 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12613 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12614 int newState = appData.showThinking
12615 // [HGM] thinking: other features now need thinking output as well
12616 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12618 if (oldState == newState) return;
12619 oldState = newState;
12620 if (gameMode == EditPosition) EditPositionDone();
12622 SendToProgram("post\n", &first);
12623 if (gameMode == TwoMachinesPlay) {
12624 SendToProgram("post\n", &second);
12627 SendToProgram("nopost\n", &first);
12628 thinkOutput[0] = NULLCHAR;
12629 if (gameMode == TwoMachinesPlay) {
12630 SendToProgram("nopost\n", &second);
12633 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12637 AskQuestionEvent(title, question, replyPrefix, which)
12638 char *title; char *question; char *replyPrefix; char *which;
12640 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12641 if (pr == NoProc) return;
12642 AskQuestion(title, question, replyPrefix, pr);
12646 DisplayMove(moveNumber)
12649 char message[MSG_SIZ];
12651 char cpThinkOutput[MSG_SIZ];
12653 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12655 if (moveNumber == forwardMostMove - 1 ||
12656 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12658 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12660 if (strchr(cpThinkOutput, '\n')) {
12661 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12664 *cpThinkOutput = NULLCHAR;
12667 /* [AS] Hide thinking from human user */
12668 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12669 *cpThinkOutput = NULLCHAR;
12670 if( thinkOutput[0] != NULLCHAR ) {
12673 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12674 cpThinkOutput[i] = '.';
12676 cpThinkOutput[i] = NULLCHAR;
12677 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12681 if (moveNumber == forwardMostMove - 1 &&
12682 gameInfo.resultDetails != NULL) {
12683 if (gameInfo.resultDetails[0] == NULLCHAR) {
12684 sprintf(res, " %s", PGNResult(gameInfo.result));
12686 sprintf(res, " {%s} %s",
12687 gameInfo.resultDetails, PGNResult(gameInfo.result));
12693 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12694 DisplayMessage(res, cpThinkOutput);
12696 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12697 WhiteOnMove(moveNumber) ? " " : ".. ",
12698 parseList[moveNumber], res);
12699 DisplayMessage(message, cpThinkOutput);
12704 DisplayAnalysisText(text)
12709 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12710 || appData.icsEngineAnalyze) {
12711 sprintf(buf, "Analysis (%s)", first.tidy);
12712 AnalysisPopUp(buf, text);
12720 while (*str && isspace(*str)) ++str;
12721 while (*str && !isspace(*str)) ++str;
12722 if (!*str) return 1;
12723 while (*str && isspace(*str)) ++str;
12724 if (!*str) return 1;
12732 char lst[MSG_SIZ / 2];
12734 static char *xtra[] = { "", " (--)", " (++)" };
12737 if (programStats.time == 0) {
12738 programStats.time = 1;
12741 if (programStats.got_only_move) {
12742 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12744 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12746 nps = (u64ToDouble(programStats.nodes) /
12747 ((double)programStats.time /100.0));
12749 cs = programStats.time % 100;
12750 s = programStats.time / 100;
12756 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12757 if (programStats.move_name[0] != NULLCHAR) {
12758 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12759 programStats.depth,
12760 programStats.nr_moves-programStats.moves_left,
12761 programStats.nr_moves, programStats.move_name,
12762 ((float)programStats.score)/100.0, lst,
12763 only_one_move(lst)?
12764 xtra[programStats.got_fail] : "",
12765 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12767 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12768 programStats.depth,
12769 programStats.nr_moves-programStats.moves_left,
12770 programStats.nr_moves, ((float)programStats.score)/100.0,
12772 only_one_move(lst)?
12773 xtra[programStats.got_fail] : "",
12774 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12777 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12778 programStats.depth,
12779 ((float)programStats.score)/100.0,
12781 only_one_move(lst)?
12782 xtra[programStats.got_fail] : "",
12783 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12786 DisplayAnalysisText(buf);
12790 DisplayComment(moveNumber, text)
12794 char title[MSG_SIZ];
12795 char buf[8000]; // comment can be long!
12798 if( appData.autoDisplayComment ) {
12799 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12800 strcpy(title, "Comment");
12802 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12803 WhiteOnMove(moveNumber) ? " " : ".. ",
12804 parseList[moveNumber]);
12806 // [HGM] PV info: display PV info together with (or as) comment
12807 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12808 if(text == NULL) text = "";
12809 score = pvInfoList[moveNumber].score;
12810 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12811 depth, (pvInfoList[moveNumber].time+50)/100, text);
12814 } else title[0] = 0;
12817 CommentPopUp(title, text);
12820 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12821 * might be busy thinking or pondering. It can be omitted if your
12822 * gnuchess is configured to stop thinking immediately on any user
12823 * input. However, that gnuchess feature depends on the FIONREAD
12824 * ioctl, which does not work properly on some flavors of Unix.
12828 ChessProgramState *cps;
12831 if (!cps->useSigint) return;
12832 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12833 switch (gameMode) {
12834 case MachinePlaysWhite:
12835 case MachinePlaysBlack:
12836 case TwoMachinesPlay:
12837 case IcsPlayingWhite:
12838 case IcsPlayingBlack:
12841 /* Skip if we know it isn't thinking */
12842 if (!cps->maybeThinking) return;
12843 if (appData.debugMode)
12844 fprintf(debugFP, "Interrupting %s\n", cps->which);
12845 InterruptChildProcess(cps->pr);
12846 cps->maybeThinking = FALSE;
12851 #endif /*ATTENTION*/
12857 if (whiteTimeRemaining <= 0) {
12860 if (appData.icsActive) {
12861 if (appData.autoCallFlag &&
12862 gameMode == IcsPlayingBlack && !blackFlag) {
12863 SendToICS(ics_prefix);
12864 SendToICS("flag\n");
12868 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12870 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12871 if (appData.autoCallFlag) {
12872 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12879 if (blackTimeRemaining <= 0) {
12882 if (appData.icsActive) {
12883 if (appData.autoCallFlag &&
12884 gameMode == IcsPlayingWhite && !whiteFlag) {
12885 SendToICS(ics_prefix);
12886 SendToICS("flag\n");
12890 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12892 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12893 if (appData.autoCallFlag) {
12894 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12907 if (!appData.clockMode || appData.icsActive ||
12908 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12911 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12913 if ( !WhiteOnMove(forwardMostMove) )
12914 /* White made time control */
12915 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12916 /* [HGM] time odds: correct new time quota for time odds! */
12917 / WhitePlayer()->timeOdds;
12919 /* Black made time control */
12920 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12921 / WhitePlayer()->other->timeOdds;
12925 DisplayBothClocks()
12927 int wom = gameMode == EditPosition ?
12928 !blackPlaysFirst : WhiteOnMove(currentMove);
12929 DisplayWhiteClock(whiteTimeRemaining, wom);
12930 DisplayBlackClock(blackTimeRemaining, !wom);
12934 /* Timekeeping seems to be a portability nightmare. I think everyone
12935 has ftime(), but I'm really not sure, so I'm including some ifdefs
12936 to use other calls if you don't. Clocks will be less accurate if
12937 you have neither ftime nor gettimeofday.
12940 /* VS 2008 requires the #include outside of the function */
12941 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12942 #include <sys/timeb.h>
12945 /* Get the current time as a TimeMark */
12950 #if HAVE_GETTIMEOFDAY
12952 struct timeval timeVal;
12953 struct timezone timeZone;
12955 gettimeofday(&timeVal, &timeZone);
12956 tm->sec = (long) timeVal.tv_sec;
12957 tm->ms = (int) (timeVal.tv_usec / 1000L);
12959 #else /*!HAVE_GETTIMEOFDAY*/
12962 // include <sys/timeb.h> / moved to just above start of function
12963 struct timeb timeB;
12966 tm->sec = (long) timeB.time;
12967 tm->ms = (int) timeB.millitm;
12969 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12970 tm->sec = (long) time(NULL);
12976 /* Return the difference in milliseconds between two
12977 time marks. We assume the difference will fit in a long!
12980 SubtractTimeMarks(tm2, tm1)
12981 TimeMark *tm2, *tm1;
12983 return 1000L*(tm2->sec - tm1->sec) +
12984 (long) (tm2->ms - tm1->ms);
12989 * Code to manage the game clocks.
12991 * In tournament play, black starts the clock and then white makes a move.
12992 * We give the human user a slight advantage if he is playing white---the
12993 * clocks don't run until he makes his first move, so it takes zero time.
12994 * Also, we don't account for network lag, so we could get out of sync
12995 * with GNU Chess's clock -- but then, referees are always right.
12998 static TimeMark tickStartTM;
12999 static long intendedTickLength;
13002 NextTickLength(timeRemaining)
13003 long timeRemaining;
13005 long nominalTickLength, nextTickLength;
13007 if (timeRemaining > 0L && timeRemaining <= 10000L)
13008 nominalTickLength = 100L;
13010 nominalTickLength = 1000L;
13011 nextTickLength = timeRemaining % nominalTickLength;
13012 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13014 return nextTickLength;
13017 /* Adjust clock one minute up or down */
13019 AdjustClock(Boolean which, int dir)
13021 if(which) blackTimeRemaining += 60000*dir;
13022 else whiteTimeRemaining += 60000*dir;
13023 DisplayBothClocks();
13026 /* Stop clocks and reset to a fresh time control */
13030 (void) StopClockTimer();
13031 if (appData.icsActive) {
13032 whiteTimeRemaining = blackTimeRemaining = 0;
13033 } else { /* [HGM] correct new time quote for time odds */
13034 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13035 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13037 if (whiteFlag || blackFlag) {
13039 whiteFlag = blackFlag = FALSE;
13041 DisplayBothClocks();
13044 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13046 /* Decrement running clock by amount of time that has passed */
13050 long timeRemaining;
13051 long lastTickLength, fudge;
13054 if (!appData.clockMode) return;
13055 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13059 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13061 /* Fudge if we woke up a little too soon */
13062 fudge = intendedTickLength - lastTickLength;
13063 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13065 if (WhiteOnMove(forwardMostMove)) {
13066 if(whiteNPS >= 0) lastTickLength = 0;
13067 timeRemaining = whiteTimeRemaining -= lastTickLength;
13068 DisplayWhiteClock(whiteTimeRemaining - fudge,
13069 WhiteOnMove(currentMove));
13071 if(blackNPS >= 0) lastTickLength = 0;
13072 timeRemaining = blackTimeRemaining -= lastTickLength;
13073 DisplayBlackClock(blackTimeRemaining - fudge,
13074 !WhiteOnMove(currentMove));
13077 if (CheckFlags()) return;
13080 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13081 StartClockTimer(intendedTickLength);
13083 /* if the time remaining has fallen below the alarm threshold, sound the
13084 * alarm. if the alarm has sounded and (due to a takeback or time control
13085 * with increment) the time remaining has increased to a level above the
13086 * threshold, reset the alarm so it can sound again.
13089 if (appData.icsActive && appData.icsAlarm) {
13091 /* make sure we are dealing with the user's clock */
13092 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13093 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13096 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13097 alarmSounded = FALSE;
13098 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13100 alarmSounded = TRUE;
13106 /* A player has just moved, so stop the previously running
13107 clock and (if in clock mode) start the other one.
13108 We redisplay both clocks in case we're in ICS mode, because
13109 ICS gives us an update to both clocks after every move.
13110 Note that this routine is called *after* forwardMostMove
13111 is updated, so the last fractional tick must be subtracted
13112 from the color that is *not* on move now.
13117 long lastTickLength;
13119 int flagged = FALSE;
13123 if (StopClockTimer() && appData.clockMode) {
13124 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13125 if (WhiteOnMove(forwardMostMove)) {
13126 if(blackNPS >= 0) lastTickLength = 0;
13127 blackTimeRemaining -= lastTickLength;
13128 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13129 // if(pvInfoList[forwardMostMove-1].time == -1)
13130 pvInfoList[forwardMostMove-1].time = // use GUI time
13131 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13133 if(whiteNPS >= 0) lastTickLength = 0;
13134 whiteTimeRemaining -= lastTickLength;
13135 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13136 // if(pvInfoList[forwardMostMove-1].time == -1)
13137 pvInfoList[forwardMostMove-1].time =
13138 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13140 flagged = CheckFlags();
13142 CheckTimeControl();
13144 if (flagged || !appData.clockMode) return;
13146 switch (gameMode) {
13147 case MachinePlaysBlack:
13148 case MachinePlaysWhite:
13149 case BeginningOfGame:
13150 if (pausing) return;
13154 case PlayFromGameFile:
13163 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13164 whiteTimeRemaining : blackTimeRemaining);
13165 StartClockTimer(intendedTickLength);
13169 /* Stop both clocks */
13173 long lastTickLength;
13176 if (!StopClockTimer()) return;
13177 if (!appData.clockMode) return;
13181 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13182 if (WhiteOnMove(forwardMostMove)) {
13183 if(whiteNPS >= 0) lastTickLength = 0;
13184 whiteTimeRemaining -= lastTickLength;
13185 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13187 if(blackNPS >= 0) lastTickLength = 0;
13188 blackTimeRemaining -= lastTickLength;
13189 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13194 /* Start clock of player on move. Time may have been reset, so
13195 if clock is already running, stop and restart it. */
13199 (void) StopClockTimer(); /* in case it was running already */
13200 DisplayBothClocks();
13201 if (CheckFlags()) return;
13203 if (!appData.clockMode) return;
13204 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13206 GetTimeMark(&tickStartTM);
13207 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13208 whiteTimeRemaining : blackTimeRemaining);
13210 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13211 whiteNPS = blackNPS = -1;
13212 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13213 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13214 whiteNPS = first.nps;
13215 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13216 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13217 blackNPS = first.nps;
13218 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13219 whiteNPS = second.nps;
13220 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13221 blackNPS = second.nps;
13222 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13224 StartClockTimer(intendedTickLength);
13231 long second, minute, hour, day;
13233 static char buf[32];
13235 if (ms > 0 && ms <= 9900) {
13236 /* convert milliseconds to tenths, rounding up */
13237 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13239 sprintf(buf, " %03.1f ", tenths/10.0);
13243 /* convert milliseconds to seconds, rounding up */
13244 /* use floating point to avoid strangeness of integer division
13245 with negative dividends on many machines */
13246 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13253 day = second / (60 * 60 * 24);
13254 second = second % (60 * 60 * 24);
13255 hour = second / (60 * 60);
13256 second = second % (60 * 60);
13257 minute = second / 60;
13258 second = second % 60;
13261 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13262 sign, day, hour, minute, second);
13264 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13266 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13273 * This is necessary because some C libraries aren't ANSI C compliant yet.
13276 StrStr(string, match)
13277 char *string, *match;
13281 length = strlen(match);
13283 for (i = strlen(string) - length; i >= 0; i--, string++)
13284 if (!strncmp(match, string, length))
13291 StrCaseStr(string, match)
13292 char *string, *match;
13296 length = strlen(match);
13298 for (i = strlen(string) - length; i >= 0; i--, string++) {
13299 for (j = 0; j < length; j++) {
13300 if (ToLower(match[j]) != ToLower(string[j]))
13303 if (j == length) return string;
13317 c1 = ToLower(*s1++);
13318 c2 = ToLower(*s2++);
13319 if (c1 > c2) return 1;
13320 if (c1 < c2) return -1;
13321 if (c1 == NULLCHAR) return 0;
13330 return isupper(c) ? tolower(c) : c;
13338 return islower(c) ? toupper(c) : c;
13340 #endif /* !_amigados */
13348 if ((ret = (char *) malloc(strlen(s) + 1))) {
13355 StrSavePtr(s, savePtr)
13356 char *s, **savePtr;
13361 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13362 strcpy(*savePtr, s);
13374 clock = time((time_t *)NULL);
13375 tm = localtime(&clock);
13376 sprintf(buf, "%04d.%02d.%02d",
13377 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13378 return StrSave(buf);
13383 PositionToFEN(move, overrideCastling)
13385 char *overrideCastling;
13387 int i, j, fromX, fromY, toX, toY;
13394 whiteToPlay = (gameMode == EditPosition) ?
13395 !blackPlaysFirst : (move % 2 == 0);
13398 /* Piece placement data */
13399 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13401 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13402 if (boards[move][i][j] == EmptySquare) {
13404 } else { ChessSquare piece = boards[move][i][j];
13405 if (emptycount > 0) {
13406 if(emptycount<10) /* [HGM] can be >= 10 */
13407 *p++ = '0' + emptycount;
13408 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13411 if(PieceToChar(piece) == '+') {
13412 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13414 piece = (ChessSquare)(DEMOTED piece);
13416 *p++ = PieceToChar(piece);
13418 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13419 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13424 if (emptycount > 0) {
13425 if(emptycount<10) /* [HGM] can be >= 10 */
13426 *p++ = '0' + emptycount;
13427 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13434 /* [HGM] print Crazyhouse or Shogi holdings */
13435 if( gameInfo.holdingsWidth ) {
13436 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13438 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13439 piece = boards[move][i][BOARD_WIDTH-1];
13440 if( piece != EmptySquare )
13441 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13442 *p++ = PieceToChar(piece);
13444 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13445 piece = boards[move][BOARD_HEIGHT-i-1][0];
13446 if( piece != EmptySquare )
13447 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13448 *p++ = PieceToChar(piece);
13451 if( q == p ) *p++ = '-';
13457 *p++ = whiteToPlay ? 'w' : 'b';
13460 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13461 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13463 if(nrCastlingRights) {
13465 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13466 /* [HGM] write directly from rights */
13467 if(castlingRights[move][2] >= 0 &&
13468 castlingRights[move][0] >= 0 )
13469 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13470 if(castlingRights[move][2] >= 0 &&
13471 castlingRights[move][1] >= 0 )
13472 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13473 if(castlingRights[move][5] >= 0 &&
13474 castlingRights[move][3] >= 0 )
13475 *p++ = castlingRights[move][3] + AAA;
13476 if(castlingRights[move][5] >= 0 &&
13477 castlingRights[move][4] >= 0 )
13478 *p++ = castlingRights[move][4] + AAA;
13481 /* [HGM] write true castling rights */
13482 if( nrCastlingRights == 6 ) {
13483 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13484 castlingRights[move][2] >= 0 ) *p++ = 'K';
13485 if(castlingRights[move][1] == BOARD_LEFT &&
13486 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13487 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13488 castlingRights[move][5] >= 0 ) *p++ = 'k';
13489 if(castlingRights[move][4] == BOARD_LEFT &&
13490 castlingRights[move][5] >= 0 ) *p++ = 'q';
13493 if (q == p) *p++ = '-'; /* No castling rights */
13497 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13498 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13499 /* En passant target square */
13500 if (move > backwardMostMove) {
13501 fromX = moveList[move - 1][0] - AAA;
13502 fromY = moveList[move - 1][1] - ONE;
13503 toX = moveList[move - 1][2] - AAA;
13504 toY = moveList[move - 1][3] - ONE;
13505 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13506 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13507 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13509 /* 2-square pawn move just happened */
13511 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13522 /* [HGM] find reversible plies */
13523 { int i = 0, j=move;
13525 if (appData.debugMode) { int k;
13526 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13527 for(k=backwardMostMove; k<=forwardMostMove; k++)
13528 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13532 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13533 if( j == backwardMostMove ) i += initialRulePlies;
13534 sprintf(p, "%d ", i);
13535 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13537 /* Fullmove number */
13538 sprintf(p, "%d", (move / 2) + 1);
13540 return StrSave(buf);
13544 ParseFEN(board, blackPlaysFirst, fen)
13546 int *blackPlaysFirst;
13556 /* [HGM] by default clear Crazyhouse holdings, if present */
13557 if(gameInfo.holdingsWidth) {
13558 for(i=0; i<BOARD_HEIGHT; i++) {
13559 board[i][0] = EmptySquare; /* black holdings */
13560 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13561 board[i][1] = (ChessSquare) 0; /* black counts */
13562 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13566 /* Piece placement data */
13567 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13570 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13571 if (*p == '/') p++;
13572 emptycount = gameInfo.boardWidth - j;
13573 while (emptycount--)
13574 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13576 #if(BOARD_SIZE >= 10)
13577 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13578 p++; emptycount=10;
13579 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13580 while (emptycount--)
13581 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13583 } else if (isdigit(*p)) {
13584 emptycount = *p++ - '0';
13585 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13586 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13587 while (emptycount--)
13588 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13589 } else if (*p == '+' || isalpha(*p)) {
13590 if (j >= gameInfo.boardWidth) return FALSE;
13592 piece = CharToPiece(*++p);
13593 if(piece == EmptySquare) return FALSE; /* unknown piece */
13594 piece = (ChessSquare) (PROMOTED piece ); p++;
13595 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13596 } else piece = CharToPiece(*p++);
13598 if(piece==EmptySquare) return FALSE; /* unknown piece */
13599 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13600 piece = (ChessSquare) (PROMOTED piece);
13601 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13604 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13610 while (*p == '/' || *p == ' ') p++;
13612 /* [HGM] look for Crazyhouse holdings here */
13613 while(*p==' ') p++;
13614 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13616 if(*p == '-' ) *p++; /* empty holdings */ else {
13617 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13618 /* if we would allow FEN reading to set board size, we would */
13619 /* have to add holdings and shift the board read so far here */
13620 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13622 if((int) piece >= (int) BlackPawn ) {
13623 i = (int)piece - (int)BlackPawn;
13624 i = PieceToNumber((ChessSquare)i);
13625 if( i >= gameInfo.holdingsSize ) return FALSE;
13626 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13627 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13629 i = (int)piece - (int)WhitePawn;
13630 i = PieceToNumber((ChessSquare)i);
13631 if( i >= gameInfo.holdingsSize ) return FALSE;
13632 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13633 board[i][BOARD_WIDTH-2]++; /* black holdings */
13637 if(*p == ']') *p++;
13640 while(*p == ' ') p++;
13645 *blackPlaysFirst = FALSE;
13648 *blackPlaysFirst = TRUE;
13654 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13655 /* return the extra info in global variiables */
13657 /* set defaults in case FEN is incomplete */
13658 FENepStatus = EP_UNKNOWN;
13659 for(i=0; i<nrCastlingRights; i++ ) {
13660 FENcastlingRights[i] =
13661 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13662 } /* assume possible unless obviously impossible */
13663 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13664 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13665 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13666 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13667 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13668 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13671 while(*p==' ') p++;
13672 if(nrCastlingRights) {
13673 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13674 /* castling indicator present, so default becomes no castlings */
13675 for(i=0; i<nrCastlingRights; i++ ) {
13676 FENcastlingRights[i] = -1;
13679 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13680 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13681 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13682 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13683 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13685 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13686 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13687 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13691 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13692 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13693 FENcastlingRights[2] = whiteKingFile;
13696 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13697 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13698 FENcastlingRights[2] = whiteKingFile;
13701 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13702 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13703 FENcastlingRights[5] = blackKingFile;
13706 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13707 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13708 FENcastlingRights[5] = blackKingFile;
13711 default: /* FRC castlings */
13712 if(c >= 'a') { /* black rights */
13713 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13714 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13715 if(i == BOARD_RGHT) break;
13716 FENcastlingRights[5] = i;
13718 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13719 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13721 FENcastlingRights[3] = c;
13723 FENcastlingRights[4] = c;
13724 } else { /* white rights */
13725 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13726 if(board[0][i] == WhiteKing) break;
13727 if(i == BOARD_RGHT) break;
13728 FENcastlingRights[2] = i;
13729 c -= AAA - 'a' + 'A';
13730 if(board[0][c] >= WhiteKing) break;
13732 FENcastlingRights[0] = c;
13734 FENcastlingRights[1] = c;
13738 if (appData.debugMode) {
13739 fprintf(debugFP, "FEN castling rights:");
13740 for(i=0; i<nrCastlingRights; i++)
13741 fprintf(debugFP, " %d", FENcastlingRights[i]);
13742 fprintf(debugFP, "\n");
13745 while(*p==' ') p++;
13748 /* read e.p. field in games that know e.p. capture */
13749 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13750 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13752 p++; FENepStatus = EP_NONE;
13754 char c = *p++ - AAA;
13756 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13757 if(*p >= '0' && *p <='9') *p++;
13763 if(sscanf(p, "%d", &i) == 1) {
13764 FENrulePlies = i; /* 50-move ply counter */
13765 /* (The move number is still ignored) */
13772 EditPositionPasteFEN(char *fen)
13775 Board initial_position;
13777 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13778 DisplayError(_("Bad FEN position in clipboard"), 0);
13781 int savedBlackPlaysFirst = blackPlaysFirst;
13782 EditPositionEvent();
13783 blackPlaysFirst = savedBlackPlaysFirst;
13784 CopyBoard(boards[0], initial_position);
13785 /* [HGM] copy FEN attributes as well */
13787 initialRulePlies = FENrulePlies;
13788 epStatus[0] = FENepStatus;
13789 for( i=0; i<nrCastlingRights; i++ )
13790 castlingRights[0][i] = FENcastlingRights[i];
13792 EditPositionDone();
13793 DisplayBothClocks();
13794 DrawPosition(FALSE, boards[currentMove]);