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 printf ("DEBUG: in init backend 2\n");
1081 if (appData.debugMode) {
1082 fprintf(debugFP, "%s\n", programVersion);
1085 if (appData.matchGames > 0) {
1086 appData.matchMode = TRUE;
1087 } else if (appData.matchMode) {
1088 appData.matchGames = 1;
1090 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1091 appData.matchGames = appData.sameColorGames;
1092 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1093 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1094 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096 printf ("DEBUG: before reset\n");
1098 printf ("DEBUG: after reset\n");
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1100 printf ("DEBUG: first if\n");
1103 printf ("DEBUG: second if 0\n");
1104 /* kludge: allow timeout for initial "feature" commands */
1106 printf ("DEBUG: second if 1\n");
1107 DisplayMessage("", _("Starting chess program"));
1108 printf ("DEBUG: second if 2\n");
1109 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 printf ("DEBUG: second if 3\n"); }
1111 printf ("DEBUG: end of init2\n");
1115 InitBackEnd3 P((void))
1117 GameMode initialMode;
1121 InitChessProgram(&first, startedFromSetupPosition);
1124 if (appData.icsActive) {
1126 /* [DM] Make a console window if needed [HGM] merged ifs */
1131 if (*appData.icsCommPort != NULLCHAR) {
1132 sprintf(buf, _("Could not open comm port %s"),
1133 appData.icsCommPort);
1135 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1136 appData.icsHost, appData.icsPort);
1138 DisplayFatalError(buf, err, 1);
1143 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1145 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1146 } else if (appData.noChessProgram) {
1152 if (*appData.cmailGameName != NULLCHAR) {
1154 OpenLoopback(&cmailPR);
1156 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1160 DisplayMessage("", "");
1161 if (StrCaseCmp(appData.initialMode, "") == 0) {
1162 initialMode = BeginningOfGame;
1163 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1164 initialMode = TwoMachinesPlay;
1165 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1166 initialMode = AnalyzeFile;
1167 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1168 initialMode = AnalyzeMode;
1169 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1170 initialMode = MachinePlaysWhite;
1171 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1172 initialMode = MachinePlaysBlack;
1173 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1174 initialMode = EditGame;
1175 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1176 initialMode = EditPosition;
1177 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1178 initialMode = Training;
1180 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1181 DisplayFatalError(buf, 0, 2);
1185 if (appData.matchMode) {
1186 /* Set up machine vs. machine match */
1187 if (appData.noChessProgram) {
1188 DisplayFatalError(_("Can't have a match with no chess programs"),
1194 if (*appData.loadGameFile != NULLCHAR) {
1195 int index = appData.loadGameIndex; // [HGM] autoinc
1196 if(index<0) lastIndex = index = 1;
1197 if (!LoadGameFromFile(appData.loadGameFile,
1199 appData.loadGameFile, FALSE)) {
1200 DisplayFatalError(_("Bad game file"), 0, 1);
1203 } else if (*appData.loadPositionFile != NULLCHAR) {
1204 int index = appData.loadPositionIndex; // [HGM] autoinc
1205 if(index<0) lastIndex = index = 1;
1206 if (!LoadPositionFromFile(appData.loadPositionFile,
1208 appData.loadPositionFile)) {
1209 DisplayFatalError(_("Bad position file"), 0, 1);
1214 } else if (*appData.cmailGameName != NULLCHAR) {
1215 /* Set up cmail mode */
1216 ReloadCmailMsgEvent(TRUE);
1218 /* Set up other modes */
1219 if (initialMode == AnalyzeFile) {
1220 if (*appData.loadGameFile == NULLCHAR) {
1221 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1225 if (*appData.loadGameFile != NULLCHAR) {
1226 (void) LoadGameFromFile(appData.loadGameFile,
1227 appData.loadGameIndex,
1228 appData.loadGameFile, TRUE);
1229 } else if (*appData.loadPositionFile != NULLCHAR) {
1230 (void) LoadPositionFromFile(appData.loadPositionFile,
1231 appData.loadPositionIndex,
1232 appData.loadPositionFile);
1233 /* [HGM] try to make self-starting even after FEN load */
1234 /* to allow automatic setup of fairy variants with wtm */
1235 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1236 gameMode = BeginningOfGame;
1237 setboardSpoiledMachineBlack = 1;
1239 /* [HGM] loadPos: make that every new game uses the setup */
1240 /* from file as long as we do not switch variant */
1241 if(!blackPlaysFirst) { int i;
1242 startedFromPositionFile = TRUE;
1243 CopyBoard(filePosition, boards[0]);
1244 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1247 if (initialMode == AnalyzeMode) {
1248 if (appData.noChessProgram) {
1249 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1252 if (appData.icsActive) {
1253 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1257 } else if (initialMode == AnalyzeFile) {
1258 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1259 ShowThinkingEvent();
1261 AnalysisPeriodicEvent(1);
1262 } else if (initialMode == MachinePlaysWhite) {
1263 if (appData.noChessProgram) {
1264 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1268 if (appData.icsActive) {
1269 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1273 MachineWhiteEvent();
1274 } else if (initialMode == MachinePlaysBlack) {
1275 if (appData.noChessProgram) {
1276 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1280 if (appData.icsActive) {
1281 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1285 MachineBlackEvent();
1286 } else if (initialMode == TwoMachinesPlay) {
1287 if (appData.noChessProgram) {
1288 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1292 if (appData.icsActive) {
1293 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1298 } else if (initialMode == EditGame) {
1300 } else if (initialMode == EditPosition) {
1301 EditPositionEvent();
1302 } else if (initialMode == Training) {
1303 if (*appData.loadGameFile == NULLCHAR) {
1304 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1313 * Establish will establish a contact to a remote host.port.
1314 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1315 * used to talk to the host.
1316 * Returns 0 if okay, error code if not.
1323 if (*appData.icsCommPort != NULLCHAR) {
1324 /* Talk to the host through a serial comm port */
1325 return OpenCommPort(appData.icsCommPort, &icsPR);
1327 } else if (*appData.gateway != NULLCHAR) {
1328 if (*appData.remoteShell == NULLCHAR) {
1329 /* Use the rcmd protocol to run telnet program on a gateway host */
1330 snprintf(buf, sizeof(buf), "%s %s %s",
1331 appData.telnetProgram, appData.icsHost, appData.icsPort);
1332 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1335 /* Use the rsh program to run telnet program on a gateway host */
1336 if (*appData.remoteUser == NULLCHAR) {
1337 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1338 appData.gateway, appData.telnetProgram,
1339 appData.icsHost, appData.icsPort);
1341 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1342 appData.remoteShell, appData.gateway,
1343 appData.remoteUser, appData.telnetProgram,
1344 appData.icsHost, appData.icsPort);
1346 return StartChildProcess(buf, "", &icsPR);
1349 } else if (appData.useTelnet) {
1350 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1353 /* TCP socket interface differs somewhat between
1354 Unix and NT; handle details in the front end.
1356 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1361 show_bytes(fp, buf, count)
1367 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1368 fprintf(fp, "\\%03o", *buf & 0xff);
1377 /* Returns an errno value */
1379 OutputMaybeTelnet(pr, message, count, outError)
1385 char buf[8192], *p, *q, *buflim;
1386 int left, newcount, outcount;
1388 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1389 *appData.gateway != NULLCHAR) {
1390 if (appData.debugMode) {
1391 fprintf(debugFP, ">ICS: ");
1392 show_bytes(debugFP, message, count);
1393 fprintf(debugFP, "\n");
1395 return OutputToProcess(pr, message, count, outError);
1398 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1405 if (appData.debugMode) {
1406 fprintf(debugFP, ">ICS: ");
1407 show_bytes(debugFP, buf, newcount);
1408 fprintf(debugFP, "\n");
1410 outcount = OutputToProcess(pr, buf, newcount, outError);
1411 if (outcount < newcount) return -1; /* to be sure */
1418 } else if (((unsigned char) *p) == TN_IAC) {
1419 *q++ = (char) TN_IAC;
1426 if (appData.debugMode) {
1427 fprintf(debugFP, ">ICS: ");
1428 show_bytes(debugFP, buf, newcount);
1429 fprintf(debugFP, "\n");
1431 outcount = OutputToProcess(pr, buf, newcount, outError);
1432 if (outcount < newcount) return -1; /* to be sure */
1437 read_from_player(isr, closure, message, count, error)
1444 int outError, outCount;
1445 static int gotEof = 0;
1447 /* Pass data read from player on to ICS */
1450 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1451 if (outCount < count) {
1452 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1454 } else if (count < 0) {
1455 RemoveInputSource(isr);
1456 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1457 } else if (gotEof++ > 0) {
1458 RemoveInputSource(isr);
1459 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1467 int count, outCount, outError;
1469 if (icsPR == NULL) return;
1472 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1473 if (outCount < count) {
1474 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* This is used for sending logon scripts to the ICS. Sending
1479 without a delay causes problems when using timestamp on ICC
1480 (at least on my machine). */
1482 SendToICSDelayed(s,msdelay)
1486 int count, outCount, outError;
1488 if (icsPR == NULL) return;
1491 if (appData.debugMode) {
1492 fprintf(debugFP, ">ICS: ");
1493 show_bytes(debugFP, s, count);
1494 fprintf(debugFP, "\n");
1496 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1498 if (outCount < count) {
1499 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1504 /* Remove all highlighting escape sequences in s
1505 Also deletes any suffix starting with '('
1508 StripHighlightAndTitle(s)
1511 static char retbuf[MSG_SIZ];
1514 while (*s != NULLCHAR) {
1515 while (*s == '\033') {
1516 while (*s != NULLCHAR && !isalpha(*s)) s++;
1517 if (*s != NULLCHAR) s++;
1519 while (*s != NULLCHAR && *s != '\033') {
1520 if (*s == '(' || *s == '[') {
1531 /* Remove all highlighting escape sequences in s */
1536 static char retbuf[MSG_SIZ];
1539 while (*s != NULLCHAR) {
1540 while (*s == '\033') {
1541 while (*s != NULLCHAR && !isalpha(*s)) s++;
1542 if (*s != NULLCHAR) s++;
1544 while (*s != NULLCHAR && *s != '\033') {
1552 char *variantNames[] = VARIANT_NAMES;
1557 return variantNames[v];
1561 /* Identify a variant from the strings the chess servers use or the
1562 PGN Variant tag names we use. */
1569 VariantClass v = VariantNormal;
1570 int i, found = FALSE;
1575 /* [HGM] skip over optional board-size prefixes */
1576 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1577 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1578 while( *e++ != '_');
1581 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1582 if (StrCaseStr(e, variantNames[i])) {
1583 v = (VariantClass) i;
1590 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1591 || StrCaseStr(e, "wild/fr")
1592 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1593 v = VariantFischeRandom;
1594 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1595 (i = 1, p = StrCaseStr(e, "w"))) {
1597 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1604 case 0: /* FICS only, actually */
1606 /* Castling legal even if K starts on d-file */
1607 v = VariantWildCastle;
1612 /* Castling illegal even if K & R happen to start in
1613 normal positions. */
1614 v = VariantNoCastle;
1627 /* Castling legal iff K & R start in normal positions */
1633 /* Special wilds for position setup; unclear what to do here */
1634 v = VariantLoadable;
1637 /* Bizarre ICC game */
1638 v = VariantTwoKings;
1641 v = VariantKriegspiel;
1647 v = VariantFischeRandom;
1650 v = VariantCrazyhouse;
1653 v = VariantBughouse;
1659 /* Not quite the same as FICS suicide! */
1660 v = VariantGiveaway;
1666 v = VariantShatranj;
1669 /* Temporary names for future ICC types. The name *will* change in
1670 the next xboard/WinBoard release after ICC defines it. */
1708 v = VariantCapablanca;
1711 v = VariantKnightmate;
1717 v = VariantCylinder;
1723 v = VariantCapaRandom;
1726 v = VariantBerolina;
1738 /* Found "wild" or "w" in the string but no number;
1739 must assume it's normal chess. */
1743 sprintf(buf, _("Unknown wild type %d"), wnum);
1744 DisplayError(buf, 0);
1750 if (appData.debugMode) {
1751 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1752 e, wnum, VariantName(v));
1757 static int leftover_start = 0, leftover_len = 0;
1758 char star_match[STAR_MATCH_N][MSG_SIZ];
1760 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1761 advance *index beyond it, and set leftover_start to the new value of
1762 *index; else return FALSE. If pattern contains the character '*', it
1763 matches any sequence of characters not containing '\r', '\n', or the
1764 character following the '*' (if any), and the matched sequence(s) are
1765 copied into star_match.
1768 looking_at(buf, index, pattern)
1773 char *bufp = &buf[*index], *patternp = pattern;
1775 char *matchp = star_match[0];
1778 if (*patternp == NULLCHAR) {
1779 *index = leftover_start = bufp - buf;
1783 if (*bufp == NULLCHAR) return FALSE;
1784 if (*patternp == '*') {
1785 if (*bufp == *(patternp + 1)) {
1787 matchp = star_match[++star_count];
1791 } else if (*bufp == '\n' || *bufp == '\r') {
1793 if (*patternp == NULLCHAR)
1798 *matchp++ = *bufp++;
1802 if (*patternp != *bufp) return FALSE;
1809 SendToPlayer(data, length)
1813 int error, outCount;
1814 outCount = OutputToProcess(NoProc, data, length, &error);
1815 if (outCount < length) {
1816 DisplayFatalError(_("Error writing to display"), error, 1);
1821 PackHolding(packed, holding)
1833 switch (runlength) {
1844 sprintf(q, "%d", runlength);
1856 /* Telnet protocol requests from the front end */
1858 TelnetRequest(ddww, option)
1859 unsigned char ddww, option;
1861 unsigned char msg[3];
1862 int outCount, outError;
1864 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1866 if (appData.debugMode) {
1867 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1883 sprintf(buf1, "%d", ddww);
1892 sprintf(buf2, "%d", option);
1895 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1900 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1902 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1909 if (!appData.icsActive) return;
1910 TelnetRequest(TN_DO, TN_ECHO);
1916 if (!appData.icsActive) return;
1917 TelnetRequest(TN_DONT, TN_ECHO);
1921 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1923 /* put the holdings sent to us by the server on the board holdings area */
1924 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1928 if(gameInfo.holdingsWidth < 2) return;
1930 if( (int)lowestPiece >= BlackPawn ) {
1933 holdingsStartRow = BOARD_HEIGHT-1;
1936 holdingsColumn = BOARD_WIDTH-1;
1937 countsColumn = BOARD_WIDTH-2;
1938 holdingsStartRow = 0;
1942 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1943 board[i][holdingsColumn] = EmptySquare;
1944 board[i][countsColumn] = (ChessSquare) 0;
1946 while( (p=*holdings++) != NULLCHAR ) {
1947 piece = CharToPiece( ToUpper(p) );
1948 if(piece == EmptySquare) continue;
1949 /*j = (int) piece - (int) WhitePawn;*/
1950 j = PieceToNumber(piece);
1951 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1952 if(j < 0) continue; /* should not happen */
1953 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1954 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1955 board[holdingsStartRow+j*direction][countsColumn]++;
1962 VariantSwitch(Board board, VariantClass newVariant)
1964 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1965 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1966 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1968 startedFromPositionFile = FALSE;
1969 if(gameInfo.variant == newVariant) return;
1971 /* [HGM] This routine is called each time an assignment is made to
1972 * gameInfo.variant during a game, to make sure the board sizes
1973 * are set to match the new variant. If that means adding or deleting
1974 * holdings, we shift the playing board accordingly
1975 * This kludge is needed because in ICS observe mode, we get boards
1976 * of an ongoing game without knowing the variant, and learn about the
1977 * latter only later. This can be because of the move list we requested,
1978 * in which case the game history is refilled from the beginning anyway,
1979 * but also when receiving holdings of a crazyhouse game. In the latter
1980 * case we want to add those holdings to the already received position.
1984 if (appData.debugMode) {
1985 fprintf(debugFP, "Switch board from %s to %s\n",
1986 VariantName(gameInfo.variant), VariantName(newVariant));
1987 setbuf(debugFP, NULL);
1989 shuffleOpenings = 0; /* [HGM] shuffle */
1990 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1991 switch(newVariant) {
1993 newWidth = 9; newHeight = 9;
1994 gameInfo.holdingsSize = 7;
1995 case VariantBughouse:
1996 case VariantCrazyhouse:
1997 newHoldingsWidth = 2; break;
1999 newHoldingsWidth = gameInfo.holdingsSize = 0;
2002 if(newWidth != gameInfo.boardWidth ||
2003 newHeight != gameInfo.boardHeight ||
2004 newHoldingsWidth != gameInfo.holdingsWidth ) {
2006 /* shift position to new playing area, if needed */
2007 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2012 for(i=0; i<newHeight; i++) {
2013 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2014 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2016 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2017 for(i=0; i<BOARD_HEIGHT; i++)
2018 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2019 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2023 gameInfo.boardWidth = newWidth;
2024 gameInfo.boardHeight = newHeight;
2025 gameInfo.holdingsWidth = newHoldingsWidth;
2026 gameInfo.variant = newVariant;
2027 InitDrawingSizes(-2, 0);
2029 /* [HGM] The following should definitely be solved in a better way */
2031 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2032 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2033 saveEP = epStatus[0];
2035 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2037 epStatus[0] = saveEP;
2038 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2039 CopyBoard(tempBoard, board); /* restore position received from ICS */
2041 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2043 forwardMostMove = oldForwardMostMove;
2044 backwardMostMove = oldBackwardMostMove;
2045 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2048 static int loggedOn = FALSE;
2050 /*-- Game start info cache: --*/
2052 char gs_kind[MSG_SIZ];
2053 static char player1Name[128] = "";
2054 static char player2Name[128] = "";
2055 static int player1Rating = -1;
2056 static int player2Rating = -1;
2057 /*----------------------------*/
2059 ColorClass curColor = ColorNormal;
2060 int suppressKibitz = 0;
2063 read_from_ics(isr, closure, data, count, error)
2070 #define BUF_SIZE 8192
2071 #define STARTED_NONE 0
2072 #define STARTED_MOVES 1
2073 #define STARTED_BOARD 2
2074 #define STARTED_OBSERVE 3
2075 #define STARTED_HOLDINGS 4
2076 #define STARTED_CHATTER 5
2077 #define STARTED_COMMENT 6
2078 #define STARTED_MOVES_NOHIDE 7
2080 static int started = STARTED_NONE;
2081 static char parse[20000];
2082 static int parse_pos = 0;
2083 static char buf[BUF_SIZE + 1];
2084 static int firstTime = TRUE, intfSet = FALSE;
2085 static ColorClass prevColor = ColorNormal;
2086 static int savingComment = FALSE;
2092 int backup; /* [DM] For zippy color lines */
2095 if (appData.debugMode) {
2097 fprintf(debugFP, "<ICS: ");
2098 show_bytes(debugFP, data, count);
2099 fprintf(debugFP, "\n");
2103 if (appData.debugMode) { int f = forwardMostMove;
2104 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2105 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2108 /* If last read ended with a partial line that we couldn't parse,
2109 prepend it to the new read and try again. */
2110 if (leftover_len > 0) {
2111 for (i=0; i<leftover_len; i++)
2112 buf[i] = buf[leftover_start + i];
2115 /* Copy in new characters, removing nulls and \r's */
2116 buf_len = leftover_len;
2117 for (i = 0; i < count; i++) {
2118 if (data[i] != NULLCHAR && data[i] != '\r')
2119 buf[buf_len++] = data[i];
2120 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2121 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
2122 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2125 buf[buf_len] = NULLCHAR;
2126 next_out = leftover_len;
2130 while (i < buf_len) {
2131 /* Deal with part of the TELNET option negotiation
2132 protocol. We refuse to do anything beyond the
2133 defaults, except that we allow the WILL ECHO option,
2134 which ICS uses to turn off password echoing when we are
2135 directly connected to it. We reject this option
2136 if localLineEditing mode is on (always on in xboard)
2137 and we are talking to port 23, which might be a real
2138 telnet server that will try to keep WILL ECHO on permanently.
2140 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2141 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2142 unsigned char option;
2144 switch ((unsigned char) buf[++i]) {
2146 if (appData.debugMode)
2147 fprintf(debugFP, "\n<WILL ");
2148 switch (option = (unsigned char) buf[++i]) {
2150 if (appData.debugMode)
2151 fprintf(debugFP, "ECHO ");
2152 /* Reply only if this is a change, according
2153 to the protocol rules. */
2154 if (remoteEchoOption) break;
2155 if (appData.localLineEditing &&
2156 atoi(appData.icsPort) == TN_PORT) {
2157 TelnetRequest(TN_DONT, TN_ECHO);
2160 TelnetRequest(TN_DO, TN_ECHO);
2161 remoteEchoOption = TRUE;
2165 if (appData.debugMode)
2166 fprintf(debugFP, "%d ", option);
2167 /* Whatever this is, we don't want it. */
2168 TelnetRequest(TN_DONT, option);
2173 if (appData.debugMode)
2174 fprintf(debugFP, "\n<WONT ");
2175 switch (option = (unsigned char) buf[++i]) {
2177 if (appData.debugMode)
2178 fprintf(debugFP, "ECHO ");
2179 /* Reply only if this is a change, according
2180 to the protocol rules. */
2181 if (!remoteEchoOption) break;
2183 TelnetRequest(TN_DONT, TN_ECHO);
2184 remoteEchoOption = FALSE;
2187 if (appData.debugMode)
2188 fprintf(debugFP, "%d ", (unsigned char) option);
2189 /* Whatever this is, it must already be turned
2190 off, because we never agree to turn on
2191 anything non-default, so according to the
2192 protocol rules, we don't reply. */
2197 if (appData.debugMode)
2198 fprintf(debugFP, "\n<DO ");
2199 switch (option = (unsigned char) buf[++i]) {
2201 /* Whatever this is, we refuse to do it. */
2202 if (appData.debugMode)
2203 fprintf(debugFP, "%d ", option);
2204 TelnetRequest(TN_WONT, option);
2209 if (appData.debugMode)
2210 fprintf(debugFP, "\n<DONT ");
2211 switch (option = (unsigned char) buf[++i]) {
2213 if (appData.debugMode)
2214 fprintf(debugFP, "%d ", option);
2215 /* Whatever this is, we are already not doing
2216 it, because we never agree to do anything
2217 non-default, so according to the protocol
2218 rules, we don't reply. */
2223 if (appData.debugMode)
2224 fprintf(debugFP, "\n<IAC ");
2225 /* Doubled IAC; pass it through */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2231 /* Drop all other telnet commands on the floor */
2234 if (oldi > next_out)
2235 SendToPlayer(&buf[next_out], oldi - next_out);
2241 /* OK, this at least will *usually* work */
2242 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2246 if (loggedOn && !intfSet) {
2247 if (ics_type == ICS_ICC) {
2249 "/set-quietly interface %s\n/set-quietly style 12\n",
2252 } else if (ics_type == ICS_CHESSNET) {
2253 sprintf(str, "/style 12\n");
2255 strcpy(str, "alias $ @\n$set interface ");
2256 strcat(str, programVersion);
2257 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2259 strcat(str, "$iset nohighlight 1\n");
2261 strcat(str, "$iset lock 1\n$style 12\n");
2267 if (started == STARTED_COMMENT) {
2268 /* Accumulate characters in comment */
2269 parse[parse_pos++] = buf[i];
2270 if (buf[i] == '\n') {
2271 parse[parse_pos] = NULLCHAR;
2272 if(!suppressKibitz) // [HGM] kibitz
2273 AppendComment(forwardMostMove, StripHighlight(parse));
2274 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2275 int nrDigit = 0, nrAlph = 0, i;
2276 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2277 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2278 parse[parse_pos] = NULLCHAR;
2279 // try to be smart: if it does not look like search info, it should go to
2280 // ICS interaction window after all, not to engine-output window.
2281 for(i=0; i<parse_pos; i++) { // count letters and digits
2282 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2283 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2284 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2286 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2287 int depth=0; float score;
2288 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2289 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2290 pvInfoList[forwardMostMove-1].depth = depth;
2291 pvInfoList[forwardMostMove-1].score = 100*score;
2293 OutputKibitz(suppressKibitz, parse);
2296 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2297 SendToPlayer(tmp, strlen(tmp));
2300 started = STARTED_NONE;
2302 /* Don't match patterns against characters in chatter */
2307 if (started == STARTED_CHATTER) {
2308 if (buf[i] != '\n') {
2309 /* Don't match patterns against characters in chatter */
2313 started = STARTED_NONE;
2316 /* Kludge to deal with rcmd protocol */
2317 if (firstTime && looking_at(buf, &i, "\001*")) {
2318 DisplayFatalError(&buf[1], 0, 1);
2324 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2327 if (appData.debugMode)
2328 fprintf(debugFP, "ics_type %d\n", ics_type);
2331 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2332 ics_type = ICS_FICS;
2334 if (appData.debugMode)
2335 fprintf(debugFP, "ics_type %d\n", ics_type);
2338 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2339 ics_type = ICS_CHESSNET;
2341 if (appData.debugMode)
2342 fprintf(debugFP, "ics_type %d\n", ics_type);
2347 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2348 looking_at(buf, &i, "Logging you in as \"*\"") ||
2349 looking_at(buf, &i, "will be \"*\""))) {
2350 strcpy(ics_handle, star_match[0]);
2354 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2356 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2357 DisplayIcsInteractionTitle(buf);
2358 have_set_title = TRUE;
2361 /* skip finger notes */
2362 if (started == STARTED_NONE &&
2363 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2364 (buf[i] == '1' && buf[i+1] == '0')) &&
2365 buf[i+2] == ':' && buf[i+3] == ' ') {
2366 started = STARTED_CHATTER;
2371 /* skip formula vars */
2372 if (started == STARTED_NONE &&
2373 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2374 started = STARTED_CHATTER;
2380 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2381 if (appData.autoKibitz && started == STARTED_NONE &&
2382 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2383 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2384 if(looking_at(buf, &i, "* kibitzes: ") &&
2385 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2386 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2387 suppressKibitz = TRUE;
2388 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2389 && (gameMode == IcsPlayingWhite)) ||
2390 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2391 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2392 started = STARTED_CHATTER; // own kibitz we simply discard
2394 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2395 parse_pos = 0; parse[0] = NULLCHAR;
2396 savingComment = TRUE;
2397 suppressKibitz = gameMode != IcsObserving ? 2 :
2398 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2402 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2403 started = STARTED_CHATTER;
2404 suppressKibitz = TRUE;
2406 } // [HGM] kibitz: end of patch
2408 if (appData.zippyTalk || appData.zippyPlay) {
2409 /* [DM] Backup address for color zippy lines */
2413 if (loggedOn == TRUE)
2414 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2415 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2417 if (ZippyControl(buf, &i) ||
2418 ZippyConverse(buf, &i) ||
2419 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2421 if (!appData.colorize) continue;
2425 } // [DM] 'else { ' deleted
2426 if (/* Don't color "message" or "messages" output */
2427 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2428 looking_at(buf, &i, "*. * at *:*: ") ||
2429 looking_at(buf, &i, "--* (*:*): ") ||
2430 /* Regular tells and says */
2431 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2432 looking_at(buf, &i, "* (your partner) tells you: ") ||
2433 looking_at(buf, &i, "* says: ") ||
2434 /* Message notifications (same color as tells) */
2435 looking_at(buf, &i, "* has left a message ") ||
2436 looking_at(buf, &i, "* just sent you a message:\n") ||
2437 /* Whispers and kibitzes */
2438 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2439 looking_at(buf, &i, "* kibitzes: ") ||
2441 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2443 if (tkind == 1 && strchr(star_match[0], ':')) {
2444 /* Avoid "tells you:" spoofs in channels */
2447 if (star_match[0][0] == NULLCHAR ||
2448 strchr(star_match[0], ' ') ||
2449 (tkind == 3 && strchr(star_match[1], ' '))) {
2450 /* Reject bogus matches */
2453 if (appData.colorize) {
2454 if (oldi > next_out) {
2455 SendToPlayer(&buf[next_out], oldi - next_out);
2460 Colorize(ColorTell, FALSE);
2461 curColor = ColorTell;
2464 Colorize(ColorKibitz, FALSE);
2465 curColor = ColorKibitz;
2468 p = strrchr(star_match[1], '(');
2475 Colorize(ColorChannel1, FALSE);
2476 curColor = ColorChannel1;
2478 Colorize(ColorChannel, FALSE);
2479 curColor = ColorChannel;
2483 curColor = ColorNormal;
2487 if (started == STARTED_NONE && appData.autoComment &&
2488 (gameMode == IcsObserving ||
2489 gameMode == IcsPlayingWhite ||
2490 gameMode == IcsPlayingBlack)) {
2491 parse_pos = i - oldi;
2492 memcpy(parse, &buf[oldi], parse_pos);
2493 parse[parse_pos] = NULLCHAR;
2494 started = STARTED_COMMENT;
2495 savingComment = TRUE;
2497 started = STARTED_CHATTER;
2498 savingComment = FALSE;
2505 if (looking_at(buf, &i, "* s-shouts: ") ||
2506 looking_at(buf, &i, "* c-shouts: ")) {
2507 if (appData.colorize) {
2508 if (oldi > next_out) {
2509 SendToPlayer(&buf[next_out], oldi - next_out);
2512 Colorize(ColorSShout, FALSE);
2513 curColor = ColorSShout;
2516 started = STARTED_CHATTER;
2520 if (looking_at(buf, &i, "--->")) {
2525 if (looking_at(buf, &i, "* shouts: ") ||
2526 looking_at(buf, &i, "--> ")) {
2527 if (appData.colorize) {
2528 if (oldi > next_out) {
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2532 Colorize(ColorShout, FALSE);
2533 curColor = ColorShout;
2536 started = STARTED_CHATTER;
2540 if (looking_at( buf, &i, "Challenge:")) {
2541 if (appData.colorize) {
2542 if (oldi > next_out) {
2543 SendToPlayer(&buf[next_out], oldi - next_out);
2546 Colorize(ColorChallenge, FALSE);
2547 curColor = ColorChallenge;
2553 if (looking_at(buf, &i, "* offers you") ||
2554 looking_at(buf, &i, "* offers to be") ||
2555 looking_at(buf, &i, "* would like to") ||
2556 looking_at(buf, &i, "* requests to") ||
2557 looking_at(buf, &i, "Your opponent offers") ||
2558 looking_at(buf, &i, "Your opponent requests")) {
2560 if (appData.colorize) {
2561 if (oldi > next_out) {
2562 SendToPlayer(&buf[next_out], oldi - next_out);
2565 Colorize(ColorRequest, FALSE);
2566 curColor = ColorRequest;
2571 if (looking_at(buf, &i, "* (*) seeking")) {
2572 if (appData.colorize) {
2573 if (oldi > next_out) {
2574 SendToPlayer(&buf[next_out], oldi - next_out);
2577 Colorize(ColorSeek, FALSE);
2578 curColor = ColorSeek;
2583 if (looking_at(buf, &i, "\\ ")) {
2584 if (prevColor != ColorNormal) {
2585 if (oldi > next_out) {
2586 SendToPlayer(&buf[next_out], oldi - next_out);
2589 Colorize(prevColor, TRUE);
2590 curColor = prevColor;
2592 if (savingComment) {
2593 parse_pos = i - oldi;
2594 memcpy(parse, &buf[oldi], parse_pos);
2595 parse[parse_pos] = NULLCHAR;
2596 started = STARTED_COMMENT;
2598 started = STARTED_CHATTER;
2603 if (looking_at(buf, &i, "Black Strength :") ||
2604 looking_at(buf, &i, "<<< style 10 board >>>") ||
2605 looking_at(buf, &i, "<10>") ||
2606 looking_at(buf, &i, "#@#")) {
2607 /* Wrong board style */
2609 SendToICS(ics_prefix);
2610 SendToICS("set style 12\n");
2611 SendToICS(ics_prefix);
2612 SendToICS("refresh\n");
2616 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2618 have_sent_ICS_logon = 1;
2622 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2623 (looking_at(buf, &i, "\n<12> ") ||
2624 looking_at(buf, &i, "<12> "))) {
2626 if (oldi > next_out) {
2627 SendToPlayer(&buf[next_out], oldi - next_out);
2630 started = STARTED_BOARD;
2635 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2636 looking_at(buf, &i, "<b1> ")) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 started = STARTED_HOLDINGS;
2646 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2648 /* Header for a move list -- first line */
2650 switch (ics_getting_history) {
2654 case BeginningOfGame:
2655 /* User typed "moves" or "oldmoves" while we
2656 were idle. Pretend we asked for these
2657 moves and soak them up so user can step
2658 through them and/or save them.
2661 gameMode = IcsObserving;
2664 ics_getting_history = H_GOT_UNREQ_HEADER;
2666 case EditGame: /*?*/
2667 case EditPosition: /*?*/
2668 /* Should above feature work in these modes too? */
2669 /* For now it doesn't */
2670 ics_getting_history = H_GOT_UNWANTED_HEADER;
2673 ics_getting_history = H_GOT_UNWANTED_HEADER;
2678 /* Is this the right one? */
2679 if (gameInfo.white && gameInfo.black &&
2680 strcmp(gameInfo.white, star_match[0]) == 0 &&
2681 strcmp(gameInfo.black, star_match[2]) == 0) {
2683 ics_getting_history = H_GOT_REQ_HEADER;
2686 case H_GOT_REQ_HEADER:
2687 case H_GOT_UNREQ_HEADER:
2688 case H_GOT_UNWANTED_HEADER:
2689 case H_GETTING_MOVES:
2690 /* Should not happen */
2691 DisplayError(_("Error gathering move list: two headers"), 0);
2692 ics_getting_history = H_FALSE;
2696 /* Save player ratings into gameInfo if needed */
2697 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2698 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2699 (gameInfo.whiteRating == -1 ||
2700 gameInfo.blackRating == -1)) {
2702 gameInfo.whiteRating = string_to_rating(star_match[1]);
2703 gameInfo.blackRating = string_to_rating(star_match[3]);
2704 if (appData.debugMode)
2705 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2706 gameInfo.whiteRating, gameInfo.blackRating);
2711 if (looking_at(buf, &i,
2712 "* * match, initial time: * minute*, increment: * second")) {
2713 /* Header for a move list -- second line */
2714 /* Initial board will follow if this is a wild game */
2715 if (gameInfo.event != NULL) free(gameInfo.event);
2716 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2717 gameInfo.event = StrSave(str);
2718 /* [HGM] we switched variant. Translate boards if needed. */
2719 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2723 if (looking_at(buf, &i, "Move ")) {
2724 /* Beginning of a move list */
2725 switch (ics_getting_history) {
2727 /* Normally should not happen */
2728 /* Maybe user hit reset while we were parsing */
2731 /* Happens if we are ignoring a move list that is not
2732 * the one we just requested. Common if the user
2733 * tries to observe two games without turning off
2736 case H_GETTING_MOVES:
2737 /* Should not happen */
2738 DisplayError(_("Error gathering move list: nested"), 0);
2739 ics_getting_history = H_FALSE;
2741 case H_GOT_REQ_HEADER:
2742 ics_getting_history = H_GETTING_MOVES;
2743 started = STARTED_MOVES;
2745 if (oldi > next_out) {
2746 SendToPlayer(&buf[next_out], oldi - next_out);
2749 case H_GOT_UNREQ_HEADER:
2750 ics_getting_history = H_GETTING_MOVES;
2751 started = STARTED_MOVES_NOHIDE;
2754 case H_GOT_UNWANTED_HEADER:
2755 ics_getting_history = H_FALSE;
2761 if (looking_at(buf, &i, "% ") ||
2762 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2763 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2764 savingComment = FALSE;
2767 case STARTED_MOVES_NOHIDE:
2768 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2769 parse[parse_pos + i - oldi] = NULLCHAR;
2770 ParseGameHistory(parse);
2772 if (appData.zippyPlay && first.initDone) {
2773 FeedMovesToProgram(&first, forwardMostMove);
2774 if (gameMode == IcsPlayingWhite) {
2775 if (WhiteOnMove(forwardMostMove)) {
2776 if (first.sendTime) {
2777 if (first.useColors) {
2778 SendToProgram("black\n", &first);
2780 SendTimeRemaining(&first, TRUE);
2783 if (first.useColors) {
2784 SendToProgram("white\ngo\n", &first);
2786 SendToProgram("go\n", &first);
2789 if (first.useColors) {
2790 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2792 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2794 first.maybeThinking = TRUE;
2796 if (first.usePlayother) {
2797 if (first.sendTime) {
2798 SendTimeRemaining(&first, TRUE);
2800 SendToProgram("playother\n", &first);
2806 } else if (gameMode == IcsPlayingBlack) {
2807 if (!WhiteOnMove(forwardMostMove)) {
2808 if (first.sendTime) {
2809 if (first.useColors) {
2810 SendToProgram("white\n", &first);
2812 SendTimeRemaining(&first, FALSE);
2815 if (first.useColors) {
2816 SendToProgram("black\ngo\n", &first);
2818 SendToProgram("go\n", &first);
2821 if (first.useColors) {
2822 SendToProgram("black\n", &first);
2824 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2826 first.maybeThinking = TRUE;
2828 if (first.usePlayother) {
2829 if (first.sendTime) {
2830 SendTimeRemaining(&first, FALSE);
2832 SendToProgram("playother\n", &first);
2841 if (gameMode == IcsObserving && ics_gamenum == -1) {
2842 /* Moves came from oldmoves or moves command
2843 while we weren't doing anything else.
2845 currentMove = forwardMostMove;
2846 ClearHighlights();/*!!could figure this out*/
2847 flipView = appData.flipView;
2848 DrawPosition(FALSE, boards[currentMove]);
2849 DisplayBothClocks();
2850 sprintf(str, "%s vs. %s",
2851 gameInfo.white, gameInfo.black);
2855 /* Moves were history of an active game */
2856 if (gameInfo.resultDetails != NULL) {
2857 free(gameInfo.resultDetails);
2858 gameInfo.resultDetails = NULL;
2861 HistorySet(parseList, backwardMostMove,
2862 forwardMostMove, currentMove-1);
2863 DisplayMove(currentMove - 1);
2864 if (started == STARTED_MOVES) next_out = i;
2865 started = STARTED_NONE;
2866 ics_getting_history = H_FALSE;
2869 case STARTED_OBSERVE:
2870 started = STARTED_NONE;
2871 SendToICS(ics_prefix);
2872 SendToICS("refresh\n");
2878 if(bookHit) { // [HGM] book: simulate book reply
2879 static char bookMove[MSG_SIZ]; // a bit generous?
2881 programStats.nodes = programStats.depth = programStats.time =
2882 programStats.score = programStats.got_only_move = 0;
2883 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2885 strcpy(bookMove, "move ");
2886 strcat(bookMove, bookHit);
2887 HandleMachineMove(bookMove, &first);
2892 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2893 started == STARTED_HOLDINGS ||
2894 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2895 /* Accumulate characters in move list or board */
2896 parse[parse_pos++] = buf[i];
2899 /* Start of game messages. Mostly we detect start of game
2900 when the first board image arrives. On some versions
2901 of the ICS, though, we need to do a "refresh" after starting
2902 to observe in order to get the current board right away. */
2903 if (looking_at(buf, &i, "Adding game * to observation list")) {
2904 started = STARTED_OBSERVE;
2908 /* Handle auto-observe */
2909 if (appData.autoObserve &&
2910 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2911 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2913 /* Choose the player that was highlighted, if any. */
2914 if (star_match[0][0] == '\033' ||
2915 star_match[1][0] != '\033') {
2916 player = star_match[0];
2918 player = star_match[2];
2920 sprintf(str, "%sobserve %s\n",
2921 ics_prefix, StripHighlightAndTitle(player));
2924 /* Save ratings from notify string */
2925 strcpy(player1Name, star_match[0]);
2926 player1Rating = string_to_rating(star_match[1]);
2927 strcpy(player2Name, star_match[2]);
2928 player2Rating = string_to_rating(star_match[3]);
2930 if (appData.debugMode)
2932 "Ratings from 'Game notification:' %s %d, %s %d\n",
2933 player1Name, player1Rating,
2934 player2Name, player2Rating);
2939 /* Deal with automatic examine mode after a game,
2940 and with IcsObserving -> IcsExamining transition */
2941 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2942 looking_at(buf, &i, "has made you an examiner of game *")) {
2944 int gamenum = atoi(star_match[0]);
2945 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2946 gamenum == ics_gamenum) {
2947 /* We were already playing or observing this game;
2948 no need to refetch history */
2949 gameMode = IcsExamining;
2951 pauseExamForwardMostMove = forwardMostMove;
2952 } else if (currentMove < forwardMostMove) {
2953 ForwardInner(forwardMostMove);
2956 /* I don't think this case really can happen */
2957 SendToICS(ics_prefix);
2958 SendToICS("refresh\n");
2963 /* Error messages */
2964 // if (ics_user_moved) {
2965 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2966 if (looking_at(buf, &i, "Illegal move") ||
2967 looking_at(buf, &i, "Not a legal move") ||
2968 looking_at(buf, &i, "Your king is in check") ||
2969 looking_at(buf, &i, "It isn't your turn") ||
2970 looking_at(buf, &i, "It is not your move")) {
2972 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2973 currentMove = --forwardMostMove;
2974 DisplayMove(currentMove - 1); /* before DMError */
2975 DrawPosition(FALSE, boards[currentMove]);
2977 DisplayBothClocks();
2979 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2985 if (looking_at(buf, &i, "still have time") ||
2986 looking_at(buf, &i, "not out of time") ||
2987 looking_at(buf, &i, "either player is out of time") ||
2988 looking_at(buf, &i, "has timeseal; checking")) {
2989 /* We must have called his flag a little too soon */
2990 whiteFlag = blackFlag = FALSE;
2994 if (looking_at(buf, &i, "added * seconds to") ||
2995 looking_at(buf, &i, "seconds were added to")) {
2996 /* Update the clocks */
2997 SendToICS(ics_prefix);
2998 SendToICS("refresh\n");
3002 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3003 ics_clock_paused = TRUE;
3008 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3009 ics_clock_paused = FALSE;
3014 /* Grab player ratings from the Creating: message.
3015 Note we have to check for the special case when
3016 the ICS inserts things like [white] or [black]. */
3017 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3018 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3020 0 player 1 name (not necessarily white)
3022 2 empty, white, or black (IGNORED)
3023 3 player 2 name (not necessarily black)
3026 The names/ratings are sorted out when the game
3027 actually starts (below).
3029 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3030 player1Rating = string_to_rating(star_match[1]);
3031 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3032 player2Rating = string_to_rating(star_match[4]);
3034 if (appData.debugMode)
3036 "Ratings from 'Creating:' %s %d, %s %d\n",
3037 player1Name, player1Rating,
3038 player2Name, player2Rating);
3043 /* Improved generic start/end-of-game messages */
3044 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3045 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3046 /* If tkind == 0: */
3047 /* star_match[0] is the game number */
3048 /* [1] is the white player's name */
3049 /* [2] is the black player's name */
3050 /* For end-of-game: */
3051 /* [3] is the reason for the game end */
3052 /* [4] is a PGN end game-token, preceded by " " */
3053 /* For start-of-game: */
3054 /* [3] begins with "Creating" or "Continuing" */
3055 /* [4] is " *" or empty (don't care). */
3056 int gamenum = atoi(star_match[0]);
3057 char *whitename, *blackname, *why, *endtoken;
3058 ChessMove endtype = (ChessMove) 0;
3061 whitename = star_match[1];
3062 blackname = star_match[2];
3063 why = star_match[3];
3064 endtoken = star_match[4];
3066 whitename = star_match[1];
3067 blackname = star_match[3];
3068 why = star_match[5];
3069 endtoken = star_match[6];
3072 /* Game start messages */
3073 if (strncmp(why, "Creating ", 9) == 0 ||
3074 strncmp(why, "Continuing ", 11) == 0) {
3075 gs_gamenum = gamenum;
3076 strcpy(gs_kind, strchr(why, ' ') + 1);
3078 if (appData.zippyPlay) {
3079 ZippyGameStart(whitename, blackname);
3085 /* Game end messages */
3086 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3087 ics_gamenum != gamenum) {
3090 while (endtoken[0] == ' ') endtoken++;
3091 switch (endtoken[0]) {
3094 endtype = GameUnfinished;
3097 endtype = BlackWins;
3100 if (endtoken[1] == '/')
3101 endtype = GameIsDrawn;
3103 endtype = WhiteWins;
3106 GameEnds(endtype, why, GE_ICS);
3108 if (appData.zippyPlay && first.initDone) {
3109 ZippyGameEnd(endtype, why);
3110 if (first.pr == NULL) {
3111 /* Start the next process early so that we'll
3112 be ready for the next challenge */
3113 StartChessProgram(&first);
3115 /* Send "new" early, in case this command takes
3116 a long time to finish, so that we'll be ready
3117 for the next challenge. */
3118 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3125 if (looking_at(buf, &i, "Removing game * from observation") ||
3126 looking_at(buf, &i, "no longer observing game *") ||
3127 looking_at(buf, &i, "Game * (*) has no examiners")) {
3128 if (gameMode == IcsObserving &&
3129 atoi(star_match[0]) == ics_gamenum)
3131 /* icsEngineAnalyze */
3132 if (appData.icsEngineAnalyze) {
3139 ics_user_moved = FALSE;
3144 if (looking_at(buf, &i, "no longer examining game *")) {
3145 if (gameMode == IcsExamining &&
3146 atoi(star_match[0]) == ics_gamenum)
3150 ics_user_moved = FALSE;
3155 /* Advance leftover_start past any newlines we find,
3156 so only partial lines can get reparsed */
3157 if (looking_at(buf, &i, "\n")) {
3158 prevColor = curColor;
3159 if (curColor != ColorNormal) {
3160 if (oldi > next_out) {
3161 SendToPlayer(&buf[next_out], oldi - next_out);
3164 Colorize(ColorNormal, FALSE);
3165 curColor = ColorNormal;
3167 if (started == STARTED_BOARD) {
3168 started = STARTED_NONE;
3169 parse[parse_pos] = NULLCHAR;
3170 ParseBoard12(parse);
3173 /* Send premove here */
3174 if (appData.premove) {
3176 if (currentMove == 0 &&
3177 gameMode == IcsPlayingWhite &&
3178 appData.premoveWhite) {
3179 sprintf(str, "%s%s\n", ics_prefix,
3180 appData.premoveWhiteText);
3181 if (appData.debugMode)
3182 fprintf(debugFP, "Sending premove:\n");
3184 } else if (currentMove == 1 &&
3185 gameMode == IcsPlayingBlack &&
3186 appData.premoveBlack) {
3187 sprintf(str, "%s%s\n", ics_prefix,
3188 appData.premoveBlackText);
3189 if (appData.debugMode)
3190 fprintf(debugFP, "Sending premove:\n");
3192 } else if (gotPremove) {
3194 ClearPremoveHighlights();
3195 if (appData.debugMode)
3196 fprintf(debugFP, "Sending premove:\n");
3197 UserMoveEvent(premoveFromX, premoveFromY,
3198 premoveToX, premoveToY,
3203 /* Usually suppress following prompt */
3204 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3205 if (looking_at(buf, &i, "*% ")) {
3206 savingComment = FALSE;
3210 } else if (started == STARTED_HOLDINGS) {
3212 char new_piece[MSG_SIZ];
3213 started = STARTED_NONE;
3214 parse[parse_pos] = NULLCHAR;
3215 if (appData.debugMode)
3216 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3217 parse, currentMove);
3218 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3219 gamenum == ics_gamenum) {
3220 if (gameInfo.variant == VariantNormal) {
3221 /* [HGM] We seem to switch variant during a game!
3222 * Presumably no holdings were displayed, so we have
3223 * to move the position two files to the right to
3224 * create room for them!
3226 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3227 /* Get a move list just to see the header, which
3228 will tell us whether this is really bug or zh */
3229 if (ics_getting_history == H_FALSE) {
3230 ics_getting_history = H_REQUESTED;
3231 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3235 new_piece[0] = NULLCHAR;
3236 sscanf(parse, "game %d white [%s black [%s <- %s",
3237 &gamenum, white_holding, black_holding,
3239 white_holding[strlen(white_holding)-1] = NULLCHAR;
3240 black_holding[strlen(black_holding)-1] = NULLCHAR;
3241 /* [HGM] copy holdings to board holdings area */
3242 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3243 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3245 if (appData.zippyPlay && first.initDone) {
3246 ZippyHoldings(white_holding, black_holding,
3250 if (tinyLayout || smallLayout) {
3251 char wh[16], bh[16];
3252 PackHolding(wh, white_holding);
3253 PackHolding(bh, black_holding);
3254 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3255 gameInfo.white, gameInfo.black);
3257 sprintf(str, "%s [%s] vs. %s [%s]",
3258 gameInfo.white, white_holding,
3259 gameInfo.black, black_holding);
3262 DrawPosition(FALSE, boards[currentMove]);
3265 /* Suppress following prompt */
3266 if (looking_at(buf, &i, "*% ")) {
3267 savingComment = FALSE;
3274 i++; /* skip unparsed character and loop back */
3277 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3278 started != STARTED_HOLDINGS && i > next_out) {
3279 SendToPlayer(&buf[next_out], i - next_out);
3282 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3284 leftover_len = buf_len - leftover_start;
3285 /* if buffer ends with something we couldn't parse,
3286 reparse it after appending the next read */
3288 } else if (count == 0) {
3289 RemoveInputSource(isr);
3290 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3292 DisplayFatalError(_("Error reading from ICS"), error, 1);
3297 /* Board style 12 looks like this:
3299 <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
3301 * The "<12> " is stripped before it gets to this routine. The two
3302 * trailing 0's (flip state and clock ticking) are later addition, and
3303 * some chess servers may not have them, or may have only the first.
3304 * Additional trailing fields may be added in the future.
3307 #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"
3309 #define RELATION_OBSERVING_PLAYED 0
3310 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3311 #define RELATION_PLAYING_MYMOVE 1
3312 #define RELATION_PLAYING_NOTMYMOVE -1
3313 #define RELATION_EXAMINING 2
3314 #define RELATION_ISOLATED_BOARD -3
3315 #define RELATION_STARTING_POSITION -4 /* FICS only */
3318 ParseBoard12(string)
3321 GameMode newGameMode;
3322 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3323 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3324 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3325 char to_play, board_chars[200];
3326 char move_str[500], str[500], elapsed_time[500];
3327 char black[32], white[32];
3329 int prevMove = currentMove;
3332 int fromX, fromY, toX, toY;
3334 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3335 char *bookHit = NULL; // [HGM] book
3337 fromX = fromY = toX = toY = -1;
3341 if (appData.debugMode)
3342 fprintf(debugFP, _("Parsing board: %s\n"), string);
3344 move_str[0] = NULLCHAR;
3345 elapsed_time[0] = NULLCHAR;
3346 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3348 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3349 if(string[i] == ' ') { ranks++; files = 0; }
3353 for(j = 0; j <i; j++) board_chars[j] = string[j];
3354 board_chars[i] = '\0';
3357 n = sscanf(string, PATTERN, &to_play, &double_push,
3358 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3359 &gamenum, white, black, &relation, &basetime, &increment,
3360 &white_stren, &black_stren, &white_time, &black_time,
3361 &moveNum, str, elapsed_time, move_str, &ics_flip,
3365 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3366 DisplayError(str, 0);
3370 /* Convert the move number to internal form */
3371 moveNum = (moveNum - 1) * 2;
3372 if (to_play == 'B') moveNum++;
3373 if (moveNum >= MAX_MOVES) {
3374 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3380 case RELATION_OBSERVING_PLAYED:
3381 case RELATION_OBSERVING_STATIC:
3382 if (gamenum == -1) {
3383 /* Old ICC buglet */
3384 relation = RELATION_OBSERVING_STATIC;
3386 newGameMode = IcsObserving;
3388 case RELATION_PLAYING_MYMOVE:
3389 case RELATION_PLAYING_NOTMYMOVE:
3391 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3392 IcsPlayingWhite : IcsPlayingBlack;
3394 case RELATION_EXAMINING:
3395 newGameMode = IcsExamining;
3397 case RELATION_ISOLATED_BOARD:
3399 /* Just display this board. If user was doing something else,
3400 we will forget about it until the next board comes. */
3401 newGameMode = IcsIdle;
3403 case RELATION_STARTING_POSITION:
3404 newGameMode = gameMode;
3408 /* Modify behavior for initial board display on move listing
3411 switch (ics_getting_history) {
3415 case H_GOT_REQ_HEADER:
3416 case H_GOT_UNREQ_HEADER:
3417 /* This is the initial position of the current game */
3418 gamenum = ics_gamenum;
3419 moveNum = 0; /* old ICS bug workaround */
3420 if (to_play == 'B') {
3421 startedFromSetupPosition = TRUE;
3422 blackPlaysFirst = TRUE;
3424 if (forwardMostMove == 0) forwardMostMove = 1;
3425 if (backwardMostMove == 0) backwardMostMove = 1;
3426 if (currentMove == 0) currentMove = 1;
3428 newGameMode = gameMode;
3429 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3431 case H_GOT_UNWANTED_HEADER:
3432 /* This is an initial board that we don't want */
3434 case H_GETTING_MOVES:
3435 /* Should not happen */
3436 DisplayError(_("Error gathering move list: extra board"), 0);
3437 ics_getting_history = H_FALSE;
3441 /* Take action if this is the first board of a new game, or of a
3442 different game than is currently being displayed. */
3443 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3444 relation == RELATION_ISOLATED_BOARD) {
3446 /* Forget the old game and get the history (if any) of the new one */
3447 if (gameMode != BeginningOfGame) {
3451 if (appData.autoRaiseBoard) BoardToTop();
3453 if (gamenum == -1) {
3454 newGameMode = IcsIdle;
3455 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3456 appData.getMoveList) {
3457 /* Need to get game history */
3458 ics_getting_history = H_REQUESTED;
3459 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3463 /* Initially flip the board to have black on the bottom if playing
3464 black or if the ICS flip flag is set, but let the user change
3465 it with the Flip View button. */
3466 flipView = appData.autoFlipView ?
3467 (newGameMode == IcsPlayingBlack) || ics_flip :
3470 /* Done with values from previous mode; copy in new ones */
3471 gameMode = newGameMode;
3473 ics_gamenum = gamenum;
3474 if (gamenum == gs_gamenum) {
3475 int klen = strlen(gs_kind);
3476 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3477 sprintf(str, "ICS %s", gs_kind);
3478 gameInfo.event = StrSave(str);
3480 gameInfo.event = StrSave("ICS game");
3482 gameInfo.site = StrSave(appData.icsHost);
3483 gameInfo.date = PGNDate();
3484 gameInfo.round = StrSave("-");
3485 gameInfo.white = StrSave(white);
3486 gameInfo.black = StrSave(black);
3487 timeControl = basetime * 60 * 1000;
3489 timeIncrement = increment * 1000;
3490 movesPerSession = 0;
3491 gameInfo.timeControl = TimeControlTagValue();
3492 VariantSwitch(board, StringToVariant(gameInfo.event) );
3493 if (appData.debugMode) {
3494 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3495 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3496 setbuf(debugFP, NULL);
3499 gameInfo.outOfBook = NULL;
3501 /* Do we have the ratings? */
3502 if (strcmp(player1Name, white) == 0 &&
3503 strcmp(player2Name, black) == 0) {
3504 if (appData.debugMode)
3505 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3506 player1Rating, player2Rating);
3507 gameInfo.whiteRating = player1Rating;
3508 gameInfo.blackRating = player2Rating;
3509 } else if (strcmp(player2Name, white) == 0 &&
3510 strcmp(player1Name, black) == 0) {
3511 if (appData.debugMode)
3512 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3513 player2Rating, player1Rating);
3514 gameInfo.whiteRating = player2Rating;
3515 gameInfo.blackRating = player1Rating;
3517 player1Name[0] = player2Name[0] = NULLCHAR;
3519 /* Silence shouts if requested */
3520 if (appData.quietPlay &&
3521 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3522 SendToICS(ics_prefix);
3523 SendToICS("set shout 0\n");
3527 /* Deal with midgame name changes */
3529 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3530 if (gameInfo.white) free(gameInfo.white);
3531 gameInfo.white = StrSave(white);
3533 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3534 if (gameInfo.black) free(gameInfo.black);
3535 gameInfo.black = StrSave(black);
3539 /* Throw away game result if anything actually changes in examine mode */
3540 if (gameMode == IcsExamining && !newGame) {
3541 gameInfo.result = GameUnfinished;
3542 if (gameInfo.resultDetails != NULL) {
3543 free(gameInfo.resultDetails);
3544 gameInfo.resultDetails = NULL;
3548 /* In pausing && IcsExamining mode, we ignore boards coming
3549 in if they are in a different variation than we are. */
3550 if (pauseExamInvalid) return;
3551 if (pausing && gameMode == IcsExamining) {
3552 if (moveNum <= pauseExamForwardMostMove) {
3553 pauseExamInvalid = TRUE;
3554 forwardMostMove = pauseExamForwardMostMove;
3559 if (appData.debugMode) {
3560 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3562 /* Parse the board */
3563 for (k = 0; k < ranks; k++) {
3564 for (j = 0; j < files; j++)
3565 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3566 if(gameInfo.holdingsWidth > 1) {
3567 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3568 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3571 CopyBoard(boards[moveNum], board);
3573 startedFromSetupPosition =
3574 !CompareBoards(board, initialPosition);
3575 if(startedFromSetupPosition)
3576 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3579 /* [HGM] Set castling rights. Take the outermost Rooks,
3580 to make it also work for FRC opening positions. Note that board12
3581 is really defective for later FRC positions, as it has no way to
3582 indicate which Rook can castle if they are on the same side of King.
3583 For the initial position we grant rights to the outermost Rooks,
3584 and remember thos rights, and we then copy them on positions
3585 later in an FRC game. This means WB might not recognize castlings with
3586 Rooks that have moved back to their original position as illegal,
3587 but in ICS mode that is not its job anyway.
3589 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3590 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3592 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593 if(board[0][i] == WhiteRook) j = i;
3594 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596 if(board[0][i] == WhiteRook) j = i;
3597 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3599 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3600 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3601 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3602 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3603 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3606 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3607 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3608 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3609 if(board[BOARD_HEIGHT-1][k] == bKing)
3610 initialRights[5] = castlingRights[moveNum][5] = k;
3612 r = castlingRights[moveNum][0] = initialRights[0];
3613 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3614 r = castlingRights[moveNum][1] = initialRights[1];
3615 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3616 r = castlingRights[moveNum][3] = initialRights[3];
3617 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3618 r = castlingRights[moveNum][4] = initialRights[4];
3619 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3620 /* wildcastle kludge: always assume King has rights */
3621 r = castlingRights[moveNum][2] = initialRights[2];
3622 r = castlingRights[moveNum][5] = initialRights[5];
3624 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3625 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3628 if (ics_getting_history == H_GOT_REQ_HEADER ||
3629 ics_getting_history == H_GOT_UNREQ_HEADER) {
3630 /* This was an initial position from a move list, not
3631 the current position */
3635 /* Update currentMove and known move number limits */
3636 newMove = newGame || moveNum > forwardMostMove;
3638 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3639 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3640 takeback = forwardMostMove - moveNum;
3641 for (i = 0; i < takeback; i++) {
3642 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3643 SendToProgram("undo\n", &first);
3648 forwardMostMove = backwardMostMove = currentMove = moveNum;
3649 if (gameMode == IcsExamining && moveNum == 0) {
3650 /* Workaround for ICS limitation: we are not told the wild
3651 type when starting to examine a game. But if we ask for
3652 the move list, the move list header will tell us */
3653 ics_getting_history = H_REQUESTED;
3654 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3657 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3658 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3659 forwardMostMove = moveNum;
3660 if (!pausing || currentMove > forwardMostMove)
3661 currentMove = forwardMostMove;
3663 /* New part of history that is not contiguous with old part */
3664 if (pausing && gameMode == IcsExamining) {
3665 pauseExamInvalid = TRUE;
3666 forwardMostMove = pauseExamForwardMostMove;
3669 forwardMostMove = backwardMostMove = currentMove = moveNum;
3670 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3671 ics_getting_history = H_REQUESTED;
3672 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3677 /* Update the clocks */
3678 if (strchr(elapsed_time, '.')) {
3680 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3681 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3683 /* Time is in seconds */
3684 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3685 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3690 if (appData.zippyPlay && newGame &&
3691 gameMode != IcsObserving && gameMode != IcsIdle &&
3692 gameMode != IcsExamining)
3693 ZippyFirstBoard(moveNum, basetime, increment);
3696 /* Put the move on the move list, first converting
3697 to canonical algebraic form. */
3699 if (appData.debugMode) {
3700 if (appData.debugMode) { int f = forwardMostMove;
3701 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3702 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3704 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3705 fprintf(debugFP, "moveNum = %d\n", moveNum);
3706 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3707 setbuf(debugFP, NULL);
3709 if (moveNum <= backwardMostMove) {
3710 /* We don't know what the board looked like before
3712 strcpy(parseList[moveNum - 1], move_str);
3713 strcat(parseList[moveNum - 1], " ");
3714 strcat(parseList[moveNum - 1], elapsed_time);
3715 moveList[moveNum - 1][0] = NULLCHAR;
3716 } else if (strcmp(move_str, "none") == 0) {
3717 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3718 /* Again, we don't know what the board looked like;
3719 this is really the start of the game. */
3720 parseList[moveNum - 1][0] = NULLCHAR;
3721 moveList[moveNum - 1][0] = NULLCHAR;
3722 backwardMostMove = moveNum;
3723 startedFromSetupPosition = TRUE;
3724 fromX = fromY = toX = toY = -1;
3726 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3727 // So we parse the long-algebraic move string in stead of the SAN move
3728 int valid; char buf[MSG_SIZ], *prom;
3730 // str looks something like "Q/a1-a2"; kill the slash
3732 sprintf(buf, "%c%s", str[0], str+2);
3733 else strcpy(buf, str); // might be castling
3734 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3735 strcat(buf, prom); // long move lacks promo specification!
3736 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3737 if(appData.debugMode)
3738 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3739 strcpy(move_str, buf);
3741 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3742 &fromX, &fromY, &toX, &toY, &promoChar)
3743 || ParseOneMove(buf, moveNum - 1, &moveType,
3744 &fromX, &fromY, &toX, &toY, &promoChar);
3745 // end of long SAN patch
3747 (void) CoordsToAlgebraic(boards[moveNum - 1],
3748 PosFlags(moveNum - 1), EP_UNKNOWN,
3749 fromY, fromX, toY, toX, promoChar,
3750 parseList[moveNum-1]);
3751 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3752 castlingRights[moveNum]) ) {
3758 if(gameInfo.variant != VariantShogi)
3759 strcat(parseList[moveNum - 1], "+");
3762 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3763 strcat(parseList[moveNum - 1], "#");
3766 strcat(parseList[moveNum - 1], " ");
3767 strcat(parseList[moveNum - 1], elapsed_time);
3768 /* currentMoveString is set as a side-effect of ParseOneMove */
3769 strcpy(moveList[moveNum - 1], currentMoveString);
3770 strcat(moveList[moveNum - 1], "\n");
3772 /* Move from ICS was illegal!? Punt. */
3773 if (appData.debugMode) {
3774 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3775 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3778 if (appData.testLegality && appData.debugMode) {
3779 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3780 DisplayError(str, 0);
3783 strcpy(parseList[moveNum - 1], move_str);
3784 strcat(parseList[moveNum - 1], " ");
3785 strcat(parseList[moveNum - 1], elapsed_time);
3786 moveList[moveNum - 1][0] = NULLCHAR;
3787 fromX = fromY = toX = toY = -1;
3790 if (appData.debugMode) {
3791 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3792 setbuf(debugFP, NULL);
3796 /* Send move to chess program (BEFORE animating it). */
3797 if (appData.zippyPlay && !newGame && newMove &&
3798 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3800 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3801 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3802 if (moveList[moveNum - 1][0] == NULLCHAR) {
3803 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3805 DisplayError(str, 0);
3807 if (first.sendTime) {
3808 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3810 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3811 if (firstMove && !bookHit) {
3813 if (first.useColors) {
3814 SendToProgram(gameMode == IcsPlayingWhite ?
3816 "black\ngo\n", &first);
3818 SendToProgram("go\n", &first);
3820 first.maybeThinking = TRUE;
3823 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3824 if (moveList[moveNum - 1][0] == NULLCHAR) {
3825 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3826 DisplayError(str, 0);
3828 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3829 SendMoveToProgram(moveNum - 1, &first);
3836 if (moveNum > 0 && !gotPremove) {
3837 /* If move comes from a remote source, animate it. If it
3838 isn't remote, it will have already been animated. */
3839 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3840 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3842 if (!pausing && appData.highlightLastMove) {
3843 SetHighlights(fromX, fromY, toX, toY);
3847 /* Start the clocks */
3848 whiteFlag = blackFlag = FALSE;
3849 appData.clockMode = !(basetime == 0 && increment == 0);
3851 ics_clock_paused = TRUE;
3853 } else if (ticking == 1) {
3854 ics_clock_paused = FALSE;
3856 if (gameMode == IcsIdle ||
3857 relation == RELATION_OBSERVING_STATIC ||
3858 relation == RELATION_EXAMINING ||
3860 DisplayBothClocks();
3864 /* Display opponents and material strengths */
3865 if (gameInfo.variant != VariantBughouse &&
3866 gameInfo.variant != VariantCrazyhouse) {
3867 if (tinyLayout || smallLayout) {
3868 if(gameInfo.variant == VariantNormal)
3869 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3870 gameInfo.white, white_stren, gameInfo.black, black_stren,
3871 basetime, increment);
3873 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3874 gameInfo.white, white_stren, gameInfo.black, black_stren,
3875 basetime, increment, (int) gameInfo.variant);
3877 if(gameInfo.variant == VariantNormal)
3878 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3879 gameInfo.white, white_stren, gameInfo.black, black_stren,
3880 basetime, increment);
3882 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3883 gameInfo.white, white_stren, gameInfo.black, black_stren,
3884 basetime, increment, VariantName(gameInfo.variant));
3887 if (appData.debugMode) {
3888 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3893 /* Display the board */
3896 if (appData.premove)
3898 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3899 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3900 ClearPremoveHighlights();
3902 DrawPosition(FALSE, boards[currentMove]);
3903 DisplayMove(moveNum - 1);
3904 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3905 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3906 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3907 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3911 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3913 if(bookHit) { // [HGM] book: simulate book reply
3914 static char bookMove[MSG_SIZ]; // a bit generous?
3916 programStats.nodes = programStats.depth = programStats.time =
3917 programStats.score = programStats.got_only_move = 0;
3918 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3920 strcpy(bookMove, "move ");
3921 strcat(bookMove, bookHit);
3922 HandleMachineMove(bookMove, &first);
3931 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3932 ics_getting_history = H_REQUESTED;
3933 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3939 AnalysisPeriodicEvent(force)
3942 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3943 && !force) || !appData.periodicUpdates)
3946 /* Send . command to Crafty to collect stats */
3947 SendToProgram(".\n", &first);
3949 /* Don't send another until we get a response (this makes
3950 us stop sending to old Crafty's which don't understand
3951 the "." command (sending illegal cmds resets node count & time,
3952 which looks bad)) */
3953 programStats.ok_to_send = 0;
3957 SendMoveToProgram(moveNum, cps)
3959 ChessProgramState *cps;
3963 if (cps->useUsermove) {
3964 SendToProgram("usermove ", cps);
3968 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3969 int len = space - parseList[moveNum];
3970 memcpy(buf, parseList[moveNum], len);
3972 buf[len] = NULLCHAR;
3974 sprintf(buf, "%s\n", parseList[moveNum]);
3976 SendToProgram(buf, cps);
3978 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3979 AlphaRank(moveList[moveNum], 4);
3980 SendToProgram(moveList[moveNum], cps);
3981 AlphaRank(moveList[moveNum], 4); // and back
3983 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3984 * the engine. It would be nice to have a better way to identify castle
3986 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3987 && cps->useOOCastle) {
3988 int fromX = moveList[moveNum][0] - AAA;
3989 int fromY = moveList[moveNum][1] - ONE;
3990 int toX = moveList[moveNum][2] - AAA;
3991 int toY = moveList[moveNum][3] - ONE;
3992 if((boards[moveNum][fromY][fromX] == WhiteKing
3993 && boards[moveNum][toY][toX] == WhiteRook)
3994 || (boards[moveNum][fromY][fromX] == BlackKing
3995 && boards[moveNum][toY][toX] == BlackRook)) {
3996 if(toX > fromX) SendToProgram("O-O\n", cps);
3997 else SendToProgram("O-O-O\n", cps);
3999 else SendToProgram(moveList[moveNum], cps);
4001 else SendToProgram(moveList[moveNum], cps);
4002 /* End of additions by Tord */
4005 /* [HGM] setting up the opening has brought engine in force mode! */
4006 /* Send 'go' if we are in a mode where machine should play. */
4007 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4008 (gameMode == TwoMachinesPlay ||
4010 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4012 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4013 SendToProgram("go\n", cps);
4014 if (appData.debugMode) {
4015 fprintf(debugFP, "(extra)\n");
4018 setboardSpoiledMachineBlack = 0;
4022 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4024 int fromX, fromY, toX, toY;
4026 char user_move[MSG_SIZ];
4030 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4031 (int)moveType, fromX, fromY, toX, toY);
4032 DisplayError(user_move + strlen("say "), 0);
4034 case WhiteKingSideCastle:
4035 case BlackKingSideCastle:
4036 case WhiteQueenSideCastleWild:
4037 case BlackQueenSideCastleWild:
4039 case WhiteHSideCastleFR:
4040 case BlackHSideCastleFR:
4042 sprintf(user_move, "o-o\n");
4044 case WhiteQueenSideCastle:
4045 case BlackQueenSideCastle:
4046 case WhiteKingSideCastleWild:
4047 case BlackKingSideCastleWild:
4049 case WhiteASideCastleFR:
4050 case BlackASideCastleFR:
4052 sprintf(user_move, "o-o-o\n");
4054 case WhitePromotionQueen:
4055 case BlackPromotionQueen:
4056 case WhitePromotionRook:
4057 case BlackPromotionRook:
4058 case WhitePromotionBishop:
4059 case BlackPromotionBishop:
4060 case WhitePromotionKnight:
4061 case BlackPromotionKnight:
4062 case WhitePromotionKing:
4063 case BlackPromotionKing:
4064 case WhitePromotionChancellor:
4065 case BlackPromotionChancellor:
4066 case WhitePromotionArchbishop:
4067 case BlackPromotionArchbishop:
4068 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4069 sprintf(user_move, "%c%c%c%c=%c\n",
4070 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071 PieceToChar(WhiteFerz));
4072 else if(gameInfo.variant == VariantGreat)
4073 sprintf(user_move, "%c%c%c%c=%c\n",
4074 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4075 PieceToChar(WhiteMan));
4077 sprintf(user_move, "%c%c%c%c=%c\n",
4078 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4079 PieceToChar(PromoPiece(moveType)));
4083 sprintf(user_move, "%c@%c%c\n",
4084 ToUpper(PieceToChar((ChessSquare) fromX)),
4085 AAA + toX, ONE + toY);
4088 case WhiteCapturesEnPassant:
4089 case BlackCapturesEnPassant:
4090 case IllegalMove: /* could be a variant we don't quite understand */
4091 sprintf(user_move, "%c%c%c%c\n",
4092 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4095 SendToICS(user_move);
4099 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4104 if (rf == DROP_RANK) {
4105 sprintf(move, "%c@%c%c\n",
4106 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4108 if (promoChar == 'x' || promoChar == NULLCHAR) {
4109 sprintf(move, "%c%c%c%c\n",
4110 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4112 sprintf(move, "%c%c%c%c%c\n",
4113 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4119 ProcessICSInitScript(f)
4124 while (fgets(buf, MSG_SIZ, f)) {
4125 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4132 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4134 AlphaRank(char *move, int n)
4136 // char *p = move, c; int x, y;
4138 if (appData.debugMode) {
4139 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4143 move[2]>='0' && move[2]<='9' &&
4144 move[3]>='a' && move[3]<='x' ) {
4146 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4147 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4149 if(move[0]>='0' && move[0]<='9' &&
4150 move[1]>='a' && move[1]<='x' &&
4151 move[2]>='0' && move[2]<='9' &&
4152 move[3]>='a' && move[3]<='x' ) {
4153 /* input move, Shogi -> normal */
4154 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4155 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4156 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4157 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4160 move[3]>='0' && move[3]<='9' &&
4161 move[2]>='a' && move[2]<='x' ) {
4163 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4164 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4167 move[0]>='a' && move[0]<='x' &&
4168 move[3]>='0' && move[3]<='9' &&
4169 move[2]>='a' && move[2]<='x' ) {
4170 /* output move, normal -> Shogi */
4171 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4172 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4173 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4174 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4175 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4177 if (appData.debugMode) {
4178 fprintf(debugFP, " out = '%s'\n", move);
4182 /* Parser for moves from gnuchess, ICS, or user typein box */
4184 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4187 ChessMove *moveType;
4188 int *fromX, *fromY, *toX, *toY;
4191 if (appData.debugMode) {
4192 fprintf(debugFP, "move to parse: %s\n", move);
4194 *moveType = yylexstr(moveNum, move);
4196 switch (*moveType) {
4197 case WhitePromotionChancellor:
4198 case BlackPromotionChancellor:
4199 case WhitePromotionArchbishop:
4200 case BlackPromotionArchbishop:
4201 case WhitePromotionQueen:
4202 case BlackPromotionQueen:
4203 case WhitePromotionRook:
4204 case BlackPromotionRook:
4205 case WhitePromotionBishop:
4206 case BlackPromotionBishop:
4207 case WhitePromotionKnight:
4208 case BlackPromotionKnight:
4209 case WhitePromotionKing:
4210 case BlackPromotionKing:
4212 case WhiteCapturesEnPassant:
4213 case BlackCapturesEnPassant:
4214 case WhiteKingSideCastle:
4215 case WhiteQueenSideCastle:
4216 case BlackKingSideCastle:
4217 case BlackQueenSideCastle:
4218 case WhiteKingSideCastleWild:
4219 case WhiteQueenSideCastleWild:
4220 case BlackKingSideCastleWild:
4221 case BlackQueenSideCastleWild:
4222 /* Code added by Tord: */
4223 case WhiteHSideCastleFR:
4224 case WhiteASideCastleFR:
4225 case BlackHSideCastleFR:
4226 case BlackASideCastleFR:
4227 /* End of code added by Tord */
4228 case IllegalMove: /* bug or odd chess variant */
4229 *fromX = currentMoveString[0] - AAA;
4230 *fromY = currentMoveString[1] - ONE;
4231 *toX = currentMoveString[2] - AAA;
4232 *toY = currentMoveString[3] - ONE;
4233 *promoChar = currentMoveString[4];
4234 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4235 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4236 if (appData.debugMode) {
4237 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4239 *fromX = *fromY = *toX = *toY = 0;
4242 if (appData.testLegality) {
4243 return (*moveType != IllegalMove);
4245 return !(fromX == fromY && toX == toY);
4250 *fromX = *moveType == WhiteDrop ?
4251 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4252 (int) CharToPiece(ToLower(currentMoveString[0]));
4254 *toX = currentMoveString[2] - AAA;
4255 *toY = currentMoveString[3] - ONE;
4256 *promoChar = NULLCHAR;
4260 case ImpossibleMove:
4261 case (ChessMove) 0: /* end of file */
4270 if (appData.debugMode) {
4271 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4274 *fromX = *fromY = *toX = *toY = 0;
4275 *promoChar = NULLCHAR;
4280 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4281 // All positions will have equal probability, but the current method will not provide a unique
4282 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4288 int piecesLeft[(int)BlackPawn];
4289 int seed, nrOfShuffles;
4291 void GetPositionNumber()
4292 { // sets global variable seed
4295 seed = appData.defaultFrcPosition;
4296 if(seed < 0) { // randomize based on time for negative FRC position numbers
4297 for(i=0; i<50; i++) seed += random();
4298 seed = random() ^ random() >> 8 ^ random() << 8;
4299 if(seed<0) seed = -seed;
4303 int put(Board board, int pieceType, int rank, int n, int shade)
4304 // put the piece on the (n-1)-th empty squares of the given shade
4308 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4309 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4310 board[rank][i] = (ChessSquare) pieceType;
4311 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4313 piecesLeft[pieceType]--;
4321 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4322 // calculate where the next piece goes, (any empty square), and put it there
4326 i = seed % squaresLeft[shade];
4327 nrOfShuffles *= squaresLeft[shade];
4328 seed /= squaresLeft[shade];
4329 put(board, pieceType, rank, i, shade);
4332 void AddTwoPieces(Board board, int pieceType, int rank)
4333 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4335 int i, n=squaresLeft[ANY], j=n-1, k;
4337 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4338 i = seed % k; // pick one
4341 while(i >= j) i -= j--;
4342 j = n - 1 - j; i += j;
4343 put(board, pieceType, rank, j, ANY);
4344 put(board, pieceType, rank, i, ANY);
4347 void SetUpShuffle(Board board, int number)
4351 GetPositionNumber(); nrOfShuffles = 1;
4353 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4354 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4355 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4357 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4359 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4360 p = (int) board[0][i];
4361 if(p < (int) BlackPawn) piecesLeft[p] ++;
4362 board[0][i] = EmptySquare;
4365 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4366 // shuffles restricted to allow normal castling put KRR first
4367 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4368 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4369 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4370 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4371 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4372 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4373 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4374 put(board, WhiteRook, 0, 0, ANY);
4375 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4378 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4379 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4380 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4381 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4382 while(piecesLeft[p] >= 2) {
4383 AddOnePiece(board, p, 0, LITE);
4384 AddOnePiece(board, p, 0, DARK);
4386 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4389 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4390 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4391 // but we leave King and Rooks for last, to possibly obey FRC restriction
4392 if(p == (int)WhiteRook) continue;
4393 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4394 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4397 // now everything is placed, except perhaps King (Unicorn) and Rooks
4399 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4400 // Last King gets castling rights
4401 while(piecesLeft[(int)WhiteUnicorn]) {
4402 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4403 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4406 while(piecesLeft[(int)WhiteKing]) {
4407 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4408 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4413 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4414 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4417 // Only Rooks can be left; simply place them all
4418 while(piecesLeft[(int)WhiteRook]) {
4419 i = put(board, WhiteRook, 0, 0, ANY);
4420 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4423 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4425 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4428 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4429 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4432 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4435 int SetCharTable( char *table, const char * map )
4436 /* [HGM] moved here from winboard.c because of its general usefulness */
4437 /* Basically a safe strcpy that uses the last character as King */
4439 int result = FALSE; int NrPieces;
4441 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4442 && NrPieces >= 12 && !(NrPieces&1)) {
4443 int i; /* [HGM] Accept even length from 12 to 34 */
4445 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4446 for( i=0; i<NrPieces/2-1; i++ ) {
4448 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4450 table[(int) WhiteKing] = map[NrPieces/2-1];
4451 table[(int) BlackKing] = map[NrPieces-1];
4459 void Prelude(Board board)
4460 { // [HGM] superchess: random selection of exo-pieces
4461 int i, j, k; ChessSquare p;
4462 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4464 GetPositionNumber(); // use FRC position number
4466 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4467 SetCharTable(pieceToChar, appData.pieceToCharTable);
4468 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4469 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4472 j = seed%4; seed /= 4;
4473 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = 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%3 + (seed%3 >= j); seed /= 3;
4477 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = 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%3; seed /= 3;
4481 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4482 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4483 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4484 j = seed%2 + (seed%2 >= j); seed /= 2;
4485 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4486 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4487 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4488 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4489 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4490 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4491 put(board, exoPieces[0], 0, 0, ANY);
4492 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4496 InitPosition(redraw)
4499 ChessSquare (* pieces)[BOARD_SIZE];
4500 int i, j, pawnRow, overrule,
4501 oldx = gameInfo.boardWidth,
4502 oldy = gameInfo.boardHeight,
4503 oldh = gameInfo.holdingsWidth,
4504 oldv = gameInfo.variant;
4506 printf ("DEBUG: in init position\n");
4508 currentMove = forwardMostMove = backwardMostMove = 0;
4509 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4511 /* [AS] Initialize pv info list [HGM] and game status */
4513 for( i=0; i<MAX_MOVES; i++ ) {
4514 pvInfoList[i].depth = 0;
4515 epStatus[i]=EP_NONE;
4516 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4519 initialRulePlies = 0; /* 50-move counter start */
4521 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4522 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4526 /* [HGM] logic here is completely changed. In stead of full positions */
4527 /* the initialized data only consist of the two backranks. The switch */
4528 /* selects which one we will use, which is than copied to the Board */
4529 /* initialPosition, which for the rest is initialized by Pawns and */
4530 /* empty squares. This initial position is then copied to boards[0], */
4531 /* possibly after shuffling, so that it remains available. */
4533 gameInfo.holdingsWidth = 0; /* default board sizes */
4534 gameInfo.boardWidth = 8;
4535 gameInfo.boardHeight = 8;
4536 gameInfo.holdingsSize = 0;
4537 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4538 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4539 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4541 switch (gameInfo.variant) {
4542 case VariantFischeRandom:
4543 shuffleOpenings = TRUE;
4547 case VariantShatranj:
4548 pieces = ShatranjArray;
4549 nrCastlingRights = 0;
4550 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4552 case VariantTwoKings:
4553 pieces = twoKingsArray;
4555 case VariantCapaRandom:
4556 shuffleOpenings = TRUE;
4557 case VariantCapablanca:
4558 pieces = CapablancaArray;
4559 gameInfo.boardWidth = 10;
4560 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4563 pieces = GothicArray;
4564 gameInfo.boardWidth = 10;
4565 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4568 pieces = JanusArray;
4569 gameInfo.boardWidth = 10;
4570 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4571 nrCastlingRights = 6;
4572 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4573 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4574 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4575 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4576 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4577 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4580 pieces = FalconArray;
4581 gameInfo.boardWidth = 10;
4582 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4584 case VariantXiangqi:
4585 pieces = XiangqiArray;
4586 gameInfo.boardWidth = 9;
4587 gameInfo.boardHeight = 10;
4588 nrCastlingRights = 0;
4589 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4592 pieces = ShogiArray;
4593 gameInfo.boardWidth = 9;
4594 gameInfo.boardHeight = 9;
4595 gameInfo.holdingsSize = 7;
4596 nrCastlingRights = 0;
4597 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4599 case VariantCourier:
4600 pieces = CourierArray;
4601 gameInfo.boardWidth = 12;
4602 nrCastlingRights = 0;
4603 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4604 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4606 case VariantKnightmate:
4607 pieces = KnightmateArray;
4608 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4611 pieces = fairyArray;
4612 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4615 pieces = GreatArray;
4616 gameInfo.boardWidth = 10;
4617 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4618 gameInfo.holdingsSize = 8;
4622 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4623 gameInfo.holdingsSize = 8;
4624 startedFromSetupPosition = TRUE;
4626 case VariantCrazyhouse:
4627 case VariantBughouse:
4629 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4630 gameInfo.holdingsSize = 5;
4632 case VariantWildCastle:
4634 /* !!?shuffle with kings guaranteed to be on d or e file */
4635 shuffleOpenings = 1;
4637 case VariantNoCastle:
4639 nrCastlingRights = 0;
4640 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4641 /* !!?unconstrained back-rank shuffle */
4642 shuffleOpenings = 1;
4645 printf ("DEBUG: in init position 1\n");
4648 if(appData.NrFiles >= 0) {
4649 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4650 gameInfo.boardWidth = appData.NrFiles;
4652 if(appData.NrRanks >= 0) {
4653 gameInfo.boardHeight = appData.NrRanks;
4655 if(appData.holdingsSize >= 0) {
4656 i = appData.holdingsSize;
4657 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4658 gameInfo.holdingsSize = i;
4660 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4661 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4662 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4664 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4665 if(pawnRow < 1) pawnRow = 1;
4667 /* User pieceToChar list overrules defaults */
4668 if(appData.pieceToCharTable != NULL)
4669 SetCharTable(pieceToChar, appData.pieceToCharTable);
4671 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4673 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4674 s = (ChessSquare) 0; /* account holding counts in guard band */
4675 for( i=0; i<BOARD_HEIGHT; i++ )
4676 initialPosition[i][j] = s;
4678 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4679 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4680 initialPosition[pawnRow][j] = WhitePawn;
4681 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4682 if(gameInfo.variant == VariantXiangqi) {
4684 initialPosition[pawnRow][j] =
4685 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4686 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4687 initialPosition[2][j] = WhiteCannon;
4688 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4692 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4694 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4697 initialPosition[1][j] = WhiteBishop;
4698 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4700 initialPosition[1][j] = WhiteRook;
4701 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4704 printf ("DEBUG: in init position 2\n");
4707 if( nrCastlingRights == -1) {
4708 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4709 /* This sets default castling rights from none to normal corners */
4710 /* Variants with other castling rights must set them themselves above */
4711 nrCastlingRights = 6;
4713 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4714 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4715 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4716 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4717 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4718 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4721 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4722 if(gameInfo.variant == VariantGreat) { // promotion commoners
4723 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4724 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4725 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4726 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4729 if(gameInfo.variant == VariantFischeRandom) {
4730 if( appData.defaultFrcPosition < 0 ) {
4731 ShuffleFRC( initialPosition );
4734 SetupFRC( initialPosition, appData.defaultFrcPosition );
4736 startedFromSetupPosition = TRUE;
4739 if (appData.debugMode) {
4740 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4742 if(shuffleOpenings) {
4743 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4744 startedFromSetupPosition = TRUE;
4747 if(startedFromPositionFile) {
4748 /* [HGM] loadPos: use PositionFile for every new game */
4749 CopyBoard(initialPosition, filePosition);
4750 for(i=0; i<nrCastlingRights; i++)
4751 castlingRights[0][i] = initialRights[i] = fileRights[i];
4752 startedFromSetupPosition = TRUE;
4755 printf ("DEBUG: in init position 3\n");
4758 CopyBoard(boards[0], initialPosition);
4759 printf ("DEBUG: in init position 3.1\n");
4760 if(oldx != gameInfo.boardWidth ||
4761 oldy != gameInfo.boardHeight ||
4762 oldh != gameInfo.holdingsWidth
4764 || oldv == VariantGothic || // For licensing popups
4765 gameInfo.variant == VariantGothic
4768 || oldv == VariantFalcon ||
4769 gameInfo.variant == VariantFalcon
4773 printf ("DEBUG: in init position 3.2\n");
4774 InitDrawingSizes(-2 ,0);
4776 printf ("DEBUG: init position 99\n");
4779 DrawPosition(TRUE, boards[currentMove]);
4781 printf ("DEBUG: end init position\n");
4785 SendBoard(cps, moveNum)
4786 ChessProgramState *cps;
4789 char message[MSG_SIZ];
4791 if (cps->useSetboard) {
4792 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4793 sprintf(message, "setboard %s\n", fen);
4794 SendToProgram(message, cps);
4800 /* Kludge to set black to move, avoiding the troublesome and now
4801 * deprecated "black" command.
4803 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4805 SendToProgram("edit\n", cps);
4806 SendToProgram("#\n", cps);
4807 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4808 bp = &boards[moveNum][i][BOARD_LEFT];
4809 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4810 if ((int) *bp < (int) BlackPawn) {
4811 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4813 if(message[0] == '+' || message[0] == '~') {
4814 sprintf(message, "%c%c%c+\n",
4815 PieceToChar((ChessSquare)(DEMOTED *bp)),
4818 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819 message[1] = BOARD_RGHT - 1 - j + '1';
4820 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4822 SendToProgram(message, cps);
4827 SendToProgram("c\n", cps);
4828 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4829 bp = &boards[moveNum][i][BOARD_LEFT];
4830 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4831 if (((int) *bp != (int) EmptySquare)
4832 && ((int) *bp >= (int) BlackPawn)) {
4833 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4835 if(message[0] == '+' || message[0] == '~') {
4836 sprintf(message, "%c%c%c+\n",
4837 PieceToChar((ChessSquare)(DEMOTED *bp)),
4840 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4841 message[1] = BOARD_RGHT - 1 - j + '1';
4842 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4844 SendToProgram(message, cps);
4849 SendToProgram(".\n", cps);
4851 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4855 IsPromotion(fromX, fromY, toX, toY)
4856 int fromX, fromY, toX, toY;
4858 /* [HGM] add Shogi promotions */
4859 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4862 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4863 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4864 /* [HGM] Note to self: line above also weeds out drops */
4865 piece = boards[currentMove][fromY][fromX];
4866 if(gameInfo.variant == VariantShogi) {
4867 promotionZoneSize = 3;
4868 highestPromotingPiece = (int)WhiteKing;
4869 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4870 and if in normal chess we then allow promotion to King, why not
4871 allow promotion of other piece in Shogi? */
4873 if((int)piece >= BlackPawn) {
4874 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4876 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4878 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4879 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4881 return ( (int)piece <= highestPromotingPiece );
4885 InPalace(row, column)
4887 { /* [HGM] for Xiangqi */
4888 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4889 column < (BOARD_WIDTH + 4)/2 &&
4890 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4895 PieceForSquare (x, y)
4899 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4902 return boards[currentMove][y][x];
4906 OKToStartUserMove(x, y)
4909 ChessSquare from_piece;
4912 if (matchMode) return FALSE;
4913 if (gameMode == EditPosition) return TRUE;
4915 if (x >= 0 && y >= 0)
4916 from_piece = boards[currentMove][y][x];
4918 from_piece = EmptySquare;
4920 if (from_piece == EmptySquare) return FALSE;
4922 white_piece = (int)from_piece >= (int)WhitePawn &&
4923 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4926 case PlayFromGameFile:
4928 case TwoMachinesPlay:
4936 case MachinePlaysWhite:
4937 case IcsPlayingBlack:
4938 if (appData.zippyPlay) return FALSE;
4940 DisplayMoveError(_("You are playing Black"));
4945 case MachinePlaysBlack:
4946 case IcsPlayingWhite:
4947 if (appData.zippyPlay) return FALSE;
4949 DisplayMoveError(_("You are playing White"));
4955 if (!white_piece && WhiteOnMove(currentMove)) {
4956 DisplayMoveError(_("It is White's turn"));
4959 if (white_piece && !WhiteOnMove(currentMove)) {
4960 DisplayMoveError(_("It is Black's turn"));
4963 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4964 /* Editing correspondence game history */
4965 /* Could disallow this or prompt for confirmation */
4968 if (currentMove < forwardMostMove) {
4969 /* Discarding moves */
4970 /* Could prompt for confirmation here,
4971 but I don't think that's such a good idea */
4972 forwardMostMove = currentMove;
4976 case BeginningOfGame:
4977 if (appData.icsActive) return FALSE;
4978 if (!appData.noChessProgram) {
4980 DisplayMoveError(_("You are playing White"));
4987 if (!white_piece && WhiteOnMove(currentMove)) {
4988 DisplayMoveError(_("It is White's turn"));
4991 if (white_piece && !WhiteOnMove(currentMove)) {
4992 DisplayMoveError(_("It is Black's turn"));
5001 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5002 && gameMode != AnalyzeFile && gameMode != Training) {
5003 DisplayMoveError(_("Displayed position is not current"));
5009 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5010 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5011 int lastLoadGameUseList = FALSE;
5012 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5013 ChessMove lastLoadGameStart = (ChessMove) 0;
5017 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5018 int fromX, fromY, toX, toY;
5022 ChessSquare pdown, pup;
5024 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5025 if ((fromX == toX) && (fromY == toY)) {
5026 return ImpossibleMove;
5029 /* [HGM] suppress all moves into holdings area and guard band */
5030 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5031 return ImpossibleMove;
5033 /* [HGM] <sameColor> moved to here from winboard.c */
5034 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5035 pdown = boards[currentMove][fromY][fromX];
5036 pup = boards[currentMove][toY][toX];
5037 if ( gameMode != EditPosition &&
5038 (WhitePawn <= pdown && pdown < BlackPawn &&
5039 WhitePawn <= pup && pup < BlackPawn ||
5040 BlackPawn <= pdown && pdown < EmptySquare &&
5041 BlackPawn <= pup && pup < EmptySquare
5042 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5043 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5044 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5046 return ImpossibleMove;
5048 /* Check if the user is playing in turn. This is complicated because we
5049 let the user "pick up" a piece before it is his turn. So the piece he
5050 tried to pick up may have been captured by the time he puts it down!
5051 Therefore we use the color the user is supposed to be playing in this
5052 test, not the color of the piece that is currently on the starting
5053 square---except in EditGame mode, where the user is playing both
5054 sides; fortunately there the capture race can't happen. (It can
5055 now happen in IcsExamining mode, but that's just too bad. The user
5056 will get a somewhat confusing message in that case.)
5060 case PlayFromGameFile:
5062 case TwoMachinesPlay:
5066 /* We switched into a game mode where moves are not accepted,
5067 perhaps while the mouse button was down. */
5068 return ImpossibleMove;
5070 case MachinePlaysWhite:
5071 /* User is moving for Black */
5072 if (WhiteOnMove(currentMove)) {
5073 DisplayMoveError(_("It is White's turn"));
5074 return ImpossibleMove;
5078 case MachinePlaysBlack:
5079 /* User is moving for White */
5080 if (!WhiteOnMove(currentMove)) {
5081 DisplayMoveError(_("It is Black's turn"));
5082 return ImpossibleMove;
5088 case BeginningOfGame:
5091 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5092 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5093 /* User is moving for Black */
5094 if (WhiteOnMove(currentMove)) {
5095 DisplayMoveError(_("It is White's turn"));
5096 return ImpossibleMove;
5099 /* User is moving for White */
5100 if (!WhiteOnMove(currentMove)) {
5101 DisplayMoveError(_("It is Black's turn"));
5102 return ImpossibleMove;
5107 case IcsPlayingBlack:
5108 /* User is moving for Black */
5109 if (WhiteOnMove(currentMove)) {
5110 if (!appData.premove) {
5111 DisplayMoveError(_("It is White's turn"));
5112 } else if (toX >= 0 && toY >= 0) {
5115 premoveFromX = fromX;
5116 premoveFromY = fromY;
5117 premovePromoChar = promoChar;
5119 if (appData.debugMode)
5120 fprintf(debugFP, "Got premove: fromX %d,"
5121 "fromY %d, toX %d, toY %d\n",
5122 fromX, fromY, toX, toY);
5124 return ImpossibleMove;
5128 case IcsPlayingWhite:
5129 /* User is moving for White */
5130 if (!WhiteOnMove(currentMove)) {
5131 if (!appData.premove) {
5132 DisplayMoveError(_("It is Black's turn"));
5133 } else if (toX >= 0 && toY >= 0) {
5136 premoveFromX = fromX;
5137 premoveFromY = fromY;
5138 premovePromoChar = promoChar;
5140 if (appData.debugMode)
5141 fprintf(debugFP, "Got premove: fromX %d,"
5142 "fromY %d, toX %d, toY %d\n",
5143 fromX, fromY, toX, toY);
5145 return ImpossibleMove;
5153 /* EditPosition, empty square, or different color piece;
5154 click-click move is possible */
5155 if (toX == -2 || toY == -2) {
5156 boards[0][fromY][fromX] = EmptySquare;
5157 return AmbiguousMove;
5158 } else if (toX >= 0 && toY >= 0) {
5159 boards[0][toY][toX] = boards[0][fromY][fromX];
5160 boards[0][fromY][fromX] = EmptySquare;
5161 return AmbiguousMove;
5163 return ImpossibleMove;
5166 /* [HGM] If move started in holdings, it means a drop */
5167 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5168 if( pup != EmptySquare ) return ImpossibleMove;
5169 if(appData.testLegality) {
5170 /* it would be more logical if LegalityTest() also figured out
5171 * which drops are legal. For now we forbid pawns on back rank.
5172 * Shogi is on its own here...
5174 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5175 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5176 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5178 return WhiteDrop; /* Not needed to specify white or black yet */
5181 userOfferedDraw = FALSE;
5183 /* [HGM] always test for legality, to get promotion info */
5184 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5185 epStatus[currentMove], castlingRights[currentMove],
5186 fromY, fromX, toY, toX, promoChar);
5188 /* [HGM] but possibly ignore an IllegalMove result */
5189 if (appData.testLegality) {
5190 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5191 DisplayMoveError(_("Illegal move"));
5192 return ImpossibleMove;
5195 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5197 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5198 function is made into one that returns an OK move type if FinishMove
5199 should be called. This to give the calling driver routine the
5200 opportunity to finish the userMove input with a promotion popup,
5201 without bothering the user with this for invalid or illegal moves */
5203 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5206 /* Common tail of UserMoveEvent and DropMenuEvent */
5208 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5210 int fromX, fromY, toX, toY;
5211 /*char*/int promoChar;
5214 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5215 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5216 // [HGM] superchess: suppress promotions to non-available piece
5217 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5218 if(WhiteOnMove(currentMove)) {
5219 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5221 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5225 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5226 move type in caller when we know the move is a legal promotion */
5227 if(moveType == NormalMove && promoChar)
5228 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5229 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5230 /* [HGM] convert drag-and-drop piece drops to standard form */
5231 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5232 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5233 fromX = boards[currentMove][fromY][fromX];
5237 /* [HGM] <popupFix> The following if has been moved here from
5238 UserMoveEvent(). Because it seemed to belon here (why not allow
5239 piece drops in training games?), and because it can only be
5240 performed after it is known to what we promote. */
5241 if (gameMode == Training) {
5242 /* compare the move played on the board to the next move in the
5243 * game. If they match, display the move and the opponent's response.
5244 * If they don't match, display an error message.
5247 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5248 CopyBoard(testBoard, boards[currentMove]);
5249 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5251 if (CompareBoards(testBoard, boards[currentMove+1])) {
5252 ForwardInner(currentMove+1);
5254 /* Autoplay the opponent's response.
5255 * if appData.animate was TRUE when Training mode was entered,
5256 * the response will be animated.
5258 saveAnimate = appData.animate;
5259 appData.animate = animateTraining;
5260 ForwardInner(currentMove+1);
5261 appData.animate = saveAnimate;
5263 /* check for the end of the game */
5264 if (currentMove >= forwardMostMove) {
5265 gameMode = PlayFromGameFile;
5267 SetTrainingModeOff();
5268 DisplayInformation(_("End of game"));
5271 DisplayError(_("Incorrect move"), 0);
5276 /* Ok, now we know that the move is good, so we can kill
5277 the previous line in Analysis Mode */
5278 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5279 forwardMostMove = currentMove;
5282 /* If we need the chess program but it's dead, restart it */
5283 ResurrectChessProgram();
5285 /* A user move restarts a paused game*/
5289 thinkOutput[0] = NULLCHAR;
5291 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5293 if (gameMode == BeginningOfGame) {
5294 if (appData.noChessProgram) {
5295 gameMode = EditGame;
5299 gameMode = MachinePlaysBlack;
5302 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5304 if (first.sendName) {
5305 sprintf(buf, "name %s\n", gameInfo.white);
5306 SendToProgram(buf, &first);
5312 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5313 /* Relay move to ICS or chess engine */
5314 if (appData.icsActive) {
5315 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5316 gameMode == IcsExamining) {
5317 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5321 if (first.sendTime && (gameMode == BeginningOfGame ||
5322 gameMode == MachinePlaysWhite ||
5323 gameMode == MachinePlaysBlack)) {
5324 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5326 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5327 // [HGM] book: if program might be playing, let it use book
5328 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5329 first.maybeThinking = TRUE;
5330 } else SendMoveToProgram(forwardMostMove-1, &first);
5331 if (currentMove == cmailOldMove + 1) {
5332 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5336 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5340 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5341 EP_UNKNOWN, castlingRights[currentMove]) ) {
5347 if (WhiteOnMove(currentMove)) {
5348 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5350 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5354 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5359 case MachinePlaysBlack:
5360 case MachinePlaysWhite:
5361 /* disable certain menu options while machine is thinking */
5362 SetMachineThinkingEnables();
5369 if(bookHit) { // [HGM] book: simulate book reply
5370 static char bookMove[MSG_SIZ]; // a bit generous?
5372 programStats.nodes = programStats.depth = programStats.time =
5373 programStats.score = programStats.got_only_move = 0;
5374 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5376 strcpy(bookMove, "move ");
5377 strcat(bookMove, bookHit);
5378 HandleMachineMove(bookMove, &first);
5384 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5385 int fromX, fromY, toX, toY;
5388 /* [HGM] This routine was added to allow calling of its two logical
5389 parts from other modules in the old way. Before, UserMoveEvent()
5390 automatically called FinishMove() if the move was OK, and returned
5391 otherwise. I separated the two, in order to make it possible to
5392 slip a promotion popup in between. But that it always needs two
5393 calls, to the first part, (now called UserMoveTest() ), and to
5394 FinishMove if the first part succeeded. Calls that do not need
5395 to do anything in between, can call this routine the old way.
5397 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5398 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5399 if(moveType != ImpossibleMove)
5400 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5403 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5405 // char * hint = lastHint;
5406 FrontEndProgramStats stats;
5408 stats.which = cps == &first ? 0 : 1;
5409 stats.depth = cpstats->depth;
5410 stats.nodes = cpstats->nodes;
5411 stats.score = cpstats->score;
5412 stats.time = cpstats->time;
5413 stats.pv = cpstats->movelist;
5414 stats.hint = lastHint;
5415 stats.an_move_index = 0;
5416 stats.an_move_count = 0;
5418 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5419 stats.hint = cpstats->move_name;
5420 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5421 stats.an_move_count = cpstats->nr_moves;
5424 SetProgramStats( &stats );
5427 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5428 { // [HGM] book: this routine intercepts moves to simulate book replies
5429 char *bookHit = NULL;
5431 //first determine if the incoming move brings opponent into his book
5432 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5433 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5434 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5435 if(bookHit != NULL && !cps->bookSuspend) {
5436 // make sure opponent is not going to reply after receiving move to book position
5437 SendToProgram("force\n", cps);
5438 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5440 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5441 // now arrange restart after book miss
5443 // after a book hit we never send 'go', and the code after the call to this routine
5444 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5446 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5447 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5448 SendToProgram(buf, cps);
5449 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5450 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5451 SendToProgram("go\n", cps);
5452 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5453 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5454 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5455 SendToProgram("go\n", cps);
5456 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5458 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5462 ChessProgramState *savedState;
5463 void DeferredBookMove(void)
5465 if(savedState->lastPing != savedState->lastPong)
5466 ScheduleDelayedEvent(DeferredBookMove, 10);
5468 HandleMachineMove(savedMessage, savedState);
5472 HandleMachineMove(message, cps)
5474 ChessProgramState *cps;
5476 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5477 char realname[MSG_SIZ];
5478 int fromX, fromY, toX, toY;
5485 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5487 * Kludge to ignore BEL characters
5489 while (*message == '\007') message++;
5492 * [HGM] engine debug message: ignore lines starting with '#' character
5494 if(cps->debug && *message == '#') return;
5497 * Look for book output
5499 if (cps == &first && bookRequested) {
5500 if (message[0] == '\t' || message[0] == ' ') {
5501 /* Part of the book output is here; append it */
5502 strcat(bookOutput, message);
5503 strcat(bookOutput, " \n");
5505 } else if (bookOutput[0] != NULLCHAR) {
5506 /* All of book output has arrived; display it */
5507 char *p = bookOutput;
5508 while (*p != NULLCHAR) {
5509 if (*p == '\t') *p = ' ';
5512 DisplayInformation(bookOutput);
5513 bookRequested = FALSE;
5514 /* Fall through to parse the current output */
5519 * Look for machine move.
5521 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5522 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5524 /* This method is only useful on engines that support ping */
5525 if (cps->lastPing != cps->lastPong) {
5526 if (gameMode == BeginningOfGame) {
5527 /* Extra move from before last new; ignore */
5528 if (appData.debugMode) {
5529 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5532 if (appData.debugMode) {
5533 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5534 cps->which, gameMode);
5537 SendToProgram("undo\n", cps);
5543 case BeginningOfGame:
5544 /* Extra move from before last reset; ignore */
5545 if (appData.debugMode) {
5546 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5553 /* Extra move after we tried to stop. The mode test is
5554 not a reliable way of detecting this problem, but it's
5555 the best we can do on engines that don't support ping.
5557 if (appData.debugMode) {
5558 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5559 cps->which, gameMode);
5561 SendToProgram("undo\n", cps);
5564 case MachinePlaysWhite:
5565 case IcsPlayingWhite:
5566 machineWhite = TRUE;
5569 case MachinePlaysBlack:
5570 case IcsPlayingBlack:
5571 machineWhite = FALSE;
5574 case TwoMachinesPlay:
5575 machineWhite = (cps->twoMachinesColor[0] == 'w');
5578 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5579 if (appData.debugMode) {
5581 "Ignoring move out of turn by %s, gameMode %d"
5582 ", forwardMost %d\n",
5583 cps->which, gameMode, forwardMostMove);
5588 if (appData.debugMode) { int f = forwardMostMove;
5589 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5590 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5592 if(cps->alphaRank) AlphaRank(machineMove, 4);
5593 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5594 &fromX, &fromY, &toX, &toY, &promoChar)) {
5595 /* Machine move could not be parsed; ignore it. */
5596 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5597 machineMove, cps->which);
5598 DisplayError(buf1, 0);
5599 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5600 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5601 if (gameMode == TwoMachinesPlay) {
5602 GameEnds(machineWhite ? BlackWins : WhiteWins,
5608 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5609 /* So we have to redo legality test with true e.p. status here, */
5610 /* to make sure an illegal e.p. capture does not slip through, */
5611 /* to cause a forfeit on a justified illegal-move complaint */
5612 /* of the opponent. */
5613 if( gameMode==TwoMachinesPlay && appData.testLegality
5614 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5617 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5618 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5619 fromY, fromX, toY, toX, promoChar);
5620 if (appData.debugMode) {
5622 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5623 castlingRights[forwardMostMove][i], castlingRank[i]);
5624 fprintf(debugFP, "castling rights\n");
5626 if(moveType == IllegalMove) {
5627 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5628 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5629 GameEnds(machineWhite ? BlackWins : WhiteWins,
5632 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5633 /* [HGM] Kludge to handle engines that send FRC-style castling
5634 when they shouldn't (like TSCP-Gothic) */
5636 case WhiteASideCastleFR:
5637 case BlackASideCastleFR:
5639 currentMoveString[2]++;
5641 case WhiteHSideCastleFR:
5642 case BlackHSideCastleFR:
5644 currentMoveString[2]--;
5646 default: ; // nothing to do, but suppresses warning of pedantic compilers
5649 hintRequested = FALSE;
5650 lastHint[0] = NULLCHAR;
5651 bookRequested = FALSE;
5652 /* Program may be pondering now */
5653 cps->maybeThinking = TRUE;
5654 if (cps->sendTime == 2) cps->sendTime = 1;
5655 if (cps->offeredDraw) cps->offeredDraw--;
5658 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5660 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5662 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5663 char buf[3*MSG_SIZ];
5665 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5666 programStats.score / 100.,
5668 programStats.time / 100.,
5669 u64ToDouble(programStats.nodes),
5670 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5671 programStats.movelist);
5676 /* currentMoveString is set as a side-effect of ParseOneMove */
5677 strcpy(machineMove, currentMoveString);
5678 strcat(machineMove, "\n");
5679 strcpy(moveList[forwardMostMove], machineMove);
5681 /* [AS] Save move info and clear stats for next move */
5682 pvInfoList[ forwardMostMove ].score = programStats.score;
5683 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5684 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5685 ClearProgramStats();
5686 thinkOutput[0] = NULLCHAR;
5687 hiddenThinkOutputState = 0;
5689 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5691 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5692 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5695 while( count < adjudicateLossPlies ) {
5696 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5699 score = -score; /* Flip score for winning side */
5702 if( score > adjudicateLossThreshold ) {
5709 if( count >= adjudicateLossPlies ) {
5710 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5712 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5713 "Xboard adjudication",
5720 if( gameMode == TwoMachinesPlay ) {
5721 // [HGM] some adjudications useful with buggy engines
5722 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5723 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5726 if( appData.testLegality )
5727 { /* [HGM] Some more adjudications for obstinate engines */
5728 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5729 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5730 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5731 static int moveCount = 6;
5733 char *reason = NULL;
5735 /* Count what is on board. */
5736 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5737 { ChessSquare p = boards[forwardMostMove][i][j];
5741 { /* count B,N,R and other of each side */
5744 NrK++; break; // [HGM] atomic: count Kings
5748 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5749 bishopsColor |= 1 << ((i^j)&1);
5754 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5755 bishopsColor |= 1 << ((i^j)&1);
5770 PawnAdvance += m; NrPawns++;
5772 NrPieces += (p != EmptySquare);
5773 NrW += ((int)p < (int)BlackPawn);
5774 if(gameInfo.variant == VariantXiangqi &&
5775 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5776 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5777 NrW -= ((int)p < (int)BlackPawn);
5781 /* Some material-based adjudications that have to be made before stalemate test */
5782 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5783 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5784 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5785 if(appData.checkMates) {
5786 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5787 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5788 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5789 "Xboard adjudication: King destroyed", GE_XBOARD );
5794 /* Bare King in Shatranj (loses) or Losers (wins) */
5795 if( NrW == 1 || NrPieces - NrW == 1) {
5796 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5797 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5798 if(appData.checkMates) {
5799 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5800 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5801 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5802 "Xboard adjudication: Bare king", GE_XBOARD );
5806 if( gameInfo.variant == VariantShatranj && --bare < 0)
5808 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5809 if(appData.checkMates) {
5810 /* but only adjudicate if adjudication enabled */
5811 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5812 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5813 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5814 "Xboard adjudication: Bare king", GE_XBOARD );
5821 // don't wait for engine to announce game end if we can judge ourselves
5822 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5823 castlingRights[forwardMostMove]) ) {
5825 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5826 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5827 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5828 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5831 reason = "Xboard adjudication: 3rd check";
5832 epStatus[forwardMostMove] = EP_CHECKMATE;
5842 reason = "Xboard adjudication: Stalemate";
5843 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5844 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5845 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5846 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5847 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5848 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5849 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5850 EP_CHECKMATE : EP_WINS);
5851 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5852 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5856 reason = "Xboard adjudication: Checkmate";
5857 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5861 switch(i = epStatus[forwardMostMove]) {
5863 result = GameIsDrawn; break;
5865 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5867 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5869 result = (ChessMove) 0;
5871 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5872 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5873 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5874 GameEnds( result, reason, GE_XBOARD );
5878 /* Next absolutely insufficient mating material. */
5879 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5880 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5881 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5882 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5883 { /* KBK, KNK, KK of KBKB with like Bishops */
5885 /* always flag draws, for judging claims */
5886 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5888 if(appData.materialDraws) {
5889 /* but only adjudicate them if adjudication enabled */
5890 SendToProgram("force\n", cps->other); // suppress reply
5891 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5892 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5898 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5900 ( NrWR == 1 && NrBR == 1 /* KRKR */
5901 || NrWQ==1 && NrBQ==1 /* KQKQ */
5902 || NrWN==2 || NrBN==2 /* KNNK */
5903 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5905 if(--moveCount < 0 && appData.trivialDraws)
5906 { /* if the first 3 moves do not show a tactical win, declare draw */
5907 SendToProgram("force\n", cps->other); // suppress reply
5908 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5909 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5910 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5913 } else moveCount = 6;
5917 if (appData.debugMode) { int i;
5918 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5919 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5920 appData.drawRepeats);
5921 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5922 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5926 /* Check for rep-draws */
5928 for(k = forwardMostMove-2;
5929 k>=backwardMostMove && k>=forwardMostMove-100 &&
5930 epStatus[k] < EP_UNKNOWN &&
5931 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5935 if (appData.debugMode) {
5936 fprintf(debugFP, " loop\n");
5939 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5941 if (appData.debugMode) {
5942 fprintf(debugFP, "match\n");
5945 /* compare castling rights */
5946 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5947 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5948 rights++; /* King lost rights, while rook still had them */
5949 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5950 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5951 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5952 rights++; /* but at least one rook lost them */
5954 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5955 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5957 if( castlingRights[forwardMostMove][5] >= 0 ) {
5958 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5959 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5963 if (appData.debugMode) {
5964 for(i=0; i<nrCastlingRights; i++)
5965 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5968 if (appData.debugMode) {
5969 fprintf(debugFP, " %d %d\n", rights, k);
5972 if( rights == 0 && ++count > appData.drawRepeats-2
5973 && appData.drawRepeats > 1) {
5974 /* adjudicate after user-specified nr of repeats */
5975 SendToProgram("force\n", cps->other); // suppress reply
5976 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5977 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5978 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5979 // [HGM] xiangqi: check for forbidden perpetuals
5980 int m, ourPerpetual = 1, hisPerpetual = 1;
5981 for(m=forwardMostMove; m>k; m-=2) {
5982 if(MateTest(boards[m], PosFlags(m),
5983 EP_NONE, castlingRights[m]) != MT_CHECK)
5984 ourPerpetual = 0; // the current mover did not always check
5985 if(MateTest(boards[m-1], PosFlags(m-1),
5986 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5987 hisPerpetual = 0; // the opponent did not always check
5989 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5990 ourPerpetual, hisPerpetual);
5991 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5992 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5993 "Xboard adjudication: perpetual checking", GE_XBOARD );
5996 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5997 break; // (or we would have caught him before). Abort repetition-checking loop.
5998 // Now check for perpetual chases
5999 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6000 hisPerpetual = PerpetualChase(k, forwardMostMove);
6001 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6002 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6003 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6004 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6007 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6008 break; // Abort repetition-checking loop.
6010 // if neither of us is checking or chasing all the time, or both are, it is draw
6012 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6015 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6016 epStatus[forwardMostMove] = EP_REP_DRAW;
6020 /* Now we test for 50-move draws. Determine ply count */
6021 count = forwardMostMove;
6022 /* look for last irreversble move */
6023 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6025 /* if we hit starting position, add initial plies */
6026 if( count == backwardMostMove )
6027 count -= initialRulePlies;
6028 count = forwardMostMove - count;
6030 epStatus[forwardMostMove] = EP_RULE_DRAW;
6031 /* this is used to judge if draw claims are legal */
6032 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6033 SendToProgram("force\n", cps->other); // suppress reply
6034 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6035 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6036 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6040 /* if draw offer is pending, treat it as a draw claim
6041 * when draw condition present, to allow engines a way to
6042 * claim draws before making their move to avoid a race
6043 * condition occurring after their move
6045 if( cps->other->offeredDraw || cps->offeredDraw ) {
6047 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6048 p = "Draw claim: 50-move rule";
6049 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6050 p = "Draw claim: 3-fold repetition";
6051 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6052 p = "Draw claim: insufficient mating material";
6054 SendToProgram("force\n", cps->other); // suppress reply
6055 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6056 GameEnds( GameIsDrawn, p, GE_XBOARD );
6057 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6063 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6064 SendToProgram("force\n", cps->other); // suppress reply
6065 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6066 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6068 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6075 if (gameMode == TwoMachinesPlay) {
6076 /* [HGM] relaying draw offers moved to after reception of move */
6077 /* and interpreting offer as claim if it brings draw condition */
6078 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6079 SendToProgram("draw\n", cps->other);
6081 if (cps->other->sendTime) {
6082 SendTimeRemaining(cps->other,
6083 cps->other->twoMachinesColor[0] == 'w');
6085 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6086 if (firstMove && !bookHit) {
6088 if (cps->other->useColors) {
6089 SendToProgram(cps->other->twoMachinesColor, cps->other);
6091 SendToProgram("go\n", cps->other);
6093 cps->other->maybeThinking = TRUE;
6096 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6098 if (!pausing && appData.ringBellAfterMoves) {
6103 * Reenable menu items that were disabled while
6104 * machine was thinking
6106 if (gameMode != TwoMachinesPlay)
6107 SetUserThinkingEnables();
6109 // [HGM] book: after book hit opponent has received move and is now in force mode
6110 // force the book reply into it, and then fake that it outputted this move by jumping
6111 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6113 static char bookMove[MSG_SIZ]; // a bit generous?
6115 strcpy(bookMove, "move ");
6116 strcat(bookMove, bookHit);
6119 programStats.nodes = programStats.depth = programStats.time =
6120 programStats.score = programStats.got_only_move = 0;
6121 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6123 if(cps->lastPing != cps->lastPong) {
6124 savedMessage = message; // args for deferred call
6126 ScheduleDelayedEvent(DeferredBookMove, 10);
6135 /* Set special modes for chess engines. Later something general
6136 * could be added here; for now there is just one kludge feature,
6137 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6138 * when "xboard" is given as an interactive command.
6140 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6141 cps->useSigint = FALSE;
6142 cps->useSigterm = FALSE;
6145 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6146 * want this, I was asked to put it in, and obliged.
6148 if (!strncmp(message, "setboard ", 9)) {
6149 Board initial_position; int i;
6151 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6153 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6154 DisplayError(_("Bad FEN received from engine"), 0);
6157 Reset(FALSE, FALSE);
6158 CopyBoard(boards[0], initial_position);
6159 initialRulePlies = FENrulePlies;
6160 epStatus[0] = FENepStatus;
6161 for( i=0; i<nrCastlingRights; i++ )
6162 castlingRights[0][i] = FENcastlingRights[i];
6163 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6164 else gameMode = MachinePlaysBlack;
6165 DrawPosition(FALSE, boards[currentMove]);
6171 * Look for communication commands
6173 if (!strncmp(message, "telluser ", 9)) {
6174 DisplayNote(message + 9);
6177 if (!strncmp(message, "tellusererror ", 14)) {
6178 DisplayError(message + 14, 0);
6181 if (!strncmp(message, "tellopponent ", 13)) {
6182 if (appData.icsActive) {
6184 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6188 DisplayNote(message + 13);
6192 if (!strncmp(message, "tellothers ", 11)) {
6193 if (appData.icsActive) {
6195 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6201 if (!strncmp(message, "tellall ", 8)) {
6202 if (appData.icsActive) {
6204 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6208 DisplayNote(message + 8);
6212 if (strncmp(message, "warning", 7) == 0) {
6213 /* Undocumented feature, use tellusererror in new code */
6214 DisplayError(message, 0);
6217 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6218 strcpy(realname, cps->tidy);
6219 strcat(realname, " query");
6220 AskQuestion(realname, buf2, buf1, cps->pr);
6223 /* Commands from the engine directly to ICS. We don't allow these to be
6224 * sent until we are logged on. Crafty kibitzes have been known to
6225 * interfere with the login process.
6228 if (!strncmp(message, "tellics ", 8)) {
6229 SendToICS(message + 8);
6233 if (!strncmp(message, "tellicsnoalias ", 15)) {
6234 SendToICS(ics_prefix);
6235 SendToICS(message + 15);
6239 /* The following are for backward compatibility only */
6240 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6241 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6242 SendToICS(ics_prefix);
6248 if (strncmp(message, "feature ", 8) == 0) {
6249 ParseFeatures(message+8, cps);
6251 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6255 * If the move is illegal, cancel it and redraw the board.
6256 * Also deal with other error cases. Matching is rather loose
6257 * here to accommodate engines written before the spec.
6259 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6260 strncmp(message, "Error", 5) == 0) {
6261 if (StrStr(message, "name") ||
6262 StrStr(message, "rating") || StrStr(message, "?") ||
6263 StrStr(message, "result") || StrStr(message, "board") ||
6264 StrStr(message, "bk") || StrStr(message, "computer") ||
6265 StrStr(message, "variant") || StrStr(message, "hint") ||
6266 StrStr(message, "random") || StrStr(message, "depth") ||
6267 StrStr(message, "accepted")) {
6270 if (StrStr(message, "protover")) {
6271 /* Program is responding to input, so it's apparently done
6272 initializing, and this error message indicates it is
6273 protocol version 1. So we don't need to wait any longer
6274 for it to initialize and send feature commands. */
6275 FeatureDone(cps, 1);
6276 cps->protocolVersion = 1;
6279 cps->maybeThinking = FALSE;
6281 if (StrStr(message, "draw")) {
6282 /* Program doesn't have "draw" command */
6283 cps->sendDrawOffers = 0;
6286 if (cps->sendTime != 1 &&
6287 (StrStr(message, "time") || StrStr(message, "otim"))) {
6288 /* Program apparently doesn't have "time" or "otim" command */
6292 if (StrStr(message, "analyze")) {
6293 cps->analysisSupport = FALSE;
6294 cps->analyzing = FALSE;
6296 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6297 DisplayError(buf2, 0);
6300 if (StrStr(message, "(no matching move)st")) {
6301 /* Special kludge for GNU Chess 4 only */
6302 cps->stKludge = TRUE;
6303 SendTimeControl(cps, movesPerSession, timeControl,
6304 timeIncrement, appData.searchDepth,
6308 if (StrStr(message, "(no matching move)sd")) {
6309 /* Special kludge for GNU Chess 4 only */
6310 cps->sdKludge = TRUE;
6311 SendTimeControl(cps, movesPerSession, timeControl,
6312 timeIncrement, appData.searchDepth,
6316 if (!StrStr(message, "llegal")) {
6319 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6320 gameMode == IcsIdle) return;
6321 if (forwardMostMove <= backwardMostMove) return;
6323 /* Following removed: it caused a bug where a real illegal move
6324 message in analyze mored would be ignored. */
6325 if (cps == &first && programStats.ok_to_send == 0) {
6326 /* Bogus message from Crafty responding to "." This filtering
6327 can miss some of the bad messages, but fortunately the bug
6328 is fixed in current Crafty versions, so it doesn't matter. */
6332 if (pausing) PauseEvent();
6333 if (gameMode == PlayFromGameFile) {
6334 /* Stop reading this game file */
6335 gameMode = EditGame;
6338 currentMove = --forwardMostMove;
6339 DisplayMove(currentMove-1); /* before DisplayMoveError */
6341 DisplayBothClocks();
6342 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6343 parseList[currentMove], cps->which);
6344 DisplayMoveError(buf1);
6345 DrawPosition(FALSE, boards[currentMove]);
6347 /* [HGM] illegal-move claim should forfeit game when Xboard */
6348 /* only passes fully legal moves */
6349 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6350 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6351 "False illegal-move claim", GE_XBOARD );
6355 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6356 /* Program has a broken "time" command that
6357 outputs a string not ending in newline.
6363 * If chess program startup fails, exit with an error message.
6364 * Attempts to recover here are futile.
6366 if ((StrStr(message, "unknown host") != NULL)
6367 || (StrStr(message, "No remote directory") != NULL)
6368 || (StrStr(message, "not found") != NULL)
6369 || (StrStr(message, "No such file") != NULL)
6370 || (StrStr(message, "can't alloc") != NULL)
6371 || (StrStr(message, "Permission denied") != NULL)) {
6373 cps->maybeThinking = FALSE;
6374 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6375 cps->which, cps->program, cps->host, message);
6376 RemoveInputSource(cps->isr);
6377 DisplayFatalError(buf1, 0, 1);
6382 * Look for hint output
6384 if (sscanf(message, "Hint: %s", buf1) == 1) {
6385 if (cps == &first && hintRequested) {
6386 hintRequested = FALSE;
6387 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6388 &fromX, &fromY, &toX, &toY, &promoChar)) {
6389 (void) CoordsToAlgebraic(boards[forwardMostMove],
6390 PosFlags(forwardMostMove), EP_UNKNOWN,
6391 fromY, fromX, toY, toX, promoChar, buf1);
6392 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6393 DisplayInformation(buf2);
6395 /* Hint move could not be parsed!? */
6396 snprintf(buf2, sizeof(buf2),
6397 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6399 DisplayError(buf2, 0);
6402 strcpy(lastHint, buf1);
6408 * Ignore other messages if game is not in progress
6410 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6411 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6414 * look for win, lose, draw, or draw offer
6416 if (strncmp(message, "1-0", 3) == 0) {
6417 char *p, *q, *r = "";
6418 p = strchr(message, '{');
6426 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6428 } else if (strncmp(message, "0-1", 3) == 0) {
6429 char *p, *q, *r = "";
6430 p = strchr(message, '{');
6438 /* Kludge for Arasan 4.1 bug */
6439 if (strcmp(r, "Black resigns") == 0) {
6440 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6443 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6445 } else if (strncmp(message, "1/2", 3) == 0) {
6446 char *p, *q, *r = "";
6447 p = strchr(message, '{');
6456 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6459 } else if (strncmp(message, "White resign", 12) == 0) {
6460 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6462 } else if (strncmp(message, "Black resign", 12) == 0) {
6463 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6465 } else if (strncmp(message, "White matches", 13) == 0 ||
6466 strncmp(message, "Black matches", 13) == 0 ) {
6467 /* [HGM] ignore GNUShogi noises */
6469 } else if (strncmp(message, "White", 5) == 0 &&
6470 message[5] != '(' &&
6471 StrStr(message, "Black") == NULL) {
6472 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6474 } else if (strncmp(message, "Black", 5) == 0 &&
6475 message[5] != '(') {
6476 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6478 } else if (strcmp(message, "resign") == 0 ||
6479 strcmp(message, "computer resigns") == 0) {
6481 case MachinePlaysBlack:
6482 case IcsPlayingBlack:
6483 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6485 case MachinePlaysWhite:
6486 case IcsPlayingWhite:
6487 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6489 case TwoMachinesPlay:
6490 if (cps->twoMachinesColor[0] == 'w')
6491 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6493 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6500 } else if (strncmp(message, "opponent mates", 14) == 0) {
6502 case MachinePlaysBlack:
6503 case IcsPlayingBlack:
6504 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6506 case MachinePlaysWhite:
6507 case IcsPlayingWhite:
6508 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6510 case TwoMachinesPlay:
6511 if (cps->twoMachinesColor[0] == 'w')
6512 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6514 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6521 } else if (strncmp(message, "computer mates", 14) == 0) {
6523 case MachinePlaysBlack:
6524 case IcsPlayingBlack:
6525 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6527 case MachinePlaysWhite:
6528 case IcsPlayingWhite:
6529 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6531 case TwoMachinesPlay:
6532 if (cps->twoMachinesColor[0] == 'w')
6533 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6535 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6542 } else if (strncmp(message, "checkmate", 9) == 0) {
6543 if (WhiteOnMove(forwardMostMove)) {
6544 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6546 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6549 } else if (strstr(message, "Draw") != NULL ||
6550 strstr(message, "game is a draw") != NULL) {
6551 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6553 } else if (strstr(message, "offer") != NULL &&
6554 strstr(message, "draw") != NULL) {
6556 if (appData.zippyPlay && first.initDone) {
6557 /* Relay offer to ICS */
6558 SendToICS(ics_prefix);
6559 SendToICS("draw\n");
6562 cps->offeredDraw = 2; /* valid until this engine moves twice */
6563 if (gameMode == TwoMachinesPlay) {
6564 if (cps->other->offeredDraw) {
6565 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6566 /* [HGM] in two-machine mode we delay relaying draw offer */
6567 /* until after we also have move, to see if it is really claim */
6571 if (cps->other->sendDrawOffers) {
6572 SendToProgram("draw\n", cps->other);
6576 } else if (gameMode == MachinePlaysWhite ||
6577 gameMode == MachinePlaysBlack) {
6578 if (userOfferedDraw) {
6579 DisplayInformation(_("Machine accepts your draw offer"));
6580 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6582 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6589 * Look for thinking output
6591 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6592 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6594 int plylev, mvleft, mvtot, curscore, time;
6595 char mvname[MOVE_LEN];
6599 int prefixHint = FALSE;
6600 mvname[0] = NULLCHAR;
6603 case MachinePlaysBlack:
6604 case IcsPlayingBlack:
6605 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6607 case MachinePlaysWhite:
6608 case IcsPlayingWhite:
6609 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6614 case IcsObserving: /* [DM] icsEngineAnalyze */
6615 if (!appData.icsEngineAnalyze) ignore = TRUE;
6617 case TwoMachinesPlay:
6618 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6629 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6630 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6632 if (plyext != ' ' && plyext != '\t') {
6636 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6637 if( cps->scoreIsAbsolute &&
6638 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6640 curscore = -curscore;
6644 programStats.depth = plylev;
6645 programStats.nodes = nodes;
6646 programStats.time = time;
6647 programStats.score = curscore;
6648 programStats.got_only_move = 0;
6650 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6653 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6654 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6655 if(WhiteOnMove(forwardMostMove))
6656 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6657 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6660 /* Buffer overflow protection */
6661 if (buf1[0] != NULLCHAR) {
6662 if (strlen(buf1) >= sizeof(programStats.movelist)
6663 && appData.debugMode) {
6665 "PV is too long; using the first %d bytes.\n",
6666 sizeof(programStats.movelist) - 1);
6669 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6671 sprintf(programStats.movelist, " no PV\n");
6674 if (programStats.seen_stat) {
6675 programStats.ok_to_send = 1;
6678 if (strchr(programStats.movelist, '(') != NULL) {
6679 programStats.line_is_book = 1;
6680 programStats.nr_moves = 0;
6681 programStats.moves_left = 0;
6683 programStats.line_is_book = 0;
6686 SendProgramStatsToFrontend( cps, &programStats );
6689 [AS] Protect the thinkOutput buffer from overflow... this
6690 is only useful if buf1 hasn't overflowed first!
6692 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6694 (gameMode == TwoMachinesPlay ?
6695 ToUpper(cps->twoMachinesColor[0]) : ' '),
6696 ((double) curscore) / 100.0,
6697 prefixHint ? lastHint : "",
6698 prefixHint ? " " : "" );
6700 if( buf1[0] != NULLCHAR ) {
6701 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6703 if( strlen(buf1) > max_len ) {
6704 if( appData.debugMode) {
6705 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6707 buf1[max_len+1] = '\0';
6710 strcat( thinkOutput, buf1 );
6713 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6714 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6715 DisplayMove(currentMove - 1);
6720 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6721 /* crafty (9.25+) says "(only move) <move>"
6722 * if there is only 1 legal move
6724 sscanf(p, "(only move) %s", buf1);
6725 sprintf(thinkOutput, "%s (only move)", buf1);
6726 sprintf(programStats.movelist, "%s (only move)", buf1);
6727 programStats.depth = 1;
6728 programStats.nr_moves = 1;
6729 programStats.moves_left = 1;
6730 programStats.nodes = 1;
6731 programStats.time = 1;
6732 programStats.got_only_move = 1;
6734 /* Not really, but we also use this member to
6735 mean "line isn't going to change" (Crafty
6736 isn't searching, so stats won't change) */
6737 programStats.line_is_book = 1;
6739 SendProgramStatsToFrontend( cps, &programStats );
6741 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6742 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6743 DisplayMove(currentMove - 1);
6747 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6748 &time, &nodes, &plylev, &mvleft,
6749 &mvtot, mvname) >= 5) {
6750 /* The stat01: line is from Crafty (9.29+) in response
6751 to the "." command */
6752 programStats.seen_stat = 1;
6753 cps->maybeThinking = TRUE;
6755 if (programStats.got_only_move || !appData.periodicUpdates)
6758 programStats.depth = plylev;
6759 programStats.time = time;
6760 programStats.nodes = nodes;
6761 programStats.moves_left = mvleft;
6762 programStats.nr_moves = mvtot;
6763 strcpy(programStats.move_name, mvname);
6764 programStats.ok_to_send = 1;
6765 programStats.movelist[0] = '\0';
6767 SendProgramStatsToFrontend( cps, &programStats );
6772 } else if (strncmp(message,"++",2) == 0) {
6773 /* Crafty 9.29+ outputs this */
6774 programStats.got_fail = 2;
6777 } else if (strncmp(message,"--",2) == 0) {
6778 /* Crafty 9.29+ outputs this */
6779 programStats.got_fail = 1;
6782 } else if (thinkOutput[0] != NULLCHAR &&
6783 strncmp(message, " ", 4) == 0) {
6784 unsigned message_len;
6787 while (*p && *p == ' ') p++;
6789 message_len = strlen( p );
6791 /* [AS] Avoid buffer overflow */
6792 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6793 strcat(thinkOutput, " ");
6794 strcat(thinkOutput, p);
6797 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6798 strcat(programStats.movelist, " ");
6799 strcat(programStats.movelist, p);
6802 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6803 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6804 DisplayMove(currentMove - 1);
6813 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6814 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6816 ChessProgramStats cpstats;
6818 if (plyext != ' ' && plyext != '\t') {
6822 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6823 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6824 curscore = -curscore;
6827 cpstats.depth = plylev;
6828 cpstats.nodes = nodes;
6829 cpstats.time = time;
6830 cpstats.score = curscore;
6831 cpstats.got_only_move = 0;
6832 cpstats.movelist[0] = '\0';
6834 if (buf1[0] != NULLCHAR) {
6835 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6838 cpstats.ok_to_send = 0;
6839 cpstats.line_is_book = 0;
6840 cpstats.nr_moves = 0;
6841 cpstats.moves_left = 0;
6843 SendProgramStatsToFrontend( cps, &cpstats );
6850 /* Parse a game score from the character string "game", and
6851 record it as the history of the current game. The game
6852 score is NOT assumed to start from the standard position.
6853 The display is not updated in any way.
6856 ParseGameHistory(game)
6860 int fromX, fromY, toX, toY, boardIndex;
6865 if (appData.debugMode)
6866 fprintf(debugFP, "Parsing game history: %s\n", game);
6868 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6869 gameInfo.site = StrSave(appData.icsHost);
6870 gameInfo.date = PGNDate();
6871 gameInfo.round = StrSave("-");
6873 /* Parse out names of players */
6874 while (*game == ' ') game++;
6876 while (*game != ' ') *p++ = *game++;
6878 gameInfo.white = StrSave(buf);
6879 while (*game == ' ') game++;
6881 while (*game != ' ' && *game != '\n') *p++ = *game++;
6883 gameInfo.black = StrSave(buf);
6886 boardIndex = blackPlaysFirst ? 1 : 0;
6889 yyboardindex = boardIndex;
6890 moveType = (ChessMove) yylex();
6892 case IllegalMove: /* maybe suicide chess, etc. */
6893 if (appData.debugMode) {
6894 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6895 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6896 setbuf(debugFP, NULL);
6898 case WhitePromotionChancellor:
6899 case BlackPromotionChancellor:
6900 case WhitePromotionArchbishop:
6901 case BlackPromotionArchbishop:
6902 case WhitePromotionQueen:
6903 case BlackPromotionQueen:
6904 case WhitePromotionRook:
6905 case BlackPromotionRook:
6906 case WhitePromotionBishop:
6907 case BlackPromotionBishop:
6908 case WhitePromotionKnight:
6909 case BlackPromotionKnight:
6910 case WhitePromotionKing:
6911 case BlackPromotionKing:
6913 case WhiteCapturesEnPassant:
6914 case BlackCapturesEnPassant:
6915 case WhiteKingSideCastle:
6916 case WhiteQueenSideCastle:
6917 case BlackKingSideCastle:
6918 case BlackQueenSideCastle:
6919 case WhiteKingSideCastleWild:
6920 case WhiteQueenSideCastleWild:
6921 case BlackKingSideCastleWild:
6922 case BlackQueenSideCastleWild:
6924 case WhiteHSideCastleFR:
6925 case WhiteASideCastleFR:
6926 case BlackHSideCastleFR:
6927 case BlackASideCastleFR:
6929 fromX = currentMoveString[0] - AAA;
6930 fromY = currentMoveString[1] - ONE;
6931 toX = currentMoveString[2] - AAA;
6932 toY = currentMoveString[3] - ONE;
6933 promoChar = currentMoveString[4];
6937 fromX = moveType == WhiteDrop ?
6938 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6939 (int) CharToPiece(ToLower(currentMoveString[0]));
6941 toX = currentMoveString[2] - AAA;
6942 toY = currentMoveString[3] - ONE;
6943 promoChar = NULLCHAR;
6947 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6948 if (appData.debugMode) {
6949 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6950 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6951 setbuf(debugFP, NULL);
6953 DisplayError(buf, 0);
6955 case ImpossibleMove:
6957 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6958 if (appData.debugMode) {
6959 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6960 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6961 setbuf(debugFP, NULL);
6963 DisplayError(buf, 0);
6965 case (ChessMove) 0: /* end of file */
6966 if (boardIndex < backwardMostMove) {
6967 /* Oops, gap. How did that happen? */
6968 DisplayError(_("Gap in move list"), 0);
6971 backwardMostMove = blackPlaysFirst ? 1 : 0;
6972 if (boardIndex > forwardMostMove) {
6973 forwardMostMove = boardIndex;
6977 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6978 strcat(parseList[boardIndex-1], " ");
6979 strcat(parseList[boardIndex-1], yy_text);
6991 case GameUnfinished:
6992 if (gameMode == IcsExamining) {
6993 if (boardIndex < backwardMostMove) {
6994 /* Oops, gap. How did that happen? */
6997 backwardMostMove = blackPlaysFirst ? 1 : 0;
7000 gameInfo.result = moveType;
7001 p = strchr(yy_text, '{');
7002 if (p == NULL) p = strchr(yy_text, '(');
7005 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7007 q = strchr(p, *p == '{' ? '}' : ')');
7008 if (q != NULL) *q = NULLCHAR;
7011 gameInfo.resultDetails = StrSave(p);
7014 if (boardIndex >= forwardMostMove &&
7015 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7016 backwardMostMove = blackPlaysFirst ? 1 : 0;
7019 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7020 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7021 parseList[boardIndex]);
7022 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7023 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7024 /* currentMoveString is set as a side-effect of yylex */
7025 strcpy(moveList[boardIndex], currentMoveString);
7026 strcat(moveList[boardIndex], "\n");
7028 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7029 castlingRights[boardIndex], &epStatus[boardIndex]);
7030 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7031 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7037 if(gameInfo.variant != VariantShogi)
7038 strcat(parseList[boardIndex - 1], "+");
7042 strcat(parseList[boardIndex - 1], "#");
7049 /* Apply a move to the given board */
7051 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7052 int fromX, fromY, toX, toY;
7058 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7060 /* [HGM] compute & store e.p. status and castling rights for new position */
7061 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7064 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7068 if( board[toY][toX] != EmptySquare )
7071 if( board[fromY][fromX] == WhitePawn ) {
7072 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7075 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7076 gameInfo.variant != VariantBerolina || toX < fromX)
7077 *ep = toX | berolina;
7078 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7079 gameInfo.variant != VariantBerolina || toX > fromX)
7083 if( board[fromY][fromX] == BlackPawn ) {
7084 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7086 if( toY-fromY== -2) {
7087 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7088 gameInfo.variant != VariantBerolina || toX < fromX)
7089 *ep = toX | berolina;
7090 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7091 gameInfo.variant != VariantBerolina || toX > fromX)
7096 for(i=0; i<nrCastlingRights; i++) {
7097 if(castling[i] == fromX && castlingRank[i] == fromY ||
7098 castling[i] == toX && castlingRank[i] == toY
7099 ) castling[i] = -1; // revoke for moved or captured piece
7104 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7105 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7106 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7108 if (fromX == toX && fromY == toY) return;
7110 if (fromY == DROP_RANK) {
7112 piece = board[toY][toX] = (ChessSquare) fromX;
7114 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7115 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7116 if(gameInfo.variant == VariantKnightmate)
7117 king += (int) WhiteUnicorn - (int) WhiteKing;
7119 /* Code added by Tord: */
7120 /* FRC castling assumed when king captures friendly rook. */
7121 if (board[fromY][fromX] == WhiteKing &&
7122 board[toY][toX] == WhiteRook) {
7123 board[fromY][fromX] = EmptySquare;
7124 board[toY][toX] = EmptySquare;
7126 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7128 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7130 } else if (board[fromY][fromX] == BlackKing &&
7131 board[toY][toX] == BlackRook) {
7132 board[fromY][fromX] = EmptySquare;
7133 board[toY][toX] = EmptySquare;
7135 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7137 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7139 /* End of code added by Tord */
7141 } else if (board[fromY][fromX] == king
7142 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7143 && toY == fromY && toX > fromX+1) {
7144 board[fromY][fromX] = EmptySquare;
7145 board[toY][toX] = king;
7146 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7147 board[fromY][BOARD_RGHT-1] = EmptySquare;
7148 } else if (board[fromY][fromX] == king
7149 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7150 && toY == fromY && toX < fromX-1) {
7151 board[fromY][fromX] = EmptySquare;
7152 board[toY][toX] = king;
7153 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7154 board[fromY][BOARD_LEFT] = EmptySquare;
7155 } else if (board[fromY][fromX] == WhitePawn
7156 && toY == BOARD_HEIGHT-1
7157 && gameInfo.variant != VariantXiangqi
7159 /* white pawn promotion */
7160 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7161 if (board[toY][toX] == EmptySquare) {
7162 board[toY][toX] = WhiteQueen;
7164 if(gameInfo.variant==VariantBughouse ||
7165 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7166 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7167 board[fromY][fromX] = EmptySquare;
7168 } else if ((fromY == BOARD_HEIGHT-4)
7170 && gameInfo.variant != VariantXiangqi
7171 && gameInfo.variant != VariantBerolina
7172 && (board[fromY][fromX] == WhitePawn)
7173 && (board[toY][toX] == EmptySquare)) {
7174 board[fromY][fromX] = EmptySquare;
7175 board[toY][toX] = WhitePawn;
7176 captured = board[toY - 1][toX];
7177 board[toY - 1][toX] = EmptySquare;
7178 } else if ((fromY == BOARD_HEIGHT-4)
7180 && gameInfo.variant == VariantBerolina
7181 && (board[fromY][fromX] == WhitePawn)
7182 && (board[toY][toX] == EmptySquare)) {
7183 board[fromY][fromX] = EmptySquare;
7184 board[toY][toX] = WhitePawn;
7185 if(oldEP & EP_BEROLIN_A) {
7186 captured = board[fromY][fromX-1];
7187 board[fromY][fromX-1] = EmptySquare;
7188 }else{ captured = board[fromY][fromX+1];
7189 board[fromY][fromX+1] = EmptySquare;
7191 } else if (board[fromY][fromX] == king
7192 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7193 && toY == fromY && toX > fromX+1) {
7194 board[fromY][fromX] = EmptySquare;
7195 board[toY][toX] = king;
7196 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7197 board[fromY][BOARD_RGHT-1] = EmptySquare;
7198 } else if (board[fromY][fromX] == king
7199 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7200 && toY == fromY && toX < fromX-1) {
7201 board[fromY][fromX] = EmptySquare;
7202 board[toY][toX] = king;
7203 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7204 board[fromY][BOARD_LEFT] = EmptySquare;
7205 } else if (fromY == 7 && fromX == 3
7206 && board[fromY][fromX] == BlackKing
7207 && toY == 7 && toX == 5) {
7208 board[fromY][fromX] = EmptySquare;
7209 board[toY][toX] = BlackKing;
7210 board[fromY][7] = EmptySquare;
7211 board[toY][4] = BlackRook;
7212 } else if (fromY == 7 && fromX == 3
7213 && board[fromY][fromX] == BlackKing
7214 && toY == 7 && toX == 1) {
7215 board[fromY][fromX] = EmptySquare;
7216 board[toY][toX] = BlackKing;
7217 board[fromY][0] = EmptySquare;
7218 board[toY][2] = BlackRook;
7219 } else if (board[fromY][fromX] == BlackPawn
7221 && gameInfo.variant != VariantXiangqi
7223 /* black pawn promotion */
7224 board[0][toX] = CharToPiece(ToLower(promoChar));
7225 if (board[0][toX] == EmptySquare) {
7226 board[0][toX] = BlackQueen;
7228 if(gameInfo.variant==VariantBughouse ||
7229 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7230 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7231 board[fromY][fromX] = EmptySquare;
7232 } else if ((fromY == 3)
7234 && gameInfo.variant != VariantXiangqi
7235 && gameInfo.variant != VariantBerolina
7236 && (board[fromY][fromX] == BlackPawn)
7237 && (board[toY][toX] == EmptySquare)) {
7238 board[fromY][fromX] = EmptySquare;
7239 board[toY][toX] = BlackPawn;
7240 captured = board[toY + 1][toX];
7241 board[toY + 1][toX] = EmptySquare;
7242 } else if ((fromY == 3)
7244 && gameInfo.variant == VariantBerolina
7245 && (board[fromY][fromX] == BlackPawn)
7246 && (board[toY][toX] == EmptySquare)) {
7247 board[fromY][fromX] = EmptySquare;
7248 board[toY][toX] = BlackPawn;
7249 if(oldEP & EP_BEROLIN_A) {
7250 captured = board[fromY][fromX-1];
7251 board[fromY][fromX-1] = EmptySquare;
7252 }else{ captured = board[fromY][fromX+1];
7253 board[fromY][fromX+1] = EmptySquare;
7256 board[toY][toX] = board[fromY][fromX];
7257 board[fromY][fromX] = EmptySquare;
7260 /* [HGM] now we promote for Shogi, if needed */
7261 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7262 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7265 if (gameInfo.holdingsWidth != 0) {
7267 /* !!A lot more code needs to be written to support holdings */
7268 /* [HGM] OK, so I have written it. Holdings are stored in the */
7269 /* penultimate board files, so they are automaticlly stored */
7270 /* in the game history. */
7271 if (fromY == DROP_RANK) {
7272 /* Delete from holdings, by decreasing count */
7273 /* and erasing image if necessary */
7275 if(p < (int) BlackPawn) { /* white drop */
7276 p -= (int)WhitePawn;
7277 if(p >= gameInfo.holdingsSize) p = 0;
7278 if(--board[p][BOARD_WIDTH-2] == 0)
7279 board[p][BOARD_WIDTH-1] = EmptySquare;
7280 } else { /* black drop */
7281 p -= (int)BlackPawn;
7282 if(p >= gameInfo.holdingsSize) p = 0;
7283 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7284 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7287 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7288 && gameInfo.variant != VariantBughouse ) {
7289 /* [HGM] holdings: Add to holdings, if holdings exist */
7290 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7291 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7292 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7295 if (p >= (int) BlackPawn) {
7296 p -= (int)BlackPawn;
7297 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7298 /* in Shogi restore piece to its original first */
7299 captured = (ChessSquare) (DEMOTED captured);
7302 p = PieceToNumber((ChessSquare)p);
7303 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7304 board[p][BOARD_WIDTH-2]++;
7305 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7307 p -= (int)WhitePawn;
7308 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7309 captured = (ChessSquare) (DEMOTED captured);
7312 p = PieceToNumber((ChessSquare)p);
7313 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7314 board[BOARD_HEIGHT-1-p][1]++;
7315 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7319 } else if (gameInfo.variant == VariantAtomic) {
7320 if (captured != EmptySquare) {
7322 for (y = toY-1; y <= toY+1; y++) {
7323 for (x = toX-1; x <= toX+1; x++) {
7324 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7325 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7326 board[y][x] = EmptySquare;
7330 board[toY][toX] = EmptySquare;
7333 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7334 /* [HGM] Shogi promotions */
7335 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7338 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7339 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7340 // [HGM] superchess: take promotion piece out of holdings
7341 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7342 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7343 if(!--board[k][BOARD_WIDTH-2])
7344 board[k][BOARD_WIDTH-1] = EmptySquare;
7346 if(!--board[BOARD_HEIGHT-1-k][1])
7347 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7353 /* Updates forwardMostMove */
7355 MakeMove(fromX, fromY, toX, toY, promoChar)
7356 int fromX, fromY, toX, toY;
7359 // forwardMostMove++; // [HGM] bare: moved downstream
7361 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7362 int timeLeft; static int lastLoadFlag=0; int king, piece;
7363 piece = boards[forwardMostMove][fromY][fromX];
7364 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7365 if(gameInfo.variant == VariantKnightmate)
7366 king += (int) WhiteUnicorn - (int) WhiteKing;
7367 if(forwardMostMove == 0) {
7369 fprintf(serverMoves, "%s;", second.tidy);
7370 fprintf(serverMoves, "%s;", first.tidy);
7371 if(!blackPlaysFirst)
7372 fprintf(serverMoves, "%s;", second.tidy);
7373 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7374 lastLoadFlag = loadFlag;
7376 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7377 // print castling suffix
7378 if( toY == fromY && piece == king ) {
7380 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7382 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7385 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7386 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7387 boards[forwardMostMove][toY][toX] == EmptySquare
7389 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7391 if(promoChar != NULLCHAR)
7392 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7394 fprintf(serverMoves, "/%d/%d",
7395 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7396 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7397 else timeLeft = blackTimeRemaining/1000;
7398 fprintf(serverMoves, "/%d", timeLeft);
7400 fflush(serverMoves);
7403 if (forwardMostMove+1 >= MAX_MOVES) {
7404 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7409 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7410 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7411 if (commentList[forwardMostMove+1] != NULL) {
7412 free(commentList[forwardMostMove+1]);
7413 commentList[forwardMostMove+1] = NULL;
7415 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7416 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7417 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7418 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7419 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7420 gameInfo.result = GameUnfinished;
7421 if (gameInfo.resultDetails != NULL) {
7422 free(gameInfo.resultDetails);
7423 gameInfo.resultDetails = NULL;
7425 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7426 moveList[forwardMostMove - 1]);
7427 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7428 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7429 fromY, fromX, toY, toX, promoChar,
7430 parseList[forwardMostMove - 1]);
7431 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7432 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7433 castlingRights[forwardMostMove]) ) {
7439 if(gameInfo.variant != VariantShogi)
7440 strcat(parseList[forwardMostMove - 1], "+");
7444 strcat(parseList[forwardMostMove - 1], "#");
7447 if (appData.debugMode) {
7448 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7453 /* Updates currentMove if not pausing */
7455 ShowMove(fromX, fromY, toX, toY)
7457 int instant = (gameMode == PlayFromGameFile) ?
7458 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7459 if(appData.noGUI) return;
7460 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7462 if (forwardMostMove == currentMove + 1) {
7463 AnimateMove(boards[forwardMostMove - 1],
7464 fromX, fromY, toX, toY);
7466 if (appData.highlightLastMove) {
7467 SetHighlights(fromX, fromY, toX, toY);
7470 currentMove = forwardMostMove;
7473 if (instant) return;
7475 DisplayMove(currentMove - 1);
7476 DrawPosition(FALSE, boards[currentMove]);
7477 DisplayBothClocks();
7478 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7481 void SendEgtPath(ChessProgramState *cps)
7482 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7483 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7485 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7488 char c, *q = name+1, *r, *s;
7490 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7491 while(*p && *p != ',') *q++ = *p++;
7493 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7494 strcmp(name, ",nalimov:") == 0 ) {
7495 // take nalimov path from the menu-changeable option first, if it is defined
7496 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7497 SendToProgram(buf,cps); // send egtbpath command for nalimov
7499 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7500 (s = StrStr(appData.egtFormats, name)) != NULL) {
7501 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7502 s = r = StrStr(s, ":") + 1; // beginning of path info
7503 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7504 c = *r; *r = 0; // temporarily null-terminate path info
7505 *--q = 0; // strip of trailig ':' from name
7506 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7508 SendToProgram(buf,cps); // send egtbpath command for this format
7510 if(*p == ',') p++; // read away comma to position for next format name
7515 InitChessProgram(cps, setup)
7516 ChessProgramState *cps;
7517 int setup; /* [HGM] needed to setup FRC opening position */
7519 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7520 if (appData.noChessProgram) return;
7521 hintRequested = FALSE;
7522 bookRequested = FALSE;
7524 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7525 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7526 if(cps->memSize) { /* [HGM] memory */
7527 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7528 SendToProgram(buf, cps);
7530 SendEgtPath(cps); /* [HGM] EGT */
7531 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7532 sprintf(buf, "cores %d\n", appData.smpCores);
7533 SendToProgram(buf, cps);
7536 SendToProgram(cps->initString, cps);
7537 if (gameInfo.variant != VariantNormal &&
7538 gameInfo.variant != VariantLoadable
7539 /* [HGM] also send variant if board size non-standard */
7540 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7542 char *v = VariantName(gameInfo.variant);
7543 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7544 /* [HGM] in protocol 1 we have to assume all variants valid */
7545 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7546 DisplayFatalError(buf, 0, 1);
7550 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7551 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7552 if( gameInfo.variant == VariantXiangqi )
7553 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7554 if( gameInfo.variant == VariantShogi )
7555 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7556 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7557 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7558 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7559 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7560 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7561 if( gameInfo.variant == VariantCourier )
7562 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7563 if( gameInfo.variant == VariantSuper )
7564 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7565 if( gameInfo.variant == VariantGreat )
7566 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7569 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7570 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7571 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7572 if(StrStr(cps->variants, b) == NULL) {
7573 // specific sized variant not known, check if general sizing allowed
7574 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7575 if(StrStr(cps->variants, "boardsize") == NULL) {
7576 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7577 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7578 DisplayFatalError(buf, 0, 1);
7581 /* [HGM] here we really should compare with the maximum supported board size */
7584 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7585 sprintf(buf, "variant %s\n", b);
7586 SendToProgram(buf, cps);
7588 currentlyInitializedVariant = gameInfo.variant;
7590 /* [HGM] send opening position in FRC to first engine */
7592 SendToProgram("force\n", cps);
7594 /* engine is now in force mode! Set flag to wake it up after first move. */
7595 setboardSpoiledMachineBlack = 1;
7599 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7600 SendToProgram(buf, cps);
7602 cps->maybeThinking = FALSE;
7603 cps->offeredDraw = 0;
7604 if (!appData.icsActive) {
7605 SendTimeControl(cps, movesPerSession, timeControl,
7606 timeIncrement, appData.searchDepth,
7609 if (appData.showThinking
7610 // [HGM] thinking: four options require thinking output to be sent
7611 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7613 SendToProgram("post\n", cps);
7615 SendToProgram("hard\n", cps);
7616 if (!appData.ponderNextMove) {
7617 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7618 it without being sure what state we are in first. "hard"
7619 is not a toggle, so that one is OK.
7621 SendToProgram("easy\n", cps);
7624 sprintf(buf, "ping %d\n", ++cps->lastPing);
7625 SendToProgram(buf, cps);
7627 cps->initDone = TRUE;
7632 StartChessProgram(cps)
7633 ChessProgramState *cps;
7638 if (appData.noChessProgram) return;
7639 cps->initDone = FALSE;
7641 if (strcmp(cps->host, "localhost") == 0) {
7642 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7643 } else if (*appData.remoteShell == NULLCHAR) {
7644 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7646 if (*appData.remoteUser == NULLCHAR) {
7647 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7650 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7651 cps->host, appData.remoteUser, cps->program);
7653 err = StartChildProcess(buf, "", &cps->pr);
7657 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7658 DisplayFatalError(buf, err, 1);
7664 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7665 if (cps->protocolVersion > 1) {
7666 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7667 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7668 cps->comboCnt = 0; // and values of combo boxes
7669 SendToProgram(buf, cps);
7671 SendToProgram("xboard\n", cps);
7677 TwoMachinesEventIfReady P((void))
7679 if (first.lastPing != first.lastPong) {
7680 DisplayMessage("", _("Waiting for first chess program"));
7681 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7684 if (second.lastPing != second.lastPong) {
7685 DisplayMessage("", _("Waiting for second chess program"));
7686 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7694 NextMatchGame P((void))
7696 int index; /* [HGM] autoinc: step lod index during match */
7698 if (*appData.loadGameFile != NULLCHAR) {
7699 index = appData.loadGameIndex;
7700 if(index < 0) { // [HGM] autoinc
7701 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7702 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7704 LoadGameFromFile(appData.loadGameFile,
7706 appData.loadGameFile, FALSE);
7707 } else if (*appData.loadPositionFile != NULLCHAR) {
7708 index = appData.loadPositionIndex;
7709 if(index < 0) { // [HGM] autoinc
7710 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7711 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7713 LoadPositionFromFile(appData.loadPositionFile,
7715 appData.loadPositionFile);
7717 TwoMachinesEventIfReady();
7720 void UserAdjudicationEvent( int result )
7722 ChessMove gameResult = GameIsDrawn;
7725 gameResult = WhiteWins;
7727 else if( result < 0 ) {
7728 gameResult = BlackWins;
7731 if( gameMode == TwoMachinesPlay ) {
7732 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7738 GameEnds(result, resultDetails, whosays)
7740 char *resultDetails;
7743 GameMode nextGameMode;
7747 if(endingGame) return; /* [HGM] crash: forbid recursion */
7750 if (appData.debugMode) {
7751 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7752 result, resultDetails ? resultDetails : "(null)", whosays);
7755 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7756 /* If we are playing on ICS, the server decides when the
7757 game is over, but the engine can offer to draw, claim
7761 if (appData.zippyPlay && first.initDone) {
7762 if (result == GameIsDrawn) {
7763 /* In case draw still needs to be claimed */
7764 SendToICS(ics_prefix);
7765 SendToICS("draw\n");
7766 } else if (StrCaseStr(resultDetails, "resign")) {
7767 SendToICS(ics_prefix);
7768 SendToICS("resign\n");
7772 endingGame = 0; /* [HGM] crash */
7776 /* If we're loading the game from a file, stop */
7777 if (whosays == GE_FILE) {
7778 (void) StopLoadGameTimer();
7782 /* Cancel draw offers */
7783 first.offeredDraw = second.offeredDraw = 0;
7785 /* If this is an ICS game, only ICS can really say it's done;
7786 if not, anyone can. */
7787 isIcsGame = (gameMode == IcsPlayingWhite ||
7788 gameMode == IcsPlayingBlack ||
7789 gameMode == IcsObserving ||
7790 gameMode == IcsExamining);
7792 if (!isIcsGame || whosays == GE_ICS) {
7793 /* OK -- not an ICS game, or ICS said it was done */
7795 if (!isIcsGame && !appData.noChessProgram)
7796 SetUserThinkingEnables();
7798 /* [HGM] if a machine claims the game end we verify this claim */
7799 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7800 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7802 ChessMove trueResult = (ChessMove) -1;
7804 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7805 first.twoMachinesColor[0] :
7806 second.twoMachinesColor[0] ;
7808 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7809 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7810 /* [HGM] verify: engine mate claims accepted if they were flagged */
7811 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7813 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7814 /* [HGM] verify: engine mate claims accepted if they were flagged */
7815 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7817 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7818 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7821 // now verify win claims, but not in drop games, as we don't understand those yet
7822 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7823 || gameInfo.variant == VariantGreat) &&
7824 (result == WhiteWins && claimer == 'w' ||
7825 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7826 if (appData.debugMode) {
7827 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7828 result, epStatus[forwardMostMove], forwardMostMove);
7830 if(result != trueResult) {
7831 sprintf(buf, "False win claim: '%s'", resultDetails);
7832 result = claimer == 'w' ? BlackWins : WhiteWins;
7833 resultDetails = buf;
7836 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7837 && (forwardMostMove <= backwardMostMove ||
7838 epStatus[forwardMostMove-1] > EP_DRAWS ||
7839 (claimer=='b')==(forwardMostMove&1))
7841 /* [HGM] verify: draws that were not flagged are false claims */
7842 sprintf(buf, "False draw claim: '%s'", resultDetails);
7843 result = claimer == 'w' ? BlackWins : WhiteWins;
7844 resultDetails = buf;
7846 /* (Claiming a loss is accepted no questions asked!) */
7849 /* [HGM] bare: don't allow bare King to win */
7850 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7851 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7852 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7853 && result != GameIsDrawn)
7854 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7855 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7856 int p = (int)boards[forwardMostMove][i][j] - color;
7857 if(p >= 0 && p <= (int)WhiteKing) k++;
7859 if (appData.debugMode) {
7860 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7861 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7864 result = GameIsDrawn;
7865 sprintf(buf, "%s but bare king", resultDetails);
7866 resultDetails = buf;
7871 if(serverMoves != NULL && !loadFlag) { char c = '=';
7872 if(result==WhiteWins) c = '+';
7873 if(result==BlackWins) c = '-';
7874 if(resultDetails != NULL)
7875 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7877 if (resultDetails != NULL) {
7878 gameInfo.result = result;
7879 gameInfo.resultDetails = StrSave(resultDetails);
7881 /* display last move only if game was not loaded from file */
7882 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7883 DisplayMove(currentMove - 1);
7885 if (forwardMostMove != 0) {
7886 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7887 if (*appData.saveGameFile != NULLCHAR) {
7888 SaveGameToFile(appData.saveGameFile, TRUE);
7889 } else if (appData.autoSaveGames) {
7892 if (*appData.savePositionFile != NULLCHAR) {
7893 SavePositionToFile(appData.savePositionFile);
7898 /* Tell program how game ended in case it is learning */
7899 /* [HGM] Moved this to after saving the PGN, just in case */
7900 /* engine died and we got here through time loss. In that */
7901 /* case we will get a fatal error writing the pipe, which */
7902 /* would otherwise lose us the PGN. */
7903 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7904 /* output during GameEnds should never be fatal anymore */
7905 if (gameMode == MachinePlaysWhite ||
7906 gameMode == MachinePlaysBlack ||
7907 gameMode == TwoMachinesPlay ||
7908 gameMode == IcsPlayingWhite ||
7909 gameMode == IcsPlayingBlack ||
7910 gameMode == BeginningOfGame) {
7912 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7914 if (first.pr != NoProc) {
7915 SendToProgram(buf, &first);
7917 if (second.pr != NoProc &&
7918 gameMode == TwoMachinesPlay) {
7919 SendToProgram(buf, &second);
7924 if (appData.icsActive) {
7925 if (appData.quietPlay &&
7926 (gameMode == IcsPlayingWhite ||
7927 gameMode == IcsPlayingBlack)) {
7928 SendToICS(ics_prefix);
7929 SendToICS("set shout 1\n");
7931 nextGameMode = IcsIdle;
7932 ics_user_moved = FALSE;
7933 /* clean up premove. It's ugly when the game has ended and the
7934 * premove highlights are still on the board.
7938 ClearPremoveHighlights();
7939 DrawPosition(FALSE, boards[currentMove]);
7941 if (whosays == GE_ICS) {
7944 if (gameMode == IcsPlayingWhite)
7946 else if(gameMode == IcsPlayingBlack)
7950 if (gameMode == IcsPlayingBlack)
7952 else if(gameMode == IcsPlayingWhite)
7959 PlayIcsUnfinishedSound();
7962 } else if (gameMode == EditGame ||
7963 gameMode == PlayFromGameFile ||
7964 gameMode == AnalyzeMode ||
7965 gameMode == AnalyzeFile) {
7966 nextGameMode = gameMode;
7968 nextGameMode = EndOfGame;
7973 nextGameMode = gameMode;
7976 if (appData.noChessProgram) {
7977 gameMode = nextGameMode;
7979 endingGame = 0; /* [HGM] crash */
7984 /* Put first chess program into idle state */
7985 if (first.pr != NoProc &&
7986 (gameMode == MachinePlaysWhite ||
7987 gameMode == MachinePlaysBlack ||
7988 gameMode == TwoMachinesPlay ||
7989 gameMode == IcsPlayingWhite ||
7990 gameMode == IcsPlayingBlack ||
7991 gameMode == BeginningOfGame)) {
7992 SendToProgram("force\n", &first);
7993 if (first.usePing) {
7995 sprintf(buf, "ping %d\n", ++first.lastPing);
7996 SendToProgram(buf, &first);
7999 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8000 /* Kill off first chess program */
8001 if (first.isr != NULL)
8002 RemoveInputSource(first.isr);
8005 if (first.pr != NoProc) {
8007 DoSleep( appData.delayBeforeQuit );
8008 SendToProgram("quit\n", &first);
8009 DoSleep( appData.delayAfterQuit );
8010 DestroyChildProcess(first.pr, first.useSigterm);
8015 /* Put second chess program into idle state */
8016 if (second.pr != NoProc &&
8017 gameMode == TwoMachinesPlay) {
8018 SendToProgram("force\n", &second);
8019 if (second.usePing) {
8021 sprintf(buf, "ping %d\n", ++second.lastPing);
8022 SendToProgram(buf, &second);
8025 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8026 /* Kill off second chess program */
8027 if (second.isr != NULL)
8028 RemoveInputSource(second.isr);
8031 if (second.pr != NoProc) {
8032 DoSleep( appData.delayBeforeQuit );
8033 SendToProgram("quit\n", &second);
8034 DoSleep( appData.delayAfterQuit );
8035 DestroyChildProcess(second.pr, second.useSigterm);
8040 if (matchMode && gameMode == TwoMachinesPlay) {
8043 if (first.twoMachinesColor[0] == 'w') {
8050 if (first.twoMachinesColor[0] == 'b') {
8059 if (matchGame < appData.matchGames) {
8061 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8062 tmp = first.twoMachinesColor;
8063 first.twoMachinesColor = second.twoMachinesColor;
8064 second.twoMachinesColor = tmp;
8066 gameMode = nextGameMode;
8068 if(appData.matchPause>10000 || appData.matchPause<10)
8069 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8070 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8071 endingGame = 0; /* [HGM] crash */
8075 gameMode = nextGameMode;
8076 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8077 first.tidy, second.tidy,
8078 first.matchWins, second.matchWins,
8079 appData.matchGames - (first.matchWins + second.matchWins));
8080 DisplayFatalError(buf, 0, 0);
8083 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8084 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8086 gameMode = nextGameMode;
8088 endingGame = 0; /* [HGM] crash */
8091 /* Assumes program was just initialized (initString sent).
8092 Leaves program in force mode. */
8094 FeedMovesToProgram(cps, upto)
8095 ChessProgramState *cps;
8100 if (appData.debugMode)
8101 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8102 startedFromSetupPosition ? "position and " : "",
8103 backwardMostMove, upto, cps->which);
8104 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8105 // [HGM] variantswitch: make engine aware of new variant
8106 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8107 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8108 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8109 SendToProgram(buf, cps);
8110 currentlyInitializedVariant = gameInfo.variant;
8112 SendToProgram("force\n", cps);
8113 if (startedFromSetupPosition) {
8114 SendBoard(cps, backwardMostMove);
8115 if (appData.debugMode) {
8116 fprintf(debugFP, "feedMoves\n");
8119 for (i = backwardMostMove; i < upto; i++) {
8120 SendMoveToProgram(i, cps);
8126 ResurrectChessProgram()
8128 /* The chess program may have exited.
8129 If so, restart it and feed it all the moves made so far. */
8131 if (appData.noChessProgram || first.pr != NoProc) return;
8133 StartChessProgram(&first);
8134 InitChessProgram(&first, FALSE);
8135 FeedMovesToProgram(&first, currentMove);
8137 if (!first.sendTime) {
8138 /* can't tell gnuchess what its clock should read,
8139 so we bow to its notion. */
8141 timeRemaining[0][currentMove] = whiteTimeRemaining;
8142 timeRemaining[1][currentMove] = blackTimeRemaining;
8145 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8146 appData.icsEngineAnalyze) && first.analysisSupport) {
8147 SendToProgram("analyze\n", &first);
8148 first.analyzing = TRUE;
8160 printf ("DEBUG: in reset\n");
8162 if (appData.debugMode) {
8163 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8164 redraw, init, gameMode);
8166 pausing = pauseExamInvalid = FALSE;
8167 startedFromSetupPosition = blackPlaysFirst = FALSE;
8169 whiteFlag = blackFlag = FALSE;
8170 userOfferedDraw = FALSE;
8171 hintRequested = bookRequested = FALSE;
8172 first.maybeThinking = FALSE;
8173 second.maybeThinking = FALSE;
8174 first.bookSuspend = FALSE; // [HGM] book
8175 second.bookSuspend = FALSE;
8176 thinkOutput[0] = NULLCHAR;
8177 lastHint[0] = NULLCHAR;
8178 ClearGameInfo(&gameInfo);
8179 gameInfo.variant = StringToVariant(appData.variant);
8180 ics_user_moved = ics_clock_paused = FALSE;
8181 ics_getting_history = H_FALSE;
8183 white_holding[0] = black_holding[0] = NULLCHAR;
8184 ClearProgramStats();
8185 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8187 printf ("DEBUG: in reset 1\n");
8192 flipView = appData.flipView;
8193 ClearPremoveHighlights();
8195 alarmSounded = FALSE;
8197 printf ("DEBUG: in reset 2\n");
8200 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8201 printf ("DEBUG: in reset2.1\n");
8202 if(appData.serverMovesName != NULL) {
8203 /* [HGM] prepare to make moves file for broadcasting */
8204 clock_t t = clock();
8205 if(serverMoves != NULL) fclose(serverMoves);
8206 serverMoves = fopen(appData.serverMovesName, "r");
8207 if(serverMoves != NULL) {
8208 fclose(serverMoves);
8209 /* delay 15 sec before overwriting, so all clients can see end */
8210 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8212 serverMoves = fopen(appData.serverMovesName, "w");
8215 printf ("DEBUG: in reset 3\n");
8218 gameMode = BeginningOfGame;
8220 printf ("DEBUG: in reset 3.0.1\n");
8222 if(appData.icsActive) gameInfo.variant = VariantNormal;
8223 InitPosition(redraw);
8224 printf ("DEBUG: in reset 3.0.1.1\n");
8225 for (i = 0; i < MAX_MOVES; i++) {
8226 if (commentList[i] != NULL) {
8227 free(commentList[i]);
8228 commentList[i] = NULL;
8231 printf ("DEBUG: in reset 3.1\n");
8234 timeRemaining[0][0] = whiteTimeRemaining;
8235 timeRemaining[1][0] = blackTimeRemaining;
8236 if (first.pr == NULL) {
8237 StartChessProgram(&first);
8240 InitChessProgram(&first, startedFromSetupPosition);
8243 printf ("DEBUG: in reset 4\n");
8246 GUI_DisplayTitle("");
8247 printf ("DEBUG: in reset 5\n");
8248 DisplayMessage("", "");
8249 printf ("DEBUG: in reset 6\n");
8250 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8252 printf ("DEBUG: end reset \n");
8259 if (!AutoPlayOneMove())
8261 if (matchMode || appData.timeDelay == 0)
8263 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8265 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8274 int fromX, fromY, toX, toY;
8276 if (appData.debugMode) {
8277 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8280 if (gameMode != PlayFromGameFile)
8283 if (currentMove >= forwardMostMove) {
8284 gameMode = EditGame;
8287 /* [AS] Clear current move marker at the end of a game */
8288 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8293 toX = moveList[currentMove][2] - AAA;
8294 toY = moveList[currentMove][3] - ONE;
8296 if (moveList[currentMove][1] == '@') {
8297 if (appData.highlightLastMove) {
8298 SetHighlights(-1, -1, toX, toY);
8301 fromX = moveList[currentMove][0] - AAA;
8302 fromY = moveList[currentMove][1] - ONE;
8304 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8306 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8308 if (appData.highlightLastMove) {
8309 SetHighlights(fromX, fromY, toX, toY);
8312 DisplayMove(currentMove);
8313 SendMoveToProgram(currentMove++, &first);
8314 DisplayBothClocks();
8315 DrawPosition(FALSE, boards[currentMove]);
8316 // [HGM] PV info: always display, routine tests if empty
8317 DisplayComment(currentMove - 1, commentList[currentMove]);
8323 LoadGameOneMove(readAhead)
8324 ChessMove readAhead;
8326 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8327 char promoChar = NULLCHAR;
8332 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8333 gameMode != AnalyzeMode && gameMode != Training) {
8338 yyboardindex = forwardMostMove;
8339 if (readAhead != (ChessMove)0) {
8340 moveType = readAhead;
8342 if (gameFileFP == NULL)
8344 moveType = (ChessMove) yylex();
8350 if (appData.debugMode)
8351 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8353 if (*p == '{' || *p == '[' || *p == '(') {
8354 p[strlen(p) - 1] = NULLCHAR;
8358 /* append the comment but don't display it */
8359 while (*p == '\n') p++;
8360 AppendComment(currentMove, p);
8363 case WhiteCapturesEnPassant:
8364 case BlackCapturesEnPassant:
8365 case WhitePromotionChancellor:
8366 case BlackPromotionChancellor:
8367 case WhitePromotionArchbishop:
8368 case BlackPromotionArchbishop:
8369 case WhitePromotionCentaur:
8370 case BlackPromotionCentaur:
8371 case WhitePromotionQueen:
8372 case BlackPromotionQueen:
8373 case WhitePromotionRook:
8374 case BlackPromotionRook:
8375 case WhitePromotionBishop:
8376 case BlackPromotionBishop:
8377 case WhitePromotionKnight:
8378 case BlackPromotionKnight:
8379 case WhitePromotionKing:
8380 case BlackPromotionKing:
8382 case WhiteKingSideCastle:
8383 case WhiteQueenSideCastle:
8384 case BlackKingSideCastle:
8385 case BlackQueenSideCastle:
8386 case WhiteKingSideCastleWild:
8387 case WhiteQueenSideCastleWild:
8388 case BlackKingSideCastleWild:
8389 case BlackQueenSideCastleWild:
8391 case WhiteHSideCastleFR:
8392 case WhiteASideCastleFR:
8393 case BlackHSideCastleFR:
8394 case BlackASideCastleFR:
8396 if (appData.debugMode)
8397 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8398 fromX = currentMoveString[0] - AAA;
8399 fromY = currentMoveString[1] - ONE;
8400 toX = currentMoveString[2] - AAA;
8401 toY = currentMoveString[3] - ONE;
8402 promoChar = currentMoveString[4];
8407 if (appData.debugMode)
8408 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8409 fromX = moveType == WhiteDrop ?
8410 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8411 (int) CharToPiece(ToLower(currentMoveString[0]));
8413 toX = currentMoveString[2] - AAA;
8414 toY = currentMoveString[3] - ONE;
8420 case GameUnfinished:
8421 if (appData.debugMode)
8422 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8423 p = strchr(yy_text, '{');
8424 if (p == NULL) p = strchr(yy_text, '(');
8427 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8429 q = strchr(p, *p == '{' ? '}' : ')');
8430 if (q != NULL) *q = NULLCHAR;
8433 GameEnds(moveType, p, GE_FILE);
8435 if (cmailMsgLoaded) {
8437 flipView = WhiteOnMove(currentMove);
8438 if (moveType == GameUnfinished) flipView = !flipView;
8439 if (appData.debugMode)
8440 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8444 case (ChessMove) 0: /* end of file */
8445 if (appData.debugMode)
8446 fprintf(debugFP, "Parser hit end of file\n");
8447 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8448 EP_UNKNOWN, castlingRights[currentMove]) ) {
8454 if (WhiteOnMove(currentMove)) {
8455 GameEnds(BlackWins, "Black mates", GE_FILE);
8457 GameEnds(WhiteWins, "White mates", GE_FILE);
8461 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8468 if (lastLoadGameStart == GNUChessGame) {
8469 /* GNUChessGames have numbers, but they aren't move numbers */
8470 if (appData.debugMode)
8471 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8472 yy_text, (int) moveType);
8473 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8475 /* else fall thru */
8480 /* Reached start of next game in file */
8481 if (appData.debugMode)
8482 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8483 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8484 EP_UNKNOWN, castlingRights[currentMove]) ) {
8490 if (WhiteOnMove(currentMove)) {
8491 GameEnds(BlackWins, "Black mates", GE_FILE);
8493 GameEnds(WhiteWins, "White mates", GE_FILE);
8497 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8503 case PositionDiagram: /* should not happen; ignore */
8504 case ElapsedTime: /* ignore */
8505 case NAG: /* ignore */
8506 if (appData.debugMode)
8507 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8508 yy_text, (int) moveType);
8509 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8512 if (appData.testLegality) {
8513 if (appData.debugMode)
8514 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8515 sprintf(move, _("Illegal move: %d.%s%s"),
8516 (forwardMostMove / 2) + 1,
8517 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8518 DisplayError(move, 0);
8521 if (appData.debugMode)
8522 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8523 yy_text, currentMoveString);
8524 fromX = currentMoveString[0] - AAA;
8525 fromY = currentMoveString[1] - ONE;
8526 toX = currentMoveString[2] - AAA;
8527 toY = currentMoveString[3] - ONE;
8528 promoChar = currentMoveString[4];
8533 if (appData.debugMode)
8534 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8535 sprintf(move, _("Ambiguous move: %d.%s%s"),
8536 (forwardMostMove / 2) + 1,
8537 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8538 DisplayError(move, 0);
8543 case ImpossibleMove:
8544 if (appData.debugMode)
8545 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8546 sprintf(move, _("Illegal move: %d.%s%s"),
8547 (forwardMostMove / 2) + 1,
8548 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8549 DisplayError(move, 0);
8555 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8556 DrawPosition(FALSE, boards[currentMove]);
8557 DisplayBothClocks();
8558 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8559 DisplayComment(currentMove - 1, commentList[currentMove]);
8561 (void) StopLoadGameTimer();
8563 cmailOldMove = forwardMostMove;
8566 /* currentMoveString is set as a side-effect of yylex */
8567 strcat(currentMoveString, "\n");
8568 strcpy(moveList[forwardMostMove], currentMoveString);
8570 thinkOutput[0] = NULLCHAR;
8571 MakeMove(fromX, fromY, toX, toY, promoChar);
8572 currentMove = forwardMostMove;
8577 /* Load the nth game from the given file */
8579 LoadGameFromFile(filename, n, title, useList)
8583 /*Boolean*/ int useList;
8588 if (strcmp(filename, "-") == 0) {
8592 f = fopen(filename, "rb");
8594 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8595 DisplayError(buf, errno);
8599 if (fseek(f, 0, 0) == -1) {
8600 /* f is not seekable; probably a pipe */
8603 if (useList && n == 0) {
8604 int error = GameListBuild(f);
8606 DisplayError(_("Cannot build game list"), error);
8607 } else if (!ListEmpty(&gameList) &&
8608 ((ListGame *) gameList.tailPred)->number > 1) {
8609 GameListPopUp(f, title);
8616 return LoadGame(f, n, title, FALSE);
8621 MakeRegisteredMove()
8623 int fromX, fromY, toX, toY;
8625 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8626 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8629 if (appData.debugMode)
8630 fprintf(debugFP, "Restoring %s for game %d\n",
8631 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8633 thinkOutput[0] = NULLCHAR;
8634 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8635 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8636 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8637 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8638 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8639 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8640 MakeMove(fromX, fromY, toX, toY, promoChar);
8641 ShowMove(fromX, fromY, toX, toY);
8643 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8644 EP_UNKNOWN, castlingRights[currentMove]) ) {
8651 if (WhiteOnMove(currentMove)) {
8652 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8654 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8659 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8666 if (WhiteOnMove(currentMove)) {
8667 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8669 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8674 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8685 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8687 CmailLoadGame(f, gameNumber, title, useList)
8695 if (gameNumber > nCmailGames) {
8696 DisplayError(_("No more games in this message"), 0);
8699 if (f == lastLoadGameFP) {
8700 int offset = gameNumber - lastLoadGameNumber;
8702 cmailMsg[0] = NULLCHAR;
8703 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8704 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8705 nCmailMovesRegistered--;
8707 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8708 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8709 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8712 if (! RegisterMove()) return FALSE;
8716 retVal = LoadGame(f, gameNumber, title, useList);
8718 /* Make move registered during previous look at this game, if any */
8719 MakeRegisteredMove();
8721 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8722 commentList[currentMove]
8723 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8724 DisplayComment(currentMove - 1, commentList[currentMove]);
8730 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8735 int gameNumber = lastLoadGameNumber + offset;
8736 if (lastLoadGameFP == NULL) {
8737 DisplayError(_("No game has been loaded yet"), 0);
8740 if (gameNumber <= 0) {
8741 DisplayError(_("Can't back up any further"), 0);
8744 if (cmailMsgLoaded) {
8745 return CmailLoadGame(lastLoadGameFP, gameNumber,
8746 lastLoadGameTitle, lastLoadGameUseList);
8748 return LoadGame(lastLoadGameFP, gameNumber,
8749 lastLoadGameTitle, lastLoadGameUseList);
8755 /* Load the nth game from open file f */
8757 LoadGame(f, gameNumber, title, useList)
8765 int gn = gameNumber;
8766 ListGame *lg = NULL;
8769 GameMode oldGameMode;
8770 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8772 if (appData.debugMode)
8773 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8775 if (gameMode == Training )
8776 SetTrainingModeOff();
8778 oldGameMode = gameMode;
8779 if (gameMode != BeginningOfGame) {
8784 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8785 fclose(lastLoadGameFP);
8789 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8792 fseek(f, lg->offset, 0);
8793 GameListHighlight(gameNumber);
8797 DisplayError(_("Game number out of range"), 0);
8802 if (fseek(f, 0, 0) == -1) {
8803 if (f == lastLoadGameFP ?
8804 gameNumber == lastLoadGameNumber + 1 :
8808 DisplayError(_("Can't seek on game file"), 0);
8814 lastLoadGameNumber = gameNumber;
8815 strcpy(lastLoadGameTitle, title);
8816 lastLoadGameUseList = useList;
8820 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8821 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8822 lg->gameInfo.black);
8824 } else if (*title != NULLCHAR) {
8825 if (gameNumber > 1) {
8826 sprintf(buf, "%s %d", title, gameNumber);
8829 DisplayTitle(title);
8833 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8834 gameMode = PlayFromGameFile;
8838 currentMove = forwardMostMove = backwardMostMove = 0;
8839 CopyBoard(boards[0], initialPosition);
8843 * Skip the first gn-1 games in the file.
8844 * Also skip over anything that precedes an identifiable
8845 * start of game marker, to avoid being confused by
8846 * garbage at the start of the file. Currently
8847 * recognized start of game markers are the move number "1",
8848 * the pattern "gnuchess .* game", the pattern
8849 * "^[#;%] [^ ]* game file", and a PGN tag block.
8850 * A game that starts with one of the latter two patterns
8851 * will also have a move number 1, possibly
8852 * following a position diagram.
8853 * 5-4-02: Let's try being more lenient and allowing a game to
8854 * start with an unnumbered move. Does that break anything?
8856 cm = lastLoadGameStart = (ChessMove) 0;
8858 yyboardindex = forwardMostMove;
8859 cm = (ChessMove) yylex();
8862 if (cmailMsgLoaded) {
8863 nCmailGames = CMAIL_MAX_GAMES - gn;
8866 DisplayError(_("Game not found in file"), 0);
8873 lastLoadGameStart = cm;
8877 switch (lastLoadGameStart) {
8884 gn--; /* count this game */
8885 lastLoadGameStart = cm;
8894 switch (lastLoadGameStart) {
8899 gn--; /* count this game */
8900 lastLoadGameStart = cm;
8903 lastLoadGameStart = cm; /* game counted already */
8911 yyboardindex = forwardMostMove;
8912 cm = (ChessMove) yylex();
8913 } while (cm == PGNTag || cm == Comment);
8920 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8921 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8922 != CMAIL_OLD_RESULT) {
8924 cmailResult[ CMAIL_MAX_GAMES
8925 - gn - 1] = CMAIL_OLD_RESULT;
8931 /* Only a NormalMove can be at the start of a game
8932 * without a position diagram. */
8933 if (lastLoadGameStart == (ChessMove) 0) {
8935 lastLoadGameStart = MoveNumberOne;
8944 if (appData.debugMode)
8945 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8947 if (cm == XBoardGame) {
8948 /* Skip any header junk before position diagram and/or move 1 */
8950 yyboardindex = forwardMostMove;
8951 cm = (ChessMove) yylex();
8953 if (cm == (ChessMove) 0 ||
8954 cm == GNUChessGame || cm == XBoardGame) {
8955 /* Empty game; pretend end-of-file and handle later */
8960 if (cm == MoveNumberOne || cm == PositionDiagram ||
8961 cm == PGNTag || cm == Comment)
8964 } else if (cm == GNUChessGame) {
8965 if (gameInfo.event != NULL) {
8966 free(gameInfo.event);
8968 gameInfo.event = StrSave(yy_text);
8971 startedFromSetupPosition = FALSE;
8972 while (cm == PGNTag) {
8973 if (appData.debugMode)
8974 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8975 err = ParsePGNTag(yy_text, &gameInfo);
8976 if (!err) numPGNTags++;
8978 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8979 if(gameInfo.variant != oldVariant) {
8980 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8982 oldVariant = gameInfo.variant;
8983 if (appData.debugMode)
8984 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8988 if (gameInfo.fen != NULL) {
8989 Board initial_position;
8990 startedFromSetupPosition = TRUE;
8991 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8993 DisplayError(_("Bad FEN position in file"), 0);
8996 CopyBoard(boards[0], initial_position);
8997 if (blackPlaysFirst) {
8998 currentMove = forwardMostMove = backwardMostMove = 1;
8999 CopyBoard(boards[1], initial_position);
9000 strcpy(moveList[0], "");
9001 strcpy(parseList[0], "");
9002 timeRemaining[0][1] = whiteTimeRemaining;
9003 timeRemaining[1][1] = blackTimeRemaining;
9004 if (commentList[0] != NULL) {
9005 commentList[1] = commentList[0];
9006 commentList[0] = NULL;
9009 currentMove = forwardMostMove = backwardMostMove = 0;
9011 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9013 initialRulePlies = FENrulePlies;
9014 epStatus[forwardMostMove] = FENepStatus;
9015 for( i=0; i< nrCastlingRights; i++ )
9016 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9018 yyboardindex = forwardMostMove;
9020 gameInfo.fen = NULL;
9023 yyboardindex = forwardMostMove;
9024 cm = (ChessMove) yylex();
9026 /* Handle comments interspersed among the tags */
9027 while (cm == Comment) {
9029 if (appData.debugMode)
9030 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9032 if (*p == '{' || *p == '[' || *p == '(') {
9033 p[strlen(p) - 1] = NULLCHAR;
9036 while (*p == '\n') p++;
9037 AppendComment(currentMove, p);
9038 yyboardindex = forwardMostMove;
9039 cm = (ChessMove) yylex();
9043 /* don't rely on existence of Event tag since if game was
9044 * pasted from clipboard the Event tag may not exist
9046 if (numPGNTags > 0){
9048 if (gameInfo.variant == VariantNormal) {
9049 gameInfo.variant = StringToVariant(gameInfo.event);
9052 if( appData.autoDisplayTags ) {
9053 tags = PGNTags(&gameInfo);
9054 TagsPopUp(tags, CmailMsg());
9059 /* Make something up, but don't display it now */
9064 if (cm == PositionDiagram) {
9067 Board initial_position;
9069 if (appData.debugMode)
9070 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9072 if (!startedFromSetupPosition) {
9074 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9075 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9085 initial_position[i][j++] = CharToPiece(*p);
9088 while (*p == ' ' || *p == '\t' ||
9089 *p == '\n' || *p == '\r') p++;
9091 if (strncmp(p, "black", strlen("black"))==0)
9092 blackPlaysFirst = TRUE;
9094 blackPlaysFirst = FALSE;
9095 startedFromSetupPosition = TRUE;
9097 CopyBoard(boards[0], initial_position);
9098 if (blackPlaysFirst) {
9099 currentMove = forwardMostMove = backwardMostMove = 1;
9100 CopyBoard(boards[1], initial_position);
9101 strcpy(moveList[0], "");
9102 strcpy(parseList[0], "");
9103 timeRemaining[0][1] = whiteTimeRemaining;
9104 timeRemaining[1][1] = blackTimeRemaining;
9105 if (commentList[0] != NULL) {
9106 commentList[1] = commentList[0];
9107 commentList[0] = NULL;
9110 currentMove = forwardMostMove = backwardMostMove = 0;
9113 yyboardindex = forwardMostMove;
9114 cm = (ChessMove) yylex();
9117 if (first.pr == NoProc) {
9118 StartChessProgram(&first);
9120 InitChessProgram(&first, FALSE);
9121 SendToProgram("force\n", &first);
9122 if (startedFromSetupPosition) {
9123 SendBoard(&first, forwardMostMove);
9124 if (appData.debugMode) {
9125 fprintf(debugFP, "Load Game\n");
9127 DisplayBothClocks();
9130 /* [HGM] server: flag to write setup moves in broadcast file as one */
9131 loadFlag = appData.suppressLoadMoves;
9133 while (cm == Comment) {
9135 if (appData.debugMode)
9136 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9138 if (*p == '{' || *p == '[' || *p == '(') {
9139 p[strlen(p) - 1] = NULLCHAR;
9142 while (*p == '\n') p++;
9143 AppendComment(currentMove, p);
9144 yyboardindex = forwardMostMove;
9145 cm = (ChessMove) yylex();
9148 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9149 cm == WhiteWins || cm == BlackWins ||
9150 cm == GameIsDrawn || cm == GameUnfinished) {
9151 DisplayMessage("", _("No moves in game"));
9152 if (cmailMsgLoaded) {
9153 if (appData.debugMode)
9154 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9158 DrawPosition(FALSE, boards[currentMove]);
9159 DisplayBothClocks();
9160 gameMode = EditGame;
9167 // [HGM] PV info: routine tests if comment empty
9168 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9169 DisplayComment(currentMove - 1, commentList[currentMove]);
9171 if (!matchMode && appData.timeDelay != 0)
9172 DrawPosition(FALSE, boards[currentMove]);
9174 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9175 programStats.ok_to_send = 1;
9178 /* if the first token after the PGN tags is a move
9179 * and not move number 1, retrieve it from the parser
9181 if (cm != MoveNumberOne)
9182 LoadGameOneMove(cm);
9184 /* load the remaining moves from the file */
9185 while (LoadGameOneMove((ChessMove)0)) {
9186 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9187 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9190 /* rewind to the start of the game */
9191 currentMove = backwardMostMove;
9193 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9195 if (oldGameMode == AnalyzeFile ||
9196 oldGameMode == AnalyzeMode) {
9200 if (matchMode || appData.timeDelay == 0) {
9202 gameMode = EditGame;
9204 } else if (appData.timeDelay > 0) {
9208 if (appData.debugMode)
9209 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9211 loadFlag = 0; /* [HGM] true game starts */
9215 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9217 ReloadPosition(offset)
9220 int positionNumber = lastLoadPositionNumber + offset;
9221 if (lastLoadPositionFP == NULL) {
9222 DisplayError(_("No position has been loaded yet"), 0);
9225 if (positionNumber <= 0) {
9226 DisplayError(_("Can't back up any further"), 0);
9229 return LoadPosition(lastLoadPositionFP, positionNumber,
9230 lastLoadPositionTitle);
9233 /* Load the nth position from the given file */
9235 LoadPositionFromFile(filename, n, title)
9243 if (strcmp(filename, "-") == 0) {
9244 return LoadPosition(stdin, n, "stdin");
9246 f = fopen(filename, "rb");
9248 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9249 DisplayError(buf, errno);
9252 return LoadPosition(f, n, title);
9257 /* Load the nth position from the given open file, and close it */
9259 LoadPosition(f, positionNumber, title)
9264 char *p, line[MSG_SIZ];
9265 Board initial_position;
9266 int i, j, fenMode, pn;
9268 if (gameMode == Training )
9269 SetTrainingModeOff();
9271 if (gameMode != BeginningOfGame) {
9274 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9275 fclose(lastLoadPositionFP);
9277 if (positionNumber == 0) positionNumber = 1;
9278 lastLoadPositionFP = f;
9279 lastLoadPositionNumber = positionNumber;
9280 strcpy(lastLoadPositionTitle, title);
9281 if (first.pr == NoProc) {
9282 StartChessProgram(&first);
9283 InitChessProgram(&first, FALSE);
9285 pn = positionNumber;
9286 if (positionNumber < 0) {
9287 /* Negative position number means to seek to that byte offset */
9288 if (fseek(f, -positionNumber, 0) == -1) {
9289 DisplayError(_("Can't seek on position file"), 0);
9294 if (fseek(f, 0, 0) == -1) {
9295 if (f == lastLoadPositionFP ?
9296 positionNumber == lastLoadPositionNumber + 1 :
9297 positionNumber == 1) {
9300 DisplayError(_("Can't seek on position file"), 0);
9305 /* See if this file is FEN or old-style xboard */
9306 if (fgets(line, MSG_SIZ, f) == NULL) {
9307 DisplayError(_("Position not found in file"), 0);
9316 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9317 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9318 case '1': case '2': case '3': case '4': case '5': case '6':
9319 case '7': case '8': case '9':
9320 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9321 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9322 case 'C': case 'W': case 'c': case 'w':
9327 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9328 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9332 if (fenMode || line[0] == '#') pn--;
9334 /* skip positions before number pn */
9335 if (fgets(line, MSG_SIZ, f) == NULL) {
9337 DisplayError(_("Position not found in file"), 0);
9340 if (fenMode || line[0] == '#') pn--;
9345 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9346 DisplayError(_("Bad FEN position in file"), 0);
9350 (void) fgets(line, MSG_SIZ, f);
9351 (void) fgets(line, MSG_SIZ, f);
9353 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9354 (void) fgets(line, MSG_SIZ, f);
9355 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9358 initial_position[i][j++] = CharToPiece(*p);
9362 blackPlaysFirst = FALSE;
9364 (void) fgets(line, MSG_SIZ, f);
9365 if (strncmp(line, "black", strlen("black"))==0)
9366 blackPlaysFirst = TRUE;
9369 startedFromSetupPosition = TRUE;
9371 SendToProgram("force\n", &first);
9372 CopyBoard(boards[0], initial_position);
9373 if (blackPlaysFirst) {
9374 currentMove = forwardMostMove = backwardMostMove = 1;
9375 strcpy(moveList[0], "");
9376 strcpy(parseList[0], "");
9377 CopyBoard(boards[1], initial_position);
9378 DisplayMessage("", _("Black to play"));
9380 currentMove = forwardMostMove = backwardMostMove = 0;
9381 DisplayMessage("", _("White to play"));
9383 /* [HGM] copy FEN attributes as well */
9385 initialRulePlies = FENrulePlies;
9386 epStatus[forwardMostMove] = FENepStatus;
9387 for( i=0; i< nrCastlingRights; i++ )
9388 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9390 SendBoard(&first, forwardMostMove);
9391 if (appData.debugMode) {
9393 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9394 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9395 fprintf(debugFP, "Load Position\n");
9398 if (positionNumber > 1) {
9399 sprintf(line, "%s %d", title, positionNumber);
9402 DisplayTitle(title);
9404 gameMode = EditGame;
9407 timeRemaining[0][1] = whiteTimeRemaining;
9408 timeRemaining[1][1] = blackTimeRemaining;
9409 DrawPosition(FALSE, boards[currentMove]);
9416 CopyPlayerNameIntoFileName(dest, src)
9419 while (*src != NULLCHAR && *src != ',') {
9424 *(*dest)++ = *src++;
9429 char *DefaultFileName(ext)
9432 static char def[MSG_SIZ];
9435 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9437 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9439 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9448 /* Save the current game to the given file */
9450 SaveGameToFile(filename, append)
9457 if (strcmp(filename, "-") == 0) {
9458 return SaveGame(stdout, 0, NULL);
9460 f = fopen(filename, append ? "a" : "w");
9462 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9463 DisplayError(buf, errno);
9466 return SaveGame(f, 0, NULL);
9475 static char buf[MSG_SIZ];
9478 p = strchr(str, ' ');
9479 if (p == NULL) return str;
9480 strncpy(buf, str, p - str);
9481 buf[p - str] = NULLCHAR;
9485 #define PGN_MAX_LINE 75
9487 #define PGN_SIDE_WHITE 0
9488 #define PGN_SIDE_BLACK 1
9491 static int FindFirstMoveOutOfBook( int side )
9495 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9496 int index = backwardMostMove;
9497 int has_book_hit = 0;
9499 if( (index % 2) != side ) {
9503 while( index < forwardMostMove ) {
9504 /* Check to see if engine is in book */
9505 int depth = pvInfoList[index].depth;
9506 int score = pvInfoList[index].score;
9512 else if( score == 0 && depth == 63 ) {
9513 in_book = 1; /* Zappa */
9515 else if( score == 2 && depth == 99 ) {
9516 in_book = 1; /* Abrok */
9519 has_book_hit += in_book;
9535 void GetOutOfBookInfo( char * buf )
9539 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9541 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9542 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9546 if( oob[0] >= 0 || oob[1] >= 0 ) {
9547 for( i=0; i<2; i++ ) {
9551 if( i > 0 && oob[0] >= 0 ) {
9555 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9556 sprintf( buf+strlen(buf), "%s%.2f",
9557 pvInfoList[idx].score >= 0 ? "+" : "",
9558 pvInfoList[idx].score / 100.0 );
9564 /* Save game in PGN style and close the file */
9569 int i, offset, linelen, newblock;
9573 int movelen, numlen, blank;
9574 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9576 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9578 tm = time((time_t *) NULL);
9580 PrintPGNTags(f, &gameInfo);
9582 if (backwardMostMove > 0 || startedFromSetupPosition) {
9583 char *fen = PositionToFEN(backwardMostMove, NULL);
9584 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9585 fprintf(f, "\n{--------------\n");
9586 PrintPosition(f, backwardMostMove);
9587 fprintf(f, "--------------}\n");
9591 /* [AS] Out of book annotation */
9592 if( appData.saveOutOfBookInfo ) {
9595 GetOutOfBookInfo( buf );
9597 if( buf[0] != '\0' ) {
9598 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9605 i = backwardMostMove;
9609 while (i < forwardMostMove) {
9610 /* Print comments preceding this move */
9611 if (commentList[i] != NULL) {
9612 if (linelen > 0) fprintf(f, "\n");
9613 fprintf(f, "{\n%s}\n", commentList[i]);
9618 /* Format move number */
9620 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9623 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9625 numtext[0] = NULLCHAR;
9628 numlen = strlen(numtext);
9631 /* Print move number */
9632 blank = linelen > 0 && numlen > 0;
9633 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9642 fprintf(f, numtext);
9646 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9647 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9648 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9649 int p = movelen - 1;
9650 if(move_buffer[p] == ' ') p--;
9651 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9652 while(p && move_buffer[--p] != '(');
9653 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9658 blank = linelen > 0 && movelen > 0;
9659 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9668 fprintf(f, move_buffer);
9671 /* [AS] Add PV info if present */
9672 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9673 /* [HGM] add time */
9674 char buf[MSG_SIZ]; int seconds = 0;
9677 if(i >= backwardMostMove) {
9679 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9680 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9682 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9683 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9685 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9687 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9690 if( seconds <= 0) buf[0] = 0; else
9691 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9692 seconds = (seconds + 4)/10; // round to full seconds
9693 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9694 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9697 sprintf( move_buffer, "{%s%.2f/%d%s}",
9698 pvInfoList[i].score >= 0 ? "+" : "",
9699 pvInfoList[i].score / 100.0,
9700 pvInfoList[i].depth,
9703 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9705 /* Print score/depth */
9706 blank = linelen > 0 && movelen > 0;
9707 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9716 fprintf(f, move_buffer);
9723 /* Start a new line */
9724 if (linelen > 0) fprintf(f, "\n");
9726 /* Print comments after last move */
9727 if (commentList[i] != NULL) {
9728 fprintf(f, "{\n%s}\n", commentList[i]);
9732 if (gameInfo.resultDetails != NULL &&
9733 gameInfo.resultDetails[0] != NULLCHAR) {
9734 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9735 PGNResult(gameInfo.result));
9737 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9744 /* Save game in old style and close the file */
9752 tm = time((time_t *) NULL);
9754 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9757 if (backwardMostMove > 0 || startedFromSetupPosition) {
9758 fprintf(f, "\n[--------------\n");
9759 PrintPosition(f, backwardMostMove);
9760 fprintf(f, "--------------]\n");
9765 i = backwardMostMove;
9766 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9768 while (i < forwardMostMove) {
9769 if (commentList[i] != NULL) {
9770 fprintf(f, "[%s]\n", commentList[i]);
9774 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9777 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9779 if (commentList[i] != NULL) {
9783 if (i >= forwardMostMove) {
9787 fprintf(f, "%s\n", parseList[i]);
9792 if (commentList[i] != NULL) {
9793 fprintf(f, "[%s]\n", commentList[i]);
9796 /* This isn't really the old style, but it's close enough */
9797 if (gameInfo.resultDetails != NULL &&
9798 gameInfo.resultDetails[0] != NULLCHAR) {
9799 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9800 gameInfo.resultDetails);
9802 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9809 /* Save the current game to open file f and close the file */
9811 SaveGame(f, dummy, dummy2)
9816 if (gameMode == EditPosition) EditPositionDone();
9817 if (appData.oldSaveStyle)
9818 return SaveGameOldStyle(f);
9820 return SaveGamePGN(f);
9823 /* Save the current position to the given file */
9825 SavePositionToFile(filename)
9831 if (strcmp(filename, "-") == 0) {
9832 return SavePosition(stdout, 0, NULL);
9834 f = fopen(filename, "a");
9836 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9837 DisplayError(buf, errno);
9840 SavePosition(f, 0, NULL);
9846 /* Save the current position to the given open file and close the file */
9848 SavePosition(f, dummy, dummy2)
9856 if (appData.oldSaveStyle) {
9857 tm = time((time_t *) NULL);
9859 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9861 fprintf(f, "[--------------\n");
9862 PrintPosition(f, currentMove);
9863 fprintf(f, "--------------]\n");
9865 fen = PositionToFEN(currentMove, NULL);
9866 fprintf(f, "%s\n", fen);
9874 ReloadCmailMsgEvent(unregister)
9878 static char *inFilename = NULL;
9879 static char *outFilename;
9881 struct stat inbuf, outbuf;
9884 /* Any registered moves are unregistered if unregister is set, */
9885 /* i.e. invoked by the signal handler */
9887 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9888 cmailMoveRegistered[i] = FALSE;
9889 if (cmailCommentList[i] != NULL) {
9890 free(cmailCommentList[i]);
9891 cmailCommentList[i] = NULL;
9894 nCmailMovesRegistered = 0;
9897 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9898 cmailResult[i] = CMAIL_NOT_RESULT;
9902 if (inFilename == NULL) {
9903 /* Because the filenames are static they only get malloced once */
9904 /* and they never get freed */
9905 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9906 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9908 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9909 sprintf(outFilename, "%s.out", appData.cmailGameName);
9912 status = stat(outFilename, &outbuf);
9914 cmailMailedMove = FALSE;
9916 status = stat(inFilename, &inbuf);
9917 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9920 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9921 counts the games, notes how each one terminated, etc.
9923 It would be nice to remove this kludge and instead gather all
9924 the information while building the game list. (And to keep it
9925 in the game list nodes instead of having a bunch of fixed-size
9926 parallel arrays.) Note this will require getting each game's
9927 termination from the PGN tags, as the game list builder does
9928 not process the game moves. --mann
9930 cmailMsgLoaded = TRUE;
9931 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9933 /* Load first game in the file or popup game menu */
9934 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9944 char string[MSG_SIZ];
9946 if ( cmailMailedMove
9947 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9948 return TRUE; /* Allow free viewing */
9951 /* Unregister move to ensure that we don't leave RegisterMove */
9952 /* with the move registered when the conditions for registering no */
9954 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9955 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9956 nCmailMovesRegistered --;
9958 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9960 free(cmailCommentList[lastLoadGameNumber - 1]);
9961 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9965 if (cmailOldMove == -1) {
9966 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9970 if (currentMove > cmailOldMove + 1) {
9971 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9975 if (currentMove < cmailOldMove) {
9976 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9980 if (forwardMostMove > currentMove) {
9981 /* Silently truncate extra moves */
9985 if ( (currentMove == cmailOldMove + 1)
9986 || ( (currentMove == cmailOldMove)
9987 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9988 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9989 if (gameInfo.result != GameUnfinished) {
9990 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9993 if (commentList[currentMove] != NULL) {
9994 cmailCommentList[lastLoadGameNumber - 1]
9995 = StrSave(commentList[currentMove]);
9997 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9999 if (appData.debugMode)
10000 fprintf(debugFP, "Saving %s for game %d\n",
10001 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10004 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10006 f = fopen(string, "w");
10007 if (appData.oldSaveStyle) {
10008 SaveGameOldStyle(f); /* also closes the file */
10010 sprintf(string, "%s.pos.out", appData.cmailGameName);
10011 f = fopen(string, "w");
10012 SavePosition(f, 0, NULL); /* also closes the file */
10014 fprintf(f, "{--------------\n");
10015 PrintPosition(f, currentMove);
10016 fprintf(f, "--------------}\n\n");
10018 SaveGame(f, 0, NULL); /* also closes the file*/
10021 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10022 nCmailMovesRegistered ++;
10023 } else if (nCmailGames == 1) {
10024 DisplayError(_("You have not made a move yet"), 0);
10035 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10036 FILE *commandOutput;
10037 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10038 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10044 if (! cmailMsgLoaded) {
10045 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10049 if (nCmailGames == nCmailResults) {
10050 DisplayError(_("No unfinished games"), 0);
10054 #if CMAIL_PROHIBIT_REMAIL
10055 if (cmailMailedMove) {
10056 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);
10057 DisplayError(msg, 0);
10062 if (! (cmailMailedMove || RegisterMove())) return;
10064 if ( cmailMailedMove
10065 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10066 sprintf(string, partCommandString,
10067 appData.debugMode ? " -v" : "", appData.cmailGameName);
10068 commandOutput = popen(string, "r");
10070 if (commandOutput == NULL) {
10071 DisplayError(_("Failed to invoke cmail"), 0);
10073 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10074 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10076 if (nBuffers > 1) {
10077 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10078 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10079 nBytes = MSG_SIZ - 1;
10081 (void) memcpy(msg, buffer, nBytes);
10083 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10085 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10086 cmailMailedMove = TRUE; /* Prevent >1 moves */
10089 for (i = 0; i < nCmailGames; i ++) {
10090 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10095 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10097 sprintf(buffer, "%s/%s.%s.archive",
10099 appData.cmailGameName,
10101 LoadGameFromFile(buffer, 1, buffer, FALSE);
10102 cmailMsgLoaded = FALSE;
10106 DisplayInformation(msg);
10107 pclose(commandOutput);
10110 if ((*cmailMsg) != '\0') {
10111 DisplayInformation(cmailMsg);
10116 #endif /* !WIN32 */
10125 int prependComma = 0;
10127 char string[MSG_SIZ]; /* Space for game-list */
10130 if (!cmailMsgLoaded) return "";
10132 if (cmailMailedMove) {
10133 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10135 /* Create a list of games left */
10136 sprintf(string, "[");
10137 for (i = 0; i < nCmailGames; i ++) {
10138 if (! ( cmailMoveRegistered[i]
10139 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10140 if (prependComma) {
10141 sprintf(number, ",%d", i + 1);
10143 sprintf(number, "%d", i + 1);
10147 strcat(string, number);
10150 strcat(string, "]");
10152 if (nCmailMovesRegistered + nCmailResults == 0) {
10153 switch (nCmailGames) {
10156 _("Still need to make move for game\n"));
10161 _("Still need to make moves for both games\n"));
10166 _("Still need to make moves for all %d games\n"),
10171 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10174 _("Still need to make a move for game %s\n"),
10179 if (nCmailResults == nCmailGames) {
10180 sprintf(cmailMsg, _("No unfinished games\n"));
10182 sprintf(cmailMsg, _("Ready to send mail\n"));
10188 _("Still need to make moves for games %s\n"),
10200 if (gameMode == Training)
10201 SetTrainingModeOff();
10204 cmailMsgLoaded = FALSE;
10205 if (appData.icsActive) {
10206 SendToICS(ics_prefix);
10207 SendToICS("refresh\n");
10217 /* Give up on clean exit */
10221 /* Keep trying for clean exit */
10225 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10227 if (telnetISR != NULL) {
10228 RemoveInputSource(telnetISR);
10230 if (icsPR != NoProc) {
10231 DestroyChildProcess(icsPR, TRUE);
10234 /* Save game if resource set and not already saved by GameEnds() */
10235 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10236 && forwardMostMove > 0) {
10237 if (*appData.saveGameFile != NULLCHAR) {
10238 SaveGameToFile(appData.saveGameFile, TRUE);
10239 } else if (appData.autoSaveGames) {
10242 if (*appData.savePositionFile != NULLCHAR) {
10243 SavePositionToFile(appData.savePositionFile);
10246 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10248 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10249 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10251 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10252 /* make sure this other one finishes before killing it! */
10253 if(endingGame) { int count = 0;
10254 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10255 while(endingGame && count++ < 10) DoSleep(1);
10256 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10259 /* Kill off chess programs */
10260 if (first.pr != NoProc) {
10263 DoSleep( appData.delayBeforeQuit );
10264 SendToProgram("quit\n", &first);
10265 DoSleep( appData.delayAfterQuit );
10266 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10268 if (second.pr != NoProc) {
10269 DoSleep( appData.delayBeforeQuit );
10270 SendToProgram("quit\n", &second);
10271 DoSleep( appData.delayAfterQuit );
10272 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10274 if (first.isr != NULL) {
10275 RemoveInputSource(first.isr);
10277 if (second.isr != NULL) {
10278 RemoveInputSource(second.isr);
10281 ShutDownFrontEnd();
10288 if (appData.debugMode)
10289 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10293 if (gameMode == MachinePlaysWhite ||
10294 gameMode == MachinePlaysBlack) {
10297 DisplayBothClocks();
10299 if (gameMode == PlayFromGameFile) {
10300 if (appData.timeDelay >= 0)
10301 AutoPlayGameLoop();
10302 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10303 Reset(FALSE, TRUE);
10304 SendToICS(ics_prefix);
10305 SendToICS("refresh\n");
10306 } else if (currentMove < forwardMostMove) {
10307 ForwardInner(forwardMostMove);
10309 pauseExamInvalid = FALSE;
10311 switch (gameMode) {
10315 pauseExamForwardMostMove = forwardMostMove;
10316 pauseExamInvalid = FALSE;
10319 case IcsPlayingWhite:
10320 case IcsPlayingBlack:
10324 case PlayFromGameFile:
10325 (void) StopLoadGameTimer();
10329 case BeginningOfGame:
10330 if (appData.icsActive) return;
10331 /* else fall through */
10332 case MachinePlaysWhite:
10333 case MachinePlaysBlack:
10334 case TwoMachinesPlay:
10335 if (forwardMostMove == 0)
10336 return; /* don't pause if no one has moved */
10337 if ((gameMode == MachinePlaysWhite &&
10338 !WhiteOnMove(forwardMostMove)) ||
10339 (gameMode == MachinePlaysBlack &&
10340 WhiteOnMove(forwardMostMove))) {
10353 char title[MSG_SIZ];
10355 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10356 strcpy(title, _("Edit comment"));
10358 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10359 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10360 parseList[currentMove - 1]);
10363 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10370 char *tags = PGNTags(&gameInfo);
10371 EditTagsPopUp(tags);
10378 if (appData.noChessProgram || gameMode == AnalyzeMode)
10381 if (gameMode != AnalyzeFile) {
10382 if (!appData.icsEngineAnalyze) {
10384 if (gameMode != EditGame) return;
10386 ResurrectChessProgram();
10387 SendToProgram("analyze\n", &first);
10388 first.analyzing = TRUE;
10389 /*first.maybeThinking = TRUE;*/
10390 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10391 AnalysisPopUp(_("Analysis"),
10392 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10394 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10399 StartAnalysisClock();
10400 GetTimeMark(&lastNodeCountTime);
10407 if (appData.noChessProgram || gameMode == AnalyzeFile)
10410 if (gameMode != AnalyzeMode) {
10412 if (gameMode != EditGame) return;
10413 ResurrectChessProgram();
10414 SendToProgram("analyze\n", &first);
10415 first.analyzing = TRUE;
10416 /*first.maybeThinking = TRUE;*/
10417 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10418 AnalysisPopUp(_("Analysis"),
10419 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10421 gameMode = AnalyzeFile;
10426 StartAnalysisClock();
10427 GetTimeMark(&lastNodeCountTime);
10432 MachineWhiteEvent()
10435 char *bookHit = NULL;
10437 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10441 if (gameMode == PlayFromGameFile ||
10442 gameMode == TwoMachinesPlay ||
10443 gameMode == Training ||
10444 gameMode == AnalyzeMode ||
10445 gameMode == EndOfGame)
10448 if (gameMode == EditPosition)
10449 EditPositionDone();
10451 if (!WhiteOnMove(currentMove)) {
10452 DisplayError(_("It is not White's turn"), 0);
10456 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10459 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10460 gameMode == AnalyzeFile)
10463 ResurrectChessProgram(); /* in case it isn't running */
10464 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10465 gameMode = MachinePlaysWhite;
10468 gameMode = MachinePlaysWhite;
10472 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10474 if (first.sendName) {
10475 sprintf(buf, "name %s\n", gameInfo.black);
10476 SendToProgram(buf, &first);
10478 if (first.sendTime) {
10479 if (first.useColors) {
10480 SendToProgram("black\n", &first); /*gnu kludge*/
10482 SendTimeRemaining(&first, TRUE);
10484 if (first.useColors) {
10485 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10487 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10488 SetMachineThinkingEnables();
10489 first.maybeThinking = TRUE;
10492 if (appData.autoFlipView && !flipView) {
10493 flipView = !flipView;
10494 DrawPosition(FALSE, NULL);
10495 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10498 if(bookHit) { // [HGM] book: simulate book reply
10499 static char bookMove[MSG_SIZ]; // a bit generous?
10501 programStats.nodes = programStats.depth = programStats.time =
10502 programStats.score = programStats.got_only_move = 0;
10503 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10505 strcpy(bookMove, "move ");
10506 strcat(bookMove, bookHit);
10507 HandleMachineMove(bookMove, &first);
10512 MachineBlackEvent()
10515 char *bookHit = NULL;
10517 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10521 if (gameMode == PlayFromGameFile ||
10522 gameMode == TwoMachinesPlay ||
10523 gameMode == Training ||
10524 gameMode == AnalyzeMode ||
10525 gameMode == EndOfGame)
10528 if (gameMode == EditPosition)
10529 EditPositionDone();
10531 if (WhiteOnMove(currentMove)) {
10532 DisplayError(_("It is not Black's turn"), 0);
10536 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10539 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10540 gameMode == AnalyzeFile)
10543 ResurrectChessProgram(); /* in case it isn't running */
10544 gameMode = MachinePlaysBlack;
10548 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10550 if (first.sendName) {
10551 sprintf(buf, "name %s\n", gameInfo.white);
10552 SendToProgram(buf, &first);
10554 if (first.sendTime) {
10555 if (first.useColors) {
10556 SendToProgram("white\n", &first); /*gnu kludge*/
10558 SendTimeRemaining(&first, FALSE);
10560 if (first.useColors) {
10561 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10563 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10564 SetMachineThinkingEnables();
10565 first.maybeThinking = TRUE;
10568 if (appData.autoFlipView && flipView) {
10569 flipView = !flipView;
10570 DrawPosition(FALSE, NULL);
10571 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10573 if(bookHit) { // [HGM] book: simulate book reply
10574 static char bookMove[MSG_SIZ]; // a bit generous?
10576 programStats.nodes = programStats.depth = programStats.time =
10577 programStats.score = programStats.got_only_move = 0;
10578 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10580 strcpy(bookMove, "move ");
10581 strcat(bookMove, bookHit);
10582 HandleMachineMove(bookMove, &first);
10588 DisplayTwoMachinesTitle()
10591 if (appData.matchGames > 0) {
10592 if (first.twoMachinesColor[0] == 'w') {
10593 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10594 gameInfo.white, gameInfo.black,
10595 first.matchWins, second.matchWins,
10596 matchGame - 1 - (first.matchWins + second.matchWins));
10598 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10599 gameInfo.white, gameInfo.black,
10600 second.matchWins, first.matchWins,
10601 matchGame - 1 - (first.matchWins + second.matchWins));
10604 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10610 TwoMachinesEvent P((void))
10614 ChessProgramState *onmove;
10615 char *bookHit = NULL;
10617 if (appData.noChessProgram) return;
10619 switch (gameMode) {
10620 case TwoMachinesPlay:
10622 case MachinePlaysWhite:
10623 case MachinePlaysBlack:
10624 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10625 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10629 case BeginningOfGame:
10630 case PlayFromGameFile:
10633 if (gameMode != EditGame) return;
10636 EditPositionDone();
10647 forwardMostMove = currentMove;
10648 ResurrectChessProgram(); /* in case first program isn't running */
10650 if (second.pr == NULL) {
10651 StartChessProgram(&second);
10652 if (second.protocolVersion == 1) {
10653 TwoMachinesEventIfReady();
10655 /* kludge: allow timeout for initial "feature" command */
10657 DisplayMessage("", _("Starting second chess program"));
10658 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10662 DisplayMessage("", "");
10663 InitChessProgram(&second, FALSE);
10664 SendToProgram("force\n", &second);
10665 if (startedFromSetupPosition) {
10666 SendBoard(&second, backwardMostMove);
10667 if (appData.debugMode) {
10668 fprintf(debugFP, "Two Machines\n");
10671 for (i = backwardMostMove; i < forwardMostMove; i++) {
10672 SendMoveToProgram(i, &second);
10675 gameMode = TwoMachinesPlay;
10679 DisplayTwoMachinesTitle();
10681 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10687 SendToProgram(first.computerString, &first);
10688 if (first.sendName) {
10689 sprintf(buf, "name %s\n", second.tidy);
10690 SendToProgram(buf, &first);
10692 SendToProgram(second.computerString, &second);
10693 if (second.sendName) {
10694 sprintf(buf, "name %s\n", first.tidy);
10695 SendToProgram(buf, &second);
10699 if (!first.sendTime || !second.sendTime) {
10700 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10701 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10703 if (onmove->sendTime) {
10704 if (onmove->useColors) {
10705 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10707 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10709 if (onmove->useColors) {
10710 SendToProgram(onmove->twoMachinesColor, onmove);
10712 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10713 // SendToProgram("go\n", onmove);
10714 onmove->maybeThinking = TRUE;
10715 SetMachineThinkingEnables();
10719 if(bookHit) { // [HGM] book: simulate book reply
10720 static char bookMove[MSG_SIZ]; // a bit generous?
10722 programStats.nodes = programStats.depth = programStats.time =
10723 programStats.score = programStats.got_only_move = 0;
10724 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10726 strcpy(bookMove, "move ");
10727 strcat(bookMove, bookHit);
10728 HandleMachineMove(bookMove, &first);
10735 if (gameMode == Training) {
10736 SetTrainingModeOff();
10737 gameMode = PlayFromGameFile;
10738 DisplayMessage("", _("Training mode off"));
10740 gameMode = Training;
10741 animateTraining = appData.animate;
10743 /* make sure we are not already at the end of the game */
10744 if (currentMove < forwardMostMove) {
10745 SetTrainingModeOn();
10746 DisplayMessage("", _("Training mode on"));
10748 gameMode = PlayFromGameFile;
10749 DisplayError(_("Already at end of game"), 0);
10758 if (!appData.icsActive) return;
10759 switch (gameMode) {
10760 case IcsPlayingWhite:
10761 case IcsPlayingBlack:
10764 case BeginningOfGame:
10772 EditPositionDone();
10785 gameMode = IcsIdle;
10796 switch (gameMode) {
10798 SetTrainingModeOff();
10800 case MachinePlaysWhite:
10801 case MachinePlaysBlack:
10802 case BeginningOfGame:
10803 SendToProgram("force\n", &first);
10804 SetUserThinkingEnables();
10806 case PlayFromGameFile:
10807 (void) StopLoadGameTimer();
10808 if (gameFileFP != NULL) {
10813 EditPositionDone();
10818 SendToProgram("force\n", &first);
10820 case TwoMachinesPlay:
10821 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10822 ResurrectChessProgram();
10823 SetUserThinkingEnables();
10826 ResurrectChessProgram();
10828 case IcsPlayingBlack:
10829 case IcsPlayingWhite:
10830 DisplayError(_("Warning: You are still playing a game"), 0);
10833 DisplayError(_("Warning: You are still observing a game"), 0);
10836 DisplayError(_("Warning: You are still examining a game"), 0);
10847 first.offeredDraw = second.offeredDraw = 0;
10849 if (gameMode == PlayFromGameFile) {
10850 whiteTimeRemaining = timeRemaining[0][currentMove];
10851 blackTimeRemaining = timeRemaining[1][currentMove];
10855 if (gameMode == MachinePlaysWhite ||
10856 gameMode == MachinePlaysBlack ||
10857 gameMode == TwoMachinesPlay ||
10858 gameMode == EndOfGame) {
10859 i = forwardMostMove;
10860 while (i > currentMove) {
10861 SendToProgram("undo\n", &first);
10864 whiteTimeRemaining = timeRemaining[0][currentMove];
10865 blackTimeRemaining = timeRemaining[1][currentMove];
10866 DisplayBothClocks();
10867 if (whiteFlag || blackFlag) {
10868 whiteFlag = blackFlag = 0;
10873 gameMode = EditGame;
10880 EditPositionEvent()
10882 if (gameMode == EditPosition) {
10888 if (gameMode != EditGame) return;
10890 gameMode = EditPosition;
10893 if (currentMove > 0)
10894 CopyBoard(boards[0], boards[currentMove]);
10896 blackPlaysFirst = !WhiteOnMove(currentMove);
10898 currentMove = forwardMostMove = backwardMostMove = 0;
10899 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10906 /* [DM] icsEngineAnalyze - possible call from other functions */
10907 if (appData.icsEngineAnalyze) {
10908 appData.icsEngineAnalyze = FALSE;
10910 DisplayMessage("",_("Close ICS engine analyze..."));
10912 if (first.analysisSupport && first.analyzing) {
10913 SendToProgram("exit\n", &first);
10914 first.analyzing = FALSE;
10917 thinkOutput[0] = NULLCHAR;
10923 startedFromSetupPosition = TRUE;
10924 InitChessProgram(&first, FALSE);
10925 SendToProgram("force\n", &first);
10926 if (blackPlaysFirst) {
10927 strcpy(moveList[0], "");
10928 strcpy(parseList[0], "");
10929 currentMove = forwardMostMove = backwardMostMove = 1;
10930 CopyBoard(boards[1], boards[0]);
10931 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10933 epStatus[1] = epStatus[0];
10934 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10937 currentMove = forwardMostMove = backwardMostMove = 0;
10939 SendBoard(&first, forwardMostMove);
10940 if (appData.debugMode) {
10941 fprintf(debugFP, "EditPosDone\n");
10944 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10945 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10946 gameMode = EditGame;
10948 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10949 ClearHighlights(); /* [AS] */
10952 /* Pause for `ms' milliseconds */
10953 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10963 } while (SubtractTimeMarks(&m2, &m1) < ms);
10966 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10968 SendMultiLineToICS(buf)
10971 char temp[MSG_SIZ+1], *p;
10978 strncpy(temp, buf, len);
10983 if (*p == '\n' || *p == '\r')
10988 strcat(temp, "\n");
10990 SendToPlayer(temp, strlen(temp));
10994 SetWhiteToPlayEvent()
10996 if (gameMode == EditPosition) {
10997 blackPlaysFirst = FALSE;
10998 DisplayBothClocks(); /* works because currentMove is 0 */
10999 } else if (gameMode == IcsExamining) {
11000 SendToICS(ics_prefix);
11001 SendToICS("tomove white\n");
11006 SetBlackToPlayEvent()
11008 if (gameMode == EditPosition) {
11009 blackPlaysFirst = TRUE;
11010 currentMove = 1; /* kludge */
11011 DisplayBothClocks();
11013 } else if (gameMode == IcsExamining) {
11014 SendToICS(ics_prefix);
11015 SendToICS("tomove black\n");
11020 EditPositionMenuEvent(selection, x, y)
11021 ChessSquare selection;
11025 ChessSquare piece = boards[0][y][x];
11027 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11029 switch (selection) {
11031 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11032 SendToICS(ics_prefix);
11033 SendToICS("bsetup clear\n");
11034 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11035 SendToICS(ics_prefix);
11036 SendToICS("clearboard\n");
11038 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11039 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11040 for (y = 0; y < BOARD_HEIGHT; y++) {
11041 if (gameMode == IcsExamining) {
11042 if (boards[currentMove][y][x] != EmptySquare) {
11043 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11048 boards[0][y][x] = p;
11053 if (gameMode == EditPosition) {
11054 DrawPosition(FALSE, boards[0]);
11059 SetWhiteToPlayEvent();
11063 SetBlackToPlayEvent();
11067 if (gameMode == IcsExamining) {
11068 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11071 boards[0][y][x] = EmptySquare;
11072 DrawPosition(FALSE, boards[0]);
11077 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11078 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11079 selection = (ChessSquare) (PROMOTED piece);
11080 } else if(piece == EmptySquare) selection = WhiteSilver;
11081 else selection = (ChessSquare)((int)piece - 1);
11085 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11086 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11087 selection = (ChessSquare) (DEMOTED piece);
11088 } else if(piece == EmptySquare) selection = BlackSilver;
11089 else selection = (ChessSquare)((int)piece + 1);
11094 if(gameInfo.variant == VariantShatranj ||
11095 gameInfo.variant == VariantXiangqi ||
11096 gameInfo.variant == VariantCourier )
11097 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11102 if(gameInfo.variant == VariantXiangqi)
11103 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11104 if(gameInfo.variant == VariantKnightmate)
11105 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11108 if (gameMode == IcsExamining) {
11109 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11110 PieceToChar(selection), AAA + x, ONE + y);
11113 boards[0][y][x] = selection;
11114 DrawPosition(FALSE, boards[0]);
11122 DropMenuEvent(selection, x, y)
11123 ChessSquare selection;
11126 ChessMove moveType;
11128 switch (gameMode) {
11129 case IcsPlayingWhite:
11130 case MachinePlaysBlack:
11131 if (!WhiteOnMove(currentMove)) {
11132 DisplayMoveError(_("It is Black's turn"));
11135 moveType = WhiteDrop;
11137 case IcsPlayingBlack:
11138 case MachinePlaysWhite:
11139 if (WhiteOnMove(currentMove)) {
11140 DisplayMoveError(_("It is White's turn"));
11143 moveType = BlackDrop;
11146 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11152 if (moveType == BlackDrop && selection < BlackPawn) {
11153 selection = (ChessSquare) ((int) selection
11154 + (int) BlackPawn - (int) WhitePawn);
11156 if (boards[currentMove][y][x] != EmptySquare) {
11157 DisplayMoveError(_("That square is occupied"));
11161 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11167 /* Accept a pending offer of any kind from opponent */
11169 if (appData.icsActive) {
11170 SendToICS(ics_prefix);
11171 SendToICS("accept\n");
11172 } else if (cmailMsgLoaded) {
11173 if (currentMove == cmailOldMove &&
11174 commentList[cmailOldMove] != NULL &&
11175 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11176 "Black offers a draw" : "White offers a draw")) {
11178 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11179 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11181 DisplayError(_("There is no pending offer on this move"), 0);
11182 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11185 /* Not used for offers from chess program */
11192 /* Decline a pending offer of any kind from opponent */
11194 if (appData.icsActive) {
11195 SendToICS(ics_prefix);
11196 SendToICS("decline\n");
11197 } else if (cmailMsgLoaded) {
11198 if (currentMove == cmailOldMove &&
11199 commentList[cmailOldMove] != NULL &&
11200 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11201 "Black offers a draw" : "White offers a draw")) {
11203 AppendComment(cmailOldMove, "Draw declined");
11204 DisplayComment(cmailOldMove - 1, "Draw declined");
11207 DisplayError(_("There is no pending offer on this move"), 0);
11210 /* Not used for offers from chess program */
11217 /* Issue ICS rematch command */
11218 if (appData.icsActive) {
11219 SendToICS(ics_prefix);
11220 SendToICS("rematch\n");
11227 /* Call your opponent's flag (claim a win on time) */
11228 if (appData.icsActive) {
11229 SendToICS(ics_prefix);
11230 SendToICS("flag\n");
11232 switch (gameMode) {
11235 case MachinePlaysWhite:
11238 GameEnds(GameIsDrawn, "Both players ran out of time",
11241 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11243 DisplayError(_("Your opponent is not out of time"), 0);
11246 case MachinePlaysBlack:
11249 GameEnds(GameIsDrawn, "Both players ran out of time",
11252 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11254 DisplayError(_("Your opponent is not out of time"), 0);
11264 /* Offer draw or accept pending draw offer from opponent */
11266 if (appData.icsActive) {
11267 /* Note: tournament rules require draw offers to be
11268 made after you make your move but before you punch
11269 your clock. Currently ICS doesn't let you do that;
11270 instead, you immediately punch your clock after making
11271 a move, but you can offer a draw at any time. */
11273 SendToICS(ics_prefix);
11274 SendToICS("draw\n");
11275 } else if (cmailMsgLoaded) {
11276 if (currentMove == cmailOldMove &&
11277 commentList[cmailOldMove] != NULL &&
11278 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11279 "Black offers a draw" : "White offers a draw")) {
11280 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11281 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11282 } else if (currentMove == cmailOldMove + 1) {
11283 char *offer = WhiteOnMove(cmailOldMove) ?
11284 "White offers a draw" : "Black offers a draw";
11285 AppendComment(currentMove, offer);
11286 DisplayComment(currentMove - 1, offer);
11287 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11289 DisplayError(_("You must make your move before offering a draw"), 0);
11290 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11292 } else if (first.offeredDraw) {
11293 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11295 if (first.sendDrawOffers) {
11296 SendToProgram("draw\n", &first);
11297 userOfferedDraw = TRUE;
11305 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11307 if (appData.icsActive) {
11308 SendToICS(ics_prefix);
11309 SendToICS("adjourn\n");
11311 /* Currently GNU Chess doesn't offer or accept Adjourns */
11319 /* Offer Abort or accept pending Abort offer from opponent */
11321 if (appData.icsActive) {
11322 SendToICS(ics_prefix);
11323 SendToICS("abort\n");
11325 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11332 /* Resign. You can do this even if it's not your turn. */
11334 if (appData.icsActive) {
11335 SendToICS(ics_prefix);
11336 SendToICS("resign\n");
11338 switch (gameMode) {
11339 case MachinePlaysWhite:
11340 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11342 case MachinePlaysBlack:
11343 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11346 if (cmailMsgLoaded) {
11348 if (WhiteOnMove(cmailOldMove)) {
11349 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11351 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11353 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11364 StopObservingEvent()
11366 /* Stop observing current games */
11367 SendToICS(ics_prefix);
11368 SendToICS("unobserve\n");
11372 StopExaminingEvent()
11374 /* Stop observing current game */
11375 SendToICS(ics_prefix);
11376 SendToICS("unexamine\n");
11380 ForwardInner(target)
11385 if (appData.debugMode)
11386 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11387 target, currentMove, forwardMostMove);
11389 if (gameMode == EditPosition)
11392 if (gameMode == PlayFromGameFile && !pausing)
11395 if (gameMode == IcsExamining && pausing)
11396 limit = pauseExamForwardMostMove;
11398 limit = forwardMostMove;
11400 if (target > limit) target = limit;
11402 if (target > 0 && moveList[target - 1][0]) {
11403 int fromX, fromY, toX, toY;
11404 toX = moveList[target - 1][2] - AAA;
11405 toY = moveList[target - 1][3] - ONE;
11406 if (moveList[target - 1][1] == '@') {
11407 if (appData.highlightLastMove) {
11408 SetHighlights(-1, -1, toX, toY);
11411 fromX = moveList[target - 1][0] - AAA;
11412 fromY = moveList[target - 1][1] - ONE;
11413 if (target == currentMove + 1) {
11414 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11416 if (appData.highlightLastMove) {
11417 SetHighlights(fromX, fromY, toX, toY);
11421 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11422 gameMode == Training || gameMode == PlayFromGameFile ||
11423 gameMode == AnalyzeFile) {
11424 while (currentMove < target) {
11425 SendMoveToProgram(currentMove++, &first);
11428 currentMove = target;
11431 if (gameMode == EditGame || gameMode == EndOfGame) {
11432 whiteTimeRemaining = timeRemaining[0][currentMove];
11433 blackTimeRemaining = timeRemaining[1][currentMove];
11435 DisplayBothClocks();
11436 DisplayMove(currentMove - 1);
11437 DrawPosition(FALSE, boards[currentMove]);
11438 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11439 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11440 DisplayComment(currentMove - 1, commentList[currentMove]);
11448 if (gameMode == IcsExamining && !pausing) {
11449 SendToICS(ics_prefix);
11450 SendToICS("forward\n");
11452 ForwardInner(currentMove + 1);
11459 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11460 /* to optimze, we temporarily turn off analysis mode while we feed
11461 * the remaining moves to the engine. Otherwise we get analysis output
11464 if (first.analysisSupport) {
11465 SendToProgram("exit\nforce\n", &first);
11466 first.analyzing = FALSE;
11470 if (gameMode == IcsExamining && !pausing) {
11471 SendToICS(ics_prefix);
11472 SendToICS("forward 999999\n");
11474 ForwardInner(forwardMostMove);
11477 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11478 /* we have fed all the moves, so reactivate analysis mode */
11479 SendToProgram("analyze\n", &first);
11480 first.analyzing = TRUE;
11481 /*first.maybeThinking = TRUE;*/
11482 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11487 BackwardInner(target)
11490 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11492 if (appData.debugMode)
11493 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11494 target, currentMove, forwardMostMove);
11496 if (gameMode == EditPosition) return;
11497 if (currentMove <= backwardMostMove) {
11499 DrawPosition(full_redraw, boards[currentMove]);
11502 if (gameMode == PlayFromGameFile && !pausing)
11505 if (moveList[target][0]) {
11506 int fromX, fromY, toX, toY;
11507 toX = moveList[target][2] - AAA;
11508 toY = moveList[target][3] - ONE;
11509 if (moveList[target][1] == '@') {
11510 if (appData.highlightLastMove) {
11511 SetHighlights(-1, -1, toX, toY);
11514 fromX = moveList[target][0] - AAA;
11515 fromY = moveList[target][1] - ONE;
11516 if (target == currentMove - 1) {
11517 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11519 if (appData.highlightLastMove) {
11520 SetHighlights(fromX, fromY, toX, toY);
11524 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11525 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11526 while (currentMove > target) {
11527 SendToProgram("undo\n", &first);
11531 currentMove = target;
11534 if (gameMode == EditGame || gameMode == EndOfGame) {
11535 whiteTimeRemaining = timeRemaining[0][currentMove];
11536 blackTimeRemaining = timeRemaining[1][currentMove];
11538 DisplayBothClocks();
11539 DisplayMove(currentMove - 1);
11540 DrawPosition(full_redraw, boards[currentMove]);
11541 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11542 // [HGM] PV info: routine tests if comment empty
11543 DisplayComment(currentMove - 1, commentList[currentMove]);
11549 if (gameMode == IcsExamining && !pausing) {
11550 SendToICS(ics_prefix);
11551 SendToICS("backward\n");
11553 BackwardInner(currentMove - 1);
11560 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11561 /* to optimze, we temporarily turn off analysis mode while we undo
11562 * all the moves. Otherwise we get analysis output after each undo.
11564 if (first.analysisSupport) {
11565 SendToProgram("exit\nforce\n", &first);
11566 first.analyzing = FALSE;
11570 if (gameMode == IcsExamining && !pausing) {
11571 SendToICS(ics_prefix);
11572 SendToICS("backward 999999\n");
11574 BackwardInner(backwardMostMove);
11577 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11578 /* we have fed all the moves, so reactivate analysis mode */
11579 SendToProgram("analyze\n", &first);
11580 first.analyzing = TRUE;
11581 /*first.maybeThinking = TRUE;*/
11582 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11589 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11590 if (to >= forwardMostMove) to = forwardMostMove;
11591 if (to <= backwardMostMove) to = backwardMostMove;
11592 if (to < currentMove) {
11602 if (gameMode != IcsExamining) {
11603 DisplayError(_("You are not examining a game"), 0);
11607 DisplayError(_("You can't revert while pausing"), 0);
11610 SendToICS(ics_prefix);
11611 SendToICS("revert\n");
11617 switch (gameMode) {
11618 case MachinePlaysWhite:
11619 case MachinePlaysBlack:
11620 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11621 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11624 if (forwardMostMove < 2) return;
11625 currentMove = forwardMostMove = forwardMostMove - 2;
11626 whiteTimeRemaining = timeRemaining[0][currentMove];
11627 blackTimeRemaining = timeRemaining[1][currentMove];
11628 DisplayBothClocks();
11629 DisplayMove(currentMove - 1);
11630 ClearHighlights();/*!! could figure this out*/
11631 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11632 SendToProgram("remove\n", &first);
11633 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11636 case BeginningOfGame:
11640 case IcsPlayingWhite:
11641 case IcsPlayingBlack:
11642 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11643 SendToICS(ics_prefix);
11644 SendToICS("takeback 2\n");
11646 SendToICS(ics_prefix);
11647 SendToICS("takeback 1\n");
11656 ChessProgramState *cps;
11658 switch (gameMode) {
11659 case MachinePlaysWhite:
11660 if (!WhiteOnMove(forwardMostMove)) {
11661 DisplayError(_("It is your turn"), 0);
11666 case MachinePlaysBlack:
11667 if (WhiteOnMove(forwardMostMove)) {
11668 DisplayError(_("It is your turn"), 0);
11673 case TwoMachinesPlay:
11674 if (WhiteOnMove(forwardMostMove) ==
11675 (first.twoMachinesColor[0] == 'w')) {
11681 case BeginningOfGame:
11685 SendToProgram("?\n", cps);
11689 TruncateGameEvent()
11692 if (gameMode != EditGame) return;
11699 if (forwardMostMove > currentMove) {
11700 if (gameInfo.resultDetails != NULL) {
11701 free(gameInfo.resultDetails);
11702 gameInfo.resultDetails = NULL;
11703 gameInfo.result = GameUnfinished;
11705 forwardMostMove = currentMove;
11706 HistorySet(parseList, backwardMostMove, forwardMostMove,
11714 if (appData.noChessProgram) return;
11715 switch (gameMode) {
11716 case MachinePlaysWhite:
11717 if (WhiteOnMove(forwardMostMove)) {
11718 DisplayError(_("Wait until your turn"), 0);
11722 case BeginningOfGame:
11723 case MachinePlaysBlack:
11724 if (!WhiteOnMove(forwardMostMove)) {
11725 DisplayError(_("Wait until your turn"), 0);
11730 DisplayError(_("No hint available"), 0);
11733 SendToProgram("hint\n", &first);
11734 hintRequested = TRUE;
11740 if (appData.noChessProgram) return;
11741 switch (gameMode) {
11742 case MachinePlaysWhite:
11743 if (WhiteOnMove(forwardMostMove)) {
11744 DisplayError(_("Wait until your turn"), 0);
11748 case BeginningOfGame:
11749 case MachinePlaysBlack:
11750 if (!WhiteOnMove(forwardMostMove)) {
11751 DisplayError(_("Wait until your turn"), 0);
11756 EditPositionDone();
11758 case TwoMachinesPlay:
11763 SendToProgram("bk\n", &first);
11764 bookOutput[0] = NULLCHAR;
11765 bookRequested = TRUE;
11771 char *tags = PGNTags(&gameInfo);
11772 TagsPopUp(tags, CmailMsg());
11776 /* end button procedures */
11779 PrintPosition(fp, move)
11785 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11786 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11787 char c = PieceToChar(boards[move][i][j]);
11788 fputc(c == 'x' ? '.' : c, fp);
11789 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11792 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11793 fprintf(fp, "white to play\n");
11795 fprintf(fp, "black to play\n");
11802 if (gameInfo.white != NULL) {
11803 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11809 /* Find last component of program's own name, using some heuristics */
11811 TidyProgramName(prog, host, buf)
11812 char *prog, *host, buf[MSG_SIZ];
11815 int local = (strcmp(host, "localhost") == 0);
11816 while (!local && (p = strchr(prog, ';')) != NULL) {
11818 while (*p == ' ') p++;
11821 if (*prog == '"' || *prog == '\'') {
11822 q = strchr(prog + 1, *prog);
11824 q = strchr(prog, ' ');
11826 if (q == NULL) q = prog + strlen(prog);
11828 while (p >= prog && *p != '/' && *p != '\\') p--;
11830 if(p == prog && *p == '"') p++;
11831 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11832 memcpy(buf, p, q - p);
11833 buf[q - p] = NULLCHAR;
11841 TimeControlTagValue()
11844 if (!appData.clockMode) {
11846 } else if (movesPerSession > 0) {
11847 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11848 } else if (timeIncrement == 0) {
11849 sprintf(buf, "%ld", timeControl/1000);
11851 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11853 return StrSave(buf);
11859 /* This routine is used only for certain modes */
11860 VariantClass v = gameInfo.variant;
11861 ClearGameInfo(&gameInfo);
11862 gameInfo.variant = v;
11864 switch (gameMode) {
11865 case MachinePlaysWhite:
11866 gameInfo.event = StrSave( appData.pgnEventHeader );
11867 gameInfo.site = StrSave(HostName());
11868 gameInfo.date = PGNDate();
11869 gameInfo.round = StrSave("-");
11870 gameInfo.white = StrSave(first.tidy);
11871 gameInfo.black = StrSave(UserName());
11872 gameInfo.timeControl = TimeControlTagValue();
11875 case MachinePlaysBlack:
11876 gameInfo.event = StrSave( appData.pgnEventHeader );
11877 gameInfo.site = StrSave(HostName());
11878 gameInfo.date = PGNDate();
11879 gameInfo.round = StrSave("-");
11880 gameInfo.white = StrSave(UserName());
11881 gameInfo.black = StrSave(first.tidy);
11882 gameInfo.timeControl = TimeControlTagValue();
11885 case TwoMachinesPlay:
11886 gameInfo.event = StrSave( appData.pgnEventHeader );
11887 gameInfo.site = StrSave(HostName());
11888 gameInfo.date = PGNDate();
11889 if (matchGame > 0) {
11891 sprintf(buf, "%d", matchGame);
11892 gameInfo.round = StrSave(buf);
11894 gameInfo.round = StrSave("-");
11896 if (first.twoMachinesColor[0] == 'w') {
11897 gameInfo.white = StrSave(first.tidy);
11898 gameInfo.black = StrSave(second.tidy);
11900 gameInfo.white = StrSave(second.tidy);
11901 gameInfo.black = StrSave(first.tidy);
11903 gameInfo.timeControl = TimeControlTagValue();
11907 gameInfo.event = StrSave("Edited game");
11908 gameInfo.site = StrSave(HostName());
11909 gameInfo.date = PGNDate();
11910 gameInfo.round = StrSave("-");
11911 gameInfo.white = StrSave("-");
11912 gameInfo.black = StrSave("-");
11916 gameInfo.event = StrSave("Edited position");
11917 gameInfo.site = StrSave(HostName());
11918 gameInfo.date = PGNDate();
11919 gameInfo.round = StrSave("-");
11920 gameInfo.white = StrSave("-");
11921 gameInfo.black = StrSave("-");
11924 case IcsPlayingWhite:
11925 case IcsPlayingBlack:
11930 case PlayFromGameFile:
11931 gameInfo.event = StrSave("Game from non-PGN file");
11932 gameInfo.site = StrSave(HostName());
11933 gameInfo.date = PGNDate();
11934 gameInfo.round = StrSave("-");
11935 gameInfo.white = StrSave("?");
11936 gameInfo.black = StrSave("?");
11945 ReplaceComment(index, text)
11951 while (*text == '\n') text++;
11952 len = strlen(text);
11953 while (len > 0 && text[len - 1] == '\n') len--;
11955 if (commentList[index] != NULL)
11956 free(commentList[index]);
11959 commentList[index] = NULL;
11962 commentList[index] = (char *) malloc(len + 2);
11963 strncpy(commentList[index], text, len);
11964 commentList[index][len] = '\n';
11965 commentList[index][len + 1] = NULLCHAR;
11978 if (ch == '\r') continue;
11980 } while (ch != '\0');
11984 AppendComment(index, text)
11991 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11994 while (*text == '\n') text++;
11995 len = strlen(text);
11996 while (len > 0 && text[len - 1] == '\n') len--;
11998 if (len == 0) return;
12000 if (commentList[index] != NULL) {
12001 old = commentList[index];
12002 oldlen = strlen(old);
12003 commentList[index] = (char *) malloc(oldlen + len + 2);
12004 strcpy(commentList[index], old);
12006 strncpy(&commentList[index][oldlen], text, len);
12007 commentList[index][oldlen + len] = '\n';
12008 commentList[index][oldlen + len + 1] = NULLCHAR;
12010 commentList[index] = (char *) malloc(len + 2);
12011 strncpy(commentList[index], text, len);
12012 commentList[index][len] = '\n';
12013 commentList[index][len + 1] = NULLCHAR;
12017 static char * FindStr( char * text, char * sub_text )
12019 char * result = strstr( text, sub_text );
12021 if( result != NULL ) {
12022 result += strlen( sub_text );
12028 /* [AS] Try to extract PV info from PGN comment */
12029 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12030 char *GetInfoFromComment( int index, char * text )
12034 if( text != NULL && index > 0 ) {
12037 int time = -1, sec = 0, deci;
12038 char * s_eval = FindStr( text, "[%eval " );
12039 char * s_emt = FindStr( text, "[%emt " );
12041 if( s_eval != NULL || s_emt != NULL ) {
12045 if( s_eval != NULL ) {
12046 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12050 if( delim != ']' ) {
12055 if( s_emt != NULL ) {
12059 /* We expect something like: [+|-]nnn.nn/dd */
12062 sep = strchr( text, '/' );
12063 if( sep == NULL || sep < (text+4) ) {
12067 time = -1; sec = -1; deci = -1;
12068 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12069 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12070 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12071 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12075 if( score_lo < 0 || score_lo >= 100 ) {
12079 if(sec >= 0) time = 600*time + 10*sec; else
12080 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12082 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12084 /* [HGM] PV time: now locate end of PV info */
12085 while( *++sep >= '0' && *sep <= '9'); // strip depth
12087 while( *++sep >= '0' && *sep <= '9'); // strip time
12089 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12091 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12092 while(*sep == ' ') sep++;
12103 pvInfoList[index-1].depth = depth;
12104 pvInfoList[index-1].score = score;
12105 pvInfoList[index-1].time = 10*time; // centi-sec
12111 SendToProgram(message, cps)
12113 ChessProgramState *cps;
12115 int count, outCount, error;
12118 if (cps->pr == NULL) return;
12121 if (appData.debugMode) {
12124 fprintf(debugFP, "%ld >%-6s: %s",
12125 SubtractTimeMarks(&now, &programStartTime),
12126 cps->which, message);
12129 count = strlen(message);
12130 outCount = OutputToProcess(cps->pr, message, count, &error);
12131 if (outCount < count && !exiting
12132 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12133 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12134 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12135 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12136 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12137 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12139 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12141 gameInfo.resultDetails = buf;
12143 DisplayFatalError(buf, error, 1);
12148 ReceiveFromProgram(isr, closure, message, count, error)
12149 InputSourceRef isr;
12157 ChessProgramState *cps = (ChessProgramState *)closure;
12159 if (isr != cps->isr) return; /* Killed intentionally */
12163 _("Error: %s chess program (%s) exited unexpectedly"),
12164 cps->which, cps->program);
12165 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12166 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12167 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12168 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12170 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12172 gameInfo.resultDetails = buf;
12174 RemoveInputSource(cps->isr);
12175 DisplayFatalError(buf, 0, 1);
12178 _("Error reading from %s chess program (%s)"),
12179 cps->which, cps->program);
12180 RemoveInputSource(cps->isr);
12182 /* [AS] Program is misbehaving badly... kill it */
12183 if( count == -2 ) {
12184 DestroyChildProcess( cps->pr, 9 );
12188 DisplayFatalError(buf, error, 1);
12193 if ((end_str = strchr(message, '\r')) != NULL)
12194 *end_str = NULLCHAR;
12195 if ((end_str = strchr(message, '\n')) != NULL)
12196 *end_str = NULLCHAR;
12198 if (appData.debugMode) {
12199 TimeMark now; int print = 1;
12200 char *quote = ""; char c; int i;
12202 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12203 char start = message[0];
12204 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12205 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12206 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12207 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12208 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12209 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12210 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12211 { quote = "# "; print = (appData.engineComments == 2); }
12212 message[0] = start; // restore original message
12216 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12217 SubtractTimeMarks(&now, &programStartTime), cps->which,
12223 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12224 if (appData.icsEngineAnalyze) {
12225 if (strstr(message, "whisper") != NULL ||
12226 strstr(message, "kibitz") != NULL ||
12227 strstr(message, "tellics") != NULL) return;
12230 HandleMachineMove(message, cps);
12235 SendTimeControl(cps, mps, tc, inc, sd, st)
12236 ChessProgramState *cps;
12237 int mps, inc, sd, st;
12243 if( timeControl_2 > 0 ) {
12244 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12245 tc = timeControl_2;
12248 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12249 inc /= cps->timeOdds;
12250 st /= cps->timeOdds;
12252 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12255 /* Set exact time per move, normally using st command */
12256 if (cps->stKludge) {
12257 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12259 if (seconds == 0) {
12260 sprintf(buf, "level 1 %d\n", st/60);
12262 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12265 sprintf(buf, "st %d\n", st);
12268 /* Set conventional or incremental time control, using level command */
12269 if (seconds == 0) {
12270 /* Note old gnuchess bug -- minutes:seconds used to not work.
12271 Fixed in later versions, but still avoid :seconds
12272 when seconds is 0. */
12273 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12275 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12276 seconds, inc/1000);
12279 SendToProgram(buf, cps);
12281 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12282 /* Orthogonally, limit search to given depth */
12284 if (cps->sdKludge) {
12285 sprintf(buf, "depth\n%d\n", sd);
12287 sprintf(buf, "sd %d\n", sd);
12289 SendToProgram(buf, cps);
12292 if(cps->nps > 0) { /* [HGM] nps */
12293 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12295 sprintf(buf, "nps %d\n", cps->nps);
12296 SendToProgram(buf, cps);
12301 ChessProgramState *WhitePlayer()
12302 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12304 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12305 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12311 SendTimeRemaining(cps, machineWhite)
12312 ChessProgramState *cps;
12313 int /*boolean*/ machineWhite;
12315 char message[MSG_SIZ];
12318 /* Note: this routine must be called when the clocks are stopped
12319 or when they have *just* been set or switched; otherwise
12320 it will be off by the time since the current tick started.
12322 if (machineWhite) {
12323 time = whiteTimeRemaining / 10;
12324 otime = blackTimeRemaining / 10;
12326 time = blackTimeRemaining / 10;
12327 otime = whiteTimeRemaining / 10;
12329 /* [HGM] translate opponent's time by time-odds factor */
12330 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12331 if (appData.debugMode) {
12332 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12335 if (time <= 0) time = 1;
12336 if (otime <= 0) otime = 1;
12338 sprintf(message, "time %ld\n", time);
12339 SendToProgram(message, cps);
12341 sprintf(message, "otim %ld\n", otime);
12342 SendToProgram(message, cps);
12346 BoolFeature(p, name, loc, cps)
12350 ChessProgramState *cps;
12353 int len = strlen(name);
12355 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12357 sscanf(*p, "%d", &val);
12359 while (**p && **p != ' ') (*p)++;
12360 sprintf(buf, "accepted %s\n", name);
12361 SendToProgram(buf, cps);
12368 IntFeature(p, name, loc, cps)
12372 ChessProgramState *cps;
12375 int len = strlen(name);
12376 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12378 sscanf(*p, "%d", loc);
12379 while (**p && **p != ' ') (*p)++;
12380 sprintf(buf, "accepted %s\n", name);
12381 SendToProgram(buf, cps);
12388 StringFeature(p, name, loc, cps)
12392 ChessProgramState *cps;
12395 int len = strlen(name);
12396 if (strncmp((*p), name, len) == 0
12397 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12399 sscanf(*p, "%[^\"]", loc);
12400 while (**p && **p != '\"') (*p)++;
12401 if (**p == '\"') (*p)++;
12402 sprintf(buf, "accepted %s\n", name);
12403 SendToProgram(buf, cps);
12410 ParseOption(Option *opt, ChessProgramState *cps)
12411 // [HGM] options: process the string that defines an engine option, and determine
12412 // name, type, default value, and allowed value range
12414 char *p, *q, buf[MSG_SIZ];
12415 int n, min = (-1)<<31, max = 1<<31, def;
12417 if(p = strstr(opt->name, " -spin ")) {
12418 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12419 if(max < min) max = min; // enforce consistency
12420 if(def < min) def = min;
12421 if(def > max) def = max;
12426 } else if(p = strstr(opt->name, " -string ")) {
12427 opt->textValue = p+9;
12428 opt->type = TextBox;
12429 } else if(p = strstr(opt->name, " -check ")) {
12430 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12431 opt->value = (def != 0);
12432 opt->type = CheckBox;
12433 } else if(p = strstr(opt->name, " -combo ")) {
12434 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12435 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12436 opt->value = n = 0;
12437 while(q = StrStr(q, " /// ")) {
12438 n++; *q = 0; // count choices, and null-terminate each of them
12440 if(*q == '*') { // remember default, which is marked with * prefix
12444 cps->comboList[cps->comboCnt++] = q;
12446 cps->comboList[cps->comboCnt++] = NULL;
12448 opt->type = ComboBox;
12449 } else if(p = strstr(opt->name, " -button")) {
12450 opt->type = Button;
12451 } else if(p = strstr(opt->name, " -save")) {
12452 opt->type = SaveButton;
12453 } else return FALSE;
12454 *p = 0; // terminate option name
12455 // now look if the command-line options define a setting for this engine option.
12456 if(cps->optionSettings && cps->optionSettings[0])
12457 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12458 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12459 sprintf(buf, "option %s", p);
12460 if(p = strstr(buf, ",")) *p = 0;
12462 SendToProgram(buf, cps);
12468 FeatureDone(cps, val)
12469 ChessProgramState* cps;
12472 DelayedEventCallback cb = GetDelayedEvent();
12473 if ((cb == InitBackEnd3 && cps == &first) ||
12474 (cb == TwoMachinesEventIfReady && cps == &second)) {
12475 CancelDelayedEvent();
12476 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12478 cps->initDone = val;
12481 /* Parse feature command from engine */
12483 ParseFeatures(args, cps)
12485 ChessProgramState *cps;
12493 while (*p == ' ') p++;
12494 if (*p == NULLCHAR) return;
12496 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12497 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12498 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12499 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12500 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12501 if (BoolFeature(&p, "reuse", &val, cps)) {
12502 /* Engine can disable reuse, but can't enable it if user said no */
12503 if (!val) cps->reuse = FALSE;
12506 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12507 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12508 if (gameMode == TwoMachinesPlay) {
12509 DisplayTwoMachinesTitle();
12515 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12516 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12517 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12518 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12519 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12520 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12521 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12522 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12523 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12524 if (IntFeature(&p, "done", &val, cps)) {
12525 FeatureDone(cps, val);
12528 /* Added by Tord: */
12529 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12530 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12531 /* End of additions by Tord */
12533 /* [HGM] added features: */
12534 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12535 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12536 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12537 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12538 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12539 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12540 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12541 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12542 if(cps->nrOptions >= MAX_OPTIONS) {
12544 sprintf(buf, "%s engine has too many options\n", cps->which);
12545 DisplayError(buf, 0);
12549 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12550 /* End of additions by HGM */
12552 /* unknown feature: complain and skip */
12554 while (*q && *q != '=') q++;
12555 sprintf(buf, "rejected %.*s\n", q-p, p);
12556 SendToProgram(buf, cps);
12562 while (*p && *p != '\"') p++;
12563 if (*p == '\"') p++;
12565 while (*p && *p != ' ') p++;
12573 PeriodicUpdatesEvent(newState)
12576 if (newState == appData.periodicUpdates)
12579 appData.periodicUpdates=newState;
12581 /* Display type changes, so update it now */
12584 /* Get the ball rolling again... */
12586 AnalysisPeriodicEvent(1);
12587 StartAnalysisClock();
12592 PonderNextMoveEvent(newState)
12595 if (newState == appData.ponderNextMove) return;
12596 if (gameMode == EditPosition) EditPositionDone();
12598 SendToProgram("hard\n", &first);
12599 if (gameMode == TwoMachinesPlay) {
12600 SendToProgram("hard\n", &second);
12603 SendToProgram("easy\n", &first);
12604 thinkOutput[0] = NULLCHAR;
12605 if (gameMode == TwoMachinesPlay) {
12606 SendToProgram("easy\n", &second);
12609 appData.ponderNextMove = newState;
12613 NewSettingEvent(option, command, value)
12619 if (gameMode == EditPosition) EditPositionDone();
12620 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12621 SendToProgram(buf, &first);
12622 if (gameMode == TwoMachinesPlay) {
12623 SendToProgram(buf, &second);
12628 ShowThinkingEvent()
12629 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12631 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12632 int newState = appData.showThinking
12633 // [HGM] thinking: other features now need thinking output as well
12634 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12636 if (oldState == newState) return;
12637 oldState = newState;
12638 if (gameMode == EditPosition) EditPositionDone();
12640 SendToProgram("post\n", &first);
12641 if (gameMode == TwoMachinesPlay) {
12642 SendToProgram("post\n", &second);
12645 SendToProgram("nopost\n", &first);
12646 thinkOutput[0] = NULLCHAR;
12647 if (gameMode == TwoMachinesPlay) {
12648 SendToProgram("nopost\n", &second);
12651 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12655 AskQuestionEvent(title, question, replyPrefix, which)
12656 char *title; char *question; char *replyPrefix; char *which;
12658 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12659 if (pr == NoProc) return;
12660 AskQuestion(title, question, replyPrefix, pr);
12664 DisplayMove(moveNumber)
12667 char message[MSG_SIZ];
12669 char cpThinkOutput[MSG_SIZ];
12671 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12673 if (moveNumber == forwardMostMove - 1 ||
12674 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12676 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12678 if (strchr(cpThinkOutput, '\n')) {
12679 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12682 *cpThinkOutput = NULLCHAR;
12685 /* [AS] Hide thinking from human user */
12686 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12687 *cpThinkOutput = NULLCHAR;
12688 if( thinkOutput[0] != NULLCHAR ) {
12691 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12692 cpThinkOutput[i] = '.';
12694 cpThinkOutput[i] = NULLCHAR;
12695 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12699 if (moveNumber == forwardMostMove - 1 &&
12700 gameInfo.resultDetails != NULL) {
12701 if (gameInfo.resultDetails[0] == NULLCHAR) {
12702 sprintf(res, " %s", PGNResult(gameInfo.result));
12704 sprintf(res, " {%s} %s",
12705 gameInfo.resultDetails, PGNResult(gameInfo.result));
12711 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12712 DisplayMessage(res, cpThinkOutput);
12714 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12715 WhiteOnMove(moveNumber) ? " " : ".. ",
12716 parseList[moveNumber], res);
12717 DisplayMessage(message, cpThinkOutput);
12722 DisplayAnalysisText(text)
12727 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12728 || appData.icsEngineAnalyze) {
12729 sprintf(buf, "Analysis (%s)", first.tidy);
12730 AnalysisPopUp(buf, text);
12738 while (*str && isspace(*str)) ++str;
12739 while (*str && !isspace(*str)) ++str;
12740 if (!*str) return 1;
12741 while (*str && isspace(*str)) ++str;
12742 if (!*str) return 1;
12750 char lst[MSG_SIZ / 2];
12752 static char *xtra[] = { "", " (--)", " (++)" };
12755 if (programStats.time == 0) {
12756 programStats.time = 1;
12759 if (programStats.got_only_move) {
12760 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12762 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12764 nps = (u64ToDouble(programStats.nodes) /
12765 ((double)programStats.time /100.0));
12767 cs = programStats.time % 100;
12768 s = programStats.time / 100;
12774 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12775 if (programStats.move_name[0] != NULLCHAR) {
12776 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12777 programStats.depth,
12778 programStats.nr_moves-programStats.moves_left,
12779 programStats.nr_moves, programStats.move_name,
12780 ((float)programStats.score)/100.0, lst,
12781 only_one_move(lst)?
12782 xtra[programStats.got_fail] : "",
12783 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12785 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12786 programStats.depth,
12787 programStats.nr_moves-programStats.moves_left,
12788 programStats.nr_moves, ((float)programStats.score)/100.0,
12790 only_one_move(lst)?
12791 xtra[programStats.got_fail] : "",
12792 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12795 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12796 programStats.depth,
12797 ((float)programStats.score)/100.0,
12799 only_one_move(lst)?
12800 xtra[programStats.got_fail] : "",
12801 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12804 DisplayAnalysisText(buf);
12808 DisplayComment(moveNumber, text)
12812 char title[MSG_SIZ];
12813 char buf[8000]; // comment can be long!
12816 if( appData.autoDisplayComment ) {
12817 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12818 strcpy(title, "Comment");
12820 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12821 WhiteOnMove(moveNumber) ? " " : ".. ",
12822 parseList[moveNumber]);
12824 // [HGM] PV info: display PV info together with (or as) comment
12825 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12826 if(text == NULL) text = "";
12827 score = pvInfoList[moveNumber].score;
12828 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12829 depth, (pvInfoList[moveNumber].time+50)/100, text);
12832 } else title[0] = 0;
12835 CommentPopUp(title, text);
12838 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12839 * might be busy thinking or pondering. It can be omitted if your
12840 * gnuchess is configured to stop thinking immediately on any user
12841 * input. However, that gnuchess feature depends on the FIONREAD
12842 * ioctl, which does not work properly on some flavors of Unix.
12846 ChessProgramState *cps;
12849 if (!cps->useSigint) return;
12850 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12851 switch (gameMode) {
12852 case MachinePlaysWhite:
12853 case MachinePlaysBlack:
12854 case TwoMachinesPlay:
12855 case IcsPlayingWhite:
12856 case IcsPlayingBlack:
12859 /* Skip if we know it isn't thinking */
12860 if (!cps->maybeThinking) return;
12861 if (appData.debugMode)
12862 fprintf(debugFP, "Interrupting %s\n", cps->which);
12863 InterruptChildProcess(cps->pr);
12864 cps->maybeThinking = FALSE;
12869 #endif /*ATTENTION*/
12875 if (whiteTimeRemaining <= 0) {
12878 if (appData.icsActive) {
12879 if (appData.autoCallFlag &&
12880 gameMode == IcsPlayingBlack && !blackFlag) {
12881 SendToICS(ics_prefix);
12882 SendToICS("flag\n");
12886 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12888 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12889 if (appData.autoCallFlag) {
12890 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12897 if (blackTimeRemaining <= 0) {
12900 if (appData.icsActive) {
12901 if (appData.autoCallFlag &&
12902 gameMode == IcsPlayingWhite && !whiteFlag) {
12903 SendToICS(ics_prefix);
12904 SendToICS("flag\n");
12908 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12910 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12911 if (appData.autoCallFlag) {
12912 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12925 if (!appData.clockMode || appData.icsActive ||
12926 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12929 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12931 if ( !WhiteOnMove(forwardMostMove) )
12932 /* White made time control */
12933 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12934 /* [HGM] time odds: correct new time quota for time odds! */
12935 / WhitePlayer()->timeOdds;
12937 /* Black made time control */
12938 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12939 / WhitePlayer()->other->timeOdds;
12943 DisplayBothClocks()
12945 int wom = gameMode == EditPosition ?
12946 !blackPlaysFirst : WhiteOnMove(currentMove);
12947 DisplayWhiteClock(whiteTimeRemaining, wom);
12948 DisplayBlackClock(blackTimeRemaining, !wom);
12952 /* Timekeeping seems to be a portability nightmare. I think everyone
12953 has ftime(), but I'm really not sure, so I'm including some ifdefs
12954 to use other calls if you don't. Clocks will be less accurate if
12955 you have neither ftime nor gettimeofday.
12958 /* VS 2008 requires the #include outside of the function */
12959 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12960 #include <sys/timeb.h>
12963 /* Get the current time as a TimeMark */
12968 #if HAVE_GETTIMEOFDAY
12970 struct timeval timeVal;
12971 struct timezone timeZone;
12973 gettimeofday(&timeVal, &timeZone);
12974 tm->sec = (long) timeVal.tv_sec;
12975 tm->ms = (int) (timeVal.tv_usec / 1000L);
12977 #else /*!HAVE_GETTIMEOFDAY*/
12980 // include <sys/timeb.h> / moved to just above start of function
12981 struct timeb timeB;
12984 tm->sec = (long) timeB.time;
12985 tm->ms = (int) timeB.millitm;
12987 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12988 tm->sec = (long) time(NULL);
12994 /* Return the difference in milliseconds between two
12995 time marks. We assume the difference will fit in a long!
12998 SubtractTimeMarks(tm2, tm1)
12999 TimeMark *tm2, *tm1;
13001 return 1000L*(tm2->sec - tm1->sec) +
13002 (long) (tm2->ms - tm1->ms);
13007 * Code to manage the game clocks.
13009 * In tournament play, black starts the clock and then white makes a move.
13010 * We give the human user a slight advantage if he is playing white---the
13011 * clocks don't run until he makes his first move, so it takes zero time.
13012 * Also, we don't account for network lag, so we could get out of sync
13013 * with GNU Chess's clock -- but then, referees are always right.
13016 static TimeMark tickStartTM;
13017 static long intendedTickLength;
13020 NextTickLength(timeRemaining)
13021 long timeRemaining;
13023 long nominalTickLength, nextTickLength;
13025 if (timeRemaining > 0L && timeRemaining <= 10000L)
13026 nominalTickLength = 100L;
13028 nominalTickLength = 1000L;
13029 nextTickLength = timeRemaining % nominalTickLength;
13030 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13032 return nextTickLength;
13035 /* Adjust clock one minute up or down */
13037 AdjustClock(Boolean which, int dir)
13039 if(which) blackTimeRemaining += 60000*dir;
13040 else whiteTimeRemaining += 60000*dir;
13041 DisplayBothClocks();
13044 /* Stop clocks and reset to a fresh time control */
13048 (void) StopClockTimer();
13049 if (appData.icsActive) {
13050 whiteTimeRemaining = blackTimeRemaining = 0;
13051 } else { /* [HGM] correct new time quote for time odds */
13052 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13053 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13055 if (whiteFlag || blackFlag) {
13057 whiteFlag = blackFlag = FALSE;
13059 DisplayBothClocks();
13062 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13064 /* Decrement running clock by amount of time that has passed */
13068 long timeRemaining;
13069 long lastTickLength, fudge;
13072 if (!appData.clockMode) return;
13073 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13077 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13079 /* Fudge if we woke up a little too soon */
13080 fudge = intendedTickLength - lastTickLength;
13081 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13083 if (WhiteOnMove(forwardMostMove)) {
13084 if(whiteNPS >= 0) lastTickLength = 0;
13085 timeRemaining = whiteTimeRemaining -= lastTickLength;
13086 DisplayWhiteClock(whiteTimeRemaining - fudge,
13087 WhiteOnMove(currentMove));
13089 if(blackNPS >= 0) lastTickLength = 0;
13090 timeRemaining = blackTimeRemaining -= lastTickLength;
13091 DisplayBlackClock(blackTimeRemaining - fudge,
13092 !WhiteOnMove(currentMove));
13095 if (CheckFlags()) return;
13098 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13099 StartClockTimer(intendedTickLength);
13101 /* if the time remaining has fallen below the alarm threshold, sound the
13102 * alarm. if the alarm has sounded and (due to a takeback or time control
13103 * with increment) the time remaining has increased to a level above the
13104 * threshold, reset the alarm so it can sound again.
13107 if (appData.icsActive && appData.icsAlarm) {
13109 /* make sure we are dealing with the user's clock */
13110 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13111 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13114 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13115 alarmSounded = FALSE;
13116 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13118 alarmSounded = TRUE;
13124 /* A player has just moved, so stop the previously running
13125 clock and (if in clock mode) start the other one.
13126 We redisplay both clocks in case we're in ICS mode, because
13127 ICS gives us an update to both clocks after every move.
13128 Note that this routine is called *after* forwardMostMove
13129 is updated, so the last fractional tick must be subtracted
13130 from the color that is *not* on move now.
13135 long lastTickLength;
13137 int flagged = FALSE;
13141 if (StopClockTimer() && appData.clockMode) {
13142 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13143 if (WhiteOnMove(forwardMostMove)) {
13144 if(blackNPS >= 0) lastTickLength = 0;
13145 blackTimeRemaining -= lastTickLength;
13146 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13147 // if(pvInfoList[forwardMostMove-1].time == -1)
13148 pvInfoList[forwardMostMove-1].time = // use GUI time
13149 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13151 if(whiteNPS >= 0) lastTickLength = 0;
13152 whiteTimeRemaining -= lastTickLength;
13153 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13154 // if(pvInfoList[forwardMostMove-1].time == -1)
13155 pvInfoList[forwardMostMove-1].time =
13156 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13158 flagged = CheckFlags();
13160 CheckTimeControl();
13162 if (flagged || !appData.clockMode) return;
13164 switch (gameMode) {
13165 case MachinePlaysBlack:
13166 case MachinePlaysWhite:
13167 case BeginningOfGame:
13168 if (pausing) return;
13172 case PlayFromGameFile:
13181 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13182 whiteTimeRemaining : blackTimeRemaining);
13183 StartClockTimer(intendedTickLength);
13187 /* Stop both clocks */
13191 long lastTickLength;
13194 if (!StopClockTimer()) return;
13195 if (!appData.clockMode) return;
13197 printf("Debug: in stop clocks\n");
13201 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13202 if (WhiteOnMove(forwardMostMove)) {
13203 if(whiteNPS >= 0) lastTickLength = 0;
13204 whiteTimeRemaining -= lastTickLength;
13205 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13207 if(blackNPS >= 0) lastTickLength = 0;
13208 blackTimeRemaining -= lastTickLength;
13209 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13212 printf("Debug: end stop clocks\n");
13215 /* Start clock of player on move. Time may have been reset, so
13216 if clock is already running, stop and restart it. */
13220 (void) StopClockTimer(); /* in case it was running already */
13221 DisplayBothClocks();
13222 if (CheckFlags()) return;
13224 if (!appData.clockMode) return;
13225 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13227 GetTimeMark(&tickStartTM);
13228 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13229 whiteTimeRemaining : blackTimeRemaining);
13231 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13232 whiteNPS = blackNPS = -1;
13233 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13234 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13235 whiteNPS = first.nps;
13236 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13237 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13238 blackNPS = first.nps;
13239 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13240 whiteNPS = second.nps;
13241 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13242 blackNPS = second.nps;
13243 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13245 StartClockTimer(intendedTickLength);
13252 long second, minute, hour, day;
13254 static char buf[32];
13256 if (ms > 0 && ms <= 9900) {
13257 /* convert milliseconds to tenths, rounding up */
13258 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13260 sprintf(buf, " %03.1f ", tenths/10.0);
13264 /* convert milliseconds to seconds, rounding up */
13265 /* use floating point to avoid strangeness of integer division
13266 with negative dividends on many machines */
13267 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13274 day = second / (60 * 60 * 24);
13275 second = second % (60 * 60 * 24);
13276 hour = second / (60 * 60);
13277 second = second % (60 * 60);
13278 minute = second / 60;
13279 second = second % 60;
13282 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13283 sign, day, hour, minute, second);
13285 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13287 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13294 * This is necessary because some C libraries aren't ANSI C compliant yet.
13297 StrStr(string, match)
13298 char *string, *match;
13302 length = strlen(match);
13304 for (i = strlen(string) - length; i >= 0; i--, string++)
13305 if (!strncmp(match, string, length))
13312 StrCaseStr(string, match)
13313 char *string, *match;
13317 length = strlen(match);
13319 for (i = strlen(string) - length; i >= 0; i--, string++) {
13320 for (j = 0; j < length; j++) {
13321 if (ToLower(match[j]) != ToLower(string[j]))
13324 if (j == length) return string;
13338 c1 = ToLower(*s1++);
13339 c2 = ToLower(*s2++);
13340 if (c1 > c2) return 1;
13341 if (c1 < c2) return -1;
13342 if (c1 == NULLCHAR) return 0;
13351 return isupper(c) ? tolower(c) : c;
13359 return islower(c) ? toupper(c) : c;
13361 #endif /* !_amigados */
13369 if ((ret = (char *) malloc(strlen(s) + 1))) {
13376 StrSavePtr(s, savePtr)
13377 char *s, **savePtr;
13382 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13383 strcpy(*savePtr, s);
13395 clock = time((time_t *)NULL);
13396 tm = localtime(&clock);
13397 sprintf(buf, "%04d.%02d.%02d",
13398 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13399 return StrSave(buf);
13404 PositionToFEN(move, overrideCastling)
13406 char *overrideCastling;
13408 int i, j, fromX, fromY, toX, toY;
13415 whiteToPlay = (gameMode == EditPosition) ?
13416 !blackPlaysFirst : (move % 2 == 0);
13419 /* Piece placement data */
13420 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13422 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13423 if (boards[move][i][j] == EmptySquare) {
13425 } else { ChessSquare piece = boards[move][i][j];
13426 if (emptycount > 0) {
13427 if(emptycount<10) /* [HGM] can be >= 10 */
13428 *p++ = '0' + emptycount;
13429 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13432 if(PieceToChar(piece) == '+') {
13433 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13435 piece = (ChessSquare)(DEMOTED piece);
13437 *p++ = PieceToChar(piece);
13439 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13440 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13445 if (emptycount > 0) {
13446 if(emptycount<10) /* [HGM] can be >= 10 */
13447 *p++ = '0' + emptycount;
13448 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13455 /* [HGM] print Crazyhouse or Shogi holdings */
13456 if( gameInfo.holdingsWidth ) {
13457 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13459 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13460 piece = boards[move][i][BOARD_WIDTH-1];
13461 if( piece != EmptySquare )
13462 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13463 *p++ = PieceToChar(piece);
13465 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13466 piece = boards[move][BOARD_HEIGHT-i-1][0];
13467 if( piece != EmptySquare )
13468 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13469 *p++ = PieceToChar(piece);
13472 if( q == p ) *p++ = '-';
13478 *p++ = whiteToPlay ? 'w' : 'b';
13481 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13482 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13484 if(nrCastlingRights) {
13486 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13487 /* [HGM] write directly from rights */
13488 if(castlingRights[move][2] >= 0 &&
13489 castlingRights[move][0] >= 0 )
13490 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13491 if(castlingRights[move][2] >= 0 &&
13492 castlingRights[move][1] >= 0 )
13493 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13494 if(castlingRights[move][5] >= 0 &&
13495 castlingRights[move][3] >= 0 )
13496 *p++ = castlingRights[move][3] + AAA;
13497 if(castlingRights[move][5] >= 0 &&
13498 castlingRights[move][4] >= 0 )
13499 *p++ = castlingRights[move][4] + AAA;
13502 /* [HGM] write true castling rights */
13503 if( nrCastlingRights == 6 ) {
13504 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13505 castlingRights[move][2] >= 0 ) *p++ = 'K';
13506 if(castlingRights[move][1] == BOARD_LEFT &&
13507 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13508 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13509 castlingRights[move][5] >= 0 ) *p++ = 'k';
13510 if(castlingRights[move][4] == BOARD_LEFT &&
13511 castlingRights[move][5] >= 0 ) *p++ = 'q';
13514 if (q == p) *p++ = '-'; /* No castling rights */
13518 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13519 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13520 /* En passant target square */
13521 if (move > backwardMostMove) {
13522 fromX = moveList[move - 1][0] - AAA;
13523 fromY = moveList[move - 1][1] - ONE;
13524 toX = moveList[move - 1][2] - AAA;
13525 toY = moveList[move - 1][3] - ONE;
13526 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13527 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13528 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13530 /* 2-square pawn move just happened */
13532 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13543 /* [HGM] find reversible plies */
13544 { int i = 0, j=move;
13546 if (appData.debugMode) { int k;
13547 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13548 for(k=backwardMostMove; k<=forwardMostMove; k++)
13549 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13553 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13554 if( j == backwardMostMove ) i += initialRulePlies;
13555 sprintf(p, "%d ", i);
13556 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13558 /* Fullmove number */
13559 sprintf(p, "%d", (move / 2) + 1);
13561 return StrSave(buf);
13565 ParseFEN(board, blackPlaysFirst, fen)
13567 int *blackPlaysFirst;
13577 /* [HGM] by default clear Crazyhouse holdings, if present */
13578 if(gameInfo.holdingsWidth) {
13579 for(i=0; i<BOARD_HEIGHT; i++) {
13580 board[i][0] = EmptySquare; /* black holdings */
13581 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13582 board[i][1] = (ChessSquare) 0; /* black counts */
13583 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13587 /* Piece placement data */
13588 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13591 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13592 if (*p == '/') p++;
13593 emptycount = gameInfo.boardWidth - j;
13594 while (emptycount--)
13595 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13597 #if(BOARD_SIZE >= 10)
13598 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13599 p++; emptycount=10;
13600 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13601 while (emptycount--)
13602 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13604 } else if (isdigit(*p)) {
13605 emptycount = *p++ - '0';
13606 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13607 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13608 while (emptycount--)
13609 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13610 } else if (*p == '+' || isalpha(*p)) {
13611 if (j >= gameInfo.boardWidth) return FALSE;
13613 piece = CharToPiece(*++p);
13614 if(piece == EmptySquare) return FALSE; /* unknown piece */
13615 piece = (ChessSquare) (PROMOTED piece ); p++;
13616 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13617 } else piece = CharToPiece(*p++);
13619 if(piece==EmptySquare) return FALSE; /* unknown piece */
13620 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13621 piece = (ChessSquare) (PROMOTED piece);
13622 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13625 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13631 while (*p == '/' || *p == ' ') p++;
13633 /* [HGM] look for Crazyhouse holdings here */
13634 while(*p==' ') p++;
13635 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13637 if(*p == '-' ) *p++; /* empty holdings */ else {
13638 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13639 /* if we would allow FEN reading to set board size, we would */
13640 /* have to add holdings and shift the board read so far here */
13641 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13643 if((int) piece >= (int) BlackPawn ) {
13644 i = (int)piece - (int)BlackPawn;
13645 i = PieceToNumber((ChessSquare)i);
13646 if( i >= gameInfo.holdingsSize ) return FALSE;
13647 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13648 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13650 i = (int)piece - (int)WhitePawn;
13651 i = PieceToNumber((ChessSquare)i);
13652 if( i >= gameInfo.holdingsSize ) return FALSE;
13653 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13654 board[i][BOARD_WIDTH-2]++; /* black holdings */
13658 if(*p == ']') *p++;
13661 while(*p == ' ') p++;
13666 *blackPlaysFirst = FALSE;
13669 *blackPlaysFirst = TRUE;
13675 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13676 /* return the extra info in global variiables */
13678 /* set defaults in case FEN is incomplete */
13679 FENepStatus = EP_UNKNOWN;
13680 for(i=0; i<nrCastlingRights; i++ ) {
13681 FENcastlingRights[i] =
13682 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13683 } /* assume possible unless obviously impossible */
13684 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13685 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13686 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13687 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13688 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13689 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13692 while(*p==' ') p++;
13693 if(nrCastlingRights) {
13694 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13695 /* castling indicator present, so default becomes no castlings */
13696 for(i=0; i<nrCastlingRights; i++ ) {
13697 FENcastlingRights[i] = -1;
13700 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13701 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13702 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13703 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13704 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13706 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13707 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13708 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13712 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13713 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13714 FENcastlingRights[2] = whiteKingFile;
13717 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13718 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13719 FENcastlingRights[2] = whiteKingFile;
13722 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13723 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13724 FENcastlingRights[5] = blackKingFile;
13727 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13728 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13729 FENcastlingRights[5] = blackKingFile;
13732 default: /* FRC castlings */
13733 if(c >= 'a') { /* black rights */
13734 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13735 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13736 if(i == BOARD_RGHT) break;
13737 FENcastlingRights[5] = i;
13739 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13740 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13742 FENcastlingRights[3] = c;
13744 FENcastlingRights[4] = c;
13745 } else { /* white rights */
13746 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13747 if(board[0][i] == WhiteKing) break;
13748 if(i == BOARD_RGHT) break;
13749 FENcastlingRights[2] = i;
13750 c -= AAA - 'a' + 'A';
13751 if(board[0][c] >= WhiteKing) break;
13753 FENcastlingRights[0] = c;
13755 FENcastlingRights[1] = c;
13759 if (appData.debugMode) {
13760 fprintf(debugFP, "FEN castling rights:");
13761 for(i=0; i<nrCastlingRights; i++)
13762 fprintf(debugFP, " %d", FENcastlingRights[i]);
13763 fprintf(debugFP, "\n");
13766 while(*p==' ') p++;
13769 /* read e.p. field in games that know e.p. capture */
13770 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13771 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13773 p++; FENepStatus = EP_NONE;
13775 char c = *p++ - AAA;
13777 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13778 if(*p >= '0' && *p <='9') *p++;
13784 if(sscanf(p, "%d", &i) == 1) {
13785 FENrulePlies = i; /* 50-move ply counter */
13786 /* (The move number is still ignored) */
13793 EditPositionPasteFEN(char *fen)
13796 Board initial_position;
13798 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13799 DisplayError(_("Bad FEN position in clipboard"), 0);
13802 int savedBlackPlaysFirst = blackPlaysFirst;
13803 EditPositionEvent();
13804 blackPlaysFirst = savedBlackPlaysFirst;
13805 CopyBoard(boards[0], initial_position);
13806 /* [HGM] copy FEN attributes as well */
13808 initialRulePlies = FENrulePlies;
13809 epStatus[0] = FENepStatus;
13810 for( i=0; i<nrCastlingRights; i++ )
13811 castlingRights[0][i] = FENcastlingRights[i];
13813 EditPositionDone();
13814 DisplayBothClocks();
13815 DrawPosition(FALSE, boards[currentMove]);