2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
240 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
246 /* States for ics_getting_history */
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
254 /* whosays values for GameEnds */
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
266 /* Different types of move when calling RegisterMove */
268 #define CMAIL_RESIGN 1
270 #define CMAIL_ACCEPT 3
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
277 /* Telnet protocol constants */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
290 assert( dst != NULL );
291 assert( src != NULL );
294 strncpy( dst, src, count );
295 dst[ count-1 ] = '\0';
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
305 assert( dst != NULL );
306 assert( src != NULL );
309 dst_len = strlen(dst);
311 assert( count > dst_len ); /* Buffer size must be greater than current length */
313 safeStrCpy( dst + dst_len, src, count - dst_len );
319 /* Some compiler can't cast u64 to double
320 * This function do the job for us:
322 * We use the highest bit for cast, this only
323 * works if the highest bit is not
324 * in use (This should not happen)
326 * We used this for all compiler
329 u64ToDouble(u64 value)
332 u64 tmp = value & u64Const(0x7fffffffffffffff);
333 r = (double)(s64)tmp;
334 if (value & u64Const(0x8000000000000000))
335 r += 9.2233720368547758080e18; /* 2^63 */
339 /* Fake up flags for now, as we aren't keeping track of castling
340 availability yet. [HGM] Change of logic: the flag now only
341 indicates the type of castlings allowed by the rule of the game.
342 The actual rights themselves are maintained in the array
343 castlingRights, as part of the game history, and are not probed
349 int flags = F_ALL_CASTLE_OK;
350 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351 switch (gameInfo.variant) {
353 flags &= ~F_ALL_CASTLE_OK;
354 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355 flags |= F_IGNORE_CHECK;
357 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
360 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
362 case VariantKriegspiel:
363 flags |= F_KRIEGSPIEL_CAPTURE;
365 case VariantCapaRandom:
366 case VariantFischeRandom:
367 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368 case VariantNoCastle:
369 case VariantShatranj:
371 flags &= ~F_ALL_CASTLE_OK;
379 FILE *gameFileFP, *debugFP;
382 [AS] Note: sometimes, the sscanf() function is used to parse the input
383 into a fixed-size buffer. Because of this, we must be prepared to
384 receive strings as long as the size of the input buffer, which is currently
385 set to 4K for Windows and 8K for the rest.
386 So, we must either allocate sufficiently large buffers here, or
387 reduce the size of the input buffer in the input reading part.
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
394 ChessProgramState first, second;
396 /* premove variables */
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
446 /* animateTraining preserves the state of appData.animate
447 * when Training mode is activated. This allows the
448 * response to be animated when appData.animate == TRUE and
449 * appData.animateDragging == TRUE.
451 Boolean animateTraining;
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char epStatus[MAX_MOVES];
460 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int initialRulePlies, FENrulePlies;
466 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 ChessSquare FIDEArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<MAX_MOVES; i++ ) {
662 pvInfoList[i].depth = -1;
664 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 appData.clockMode = FALSE;
813 first.sendTime = second.sendTime = 0;
817 /* Override some settings from environment variables, for backward
818 compatibility. Unfortunately it's not feasible to have the env
819 vars just set defaults, at least in xboard. Ugh.
821 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826 if (appData.noChessProgram) {
827 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828 sprintf(programVersion, "%s", PACKAGE_STRING);
833 while (*q != ' ' && *q != NULLCHAR) q++;
835 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837 sprintf(programVersion, "%s + ", PACKAGE_STRING);
838 strncat(programVersion, p, q - p);
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 if (!appData.icsActive) {
848 /* Check for variants that are supported only in ICS mode,
849 or not at all. Some that are accepted here nevertheless
850 have bugs; see comments below.
852 VariantClass variant = StringToVariant(appData.variant);
854 case VariantBughouse: /* need four players and two boards */
855 case VariantKriegspiel: /* need to hide pieces and move details */
856 /* case VariantFischeRandom: (Fabien: moved below) */
857 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
862 case VariantLoadable:
872 sprintf(buf, _("Unknown variant name %s"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
876 case VariantXiangqi: /* [HGM] repetition rules not implemented */
877 case VariantFairy: /* [HGM] TestLegality definitely off! */
878 case VariantGothic: /* [HGM] should work */
879 case VariantCapablanca: /* [HGM] should work */
880 case VariantCourier: /* [HGM] initial forced moves not implemented */
881 case VariantShogi: /* [HGM] drops not tested for legality */
882 case VariantKnightmate: /* [HGM] should work */
883 case VariantCylinder: /* [HGM] untested */
884 case VariantFalcon: /* [HGM] untested */
885 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886 offboard interposition not understood */
887 case VariantNormal: /* definitely works! */
888 case VariantWildCastle: /* pieces not automatically shuffled */
889 case VariantNoCastle: /* pieces not automatically shuffled */
890 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891 case VariantLosers: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantSuicide: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantGiveaway: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantTwoKings: /* should work */
898 case VariantAtomic: /* should work except for win condition */
899 case Variant3Check: /* should work except for win condition */
900 case VariantShatranj: /* should work except for all win conditions */
901 case VariantBerolina: /* might work if TestLegality is off */
902 case VariantCapaRandom: /* should work */
903 case VariantJanus: /* should work */
904 case VariantSuper: /* experimental */
905 case VariantGreat: /* experimental, requires legality testing to be off */
910 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
911 InitEngineUCI( installDir, &second );
914 int NextIntegerFromString( char ** str, long * value )
919 while( *s == ' ' || *s == '\t' ) {
925 if( *s >= '0' && *s <= '9' ) {
926 while( *s >= '0' && *s <= '9' ) {
927 *value = *value * 10 + (*s - '0');
939 int NextTimeControlFromString( char ** str, long * value )
942 int result = NextIntegerFromString( str, &temp );
945 *value = temp * 60; /* Minutes */
948 result = NextIntegerFromString( str, &temp );
949 *value += temp; /* Seconds */
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 { /* [HGM] routine added to read '+moves/time' for secondary time control */
958 int result = -1; long temp, temp2;
960 if(**str != '+') return -1; // old params remain in force!
962 if( NextTimeControlFromString( str, &temp ) ) return -1;
965 /* time only: incremental or sudden-death time control */
966 if(**str == '+') { /* increment follows; read it */
968 if(result = NextIntegerFromString( str, &temp2)) return -1;
971 *moves = 0; *tc = temp * 1000;
973 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
975 (*str)++; /* classical time control */
976 result = NextTimeControlFromString( str, &temp2);
985 int GetTimeQuota(int movenr)
986 { /* [HGM] get time to add from the multi-session time-control string */
987 int moves=1; /* kludge to force reading of first session */
988 long time, increment;
989 char *s = fullTimeControlString;
991 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995 if(movenr == -1) return time; /* last move before new session */
996 if(!moves) return increment; /* current session is incremental */
997 if(movenr >= 0) movenr -= moves; /* we already finished this session */
998 } while(movenr >= -1); /* try again for next session */
1000 return 0; // no new time quota on this move
1004 ParseTimeControl(tc, ti, mps)
1010 int matched, min, sec;
1012 matched = sscanf(tc, "%d:%d", &min, &sec);
1014 timeControl = min * 60 * 1000;
1015 } else if (matched == 2) {
1016 timeControl = (min * 60 + sec) * 1000;
1025 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1028 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029 else sprintf(buf, "+%s+%d", tc, ti);
1032 sprintf(buf, "+%d/%s", mps, tc);
1033 else sprintf(buf, "+%s", tc);
1035 fullTimeControlString = StrSave(buf);
1037 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042 /* Parse second time control */
1045 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1053 timeControl_2 = tc2 * 1000;
1063 timeControl = tc1 * 1000;
1067 timeIncrement = ti * 1000; /* convert to ms */
1068 movesPerSession = 0;
1071 movesPerSession = mps;
1079 if (appData.debugMode) {
1080 fprintf(debugFP, "%s\n", programVersion);
1083 if (appData.matchGames > 0) {
1084 appData.matchMode = TRUE;
1085 } else if (appData.matchMode) {
1086 appData.matchGames = 1;
1088 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089 appData.matchGames = appData.sameColorGames;
1090 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1095 if (appData.noChessProgram || first.protocolVersion == 1) {
1098 /* kludge: allow timeout for initial "feature" commands */
1100 DisplayMessage("", _("Starting chess program"));
1101 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106 InitBackEnd3 P((void))
1108 GameMode initialMode;
1112 InitChessProgram(&first, startedFromSetupPosition);
1115 if (appData.icsActive) {
1117 /* [DM] Make a console window if needed [HGM] merged ifs */
1122 if (*appData.icsCommPort != NULLCHAR) {
1123 sprintf(buf, _("Could not open comm port %s"),
1124 appData.icsCommPort);
1126 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1127 appData.icsHost, appData.icsPort);
1129 DisplayFatalError(buf, err, 1);
1134 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137 } else if (appData.noChessProgram) {
1143 if (*appData.cmailGameName != NULLCHAR) {
1145 OpenLoopback(&cmailPR);
1147 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151 DisplayMessage("", "");
1152 if (StrCaseCmp(appData.initialMode, "") == 0) {
1153 initialMode = BeginningOfGame;
1154 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155 initialMode = TwoMachinesPlay;
1156 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157 initialMode = AnalyzeFile;
1158 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159 initialMode = AnalyzeMode;
1160 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161 initialMode = MachinePlaysWhite;
1162 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163 initialMode = MachinePlaysBlack;
1164 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165 initialMode = EditGame;
1166 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167 initialMode = EditPosition;
1168 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169 initialMode = Training;
1171 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172 DisplayFatalError(buf, 0, 2);
1176 if (appData.matchMode) {
1177 /* Set up machine vs. machine match */
1178 if (appData.noChessProgram) {
1179 DisplayFatalError(_("Can't have a match with no chess programs"),
1185 if (*appData.loadGameFile != NULLCHAR) {
1186 int index = appData.loadGameIndex; // [HGM] autoinc
1187 if(index<0) lastIndex = index = 1;
1188 if (!LoadGameFromFile(appData.loadGameFile,
1190 appData.loadGameFile, FALSE)) {
1191 DisplayFatalError(_("Bad game file"), 0, 1);
1194 } else if (*appData.loadPositionFile != NULLCHAR) {
1195 int index = appData.loadPositionIndex; // [HGM] autoinc
1196 if(index<0) lastIndex = index = 1;
1197 if (!LoadPositionFromFile(appData.loadPositionFile,
1199 appData.loadPositionFile)) {
1200 DisplayFatalError(_("Bad position file"), 0, 1);
1205 } else if (*appData.cmailGameName != NULLCHAR) {
1206 /* Set up cmail mode */
1207 ReloadCmailMsgEvent(TRUE);
1209 /* Set up other modes */
1210 if (initialMode == AnalyzeFile) {
1211 if (*appData.loadGameFile == NULLCHAR) {
1212 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216 if (*appData.loadGameFile != NULLCHAR) {
1217 (void) LoadGameFromFile(appData.loadGameFile,
1218 appData.loadGameIndex,
1219 appData.loadGameFile, TRUE);
1220 } else if (*appData.loadPositionFile != NULLCHAR) {
1221 (void) LoadPositionFromFile(appData.loadPositionFile,
1222 appData.loadPositionIndex,
1223 appData.loadPositionFile);
1224 /* [HGM] try to make self-starting even after FEN load */
1225 /* to allow automatic setup of fairy variants with wtm */
1226 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227 gameMode = BeginningOfGame;
1228 setboardSpoiledMachineBlack = 1;
1230 /* [HGM] loadPos: make that every new game uses the setup */
1231 /* from file as long as we do not switch variant */
1232 if(!blackPlaysFirst) { int i;
1233 startedFromPositionFile = TRUE;
1234 CopyBoard(filePosition, boards[0]);
1235 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1238 if (initialMode == AnalyzeMode) {
1239 if (appData.noChessProgram) {
1240 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1243 if (appData.icsActive) {
1244 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248 } else if (initialMode == AnalyzeFile) {
1249 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250 ShowThinkingEvent();
1252 AnalysisPeriodicEvent(1);
1253 } else if (initialMode == MachinePlaysWhite) {
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259 if (appData.icsActive) {
1260 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264 MachineWhiteEvent();
1265 } else if (initialMode == MachinePlaysBlack) {
1266 if (appData.noChessProgram) {
1267 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271 if (appData.icsActive) {
1272 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276 MachineBlackEvent();
1277 } else if (initialMode == TwoMachinesPlay) {
1278 if (appData.noChessProgram) {
1279 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283 if (appData.icsActive) {
1284 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289 } else if (initialMode == EditGame) {
1291 } else if (initialMode == EditPosition) {
1292 EditPositionEvent();
1293 } else if (initialMode == Training) {
1294 if (*appData.loadGameFile == NULLCHAR) {
1295 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1304 * Establish will establish a contact to a remote host.port.
1305 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306 * used to talk to the host.
1307 * Returns 0 if okay, error code if not.
1314 if (*appData.icsCommPort != NULLCHAR) {
1315 /* Talk to the host through a serial comm port */
1316 return OpenCommPort(appData.icsCommPort, &icsPR);
1318 } else if (*appData.gateway != NULLCHAR) {
1319 if (*appData.remoteShell == NULLCHAR) {
1320 /* Use the rcmd protocol to run telnet program on a gateway host */
1321 snprintf(buf, sizeof(buf), "%s %s %s",
1322 appData.telnetProgram, appData.icsHost, appData.icsPort);
1323 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1326 /* Use the rsh program to run telnet program on a gateway host */
1327 if (*appData.remoteUser == NULLCHAR) {
1328 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329 appData.gateway, appData.telnetProgram,
1330 appData.icsHost, appData.icsPort);
1332 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333 appData.remoteShell, appData.gateway,
1334 appData.remoteUser, appData.telnetProgram,
1335 appData.icsHost, appData.icsPort);
1337 return StartChildProcess(buf, "", &icsPR);
1340 } else if (appData.useTelnet) {
1341 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1344 /* TCP socket interface differs somewhat between
1345 Unix and NT; handle details in the front end.
1347 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352 show_bytes(fp, buf, count)
1358 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359 fprintf(fp, "\\%03o", *buf & 0xff);
1368 /* Returns an errno value */
1370 OutputMaybeTelnet(pr, message, count, outError)
1376 char buf[8192], *p, *q, *buflim;
1377 int left, newcount, outcount;
1379 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380 *appData.gateway != NULLCHAR) {
1381 if (appData.debugMode) {
1382 fprintf(debugFP, ">ICS: ");
1383 show_bytes(debugFP, message, count);
1384 fprintf(debugFP, "\n");
1386 return OutputToProcess(pr, message, count, outError);
1389 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1396 if (appData.debugMode) {
1397 fprintf(debugFP, ">ICS: ");
1398 show_bytes(debugFP, buf, newcount);
1399 fprintf(debugFP, "\n");
1401 outcount = OutputToProcess(pr, buf, newcount, outError);
1402 if (outcount < newcount) return -1; /* to be sure */
1409 } else if (((unsigned char) *p) == TN_IAC) {
1410 *q++ = (char) TN_IAC;
1417 if (appData.debugMode) {
1418 fprintf(debugFP, ">ICS: ");
1419 show_bytes(debugFP, buf, newcount);
1420 fprintf(debugFP, "\n");
1422 outcount = OutputToProcess(pr, buf, newcount, outError);
1423 if (outcount < newcount) return -1; /* to be sure */
1428 read_from_player(isr, closure, message, count, error)
1435 int outError, outCount;
1436 static int gotEof = 0;
1438 /* Pass data read from player on to ICS */
1441 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442 if (outCount < count) {
1443 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445 } else if (count < 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448 } else if (gotEof++ > 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1458 int count, outCount, outError;
1460 if (icsPR == NULL) return;
1463 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464 if (outCount < count) {
1465 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469 /* This is used for sending logon scripts to the ICS. Sending
1470 without a delay causes problems when using timestamp on ICC
1471 (at least on my machine). */
1473 SendToICSDelayed(s,msdelay)
1477 int count, outCount, outError;
1479 if (icsPR == NULL) return;
1482 if (appData.debugMode) {
1483 fprintf(debugFP, ">ICS: ");
1484 show_bytes(debugFP, s, count);
1485 fprintf(debugFP, "\n");
1487 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489 if (outCount < count) {
1490 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1495 /* Remove all highlighting escape sequences in s
1496 Also deletes any suffix starting with '('
1499 StripHighlightAndTitle(s)
1502 static char retbuf[MSG_SIZ];
1505 while (*s != NULLCHAR) {
1506 while (*s == '\033') {
1507 while (*s != NULLCHAR && !isalpha(*s)) s++;
1508 if (*s != NULLCHAR) s++;
1510 while (*s != NULLCHAR && *s != '\033') {
1511 if (*s == '(' || *s == '[') {
1522 /* Remove all highlighting escape sequences in s */
1527 static char retbuf[MSG_SIZ];
1530 while (*s != NULLCHAR) {
1531 while (*s == '\033') {
1532 while (*s != NULLCHAR && !isalpha(*s)) s++;
1533 if (*s != NULLCHAR) s++;
1535 while (*s != NULLCHAR && *s != '\033') {
1543 char *variantNames[] = VARIANT_NAMES;
1548 return variantNames[v];
1552 /* Identify a variant from the strings the chess servers use or the
1553 PGN Variant tag names we use. */
1560 VariantClass v = VariantNormal;
1561 int i, found = FALSE;
1566 /* [HGM] skip over optional board-size prefixes */
1567 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569 while( *e++ != '_');
1572 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573 if (StrCaseStr(e, variantNames[i])) {
1574 v = (VariantClass) i;
1581 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582 || StrCaseStr(e, "wild/fr")
1583 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584 v = VariantFischeRandom;
1585 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586 (i = 1, p = StrCaseStr(e, "w"))) {
1588 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1595 case 0: /* FICS only, actually */
1597 /* Castling legal even if K starts on d-file */
1598 v = VariantWildCastle;
1603 /* Castling illegal even if K & R happen to start in
1604 normal positions. */
1605 v = VariantNoCastle;
1618 /* Castling legal iff K & R start in normal positions */
1624 /* Special wilds for position setup; unclear what to do here */
1625 v = VariantLoadable;
1628 /* Bizarre ICC game */
1629 v = VariantTwoKings;
1632 v = VariantKriegspiel;
1638 v = VariantFischeRandom;
1641 v = VariantCrazyhouse;
1644 v = VariantBughouse;
1650 /* Not quite the same as FICS suicide! */
1651 v = VariantGiveaway;
1657 v = VariantShatranj;
1660 /* Temporary names for future ICC types. The name *will* change in
1661 the next xboard/WinBoard release after ICC defines it. */
1699 v = VariantCapablanca;
1702 v = VariantKnightmate;
1708 v = VariantCylinder;
1714 v = VariantCapaRandom;
1717 v = VariantBerolina;
1729 /* Found "wild" or "w" in the string but no number;
1730 must assume it's normal chess. */
1734 sprintf(buf, _("Unknown wild type %d"), wnum);
1735 DisplayError(buf, 0);
1741 if (appData.debugMode) {
1742 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743 e, wnum, VariantName(v));
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752 advance *index beyond it, and set leftover_start to the new value of
1753 *index; else return FALSE. If pattern contains the character '*', it
1754 matches any sequence of characters not containing '\r', '\n', or the
1755 character following the '*' (if any), and the matched sequence(s) are
1756 copied into star_match.
1759 looking_at(buf, index, pattern)
1764 char *bufp = &buf[*index], *patternp = pattern;
1766 char *matchp = star_match[0];
1769 if (*patternp == NULLCHAR) {
1770 *index = leftover_start = bufp - buf;
1774 if (*bufp == NULLCHAR) return FALSE;
1775 if (*patternp == '*') {
1776 if (*bufp == *(patternp + 1)) {
1778 matchp = star_match[++star_count];
1782 } else if (*bufp == '\n' || *bufp == '\r') {
1784 if (*patternp == NULLCHAR)
1789 *matchp++ = *bufp++;
1793 if (*patternp != *bufp) return FALSE;
1800 SendToPlayer(data, length)
1804 int error, outCount;
1805 outCount = OutputToProcess(NoProc, data, length, &error);
1806 if (outCount < length) {
1807 DisplayFatalError(_("Error writing to display"), error, 1);
1812 PackHolding(packed, holding)
1824 switch (runlength) {
1835 sprintf(q, "%d", runlength);
1847 /* Telnet protocol requests from the front end */
1849 TelnetRequest(ddww, option)
1850 unsigned char ddww, option;
1852 unsigned char msg[3];
1853 int outCount, outError;
1855 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857 if (appData.debugMode) {
1858 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1874 sprintf(buf1, "%d", ddww);
1883 sprintf(buf2, "%d", option);
1886 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1891 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DO, TN_ECHO);
1907 if (!appData.icsActive) return;
1908 TelnetRequest(TN_DONT, TN_ECHO);
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 /* put the holdings sent to us by the server on the board holdings area */
1915 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1919 if(gameInfo.holdingsWidth < 2) return;
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1953 VariantSwitch(Board board, VariantClass newVariant)
1955 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959 startedFromPositionFile = FALSE;
1960 if(gameInfo.variant == newVariant) return;
1962 /* [HGM] This routine is called each time an assignment is made to
1963 * gameInfo.variant during a game, to make sure the board sizes
1964 * are set to match the new variant. If that means adding or deleting
1965 * holdings, we shift the playing board accordingly
1966 * This kludge is needed because in ICS observe mode, we get boards
1967 * of an ongoing game without knowing the variant, and learn about the
1968 * latter only later. This can be because of the move list we requested,
1969 * in which case the game history is refilled from the beginning anyway,
1970 * but also when receiving holdings of a crazyhouse game. In the latter
1971 * case we want to add those holdings to the already received position.
1975 if (appData.debugMode) {
1976 fprintf(debugFP, "Switch board from %s to %s\n",
1977 VariantName(gameInfo.variant), VariantName(newVariant));
1978 setbuf(debugFP, NULL);
1980 shuffleOpenings = 0; /* [HGM] shuffle */
1981 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 switch(newVariant) {
1984 newWidth = 9; newHeight = 9;
1985 gameInfo.holdingsSize = 7;
1986 case VariantBughouse:
1987 case VariantCrazyhouse:
1988 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = gameInfo.holdingsSize = 0;
1993 if(newWidth != gameInfo.boardWidth ||
1994 newHeight != gameInfo.boardHeight ||
1995 newHoldingsWidth != gameInfo.holdingsWidth ) {
1997 /* shift position to new playing area, if needed */
1998 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999 for(i=0; i<BOARD_HEIGHT; i++)
2000 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003 for(i=0; i<newHeight; i++) {
2004 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 gameInfo.boardWidth = newWidth;
2015 gameInfo.boardHeight = newHeight;
2016 gameInfo.holdingsWidth = newHoldingsWidth;
2017 gameInfo.variant = newVariant;
2018 InitDrawingSizes(-2, 0);
2020 /* [HGM] The following should definitely be solved in a better way */
2022 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024 saveEP = epStatus[0];
2026 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2028 epStatus[0] = saveEP;
2029 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030 CopyBoard(tempBoard, board); /* restore position received from ICS */
2032 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034 forwardMostMove = oldForwardMostMove;
2035 backwardMostMove = oldBackwardMostMove;
2036 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2039 static int loggedOn = FALSE;
2041 /*-- Game start info cache: --*/
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2054 read_from_ics(isr, closure, data, count, error)
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2071 static int started = STARTED_NONE;
2072 static char parse[20000];
2073 static int parse_pos = 0;
2074 static char buf[BUF_SIZE + 1];
2075 static int firstTime = TRUE, intfSet = FALSE;
2076 static ColorClass prevColor = ColorNormal;
2077 static int savingComment = FALSE;
2083 int backup; /* [DM] For zippy color lines */
2086 if (appData.debugMode) {
2088 fprintf(debugFP, "<ICS: ");
2089 show_bytes(debugFP, data, count);
2090 fprintf(debugFP, "\n");
2094 if (appData.debugMode) { int f = forwardMostMove;
2095 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* Copy in new characters, removing nulls and \r's */
2107 buf_len = leftover_len;
2108 for (i = 0; i < count; i++) {
2109 if (data[i] != NULLCHAR && data[i] != '\r')
2110 buf[buf_len++] = data[i];
2111 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2112 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
2113 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2116 buf[buf_len] = NULLCHAR;
2117 next_out = leftover_len;
2121 while (i < buf_len) {
2122 /* Deal with part of the TELNET option negotiation
2123 protocol. We refuse to do anything beyond the
2124 defaults, except that we allow the WILL ECHO option,
2125 which ICS uses to turn off password echoing when we are
2126 directly connected to it. We reject this option
2127 if localLineEditing mode is on (always on in xboard)
2128 and we are talking to port 23, which might be a real
2129 telnet server that will try to keep WILL ECHO on permanently.
2131 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2132 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2133 unsigned char option;
2135 switch ((unsigned char) buf[++i]) {
2137 if (appData.debugMode)
2138 fprintf(debugFP, "\n<WILL ");
2139 switch (option = (unsigned char) buf[++i]) {
2141 if (appData.debugMode)
2142 fprintf(debugFP, "ECHO ");
2143 /* Reply only if this is a change, according
2144 to the protocol rules. */
2145 if (remoteEchoOption) break;
2146 if (appData.localLineEditing &&
2147 atoi(appData.icsPort) == TN_PORT) {
2148 TelnetRequest(TN_DONT, TN_ECHO);
2151 TelnetRequest(TN_DO, TN_ECHO);
2152 remoteEchoOption = TRUE;
2156 if (appData.debugMode)
2157 fprintf(debugFP, "%d ", option);
2158 /* Whatever this is, we don't want it. */
2159 TelnetRequest(TN_DONT, option);
2164 if (appData.debugMode)
2165 fprintf(debugFP, "\n<WONT ");
2166 switch (option = (unsigned char) buf[++i]) {
2168 if (appData.debugMode)
2169 fprintf(debugFP, "ECHO ");
2170 /* Reply only if this is a change, according
2171 to the protocol rules. */
2172 if (!remoteEchoOption) break;
2174 TelnetRequest(TN_DONT, TN_ECHO);
2175 remoteEchoOption = FALSE;
2178 if (appData.debugMode)
2179 fprintf(debugFP, "%d ", (unsigned char) option);
2180 /* Whatever this is, it must already be turned
2181 off, because we never agree to turn on
2182 anything non-default, so according to the
2183 protocol rules, we don't reply. */
2188 if (appData.debugMode)
2189 fprintf(debugFP, "\n<DO ");
2190 switch (option = (unsigned char) buf[++i]) {
2192 /* Whatever this is, we refuse to do it. */
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", option);
2195 TelnetRequest(TN_WONT, option);
2200 if (appData.debugMode)
2201 fprintf(debugFP, "\n<DONT ");
2202 switch (option = (unsigned char) buf[++i]) {
2204 if (appData.debugMode)
2205 fprintf(debugFP, "%d ", option);
2206 /* Whatever this is, we are already not doing
2207 it, because we never agree to do anything
2208 non-default, so according to the protocol
2209 rules, we don't reply. */
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<IAC ");
2216 /* Doubled IAC; pass it through */
2220 if (appData.debugMode)
2221 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2222 /* Drop all other telnet commands on the floor */
2225 if (oldi > next_out)
2226 SendToPlayer(&buf[next_out], oldi - next_out);
2232 /* OK, this at least will *usually* work */
2233 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2237 if (loggedOn && !intfSet) {
2238 if (ics_type == ICS_ICC) {
2240 "/set-quietly interface %s\n/set-quietly style 12\n",
2243 } else if (ics_type == ICS_CHESSNET) {
2244 sprintf(str, "/style 12\n");
2246 strcpy(str, "alias $ @\n$set interface ");
2247 strcat(str, programVersion);
2248 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2250 strcat(str, "$iset nohighlight 1\n");
2252 strcat(str, "$iset lock 1\n$style 12\n");
2258 if (started == STARTED_COMMENT) {
2259 /* Accumulate characters in comment */
2260 parse[parse_pos++] = buf[i];
2261 if (buf[i] == '\n') {
2262 parse[parse_pos] = NULLCHAR;
2263 if(!suppressKibitz) // [HGM] kibitz
2264 AppendComment(forwardMostMove, StripHighlight(parse));
2265 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2266 int nrDigit = 0, nrAlph = 0, i;
2267 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2268 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2269 parse[parse_pos] = NULLCHAR;
2270 // try to be smart: if it does not look like search info, it should go to
2271 // ICS interaction window after all, not to engine-output window.
2272 for(i=0; i<parse_pos; i++) { // count letters and digits
2273 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2274 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2275 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2277 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2278 int depth=0; float score;
2279 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2280 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2281 pvInfoList[forwardMostMove-1].depth = depth;
2282 pvInfoList[forwardMostMove-1].score = 100*score;
2284 OutputKibitz(suppressKibitz, parse);
2287 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2288 SendToPlayer(tmp, strlen(tmp));
2291 started = STARTED_NONE;
2293 /* Don't match patterns against characters in chatter */
2298 if (started == STARTED_CHATTER) {
2299 if (buf[i] != '\n') {
2300 /* Don't match patterns against characters in chatter */
2304 started = STARTED_NONE;
2307 /* Kludge to deal with rcmd protocol */
2308 if (firstTime && looking_at(buf, &i, "\001*")) {
2309 DisplayFatalError(&buf[1], 0, 1);
2315 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2318 if (appData.debugMode)
2319 fprintf(debugFP, "ics_type %d\n", ics_type);
2322 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2323 ics_type = ICS_FICS;
2325 if (appData.debugMode)
2326 fprintf(debugFP, "ics_type %d\n", ics_type);
2329 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2330 ics_type = ICS_CHESSNET;
2332 if (appData.debugMode)
2333 fprintf(debugFP, "ics_type %d\n", ics_type);
2338 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2339 looking_at(buf, &i, "Logging you in as \"*\"") ||
2340 looking_at(buf, &i, "will be \"*\""))) {
2341 strcpy(ics_handle, star_match[0]);
2345 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2347 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2348 DisplayIcsInteractionTitle(buf);
2349 have_set_title = TRUE;
2352 /* skip finger notes */
2353 if (started == STARTED_NONE &&
2354 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2355 (buf[i] == '1' && buf[i+1] == '0')) &&
2356 buf[i+2] == ':' && buf[i+3] == ' ') {
2357 started = STARTED_CHATTER;
2362 /* skip formula vars */
2363 if (started == STARTED_NONE &&
2364 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2365 started = STARTED_CHATTER;
2371 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2372 if (appData.autoKibitz && started == STARTED_NONE &&
2373 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2374 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2375 if(looking_at(buf, &i, "* kibitzes: ") &&
2376 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2377 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2378 suppressKibitz = TRUE;
2379 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2380 && (gameMode == IcsPlayingWhite)) ||
2381 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2382 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2383 started = STARTED_CHATTER; // own kibitz we simply discard
2385 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2386 parse_pos = 0; parse[0] = NULLCHAR;
2387 savingComment = TRUE;
2388 suppressKibitz = gameMode != IcsObserving ? 2 :
2389 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2393 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2394 started = STARTED_CHATTER;
2395 suppressKibitz = TRUE;
2397 } // [HGM] kibitz: end of patch
2399 if (appData.zippyTalk || appData.zippyPlay) {
2400 /* [DM] Backup address for color zippy lines */
2404 if (loggedOn == TRUE)
2405 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2406 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2408 if (ZippyControl(buf, &i) ||
2409 ZippyConverse(buf, &i) ||
2410 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2412 if (!appData.colorize) continue;
2416 } // [DM] 'else { ' deleted
2417 if (/* Don't color "message" or "messages" output */
2418 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2419 looking_at(buf, &i, "*. * at *:*: ") ||
2420 looking_at(buf, &i, "--* (*:*): ") ||
2421 /* Regular tells and says */
2422 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2423 looking_at(buf, &i, "* (your partner) tells you: ") ||
2424 looking_at(buf, &i, "* says: ") ||
2425 /* Message notifications (same color as tells) */
2426 looking_at(buf, &i, "* has left a message ") ||
2427 looking_at(buf, &i, "* just sent you a message:\n") ||
2428 /* Whispers and kibitzes */
2429 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2430 looking_at(buf, &i, "* kibitzes: ") ||
2432 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2434 if (tkind == 1 && strchr(star_match[0], ':')) {
2435 /* Avoid "tells you:" spoofs in channels */
2438 if (star_match[0][0] == NULLCHAR ||
2439 strchr(star_match[0], ' ') ||
2440 (tkind == 3 && strchr(star_match[1], ' '))) {
2441 /* Reject bogus matches */
2444 if (appData.colorize) {
2445 if (oldi > next_out) {
2446 SendToPlayer(&buf[next_out], oldi - next_out);
2451 Colorize(ColorTell, FALSE);
2452 curColor = ColorTell;
2455 Colorize(ColorKibitz, FALSE);
2456 curColor = ColorKibitz;
2459 p = strrchr(star_match[1], '(');
2466 Colorize(ColorChannel1, FALSE);
2467 curColor = ColorChannel1;
2469 Colorize(ColorChannel, FALSE);
2470 curColor = ColorChannel;
2474 curColor = ColorNormal;
2478 if (started == STARTED_NONE && appData.autoComment &&
2479 (gameMode == IcsObserving ||
2480 gameMode == IcsPlayingWhite ||
2481 gameMode == IcsPlayingBlack)) {
2482 parse_pos = i - oldi;
2483 memcpy(parse, &buf[oldi], parse_pos);
2484 parse[parse_pos] = NULLCHAR;
2485 started = STARTED_COMMENT;
2486 savingComment = TRUE;
2488 started = STARTED_CHATTER;
2489 savingComment = FALSE;
2496 if (looking_at(buf, &i, "* s-shouts: ") ||
2497 looking_at(buf, &i, "* c-shouts: ")) {
2498 if (appData.colorize) {
2499 if (oldi > next_out) {
2500 SendToPlayer(&buf[next_out], oldi - next_out);
2503 Colorize(ColorSShout, FALSE);
2504 curColor = ColorSShout;
2507 started = STARTED_CHATTER;
2511 if (looking_at(buf, &i, "--->")) {
2516 if (looking_at(buf, &i, "* shouts: ") ||
2517 looking_at(buf, &i, "--> ")) {
2518 if (appData.colorize) {
2519 if (oldi > next_out) {
2520 SendToPlayer(&buf[next_out], oldi - next_out);
2523 Colorize(ColorShout, FALSE);
2524 curColor = ColorShout;
2527 started = STARTED_CHATTER;
2531 if (looking_at( buf, &i, "Challenge:")) {
2532 if (appData.colorize) {
2533 if (oldi > next_out) {
2534 SendToPlayer(&buf[next_out], oldi - next_out);
2537 Colorize(ColorChallenge, FALSE);
2538 curColor = ColorChallenge;
2544 if (looking_at(buf, &i, "* offers you") ||
2545 looking_at(buf, &i, "* offers to be") ||
2546 looking_at(buf, &i, "* would like to") ||
2547 looking_at(buf, &i, "* requests to") ||
2548 looking_at(buf, &i, "Your opponent offers") ||
2549 looking_at(buf, &i, "Your opponent requests")) {
2551 if (appData.colorize) {
2552 if (oldi > next_out) {
2553 SendToPlayer(&buf[next_out], oldi - next_out);
2556 Colorize(ColorRequest, FALSE);
2557 curColor = ColorRequest;
2562 if (looking_at(buf, &i, "* (*) seeking")) {
2563 if (appData.colorize) {
2564 if (oldi > next_out) {
2565 SendToPlayer(&buf[next_out], oldi - next_out);
2568 Colorize(ColorSeek, FALSE);
2569 curColor = ColorSeek;
2574 if (looking_at(buf, &i, "\\ ")) {
2575 if (prevColor != ColorNormal) {
2576 if (oldi > next_out) {
2577 SendToPlayer(&buf[next_out], oldi - next_out);
2580 Colorize(prevColor, TRUE);
2581 curColor = prevColor;
2583 if (savingComment) {
2584 parse_pos = i - oldi;
2585 memcpy(parse, &buf[oldi], parse_pos);
2586 parse[parse_pos] = NULLCHAR;
2587 started = STARTED_COMMENT;
2589 started = STARTED_CHATTER;
2594 if (looking_at(buf, &i, "Black Strength :") ||
2595 looking_at(buf, &i, "<<< style 10 board >>>") ||
2596 looking_at(buf, &i, "<10>") ||
2597 looking_at(buf, &i, "#@#")) {
2598 /* Wrong board style */
2600 SendToICS(ics_prefix);
2601 SendToICS("set style 12\n");
2602 SendToICS(ics_prefix);
2603 SendToICS("refresh\n");
2607 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2609 have_sent_ICS_logon = 1;
2613 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2614 (looking_at(buf, &i, "\n<12> ") ||
2615 looking_at(buf, &i, "<12> "))) {
2617 if (oldi > next_out) {
2618 SendToPlayer(&buf[next_out], oldi - next_out);
2621 started = STARTED_BOARD;
2626 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2627 looking_at(buf, &i, "<b1> ")) {
2628 if (oldi > next_out) {
2629 SendToPlayer(&buf[next_out], oldi - next_out);
2632 started = STARTED_HOLDINGS;
2637 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2639 /* Header for a move list -- first line */
2641 switch (ics_getting_history) {
2645 case BeginningOfGame:
2646 /* User typed "moves" or "oldmoves" while we
2647 were idle. Pretend we asked for these
2648 moves and soak them up so user can step
2649 through them and/or save them.
2652 gameMode = IcsObserving;
2655 ics_getting_history = H_GOT_UNREQ_HEADER;
2657 case EditGame: /*?*/
2658 case EditPosition: /*?*/
2659 /* Should above feature work in these modes too? */
2660 /* For now it doesn't */
2661 ics_getting_history = H_GOT_UNWANTED_HEADER;
2664 ics_getting_history = H_GOT_UNWANTED_HEADER;
2669 /* Is this the right one? */
2670 if (gameInfo.white && gameInfo.black &&
2671 strcmp(gameInfo.white, star_match[0]) == 0 &&
2672 strcmp(gameInfo.black, star_match[2]) == 0) {
2674 ics_getting_history = H_GOT_REQ_HEADER;
2677 case H_GOT_REQ_HEADER:
2678 case H_GOT_UNREQ_HEADER:
2679 case H_GOT_UNWANTED_HEADER:
2680 case H_GETTING_MOVES:
2681 /* Should not happen */
2682 DisplayError(_("Error gathering move list: two headers"), 0);
2683 ics_getting_history = H_FALSE;
2687 /* Save player ratings into gameInfo if needed */
2688 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2689 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2690 (gameInfo.whiteRating == -1 ||
2691 gameInfo.blackRating == -1)) {
2693 gameInfo.whiteRating = string_to_rating(star_match[1]);
2694 gameInfo.blackRating = string_to_rating(star_match[3]);
2695 if (appData.debugMode)
2696 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2697 gameInfo.whiteRating, gameInfo.blackRating);
2702 if (looking_at(buf, &i,
2703 "* * match, initial time: * minute*, increment: * second")) {
2704 /* Header for a move list -- second line */
2705 /* Initial board will follow if this is a wild game */
2706 if (gameInfo.event != NULL) free(gameInfo.event);
2707 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2708 gameInfo.event = StrSave(str);
2709 /* [HGM] we switched variant. Translate boards if needed. */
2710 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2714 if (looking_at(buf, &i, "Move ")) {
2715 /* Beginning of a move list */
2716 switch (ics_getting_history) {
2718 /* Normally should not happen */
2719 /* Maybe user hit reset while we were parsing */
2722 /* Happens if we are ignoring a move list that is not
2723 * the one we just requested. Common if the user
2724 * tries to observe two games without turning off
2727 case H_GETTING_MOVES:
2728 /* Should not happen */
2729 DisplayError(_("Error gathering move list: nested"), 0);
2730 ics_getting_history = H_FALSE;
2732 case H_GOT_REQ_HEADER:
2733 ics_getting_history = H_GETTING_MOVES;
2734 started = STARTED_MOVES;
2736 if (oldi > next_out) {
2737 SendToPlayer(&buf[next_out], oldi - next_out);
2740 case H_GOT_UNREQ_HEADER:
2741 ics_getting_history = H_GETTING_MOVES;
2742 started = STARTED_MOVES_NOHIDE;
2745 case H_GOT_UNWANTED_HEADER:
2746 ics_getting_history = H_FALSE;
2752 if (looking_at(buf, &i, "% ") ||
2753 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2754 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2755 savingComment = FALSE;
2758 case STARTED_MOVES_NOHIDE:
2759 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2760 parse[parse_pos + i - oldi] = NULLCHAR;
2761 ParseGameHistory(parse);
2763 if (appData.zippyPlay && first.initDone) {
2764 FeedMovesToProgram(&first, forwardMostMove);
2765 if (gameMode == IcsPlayingWhite) {
2766 if (WhiteOnMove(forwardMostMove)) {
2767 if (first.sendTime) {
2768 if (first.useColors) {
2769 SendToProgram("black\n", &first);
2771 SendTimeRemaining(&first, TRUE);
2774 if (first.useColors) {
2775 SendToProgram("white\ngo\n", &first);
2777 SendToProgram("go\n", &first);
2780 if (first.useColors) {
2781 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785 first.maybeThinking = TRUE;
2787 if (first.usePlayother) {
2788 if (first.sendTime) {
2789 SendTimeRemaining(&first, TRUE);
2791 SendToProgram("playother\n", &first);
2797 } else if (gameMode == IcsPlayingBlack) {
2798 if (!WhiteOnMove(forwardMostMove)) {
2799 if (first.sendTime) {
2800 if (first.useColors) {
2801 SendToProgram("white\n", &first);
2803 SendTimeRemaining(&first, FALSE);
2806 if (first.useColors) {
2807 SendToProgram("black\ngo\n", &first);
2809 SendToProgram("go\n", &first);
2812 if (first.useColors) {
2813 SendToProgram("black\n", &first);
2815 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2817 first.maybeThinking = TRUE;
2819 if (first.usePlayother) {
2820 if (first.sendTime) {
2821 SendTimeRemaining(&first, FALSE);
2823 SendToProgram("playother\n", &first);
2832 if (gameMode == IcsObserving && ics_gamenum == -1) {
2833 /* Moves came from oldmoves or moves command
2834 while we weren't doing anything else.
2836 currentMove = forwardMostMove;
2837 ClearHighlights();/*!!could figure this out*/
2838 flipView = appData.flipView;
2839 DrawPosition(FALSE, boards[currentMove]);
2840 DisplayBothClocks();
2841 sprintf(str, "%s vs. %s",
2842 gameInfo.white, gameInfo.black);
2846 /* Moves were history of an active game */
2847 if (gameInfo.resultDetails != NULL) {
2848 free(gameInfo.resultDetails);
2849 gameInfo.resultDetails = NULL;
2852 HistorySet(parseList, backwardMostMove,
2853 forwardMostMove, currentMove-1);
2854 DisplayMove(currentMove - 1);
2855 if (started == STARTED_MOVES) next_out = i;
2856 started = STARTED_NONE;
2857 ics_getting_history = H_FALSE;
2860 case STARTED_OBSERVE:
2861 started = STARTED_NONE;
2862 SendToICS(ics_prefix);
2863 SendToICS("refresh\n");
2869 if(bookHit) { // [HGM] book: simulate book reply
2870 static char bookMove[MSG_SIZ]; // a bit generous?
2872 programStats.nodes = programStats.depth = programStats.time =
2873 programStats.score = programStats.got_only_move = 0;
2874 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2876 strcpy(bookMove, "move ");
2877 strcat(bookMove, bookHit);
2878 HandleMachineMove(bookMove, &first);
2883 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2884 started == STARTED_HOLDINGS ||
2885 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2886 /* Accumulate characters in move list or board */
2887 parse[parse_pos++] = buf[i];
2890 /* Start of game messages. Mostly we detect start of game
2891 when the first board image arrives. On some versions
2892 of the ICS, though, we need to do a "refresh" after starting
2893 to observe in order to get the current board right away. */
2894 if (looking_at(buf, &i, "Adding game * to observation list")) {
2895 started = STARTED_OBSERVE;
2899 /* Handle auto-observe */
2900 if (appData.autoObserve &&
2901 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2902 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2904 /* Choose the player that was highlighted, if any. */
2905 if (star_match[0][0] == '\033' ||
2906 star_match[1][0] != '\033') {
2907 player = star_match[0];
2909 player = star_match[2];
2911 sprintf(str, "%sobserve %s\n",
2912 ics_prefix, StripHighlightAndTitle(player));
2915 /* Save ratings from notify string */
2916 strcpy(player1Name, star_match[0]);
2917 player1Rating = string_to_rating(star_match[1]);
2918 strcpy(player2Name, star_match[2]);
2919 player2Rating = string_to_rating(star_match[3]);
2921 if (appData.debugMode)
2923 "Ratings from 'Game notification:' %s %d, %s %d\n",
2924 player1Name, player1Rating,
2925 player2Name, player2Rating);
2930 /* Deal with automatic examine mode after a game,
2931 and with IcsObserving -> IcsExamining transition */
2932 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2933 looking_at(buf, &i, "has made you an examiner of game *")) {
2935 int gamenum = atoi(star_match[0]);
2936 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2937 gamenum == ics_gamenum) {
2938 /* We were already playing or observing this game;
2939 no need to refetch history */
2940 gameMode = IcsExamining;
2942 pauseExamForwardMostMove = forwardMostMove;
2943 } else if (currentMove < forwardMostMove) {
2944 ForwardInner(forwardMostMove);
2947 /* I don't think this case really can happen */
2948 SendToICS(ics_prefix);
2949 SendToICS("refresh\n");
2954 /* Error messages */
2955 // if (ics_user_moved) {
2956 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2957 if (looking_at(buf, &i, "Illegal move") ||
2958 looking_at(buf, &i, "Not a legal move") ||
2959 looking_at(buf, &i, "Your king is in check") ||
2960 looking_at(buf, &i, "It isn't your turn") ||
2961 looking_at(buf, &i, "It is not your move")) {
2963 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2964 currentMove = --forwardMostMove;
2965 DisplayMove(currentMove - 1); /* before DMError */
2966 DrawPosition(FALSE, boards[currentMove]);
2968 DisplayBothClocks();
2970 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2976 if (looking_at(buf, &i, "still have time") ||
2977 looking_at(buf, &i, "not out of time") ||
2978 looking_at(buf, &i, "either player is out of time") ||
2979 looking_at(buf, &i, "has timeseal; checking")) {
2980 /* We must have called his flag a little too soon */
2981 whiteFlag = blackFlag = FALSE;
2985 if (looking_at(buf, &i, "added * seconds to") ||
2986 looking_at(buf, &i, "seconds were added to")) {
2987 /* Update the clocks */
2988 SendToICS(ics_prefix);
2989 SendToICS("refresh\n");
2993 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994 ics_clock_paused = TRUE;
2999 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000 ics_clock_paused = FALSE;
3005 /* Grab player ratings from the Creating: message.
3006 Note we have to check for the special case when
3007 the ICS inserts things like [white] or [black]. */
3008 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3011 0 player 1 name (not necessarily white)
3013 2 empty, white, or black (IGNORED)
3014 3 player 2 name (not necessarily black)
3017 The names/ratings are sorted out when the game
3018 actually starts (below).
3020 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021 player1Rating = string_to_rating(star_match[1]);
3022 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023 player2Rating = string_to_rating(star_match[4]);
3025 if (appData.debugMode)
3027 "Ratings from 'Creating:' %s %d, %s %d\n",
3028 player1Name, player1Rating,
3029 player2Name, player2Rating);
3034 /* Improved generic start/end-of-game messages */
3035 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037 /* If tkind == 0: */
3038 /* star_match[0] is the game number */
3039 /* [1] is the white player's name */
3040 /* [2] is the black player's name */
3041 /* For end-of-game: */
3042 /* [3] is the reason for the game end */
3043 /* [4] is a PGN end game-token, preceded by " " */
3044 /* For start-of-game: */
3045 /* [3] begins with "Creating" or "Continuing" */
3046 /* [4] is " *" or empty (don't care). */
3047 int gamenum = atoi(star_match[0]);
3048 char *whitename, *blackname, *why, *endtoken;
3049 ChessMove endtype = (ChessMove) 0;
3052 whitename = star_match[1];
3053 blackname = star_match[2];
3054 why = star_match[3];
3055 endtoken = star_match[4];
3057 whitename = star_match[1];
3058 blackname = star_match[3];
3059 why = star_match[5];
3060 endtoken = star_match[6];
3063 /* Game start messages */
3064 if (strncmp(why, "Creating ", 9) == 0 ||
3065 strncmp(why, "Continuing ", 11) == 0) {
3066 gs_gamenum = gamenum;
3067 strcpy(gs_kind, strchr(why, ' ') + 1);
3069 if (appData.zippyPlay) {
3070 ZippyGameStart(whitename, blackname);
3076 /* Game end messages */
3077 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078 ics_gamenum != gamenum) {
3081 while (endtoken[0] == ' ') endtoken++;
3082 switch (endtoken[0]) {
3085 endtype = GameUnfinished;
3088 endtype = BlackWins;
3091 if (endtoken[1] == '/')
3092 endtype = GameIsDrawn;
3094 endtype = WhiteWins;
3097 GameEnds(endtype, why, GE_ICS);
3099 if (appData.zippyPlay && first.initDone) {
3100 ZippyGameEnd(endtype, why);
3101 if (first.pr == NULL) {
3102 /* Start the next process early so that we'll
3103 be ready for the next challenge */
3104 StartChessProgram(&first);
3106 /* Send "new" early, in case this command takes
3107 a long time to finish, so that we'll be ready
3108 for the next challenge. */
3109 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3116 if (looking_at(buf, &i, "Removing game * from observation") ||
3117 looking_at(buf, &i, "no longer observing game *") ||
3118 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119 if (gameMode == IcsObserving &&
3120 atoi(star_match[0]) == ics_gamenum)
3122 /* icsEngineAnalyze */
3123 if (appData.icsEngineAnalyze) {
3130 ics_user_moved = FALSE;
3135 if (looking_at(buf, &i, "no longer examining game *")) {
3136 if (gameMode == IcsExamining &&
3137 atoi(star_match[0]) == ics_gamenum)
3141 ics_user_moved = FALSE;
3146 /* Advance leftover_start past any newlines we find,
3147 so only partial lines can get reparsed */
3148 if (looking_at(buf, &i, "\n")) {
3149 prevColor = curColor;
3150 if (curColor != ColorNormal) {
3151 if (oldi > next_out) {
3152 SendToPlayer(&buf[next_out], oldi - next_out);
3155 Colorize(ColorNormal, FALSE);
3156 curColor = ColorNormal;
3158 if (started == STARTED_BOARD) {
3159 started = STARTED_NONE;
3160 parse[parse_pos] = NULLCHAR;
3161 ParseBoard12(parse);
3164 /* Send premove here */
3165 if (appData.premove) {
3167 if (currentMove == 0 &&
3168 gameMode == IcsPlayingWhite &&
3169 appData.premoveWhite) {
3170 sprintf(str, "%s%s\n", ics_prefix,
3171 appData.premoveWhiteText);
3172 if (appData.debugMode)
3173 fprintf(debugFP, "Sending premove:\n");
3175 } else if (currentMove == 1 &&
3176 gameMode == IcsPlayingBlack &&
3177 appData.premoveBlack) {
3178 sprintf(str, "%s%s\n", ics_prefix,
3179 appData.premoveBlackText);
3180 if (appData.debugMode)
3181 fprintf(debugFP, "Sending premove:\n");
3183 } else if (gotPremove) {
3185 ClearPremoveHighlights();
3186 if (appData.debugMode)
3187 fprintf(debugFP, "Sending premove:\n");
3188 UserMoveEvent(premoveFromX, premoveFromY,
3189 premoveToX, premoveToY,
3194 /* Usually suppress following prompt */
3195 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196 if (looking_at(buf, &i, "*% ")) {
3197 savingComment = FALSE;
3201 } else if (started == STARTED_HOLDINGS) {
3203 char new_piece[MSG_SIZ];
3204 started = STARTED_NONE;
3205 parse[parse_pos] = NULLCHAR;
3206 if (appData.debugMode)
3207 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208 parse, currentMove);
3209 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210 gamenum == ics_gamenum) {
3211 if (gameInfo.variant == VariantNormal) {
3212 /* [HGM] We seem to switch variant during a game!
3213 * Presumably no holdings were displayed, so we have
3214 * to move the position two files to the right to
3215 * create room for them!
3217 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218 /* Get a move list just to see the header, which
3219 will tell us whether this is really bug or zh */
3220 if (ics_getting_history == H_FALSE) {
3221 ics_getting_history = H_REQUESTED;
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3226 new_piece[0] = NULLCHAR;
3227 sscanf(parse, "game %d white [%s black [%s <- %s",
3228 &gamenum, white_holding, black_holding,
3230 white_holding[strlen(white_holding)-1] = NULLCHAR;
3231 black_holding[strlen(black_holding)-1] = NULLCHAR;
3232 /* [HGM] copy holdings to board holdings area */
3233 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3236 if (appData.zippyPlay && first.initDone) {
3237 ZippyHoldings(white_holding, black_holding,
3241 if (tinyLayout || smallLayout) {
3242 char wh[16], bh[16];
3243 PackHolding(wh, white_holding);
3244 PackHolding(bh, black_holding);
3245 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246 gameInfo.white, gameInfo.black);
3248 sprintf(str, "%s [%s] vs. %s [%s]",
3249 gameInfo.white, white_holding,
3250 gameInfo.black, black_holding);
3253 DrawPosition(FALSE, boards[currentMove]);
3256 /* Suppress following prompt */
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3265 i++; /* skip unparsed character and loop back */
3268 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269 started != STARTED_HOLDINGS && i > next_out) {
3270 SendToPlayer(&buf[next_out], i - next_out);
3273 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3275 leftover_len = buf_len - leftover_start;
3276 /* if buffer ends with something we couldn't parse,
3277 reparse it after appending the next read */
3279 } else if (count == 0) {
3280 RemoveInputSource(isr);
3281 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3283 DisplayFatalError(_("Error reading from ICS"), error, 1);
3288 /* Board style 12 looks like this:
3290 <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
3292 * The "<12> " is stripped before it gets to this routine. The two
3293 * trailing 0's (flip state and clock ticking) are later addition, and
3294 * some chess servers may not have them, or may have only the first.
3295 * Additional trailing fields may be added in the future.
3298 #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"
3300 #define RELATION_OBSERVING_PLAYED 0
3301 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE 1
3303 #define RELATION_PLAYING_NOTMYMOVE -1
3304 #define RELATION_EXAMINING 2
3305 #define RELATION_ISOLATED_BOARD -3
3306 #define RELATION_STARTING_POSITION -4 /* FICS only */
3309 ParseBoard12(string)
3312 GameMode newGameMode;
3313 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316 char to_play, board_chars[200];
3317 char move_str[500], str[500], elapsed_time[500];
3318 char black[32], white[32];
3320 int prevMove = currentMove;
3323 int fromX, fromY, toX, toY;
3325 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326 char *bookHit = NULL; // [HGM] book
3328 fromX = fromY = toX = toY = -1;
3332 if (appData.debugMode)
3333 fprintf(debugFP, _("Parsing board: %s\n"), string);
3335 move_str[0] = NULLCHAR;
3336 elapsed_time[0] = NULLCHAR;
3337 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3339 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340 if(string[i] == ' ') { ranks++; files = 0; }
3344 for(j = 0; j <i; j++) board_chars[j] = string[j];
3345 board_chars[i] = '\0';
3348 n = sscanf(string, PATTERN, &to_play, &double_push,
3349 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350 &gamenum, white, black, &relation, &basetime, &increment,
3351 &white_stren, &black_stren, &white_time, &black_time,
3352 &moveNum, str, elapsed_time, move_str, &ics_flip,
3356 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357 DisplayError(str, 0);
3361 /* Convert the move number to internal form */
3362 moveNum = (moveNum - 1) * 2;
3363 if (to_play == 'B') moveNum++;
3364 if (moveNum >= MAX_MOVES) {
3365 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3371 case RELATION_OBSERVING_PLAYED:
3372 case RELATION_OBSERVING_STATIC:
3373 if (gamenum == -1) {
3374 /* Old ICC buglet */
3375 relation = RELATION_OBSERVING_STATIC;
3377 newGameMode = IcsObserving;
3379 case RELATION_PLAYING_MYMOVE:
3380 case RELATION_PLAYING_NOTMYMOVE:
3382 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383 IcsPlayingWhite : IcsPlayingBlack;
3385 case RELATION_EXAMINING:
3386 newGameMode = IcsExamining;
3388 case RELATION_ISOLATED_BOARD:
3390 /* Just display this board. If user was doing something else,
3391 we will forget about it until the next board comes. */
3392 newGameMode = IcsIdle;
3394 case RELATION_STARTING_POSITION:
3395 newGameMode = gameMode;
3399 /* Modify behavior for initial board display on move listing
3402 switch (ics_getting_history) {
3406 case H_GOT_REQ_HEADER:
3407 case H_GOT_UNREQ_HEADER:
3408 /* This is the initial position of the current game */
3409 gamenum = ics_gamenum;
3410 moveNum = 0; /* old ICS bug workaround */
3411 if (to_play == 'B') {
3412 startedFromSetupPosition = TRUE;
3413 blackPlaysFirst = TRUE;
3415 if (forwardMostMove == 0) forwardMostMove = 1;
3416 if (backwardMostMove == 0) backwardMostMove = 1;
3417 if (currentMove == 0) currentMove = 1;
3419 newGameMode = gameMode;
3420 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3422 case H_GOT_UNWANTED_HEADER:
3423 /* This is an initial board that we don't want */
3425 case H_GETTING_MOVES:
3426 /* Should not happen */
3427 DisplayError(_("Error gathering move list: extra board"), 0);
3428 ics_getting_history = H_FALSE;
3432 /* Take action if this is the first board of a new game, or of a
3433 different game than is currently being displayed. */
3434 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435 relation == RELATION_ISOLATED_BOARD) {
3437 /* Forget the old game and get the history (if any) of the new one */
3438 if (gameMode != BeginningOfGame) {
3442 if (appData.autoRaiseBoard) BoardToTop();
3444 if (gamenum == -1) {
3445 newGameMode = IcsIdle;
3446 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447 appData.getMoveList) {
3448 /* Need to get game history */
3449 ics_getting_history = H_REQUESTED;
3450 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3454 /* Initially flip the board to have black on the bottom if playing
3455 black or if the ICS flip flag is set, but let the user change
3456 it with the Flip View button. */
3457 flipView = appData.autoFlipView ?
3458 (newGameMode == IcsPlayingBlack) || ics_flip :
3461 /* Done with values from previous mode; copy in new ones */
3462 gameMode = newGameMode;
3464 ics_gamenum = gamenum;
3465 if (gamenum == gs_gamenum) {
3466 int klen = strlen(gs_kind);
3467 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468 sprintf(str, "ICS %s", gs_kind);
3469 gameInfo.event = StrSave(str);
3471 gameInfo.event = StrSave("ICS game");
3473 gameInfo.site = StrSave(appData.icsHost);
3474 gameInfo.date = PGNDate();
3475 gameInfo.round = StrSave("-");
3476 gameInfo.white = StrSave(white);
3477 gameInfo.black = StrSave(black);
3478 timeControl = basetime * 60 * 1000;
3480 timeIncrement = increment * 1000;
3481 movesPerSession = 0;
3482 gameInfo.timeControl = TimeControlTagValue();
3483 VariantSwitch(board, StringToVariant(gameInfo.event) );
3484 if (appData.debugMode) {
3485 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487 setbuf(debugFP, NULL);
3490 gameInfo.outOfBook = NULL;
3492 /* Do we have the ratings? */
3493 if (strcmp(player1Name, white) == 0 &&
3494 strcmp(player2Name, black) == 0) {
3495 if (appData.debugMode)
3496 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497 player1Rating, player2Rating);
3498 gameInfo.whiteRating = player1Rating;
3499 gameInfo.blackRating = player2Rating;
3500 } else if (strcmp(player2Name, white) == 0 &&
3501 strcmp(player1Name, black) == 0) {
3502 if (appData.debugMode)
3503 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504 player2Rating, player1Rating);
3505 gameInfo.whiteRating = player2Rating;
3506 gameInfo.blackRating = player1Rating;
3508 player1Name[0] = player2Name[0] = NULLCHAR;
3510 /* Silence shouts if requested */
3511 if (appData.quietPlay &&
3512 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513 SendToICS(ics_prefix);
3514 SendToICS("set shout 0\n");
3518 /* Deal with midgame name changes */
3520 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521 if (gameInfo.white) free(gameInfo.white);
3522 gameInfo.white = StrSave(white);
3524 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525 if (gameInfo.black) free(gameInfo.black);
3526 gameInfo.black = StrSave(black);
3530 /* Throw away game result if anything actually changes in examine mode */
3531 if (gameMode == IcsExamining && !newGame) {
3532 gameInfo.result = GameUnfinished;
3533 if (gameInfo.resultDetails != NULL) {
3534 free(gameInfo.resultDetails);
3535 gameInfo.resultDetails = NULL;
3539 /* In pausing && IcsExamining mode, we ignore boards coming
3540 in if they are in a different variation than we are. */
3541 if (pauseExamInvalid) return;
3542 if (pausing && gameMode == IcsExamining) {
3543 if (moveNum <= pauseExamForwardMostMove) {
3544 pauseExamInvalid = TRUE;
3545 forwardMostMove = pauseExamForwardMostMove;
3550 if (appData.debugMode) {
3551 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3553 /* Parse the board */
3554 for (k = 0; k < ranks; k++) {
3555 for (j = 0; j < files; j++)
3556 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557 if(gameInfo.holdingsWidth > 1) {
3558 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3562 CopyBoard(boards[moveNum], board);
3564 startedFromSetupPosition =
3565 !CompareBoards(board, initialPosition);
3566 if(startedFromSetupPosition)
3567 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3570 /* [HGM] Set castling rights. Take the outermost Rooks,
3571 to make it also work for FRC opening positions. Note that board12
3572 is really defective for later FRC positions, as it has no way to
3573 indicate which Rook can castle if they are on the same side of King.
3574 For the initial position we grant rights to the outermost Rooks,
3575 and remember thos rights, and we then copy them on positions
3576 later in an FRC game. This means WB might not recognize castlings with
3577 Rooks that have moved back to their original position as illegal,
3578 but in ICS mode that is not its job anyway.
3580 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584 if(board[0][i] == WhiteRook) j = i;
3585 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3596 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600 if(board[BOARD_HEIGHT-1][k] == bKing)
3601 initialRights[5] = castlingRights[moveNum][5] = k;
3603 r = castlingRights[moveNum][0] = initialRights[0];
3604 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605 r = castlingRights[moveNum][1] = initialRights[1];
3606 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607 r = castlingRights[moveNum][3] = initialRights[3];
3608 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609 r = castlingRights[moveNum][4] = initialRights[4];
3610 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611 /* wildcastle kludge: always assume King has rights */
3612 r = castlingRights[moveNum][2] = initialRights[2];
3613 r = castlingRights[moveNum][5] = initialRights[5];
3615 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3619 if (ics_getting_history == H_GOT_REQ_HEADER ||
3620 ics_getting_history == H_GOT_UNREQ_HEADER) {
3621 /* This was an initial position from a move list, not
3622 the current position */
3626 /* Update currentMove and known move number limits */
3627 newMove = newGame || moveNum > forwardMostMove;
3629 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631 takeback = forwardMostMove - moveNum;
3632 for (i = 0; i < takeback; i++) {
3633 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634 SendToProgram("undo\n", &first);
3639 forwardMostMove = backwardMostMove = currentMove = moveNum;
3640 if (gameMode == IcsExamining && moveNum == 0) {
3641 /* Workaround for ICS limitation: we are not told the wild
3642 type when starting to examine a game. But if we ask for
3643 the move list, the move list header will tell us */
3644 ics_getting_history = H_REQUESTED;
3645 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3648 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650 forwardMostMove = moveNum;
3651 if (!pausing || currentMove > forwardMostMove)
3652 currentMove = forwardMostMove;
3654 /* New part of history that is not contiguous with old part */
3655 if (pausing && gameMode == IcsExamining) {
3656 pauseExamInvalid = TRUE;
3657 forwardMostMove = pauseExamForwardMostMove;
3660 forwardMostMove = backwardMostMove = currentMove = moveNum;
3661 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662 ics_getting_history = H_REQUESTED;
3663 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3668 /* Update the clocks */
3669 if (strchr(elapsed_time, '.')) {
3671 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3674 /* Time is in seconds */
3675 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3681 if (appData.zippyPlay && newGame &&
3682 gameMode != IcsObserving && gameMode != IcsIdle &&
3683 gameMode != IcsExamining)
3684 ZippyFirstBoard(moveNum, basetime, increment);
3687 /* Put the move on the move list, first converting
3688 to canonical algebraic form. */
3690 if (appData.debugMode) {
3691 if (appData.debugMode) { int f = forwardMostMove;
3692 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3695 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696 fprintf(debugFP, "moveNum = %d\n", moveNum);
3697 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698 setbuf(debugFP, NULL);
3700 if (moveNum <= backwardMostMove) {
3701 /* We don't know what the board looked like before
3703 strcpy(parseList[moveNum - 1], move_str);
3704 strcat(parseList[moveNum - 1], " ");
3705 strcat(parseList[moveNum - 1], elapsed_time);
3706 moveList[moveNum - 1][0] = NULLCHAR;
3707 } else if (strcmp(move_str, "none") == 0) {
3708 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709 /* Again, we don't know what the board looked like;
3710 this is really the start of the game. */
3711 parseList[moveNum - 1][0] = NULLCHAR;
3712 moveList[moveNum - 1][0] = NULLCHAR;
3713 backwardMostMove = moveNum;
3714 startedFromSetupPosition = TRUE;
3715 fromX = fromY = toX = toY = -1;
3717 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3718 // So we parse the long-algebraic move string in stead of the SAN move
3719 int valid; char buf[MSG_SIZ], *prom;
3721 // str looks something like "Q/a1-a2"; kill the slash
3723 sprintf(buf, "%c%s", str[0], str+2);
3724 else strcpy(buf, str); // might be castling
3725 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3726 strcat(buf, prom); // long move lacks promo specification!
3727 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728 if(appData.debugMode)
3729 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730 strcpy(move_str, buf);
3732 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733 &fromX, &fromY, &toX, &toY, &promoChar)
3734 || ParseOneMove(buf, moveNum - 1, &moveType,
3735 &fromX, &fromY, &toX, &toY, &promoChar);
3736 // end of long SAN patch
3738 (void) CoordsToAlgebraic(boards[moveNum - 1],
3739 PosFlags(moveNum - 1), EP_UNKNOWN,
3740 fromY, fromX, toY, toX, promoChar,
3741 parseList[moveNum-1]);
3742 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743 castlingRights[moveNum]) ) {
3749 if(gameInfo.variant != VariantShogi)
3750 strcat(parseList[moveNum - 1], "+");
3753 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754 strcat(parseList[moveNum - 1], "#");
3757 strcat(parseList[moveNum - 1], " ");
3758 strcat(parseList[moveNum - 1], elapsed_time);
3759 /* currentMoveString is set as a side-effect of ParseOneMove */
3760 strcpy(moveList[moveNum - 1], currentMoveString);
3761 strcat(moveList[moveNum - 1], "\n");
3763 /* Move from ICS was illegal!? Punt. */
3764 if (appData.debugMode) {
3765 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3769 if (appData.testLegality && appData.debugMode) {
3770 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771 DisplayError(str, 0);
3774 strcpy(parseList[moveNum - 1], move_str);
3775 strcat(parseList[moveNum - 1], " ");
3776 strcat(parseList[moveNum - 1], elapsed_time);
3777 moveList[moveNum - 1][0] = NULLCHAR;
3778 fromX = fromY = toX = toY = -1;
3781 if (appData.debugMode) {
3782 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783 setbuf(debugFP, NULL);
3787 /* Send move to chess program (BEFORE animating it). */
3788 if (appData.zippyPlay && !newGame && newMove &&
3789 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796 DisplayError(str, 0);
3798 if (first.sendTime) {
3799 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802 if (firstMove && !bookHit) {
3804 if (first.useColors) {
3805 SendToProgram(gameMode == IcsPlayingWhite ?
3807 "black\ngo\n", &first);
3809 SendToProgram("go\n", &first);
3811 first.maybeThinking = TRUE;
3814 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815 if (moveList[moveNum - 1][0] == NULLCHAR) {
3816 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817 DisplayError(str, 0);
3819 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820 SendMoveToProgram(moveNum - 1, &first);
3827 if (moveNum > 0 && !gotPremove) {
3828 /* If move comes from a remote source, animate it. If it
3829 isn't remote, it will have already been animated. */
3830 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3833 if (!pausing && appData.highlightLastMove) {
3834 SetHighlights(fromX, fromY, toX, toY);
3838 /* Start the clocks */
3839 whiteFlag = blackFlag = FALSE;
3840 appData.clockMode = !(basetime == 0 && increment == 0);
3842 ics_clock_paused = TRUE;
3844 } else if (ticking == 1) {
3845 ics_clock_paused = FALSE;
3847 if (gameMode == IcsIdle ||
3848 relation == RELATION_OBSERVING_STATIC ||
3849 relation == RELATION_EXAMINING ||
3851 DisplayBothClocks();
3855 /* Display opponents and material strengths */
3856 if (gameInfo.variant != VariantBughouse &&
3857 gameInfo.variant != VariantCrazyhouse) {
3858 if (tinyLayout || smallLayout) {
3859 if(gameInfo.variant == VariantNormal)
3860 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3861 gameInfo.white, white_stren, gameInfo.black, black_stren,
3862 basetime, increment);
3864 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3865 gameInfo.white, white_stren, gameInfo.black, black_stren,
3866 basetime, increment, (int) gameInfo.variant);
3868 if(gameInfo.variant == VariantNormal)
3869 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3870 gameInfo.white, white_stren, gameInfo.black, black_stren,
3871 basetime, increment);
3873 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3874 gameInfo.white, white_stren, gameInfo.black, black_stren,
3875 basetime, increment, VariantName(gameInfo.variant));
3878 if (appData.debugMode) {
3879 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3884 /* Display the board */
3887 if (appData.premove)
3889 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891 ClearPremoveHighlights();
3893 DrawPosition(FALSE, boards[currentMove]);
3894 DisplayMove(moveNum - 1);
3895 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3898 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3902 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3904 if(bookHit) { // [HGM] book: simulate book reply
3905 static char bookMove[MSG_SIZ]; // a bit generous?
3907 programStats.nodes = programStats.depth = programStats.time =
3908 programStats.score = programStats.got_only_move = 0;
3909 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3911 strcpy(bookMove, "move ");
3912 strcat(bookMove, bookHit);
3913 HandleMachineMove(bookMove, &first);
3922 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923 ics_getting_history = H_REQUESTED;
3924 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3930 AnalysisPeriodicEvent(force)
3933 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934 && !force) || !appData.periodicUpdates)
3937 /* Send . command to Crafty to collect stats */
3938 SendToProgram(".\n", &first);
3940 /* Don't send another until we get a response (this makes
3941 us stop sending to old Crafty's which don't understand
3942 the "." command (sending illegal cmds resets node count & time,
3943 which looks bad)) */
3944 programStats.ok_to_send = 0;
3948 SendMoveToProgram(moveNum, cps)
3950 ChessProgramState *cps;
3954 if (cps->useUsermove) {
3955 SendToProgram("usermove ", cps);
3959 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3960 int len = space - parseList[moveNum];
3961 memcpy(buf, parseList[moveNum], len);
3963 buf[len] = NULLCHAR;
3965 sprintf(buf, "%s\n", parseList[moveNum]);
3967 SendToProgram(buf, cps);
3969 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3970 AlphaRank(moveList[moveNum], 4);
3971 SendToProgram(moveList[moveNum], cps);
3972 AlphaRank(moveList[moveNum], 4); // and back
3974 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3975 * the engine. It would be nice to have a better way to identify castle
3977 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3978 && cps->useOOCastle) {
3979 int fromX = moveList[moveNum][0] - AAA;
3980 int fromY = moveList[moveNum][1] - ONE;
3981 int toX = moveList[moveNum][2] - AAA;
3982 int toY = moveList[moveNum][3] - ONE;
3983 if((boards[moveNum][fromY][fromX] == WhiteKing
3984 && boards[moveNum][toY][toX] == WhiteRook)
3985 || (boards[moveNum][fromY][fromX] == BlackKing
3986 && boards[moveNum][toY][toX] == BlackRook)) {
3987 if(toX > fromX) SendToProgram("O-O\n", cps);
3988 else SendToProgram("O-O-O\n", cps);
3990 else SendToProgram(moveList[moveNum], cps);
3992 else SendToProgram(moveList[moveNum], cps);
3993 /* End of additions by Tord */
3996 /* [HGM] setting up the opening has brought engine in force mode! */
3997 /* Send 'go' if we are in a mode where machine should play. */
3998 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
3999 (gameMode == TwoMachinesPlay ||
4001 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4003 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4004 SendToProgram("go\n", cps);
4005 if (appData.debugMode) {
4006 fprintf(debugFP, "(extra)\n");
4009 setboardSpoiledMachineBlack = 0;
4013 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4015 int fromX, fromY, toX, toY;
4017 char user_move[MSG_SIZ];
4021 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4022 (int)moveType, fromX, fromY, toX, toY);
4023 DisplayError(user_move + strlen("say "), 0);
4025 case WhiteKingSideCastle:
4026 case BlackKingSideCastle:
4027 case WhiteQueenSideCastleWild:
4028 case BlackQueenSideCastleWild:
4030 case WhiteHSideCastleFR:
4031 case BlackHSideCastleFR:
4033 sprintf(user_move, "o-o\n");
4035 case WhiteQueenSideCastle:
4036 case BlackQueenSideCastle:
4037 case WhiteKingSideCastleWild:
4038 case BlackKingSideCastleWild:
4040 case WhiteASideCastleFR:
4041 case BlackASideCastleFR:
4043 sprintf(user_move, "o-o-o\n");
4045 case WhitePromotionQueen:
4046 case BlackPromotionQueen:
4047 case WhitePromotionRook:
4048 case BlackPromotionRook:
4049 case WhitePromotionBishop:
4050 case BlackPromotionBishop:
4051 case WhitePromotionKnight:
4052 case BlackPromotionKnight:
4053 case WhitePromotionKing:
4054 case BlackPromotionKing:
4055 case WhitePromotionChancellor:
4056 case BlackPromotionChancellor:
4057 case WhitePromotionArchbishop:
4058 case BlackPromotionArchbishop:
4059 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4060 sprintf(user_move, "%c%c%c%c=%c\n",
4061 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4062 PieceToChar(WhiteFerz));
4063 else if(gameInfo.variant == VariantGreat)
4064 sprintf(user_move, "%c%c%c%c=%c\n",
4065 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4066 PieceToChar(WhiteMan));
4068 sprintf(user_move, "%c%c%c%c=%c\n",
4069 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070 PieceToChar(PromoPiece(moveType)));
4074 sprintf(user_move, "%c@%c%c\n",
4075 ToUpper(PieceToChar((ChessSquare) fromX)),
4076 AAA + toX, ONE + toY);
4079 case WhiteCapturesEnPassant:
4080 case BlackCapturesEnPassant:
4081 case IllegalMove: /* could be a variant we don't quite understand */
4082 sprintf(user_move, "%c%c%c%c\n",
4083 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4086 SendToICS(user_move);
4090 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4095 if (rf == DROP_RANK) {
4096 sprintf(move, "%c@%c%c\n",
4097 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4099 if (promoChar == 'x' || promoChar == NULLCHAR) {
4100 sprintf(move, "%c%c%c%c\n",
4101 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4103 sprintf(move, "%c%c%c%c%c\n",
4104 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4110 ProcessICSInitScript(f)
4115 while (fgets(buf, MSG_SIZ, f)) {
4116 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4123 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4125 AlphaRank(char *move, int n)
4127 // char *p = move, c; int x, y;
4129 if (appData.debugMode) {
4130 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4134 move[2]>='0' && move[2]<='9' &&
4135 move[3]>='a' && move[3]<='x' ) {
4137 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4138 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4140 if(move[0]>='0' && move[0]<='9' &&
4141 move[1]>='a' && move[1]<='x' &&
4142 move[2]>='0' && move[2]<='9' &&
4143 move[3]>='a' && move[3]<='x' ) {
4144 /* input move, Shogi -> normal */
4145 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4146 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4147 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4148 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4151 move[3]>='0' && move[3]<='9' &&
4152 move[2]>='a' && move[2]<='x' ) {
4154 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4155 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4158 move[0]>='a' && move[0]<='x' &&
4159 move[3]>='0' && move[3]<='9' &&
4160 move[2]>='a' && move[2]<='x' ) {
4161 /* output move, normal -> Shogi */
4162 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4163 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4164 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4168 if (appData.debugMode) {
4169 fprintf(debugFP, " out = '%s'\n", move);
4173 /* Parser for moves from gnuchess, ICS, or user typein box */
4175 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4178 ChessMove *moveType;
4179 int *fromX, *fromY, *toX, *toY;
4182 if (appData.debugMode) {
4183 fprintf(debugFP, "move to parse: %s\n", move);
4185 *moveType = yylexstr(moveNum, move);
4187 switch (*moveType) {
4188 case WhitePromotionChancellor:
4189 case BlackPromotionChancellor:
4190 case WhitePromotionArchbishop:
4191 case BlackPromotionArchbishop:
4192 case WhitePromotionQueen:
4193 case BlackPromotionQueen:
4194 case WhitePromotionRook:
4195 case BlackPromotionRook:
4196 case WhitePromotionBishop:
4197 case BlackPromotionBishop:
4198 case WhitePromotionKnight:
4199 case BlackPromotionKnight:
4200 case WhitePromotionKing:
4201 case BlackPromotionKing:
4203 case WhiteCapturesEnPassant:
4204 case BlackCapturesEnPassant:
4205 case WhiteKingSideCastle:
4206 case WhiteQueenSideCastle:
4207 case BlackKingSideCastle:
4208 case BlackQueenSideCastle:
4209 case WhiteKingSideCastleWild:
4210 case WhiteQueenSideCastleWild:
4211 case BlackKingSideCastleWild:
4212 case BlackQueenSideCastleWild:
4213 /* Code added by Tord: */
4214 case WhiteHSideCastleFR:
4215 case WhiteASideCastleFR:
4216 case BlackHSideCastleFR:
4217 case BlackASideCastleFR:
4218 /* End of code added by Tord */
4219 case IllegalMove: /* bug or odd chess variant */
4220 *fromX = currentMoveString[0] - AAA;
4221 *fromY = currentMoveString[1] - ONE;
4222 *toX = currentMoveString[2] - AAA;
4223 *toY = currentMoveString[3] - ONE;
4224 *promoChar = currentMoveString[4];
4225 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4226 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4227 if (appData.debugMode) {
4228 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4230 *fromX = *fromY = *toX = *toY = 0;
4233 if (appData.testLegality) {
4234 return (*moveType != IllegalMove);
4236 return !(fromX == fromY && toX == toY);
4241 *fromX = *moveType == WhiteDrop ?
4242 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4243 (int) CharToPiece(ToLower(currentMoveString[0]));
4245 *toX = currentMoveString[2] - AAA;
4246 *toY = currentMoveString[3] - ONE;
4247 *promoChar = NULLCHAR;
4251 case ImpossibleMove:
4252 case (ChessMove) 0: /* end of file */
4261 if (appData.debugMode) {
4262 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4265 *fromX = *fromY = *toX = *toY = 0;
4266 *promoChar = NULLCHAR;
4271 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4272 // All positions will have equal probability, but the current method will not provide a unique
4273 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4279 int piecesLeft[(int)BlackPawn];
4280 int seed, nrOfShuffles;
4282 void GetPositionNumber()
4283 { // sets global variable seed
4286 seed = appData.defaultFrcPosition;
4287 if(seed < 0) { // randomize based on time for negative FRC position numbers
4288 for(i=0; i<50; i++) seed += random();
4289 seed = random() ^ random() >> 8 ^ random() << 8;
4290 if(seed<0) seed = -seed;
4294 int put(Board board, int pieceType, int rank, int n, int shade)
4295 // put the piece on the (n-1)-th empty squares of the given shade
4299 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4300 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4301 board[rank][i] = (ChessSquare) pieceType;
4302 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4304 piecesLeft[pieceType]--;
4312 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4313 // calculate where the next piece goes, (any empty square), and put it there
4317 i = seed % squaresLeft[shade];
4318 nrOfShuffles *= squaresLeft[shade];
4319 seed /= squaresLeft[shade];
4320 put(board, pieceType, rank, i, shade);
4323 void AddTwoPieces(Board board, int pieceType, int rank)
4324 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4326 int i, n=squaresLeft[ANY], j=n-1, k;
4328 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4329 i = seed % k; // pick one
4332 while(i >= j) i -= j--;
4333 j = n - 1 - j; i += j;
4334 put(board, pieceType, rank, j, ANY);
4335 put(board, pieceType, rank, i, ANY);
4338 void SetUpShuffle(Board board, int number)
4342 GetPositionNumber(); nrOfShuffles = 1;
4344 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4345 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4346 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4348 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4350 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4351 p = (int) board[0][i];
4352 if(p < (int) BlackPawn) piecesLeft[p] ++;
4353 board[0][i] = EmptySquare;
4356 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4357 // shuffles restricted to allow normal castling put KRR first
4358 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4359 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4360 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4361 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4362 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4363 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4364 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4365 put(board, WhiteRook, 0, 0, ANY);
4366 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4369 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4370 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4371 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4372 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4373 while(piecesLeft[p] >= 2) {
4374 AddOnePiece(board, p, 0, LITE);
4375 AddOnePiece(board, p, 0, DARK);
4377 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4380 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4381 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4382 // but we leave King and Rooks for last, to possibly obey FRC restriction
4383 if(p == (int)WhiteRook) continue;
4384 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4385 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4388 // now everything is placed, except perhaps King (Unicorn) and Rooks
4390 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4391 // Last King gets castling rights
4392 while(piecesLeft[(int)WhiteUnicorn]) {
4393 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4394 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4397 while(piecesLeft[(int)WhiteKing]) {
4398 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4399 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4404 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4405 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4408 // Only Rooks can be left; simply place them all
4409 while(piecesLeft[(int)WhiteRook]) {
4410 i = put(board, WhiteRook, 0, 0, ANY);
4411 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4414 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4416 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4419 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4420 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4423 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4426 int SetCharTable( char *table, const char * map )
4427 /* [HGM] moved here from winboard.c because of its general usefulness */
4428 /* Basically a safe strcpy that uses the last character as King */
4430 int result = FALSE; int NrPieces;
4432 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4433 && NrPieces >= 12 && !(NrPieces&1)) {
4434 int i; /* [HGM] Accept even length from 12 to 34 */
4436 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4437 for( i=0; i<NrPieces/2-1; i++ ) {
4439 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4441 table[(int) WhiteKing] = map[NrPieces/2-1];
4442 table[(int) BlackKing] = map[NrPieces-1];
4450 void Prelude(Board board)
4451 { // [HGM] superchess: random selection of exo-pieces
4452 int i, j, k; ChessSquare p;
4453 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4455 GetPositionNumber(); // use FRC position number
4457 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4458 SetCharTable(pieceToChar, appData.pieceToCharTable);
4459 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4460 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4463 j = seed%4; seed /= 4;
4464 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4465 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4466 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4467 j = seed%3 + (seed%3 >= j); seed /= 3;
4468 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4469 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4470 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4471 j = seed%3; seed /= 3;
4472 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4473 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4474 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4475 j = seed%2 + (seed%2 >= j); seed /= 2;
4476 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4477 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4478 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4479 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4480 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4481 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4482 put(board, exoPieces[0], 0, 0, ANY);
4483 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4487 InitPosition(redraw)
4490 ChessSquare (* pieces)[BOARD_SIZE];
4491 int i, j, pawnRow, overrule,
4492 oldx = gameInfo.boardWidth,
4493 oldy = gameInfo.boardHeight,
4494 oldh = gameInfo.holdingsWidth,
4495 oldv = gameInfo.variant;
4497 currentMove = forwardMostMove = backwardMostMove = 0;
4498 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4500 /* [AS] Initialize pv info list [HGM] and game status */
4502 for( i=0; i<MAX_MOVES; i++ ) {
4503 pvInfoList[i].depth = 0;
4504 epStatus[i]=EP_NONE;
4505 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4508 initialRulePlies = 0; /* 50-move counter start */
4510 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4511 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4515 /* [HGM] logic here is completely changed. In stead of full positions */
4516 /* the initialized data only consist of the two backranks. The switch */
4517 /* selects which one we will use, which is than copied to the Board */
4518 /* initialPosition, which for the rest is initialized by Pawns and */
4519 /* empty squares. This initial position is then copied to boards[0], */
4520 /* possibly after shuffling, so that it remains available. */
4522 gameInfo.holdingsWidth = 0; /* default board sizes */
4523 gameInfo.boardWidth = 8;
4524 gameInfo.boardHeight = 8;
4525 gameInfo.holdingsSize = 0;
4526 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4527 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4528 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4530 switch (gameInfo.variant) {
4531 case VariantFischeRandom:
4532 shuffleOpenings = TRUE;
4536 case VariantShatranj:
4537 pieces = ShatranjArray;
4538 nrCastlingRights = 0;
4539 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4541 case VariantTwoKings:
4542 pieces = twoKingsArray;
4544 case VariantCapaRandom:
4545 shuffleOpenings = TRUE;
4546 case VariantCapablanca:
4547 pieces = CapablancaArray;
4548 gameInfo.boardWidth = 10;
4549 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4552 pieces = GothicArray;
4553 gameInfo.boardWidth = 10;
4554 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4557 pieces = JanusArray;
4558 gameInfo.boardWidth = 10;
4559 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4560 nrCastlingRights = 6;
4561 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4562 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4563 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4564 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4565 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4566 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4569 pieces = FalconArray;
4570 gameInfo.boardWidth = 10;
4571 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4573 case VariantXiangqi:
4574 pieces = XiangqiArray;
4575 gameInfo.boardWidth = 9;
4576 gameInfo.boardHeight = 10;
4577 nrCastlingRights = 0;
4578 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4581 pieces = ShogiArray;
4582 gameInfo.boardWidth = 9;
4583 gameInfo.boardHeight = 9;
4584 gameInfo.holdingsSize = 7;
4585 nrCastlingRights = 0;
4586 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4588 case VariantCourier:
4589 pieces = CourierArray;
4590 gameInfo.boardWidth = 12;
4591 nrCastlingRights = 0;
4592 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4593 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4595 case VariantKnightmate:
4596 pieces = KnightmateArray;
4597 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4600 pieces = fairyArray;
4601 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4604 pieces = GreatArray;
4605 gameInfo.boardWidth = 10;
4606 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4607 gameInfo.holdingsSize = 8;
4611 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4612 gameInfo.holdingsSize = 8;
4613 startedFromSetupPosition = TRUE;
4615 case VariantCrazyhouse:
4616 case VariantBughouse:
4618 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4619 gameInfo.holdingsSize = 5;
4621 case VariantWildCastle:
4623 /* !!?shuffle with kings guaranteed to be on d or e file */
4624 shuffleOpenings = 1;
4626 case VariantNoCastle:
4628 nrCastlingRights = 0;
4629 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4630 /* !!?unconstrained back-rank shuffle */
4631 shuffleOpenings = 1;
4636 if(appData.NrFiles >= 0) {
4637 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4638 gameInfo.boardWidth = appData.NrFiles;
4640 if(appData.NrRanks >= 0) {
4641 gameInfo.boardHeight = appData.NrRanks;
4643 if(appData.holdingsSize >= 0) {
4644 i = appData.holdingsSize;
4645 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4646 gameInfo.holdingsSize = i;
4648 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4649 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4650 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4652 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4653 if(pawnRow < 1) pawnRow = 1;
4655 /* User pieceToChar list overrules defaults */
4656 if(appData.pieceToCharTable != NULL)
4657 SetCharTable(pieceToChar, appData.pieceToCharTable);
4659 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4661 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4662 s = (ChessSquare) 0; /* account holding counts in guard band */
4663 for( i=0; i<BOARD_HEIGHT; i++ )
4664 initialPosition[i][j] = s;
4666 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4667 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4668 initialPosition[pawnRow][j] = WhitePawn;
4669 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4670 if(gameInfo.variant == VariantXiangqi) {
4672 initialPosition[pawnRow][j] =
4673 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4674 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4675 initialPosition[2][j] = WhiteCannon;
4676 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4680 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4682 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4685 initialPosition[1][j] = WhiteBishop;
4686 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4688 initialPosition[1][j] = WhiteRook;
4689 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4692 if( nrCastlingRights == -1) {
4693 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4694 /* This sets default castling rights from none to normal corners */
4695 /* Variants with other castling rights must set them themselves above */
4696 nrCastlingRights = 6;
4698 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4699 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4700 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4701 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4702 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4703 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4706 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4707 if(gameInfo.variant == VariantGreat) { // promotion commoners
4708 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4709 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4710 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4711 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4714 if(gameInfo.variant == VariantFischeRandom) {
4715 if( appData.defaultFrcPosition < 0 ) {
4716 ShuffleFRC( initialPosition );
4719 SetupFRC( initialPosition, appData.defaultFrcPosition );
4721 startedFromSetupPosition = TRUE;
4724 if (appData.debugMode) {
4725 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4727 if(shuffleOpenings) {
4728 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4729 startedFromSetupPosition = TRUE;
4732 if(startedFromPositionFile) {
4733 /* [HGM] loadPos: use PositionFile for every new game */
4734 CopyBoard(initialPosition, filePosition);
4735 for(i=0; i<nrCastlingRights; i++)
4736 castlingRights[0][i] = initialRights[i] = fileRights[i];
4737 startedFromSetupPosition = TRUE;
4740 CopyBoard(boards[0], initialPosition);
4741 if(oldx != gameInfo.boardWidth ||
4742 oldy != gameInfo.boardHeight ||
4743 oldh != gameInfo.holdingsWidth
4745 || oldv == VariantGothic || // For licensing popups
4746 gameInfo.variant == VariantGothic
4749 || oldv == VariantFalcon ||
4750 gameInfo.variant == VariantFalcon
4754 InitDrawingSizes(-2 ,0);
4758 DrawPosition(TRUE, boards[currentMove]);
4763 SendBoard(cps, moveNum)
4764 ChessProgramState *cps;
4767 char message[MSG_SIZ];
4769 if (cps->useSetboard) {
4770 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771 sprintf(message, "setboard %s\n", fen);
4772 SendToProgram(message, cps);
4778 /* Kludge to set black to move, avoiding the troublesome and now
4779 * deprecated "black" command.
4781 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4783 SendToProgram("edit\n", cps);
4784 SendToProgram("#\n", cps);
4785 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786 bp = &boards[moveNum][i][BOARD_LEFT];
4787 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788 if ((int) *bp < (int) BlackPawn) {
4789 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4791 if(message[0] == '+' || message[0] == '~') {
4792 sprintf(message, "%c%c%c+\n",
4793 PieceToChar((ChessSquare)(DEMOTED *bp)),
4796 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797 message[1] = BOARD_RGHT - 1 - j + '1';
4798 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4800 SendToProgram(message, cps);
4805 SendToProgram("c\n", cps);
4806 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807 bp = &boards[moveNum][i][BOARD_LEFT];
4808 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809 if (((int) *bp != (int) EmptySquare)
4810 && ((int) *bp >= (int) BlackPawn)) {
4811 sprintf(message, "%c%c%c\n", ToUpper(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(".\n", cps);
4829 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4833 IsPromotion(fromX, fromY, toX, toY)
4834 int fromX, fromY, toX, toY;
4836 /* [HGM] add Shogi promotions */
4837 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4840 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842 /* [HGM] Note to self: line above also weeds out drops */
4843 piece = boards[currentMove][fromY][fromX];
4844 if(gameInfo.variant == VariantShogi) {
4845 promotionZoneSize = 3;
4846 highestPromotingPiece = (int)WhiteKing;
4847 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848 and if in normal chess we then allow promotion to King, why not
4849 allow promotion of other piece in Shogi? */
4851 if((int)piece >= BlackPawn) {
4852 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4854 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4856 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4857 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4859 return ( (int)piece <= highestPromotingPiece );
4863 InPalace(row, column)
4865 { /* [HGM] for Xiangqi */
4866 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867 column < (BOARD_WIDTH + 4)/2 &&
4868 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4873 PieceForSquare (x, y)
4877 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4880 return boards[currentMove][y][x];
4884 OKToStartUserMove(x, y)
4887 ChessSquare from_piece;
4890 if (matchMode) return FALSE;
4891 if (gameMode == EditPosition) return TRUE;
4893 if (x >= 0 && y >= 0)
4894 from_piece = boards[currentMove][y][x];
4896 from_piece = EmptySquare;
4898 if (from_piece == EmptySquare) return FALSE;
4900 white_piece = (int)from_piece >= (int)WhitePawn &&
4901 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4904 case PlayFromGameFile:
4906 case TwoMachinesPlay:
4914 case MachinePlaysWhite:
4915 case IcsPlayingBlack:
4916 if (appData.zippyPlay) return FALSE;
4918 DisplayMoveError(_("You are playing Black"));
4923 case MachinePlaysBlack:
4924 case IcsPlayingWhite:
4925 if (appData.zippyPlay) return FALSE;
4927 DisplayMoveError(_("You are playing White"));
4933 if (!white_piece && WhiteOnMove(currentMove)) {
4934 DisplayMoveError(_("It is White's turn"));
4937 if (white_piece && !WhiteOnMove(currentMove)) {
4938 DisplayMoveError(_("It is Black's turn"));
4941 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942 /* Editing correspondence game history */
4943 /* Could disallow this or prompt for confirmation */
4946 if (currentMove < forwardMostMove) {
4947 /* Discarding moves */
4948 /* Could prompt for confirmation here,
4949 but I don't think that's such a good idea */
4950 forwardMostMove = currentMove;
4954 case BeginningOfGame:
4955 if (appData.icsActive) return FALSE;
4956 if (!appData.noChessProgram) {
4958 DisplayMoveError(_("You are playing White"));
4965 if (!white_piece && WhiteOnMove(currentMove)) {
4966 DisplayMoveError(_("It is White's turn"));
4969 if (white_piece && !WhiteOnMove(currentMove)) {
4970 DisplayMoveError(_("It is Black's turn"));
4979 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980 && gameMode != AnalyzeFile && gameMode != Training) {
4981 DisplayMoveError(_("Displayed position is not current"));
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4995 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4996 int fromX, fromY, toX, toY;
5000 ChessSquare pdown, pup;
5002 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003 if ((fromX == toX) && (fromY == toY)) {
5004 return ImpossibleMove;
5007 /* [HGM] suppress all moves into holdings area and guard band */
5008 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5009 return ImpossibleMove;
5011 /* [HGM] <sameColor> moved to here from winboard.c */
5012 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5013 pdown = boards[currentMove][fromY][fromX];
5014 pup = boards[currentMove][toY][toX];
5015 if ( gameMode != EditPosition &&
5016 (WhitePawn <= pdown && pdown < BlackPawn &&
5017 WhitePawn <= pup && pup < BlackPawn ||
5018 BlackPawn <= pdown && pdown < EmptySquare &&
5019 BlackPawn <= pup && pup < EmptySquare
5020 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5021 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5022 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5024 return ImpossibleMove;
5026 /* Check if the user is playing in turn. This is complicated because we
5027 let the user "pick up" a piece before it is his turn. So the piece he
5028 tried to pick up may have been captured by the time he puts it down!
5029 Therefore we use the color the user is supposed to be playing in this
5030 test, not the color of the piece that is currently on the starting
5031 square---except in EditGame mode, where the user is playing both
5032 sides; fortunately there the capture race can't happen. (It can
5033 now happen in IcsExamining mode, but that's just too bad. The user
5034 will get a somewhat confusing message in that case.)
5038 case PlayFromGameFile:
5040 case TwoMachinesPlay:
5044 /* We switched into a game mode where moves are not accepted,
5045 perhaps while the mouse button was down. */
5046 return ImpossibleMove;
5048 case MachinePlaysWhite:
5049 /* User is moving for Black */
5050 if (WhiteOnMove(currentMove)) {
5051 DisplayMoveError(_("It is White's turn"));
5052 return ImpossibleMove;
5056 case MachinePlaysBlack:
5057 /* User is moving for White */
5058 if (!WhiteOnMove(currentMove)) {
5059 DisplayMoveError(_("It is Black's turn"));
5060 return ImpossibleMove;
5066 case BeginningOfGame:
5069 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5070 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5071 /* User is moving for Black */
5072 if (WhiteOnMove(currentMove)) {
5073 DisplayMoveError(_("It is White's turn"));
5074 return ImpossibleMove;
5077 /* User is moving for White */
5078 if (!WhiteOnMove(currentMove)) {
5079 DisplayMoveError(_("It is Black's turn"));
5080 return ImpossibleMove;
5085 case IcsPlayingBlack:
5086 /* User is moving for Black */
5087 if (WhiteOnMove(currentMove)) {
5088 if (!appData.premove) {
5089 DisplayMoveError(_("It is White's turn"));
5090 } else if (toX >= 0 && toY >= 0) {
5093 premoveFromX = fromX;
5094 premoveFromY = fromY;
5095 premovePromoChar = promoChar;
5097 if (appData.debugMode)
5098 fprintf(debugFP, "Got premove: fromX %d,"
5099 "fromY %d, toX %d, toY %d\n",
5100 fromX, fromY, toX, toY);
5102 return ImpossibleMove;
5106 case IcsPlayingWhite:
5107 /* User is moving for White */
5108 if (!WhiteOnMove(currentMove)) {
5109 if (!appData.premove) {
5110 DisplayMoveError(_("It is Black's turn"));
5111 } else if (toX >= 0 && toY >= 0) {
5114 premoveFromX = fromX;
5115 premoveFromY = fromY;
5116 premovePromoChar = promoChar;
5118 if (appData.debugMode)
5119 fprintf(debugFP, "Got premove: fromX %d,"
5120 "fromY %d, toX %d, toY %d\n",
5121 fromX, fromY, toX, toY);
5123 return ImpossibleMove;
5131 /* EditPosition, empty square, or different color piece;
5132 click-click move is possible */
5133 if (toX == -2 || toY == -2) {
5134 boards[0][fromY][fromX] = EmptySquare;
5135 return AmbiguousMove;
5136 } else if (toX >= 0 && toY >= 0) {
5137 boards[0][toY][toX] = boards[0][fromY][fromX];
5138 boards[0][fromY][fromX] = EmptySquare;
5139 return AmbiguousMove;
5141 return ImpossibleMove;
5144 /* [HGM] If move started in holdings, it means a drop */
5145 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5146 if( pup != EmptySquare ) return ImpossibleMove;
5147 if(appData.testLegality) {
5148 /* it would be more logical if LegalityTest() also figured out
5149 * which drops are legal. For now we forbid pawns on back rank.
5150 * Shogi is on its own here...
5152 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5153 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5154 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5156 return WhiteDrop; /* Not needed to specify white or black yet */
5159 userOfferedDraw = FALSE;
5161 /* [HGM] always test for legality, to get promotion info */
5162 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5163 epStatus[currentMove], castlingRights[currentMove],
5164 fromY, fromX, toY, toX, promoChar);
5166 /* [HGM] but possibly ignore an IllegalMove result */
5167 if (appData.testLegality) {
5168 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5169 DisplayMoveError(_("Illegal move"));
5170 return ImpossibleMove;
5173 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5175 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5176 function is made into one that returns an OK move type if FinishMove
5177 should be called. This to give the calling driver routine the
5178 opportunity to finish the userMove input with a promotion popup,
5179 without bothering the user with this for invalid or illegal moves */
5181 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5184 /* Common tail of UserMoveEvent and DropMenuEvent */
5186 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5188 int fromX, fromY, toX, toY;
5189 /*char*/int promoChar;
5193 if(appData.debugMode)
5194 fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5196 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5198 // [HGM] superchess: suppress promotions to non-available piece
5199 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5200 if(WhiteOnMove(currentMove))
5202 if(!boards[currentMove][k][BOARD_WIDTH-2])
5207 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5212 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5213 move type in caller when we know the move is a legal promotion */
5214 if(moveType == NormalMove && promoChar)
5215 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5217 if(appData.debugMode)
5218 fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5220 /* [HGM] convert drag-and-drop piece drops to standard form */
5221 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5223 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5224 fromX = boards[currentMove][fromY][fromX];
5228 /* [HGM] <popupFix> The following if has been moved here from
5229 UserMoveEvent(). Because it seemed to belon here (why not allow
5230 piece drops in training games?), and because it can only be
5231 performed after it is known to what we promote. */
5232 if (gameMode == Training)
5234 /* compare the move played on the board to the next move in the
5235 * game. If they match, display the move and the opponent's response.
5236 * If they don't match, display an error message.
5239 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5240 CopyBoard(testBoard, boards[currentMove]);
5241 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5243 if (CompareBoards(testBoard, boards[currentMove+1]))
5245 ForwardInner(currentMove+1);
5247 /* Autoplay the opponent's response.
5248 * if appData.animate was TRUE when Training mode was entered,
5249 * the response will be animated.
5251 saveAnimate = appData.animate;
5252 appData.animate = animateTraining;
5253 ForwardInner(currentMove+1);
5254 appData.animate = saveAnimate;
5256 /* check for the end of the game */
5257 if (currentMove >= forwardMostMove)
5259 gameMode = PlayFromGameFile;
5261 SetTrainingModeOff();
5262 DisplayInformation(_("End of game"));
5267 DisplayError(_("Incorrect move"), 0);
5272 /* Ok, now we know that the move is good, so we can kill
5273 the previous line in Analysis Mode */
5274 if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5276 forwardMostMove = currentMove;
5279 /* If we need the chess program but it's dead, restart it */
5280 ResurrectChessProgram();
5282 /* A user move restarts a paused game*/
5286 thinkOutput[0] = NULLCHAR;
5288 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5290 if (gameMode == BeginningOfGame)
5292 if (appData.noChessProgram)
5294 gameMode = EditGame;
5300 gameMode = MachinePlaysBlack;
5303 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5307 sprintf(buf, "name %s\n", gameInfo.white);
5308 SendToProgram(buf, &first);
5314 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5316 /* Relay move to ICS or chess engine */
5317 if (appData.icsActive)
5319 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5320 gameMode == IcsExamining)
5322 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5328 if (first.sendTime && (gameMode == BeginningOfGame ||
5329 gameMode == MachinePlaysWhite ||
5330 gameMode == MachinePlaysBlack))
5332 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5334 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5336 // [HGM] book: if program might be playing, let it use book
5337 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5338 first.maybeThinking = TRUE;
5341 SendMoveToProgram(forwardMostMove-1, &first);
5342 if (currentMove == cmailOldMove + 1)
5344 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5348 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5353 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5354 EP_UNKNOWN, castlingRights[currentMove]) )
5361 if (WhiteOnMove(currentMove))
5363 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5367 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5371 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5376 case MachinePlaysBlack:
5377 case MachinePlaysWhite:
5378 /* disable certain menu options while machine is thinking */
5379 SetMachineThinkingEnables();
5387 { // [HGM] book: simulate book reply
5388 static char bookMove[MSG_SIZ]; // a bit generous?
5390 programStats.nodes = programStats.depth = programStats.time =
5391 programStats.score = programStats.got_only_move = 0;
5392 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5394 strcpy(bookMove, "move ");
5395 strcat(bookMove, bookHit);
5396 HandleMachineMove(bookMove, &first);
5403 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5404 int fromX, fromY, toX, toY;
5407 /* [HGM] This routine was added to allow calling of its two logical
5408 parts from other modules in the old way. Before, UserMoveEvent()
5409 automatically called FinishMove() if the move was OK, and returned
5410 otherwise. I separated the two, in order to make it possible to
5411 slip a promotion popup in between. But that it always needs two
5412 calls, to the first part, (now called UserMoveTest() ), and to
5413 FinishMove if the first part succeeded. Calls that do not need
5414 to do anything in between, can call this routine the old way.
5416 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5417 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5418 if(moveType != ImpossibleMove)
5419 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5422 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5424 // char * hint = lastHint;
5425 FrontEndProgramStats stats;
5427 stats.which = cps == &first ? 0 : 1;
5428 stats.depth = cpstats->depth;
5429 stats.nodes = cpstats->nodes;
5430 stats.score = cpstats->score;
5431 stats.time = cpstats->time;
5432 stats.pv = cpstats->movelist;
5433 stats.hint = lastHint;
5434 stats.an_move_index = 0;
5435 stats.an_move_count = 0;
5437 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5438 stats.hint = cpstats->move_name;
5439 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5440 stats.an_move_count = cpstats->nr_moves;
5443 SetProgramStats( &stats );
5446 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5447 { // [HGM] book: this routine intercepts moves to simulate book replies
5448 char *bookHit = NULL;
5450 //first determine if the incoming move brings opponent into his book
5451 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5452 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5453 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5454 if(bookHit != NULL && !cps->bookSuspend) {
5455 // make sure opponent is not going to reply after receiving move to book position
5456 SendToProgram("force\n", cps);
5457 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5459 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5460 // now arrange restart after book miss
5462 // after a book hit we never send 'go', and the code after the call to this routine
5463 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5465 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5466 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5467 SendToProgram(buf, cps);
5468 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5469 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5470 SendToProgram("go\n", cps);
5471 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5472 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5473 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5474 SendToProgram("go\n", cps);
5475 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5477 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5481 ChessProgramState *savedState;
5482 void DeferredBookMove(void)
5484 if(savedState->lastPing != savedState->lastPong)
5485 ScheduleDelayedEvent(DeferredBookMove, 10);
5487 HandleMachineMove(savedMessage, savedState);
5491 HandleMachineMove(message, cps)
5493 ChessProgramState *cps;
5495 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5496 char realname[MSG_SIZ];
5497 int fromX, fromY, toX, toY;
5504 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5506 * Kludge to ignore BEL characters
5508 while (*message == '\007') message++;
5511 * [HGM] engine debug message: ignore lines starting with '#' character
5513 if(cps->debug && *message == '#') return;
5516 * Look for book output
5518 if (cps == &first && bookRequested) {
5519 if (message[0] == '\t' || message[0] == ' ') {
5520 /* Part of the book output is here; append it */
5521 strcat(bookOutput, message);
5522 strcat(bookOutput, " \n");
5524 } else if (bookOutput[0] != NULLCHAR) {
5525 /* All of book output has arrived; display it */
5526 char *p = bookOutput;
5527 while (*p != NULLCHAR) {
5528 if (*p == '\t') *p = ' ';
5531 DisplayInformation(bookOutput);
5532 bookRequested = FALSE;
5533 /* Fall through to parse the current output */
5538 * Look for machine move.
5540 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5541 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5543 /* This method is only useful on engines that support ping */
5544 if (cps->lastPing != cps->lastPong) {
5545 if (gameMode == BeginningOfGame) {
5546 /* Extra move from before last new; ignore */
5547 if (appData.debugMode) {
5548 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5551 if (appData.debugMode) {
5552 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5553 cps->which, gameMode);
5556 SendToProgram("undo\n", cps);
5562 case BeginningOfGame:
5563 /* Extra move from before last reset; ignore */
5564 if (appData.debugMode) {
5565 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5572 /* Extra move after we tried to stop. The mode test is
5573 not a reliable way of detecting this problem, but it's
5574 the best we can do on engines that don't support ping.
5576 if (appData.debugMode) {
5577 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5578 cps->which, gameMode);
5580 SendToProgram("undo\n", cps);
5583 case MachinePlaysWhite:
5584 case IcsPlayingWhite:
5585 machineWhite = TRUE;
5588 case MachinePlaysBlack:
5589 case IcsPlayingBlack:
5590 machineWhite = FALSE;
5593 case TwoMachinesPlay:
5594 machineWhite = (cps->twoMachinesColor[0] == 'w');
5597 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5598 if (appData.debugMode) {
5600 "Ignoring move out of turn by %s, gameMode %d"
5601 ", forwardMost %d\n",
5602 cps->which, gameMode, forwardMostMove);
5607 if (appData.debugMode) { int f = forwardMostMove;
5608 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5609 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5611 if(cps->alphaRank) AlphaRank(machineMove, 4);
5612 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5613 &fromX, &fromY, &toX, &toY, &promoChar)) {
5614 /* Machine move could not be parsed; ignore it. */
5615 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5616 machineMove, cps->which);
5617 DisplayError(buf1, 0);
5618 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5619 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5620 if (gameMode == TwoMachinesPlay) {
5621 GameEnds(machineWhite ? BlackWins : WhiteWins,
5627 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5628 /* So we have to redo legality test with true e.p. status here, */
5629 /* to make sure an illegal e.p. capture does not slip through, */
5630 /* to cause a forfeit on a justified illegal-move complaint */
5631 /* of the opponent. */
5632 if( gameMode==TwoMachinesPlay && appData.testLegality
5633 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5636 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5637 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5638 fromY, fromX, toY, toX, promoChar);
5639 if (appData.debugMode) {
5641 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5642 castlingRights[forwardMostMove][i], castlingRank[i]);
5643 fprintf(debugFP, "castling rights\n");
5645 if(moveType == IllegalMove) {
5646 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5647 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5648 GameEnds(machineWhite ? BlackWins : WhiteWins,
5651 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5652 /* [HGM] Kludge to handle engines that send FRC-style castling
5653 when they shouldn't (like TSCP-Gothic) */
5655 case WhiteASideCastleFR:
5656 case BlackASideCastleFR:
5658 currentMoveString[2]++;
5660 case WhiteHSideCastleFR:
5661 case BlackHSideCastleFR:
5663 currentMoveString[2]--;
5665 default: ; // nothing to do, but suppresses warning of pedantic compilers
5668 hintRequested = FALSE;
5669 lastHint[0] = NULLCHAR;
5670 bookRequested = FALSE;
5671 /* Program may be pondering now */
5672 cps->maybeThinking = TRUE;
5673 if (cps->sendTime == 2) cps->sendTime = 1;
5674 if (cps->offeredDraw) cps->offeredDraw--;
5677 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5679 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5681 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5682 char buf[3*MSG_SIZ];
5684 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %.0f nodes, %1.0f knps) PV=%s\n",
5685 programStats.score / 100.,
5687 programStats.time / 100.,
5688 u64ToDouble(programStats.nodes),
5689 u64ToDouble(programStats.nodes) / (10*abs(programStats.time) + 1.),
5690 programStats.movelist);
5695 /* currentMoveString is set as a side-effect of ParseOneMove */
5696 strcpy(machineMove, currentMoveString);
5697 strcat(machineMove, "\n");
5698 strcpy(moveList[forwardMostMove], machineMove);
5700 /* [AS] Save move info and clear stats for next move */
5701 pvInfoList[ forwardMostMove ].score = programStats.score;
5702 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5703 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5704 ClearProgramStats();
5705 thinkOutput[0] = NULLCHAR;
5706 hiddenThinkOutputState = 0;
5708 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5710 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5711 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5714 while( count < adjudicateLossPlies ) {
5715 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5718 score = -score; /* Flip score for winning side */
5721 if( score > adjudicateLossThreshold ) {
5728 if( count >= adjudicateLossPlies ) {
5729 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5731 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5732 "Xboard adjudication",
5739 if( gameMode == TwoMachinesPlay ) {
5740 // [HGM] some adjudications useful with buggy engines
5741 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5742 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5745 if( appData.testLegality )
5746 { /* [HGM] Some more adjudications for obstinate engines */
5747 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5748 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5749 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5750 static int moveCount = 6;
5752 char *reason = NULL;
5754 /* Count what is on board. */
5755 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5756 { ChessSquare p = boards[forwardMostMove][i][j];
5760 { /* count B,N,R and other of each side */
5763 NrK++; break; // [HGM] atomic: count Kings
5767 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5768 bishopsColor |= 1 << ((i^j)&1);
5773 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5774 bishopsColor |= 1 << ((i^j)&1);
5789 PawnAdvance += m; NrPawns++;
5791 NrPieces += (p != EmptySquare);
5792 NrW += ((int)p < (int)BlackPawn);
5793 if(gameInfo.variant == VariantXiangqi &&
5794 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5795 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5796 NrW -= ((int)p < (int)BlackPawn);
5800 /* Some material-based adjudications that have to be made before stalemate test */
5801 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5802 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5803 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5804 if(appData.checkMates) {
5805 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5806 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5807 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5808 "Xboard adjudication: King destroyed", GE_XBOARD );
5813 /* Bare King in Shatranj (loses) or Losers (wins) */
5814 if( NrW == 1 || NrPieces - NrW == 1) {
5815 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5816 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5817 if(appData.checkMates) {
5818 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5819 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5820 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5821 "Xboard adjudication: Bare king", GE_XBOARD );
5825 if( gameInfo.variant == VariantShatranj && --bare < 0)
5827 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5828 if(appData.checkMates) {
5829 /* but only adjudicate if adjudication enabled */
5830 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5831 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5832 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5833 "Xboard adjudication: Bare king", GE_XBOARD );
5840 // don't wait for engine to announce game end if we can judge ourselves
5841 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5842 castlingRights[forwardMostMove]) ) {
5844 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5845 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5846 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5847 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5850 reason = "Xboard adjudication: 3rd check";
5851 epStatus[forwardMostMove] = EP_CHECKMATE;
5861 reason = "Xboard adjudication: Stalemate";
5862 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5863 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5864 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5865 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5866 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5867 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5868 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5869 EP_CHECKMATE : EP_WINS);
5870 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5871 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5875 reason = "Xboard adjudication: Checkmate";
5876 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5880 switch(i = epStatus[forwardMostMove]) {
5882 result = GameIsDrawn; break;
5884 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5886 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5888 result = (ChessMove) 0;
5890 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5891 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5892 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5893 GameEnds( result, reason, GE_XBOARD );
5897 /* Next absolutely insufficient mating material. */
5898 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5899 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5900 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5901 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5902 { /* KBK, KNK, KK of KBKB with like Bishops */
5904 /* always flag draws, for judging claims */
5905 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5907 if(appData.materialDraws) {
5908 /* but only adjudicate them if adjudication enabled */
5909 SendToProgram("force\n", cps->other); // suppress reply
5910 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5911 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5912 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5917 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5919 ( NrWR == 1 && NrBR == 1 /* KRKR */
5920 || NrWQ==1 && NrBQ==1 /* KQKQ */
5921 || NrWN==2 || NrBN==2 /* KNNK */
5922 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5924 if(--moveCount < 0 && appData.trivialDraws)
5925 { /* if the first 3 moves do not show a tactical win, declare draw */
5926 SendToProgram("force\n", cps->other); // suppress reply
5927 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5928 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5929 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5932 } else moveCount = 6;
5936 if (appData.debugMode) { int i;
5937 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5938 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5939 appData.drawRepeats);
5940 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5941 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5945 /* Check for rep-draws */
5947 for(k = forwardMostMove-2;
5948 k>=backwardMostMove && k>=forwardMostMove-100 &&
5949 epStatus[k] < EP_UNKNOWN &&
5950 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5954 if (appData.debugMode) {
5955 fprintf(debugFP, " loop\n");
5958 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5960 if (appData.debugMode) {
5961 fprintf(debugFP, "match\n");
5964 /* compare castling rights */
5965 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5966 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5967 rights++; /* King lost rights, while rook still had them */
5968 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5969 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5970 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5971 rights++; /* but at least one rook lost them */
5973 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5974 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5976 if( castlingRights[forwardMostMove][5] >= 0 ) {
5977 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5978 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5982 if (appData.debugMode) {
5983 for(i=0; i<nrCastlingRights; i++)
5984 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5987 if (appData.debugMode) {
5988 fprintf(debugFP, " %d %d\n", rights, k);
5991 if( rights == 0 && ++count > appData.drawRepeats-2
5992 && appData.drawRepeats > 1) {
5993 /* adjudicate after user-specified nr of repeats */
5994 SendToProgram("force\n", cps->other); // suppress reply
5995 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5996 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5997 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5998 // [HGM] xiangqi: check for forbidden perpetuals
5999 int m, ourPerpetual = 1, hisPerpetual = 1;
6000 for(m=forwardMostMove; m>k; m-=2) {
6001 if(MateTest(boards[m], PosFlags(m),
6002 EP_NONE, castlingRights[m]) != MT_CHECK)
6003 ourPerpetual = 0; // the current mover did not always check
6004 if(MateTest(boards[m-1], PosFlags(m-1),
6005 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6006 hisPerpetual = 0; // the opponent did not always check
6008 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6009 ourPerpetual, hisPerpetual);
6010 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6011 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6012 "Xboard adjudication: perpetual checking", GE_XBOARD );
6015 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6016 break; // (or we would have caught him before). Abort repetition-checking loop.
6017 // Now check for perpetual chases
6018 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6019 hisPerpetual = PerpetualChase(k, forwardMostMove);
6020 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6021 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6022 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6023 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6026 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6027 break; // Abort repetition-checking loop.
6029 // if neither of us is checking or chasing all the time, or both are, it is draw
6031 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6034 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6035 epStatus[forwardMostMove] = EP_REP_DRAW;
6039 /* Now we test for 50-move draws. Determine ply count */
6040 count = forwardMostMove;
6041 /* look for last irreversble move */
6042 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6044 /* if we hit starting position, add initial plies */
6045 if( count == backwardMostMove )
6046 count -= initialRulePlies;
6047 count = forwardMostMove - count;
6049 epStatus[forwardMostMove] = EP_RULE_DRAW;
6050 /* this is used to judge if draw claims are legal */
6051 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6052 SendToProgram("force\n", cps->other); // suppress reply
6053 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6054 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6059 /* if draw offer is pending, treat it as a draw claim
6060 * when draw condition present, to allow engines a way to
6061 * claim draws before making their move to avoid a race
6062 * condition occurring after their move
6064 if( cps->other->offeredDraw || cps->offeredDraw ) {
6066 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6067 p = "Draw claim: 50-move rule";
6068 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6069 p = "Draw claim: 3-fold repetition";
6070 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6071 p = "Draw claim: insufficient mating material";
6073 SendToProgram("force\n", cps->other); // suppress reply
6074 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6075 GameEnds( GameIsDrawn, p, GE_XBOARD );
6076 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6082 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6083 SendToProgram("force\n", cps->other); // suppress reply
6084 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6085 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6087 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6094 if (gameMode == TwoMachinesPlay) {
6095 /* [HGM] relaying draw offers moved to after reception of move */
6096 /* and interpreting offer as claim if it brings draw condition */
6097 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6098 SendToProgram("draw\n", cps->other);
6100 if (cps->other->sendTime) {
6101 SendTimeRemaining(cps->other,
6102 cps->other->twoMachinesColor[0] == 'w');
6104 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6105 if (firstMove && !bookHit) {
6107 if (cps->other->useColors) {
6108 SendToProgram(cps->other->twoMachinesColor, cps->other);
6110 SendToProgram("go\n", cps->other);
6112 cps->other->maybeThinking = TRUE;
6115 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6117 if (!pausing && appData.ringBellAfterMoves) {
6122 * Reenable menu items that were disabled while
6123 * machine was thinking
6125 if (gameMode != TwoMachinesPlay)
6126 SetUserThinkingEnables();
6128 // [HGM] book: after book hit opponent has received move and is now in force mode
6129 // force the book reply into it, and then fake that it outputted this move by jumping
6130 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6132 static char bookMove[MSG_SIZ]; // a bit generous?
6134 strcpy(bookMove, "move ");
6135 strcat(bookMove, bookHit);
6138 programStats.nodes = programStats.depth = programStats.time =
6139 programStats.score = programStats.got_only_move = 0;
6140 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6142 if(cps->lastPing != cps->lastPong) {
6143 savedMessage = message; // args for deferred call
6145 ScheduleDelayedEvent(DeferredBookMove, 10);
6154 /* Set special modes for chess engines. Later something general
6155 * could be added here; for now there is just one kludge feature,
6156 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6157 * when "xboard" is given as an interactive command.
6159 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6160 cps->useSigint = FALSE;
6161 cps->useSigterm = FALSE;
6164 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6165 * want this, I was asked to put it in, and obliged.
6167 if (!strncmp(message, "setboard ", 9)) {
6168 Board initial_position; int i;
6170 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6172 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6173 DisplayError(_("Bad FEN received from engine"), 0);
6176 Reset(FALSE, FALSE);
6177 CopyBoard(boards[0], initial_position);
6178 initialRulePlies = FENrulePlies;
6179 epStatus[0] = FENepStatus;
6180 for( i=0; i<nrCastlingRights; i++ )
6181 castlingRights[0][i] = FENcastlingRights[i];
6182 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6183 else gameMode = MachinePlaysBlack;
6184 DrawPosition(FALSE, boards[currentMove]);
6190 * Look for communication commands
6192 if (!strncmp(message, "telluser ", 9)) {
6193 DisplayNote(message + 9);
6196 if (!strncmp(message, "tellusererror ", 14)) {
6197 DisplayError(message + 14, 0);
6200 if (!strncmp(message, "tellopponent ", 13)) {
6201 if (appData.icsActive) {
6203 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6207 DisplayNote(message + 13);
6211 if (!strncmp(message, "tellothers ", 11)) {
6212 if (appData.icsActive) {
6214 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6220 if (!strncmp(message, "tellall ", 8)) {
6221 if (appData.icsActive) {
6223 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6227 DisplayNote(message + 8);
6231 if (strncmp(message, "warning", 7) == 0) {
6232 /* Undocumented feature, use tellusererror in new code */
6233 DisplayError(message, 0);
6236 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6237 strcpy(realname, cps->tidy);
6238 strcat(realname, " query");
6239 AskQuestion(realname, buf2, buf1, cps->pr);
6242 /* Commands from the engine directly to ICS. We don't allow these to be
6243 * sent until we are logged on. Crafty kibitzes have been known to
6244 * interfere with the login process.
6247 if (!strncmp(message, "tellics ", 8)) {
6248 SendToICS(message + 8);
6252 if (!strncmp(message, "tellicsnoalias ", 15)) {
6253 SendToICS(ics_prefix);
6254 SendToICS(message + 15);
6258 /* The following are for backward compatibility only */
6259 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6260 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6261 SendToICS(ics_prefix);
6267 if (strncmp(message, "feature ", 8) == 0) {
6268 ParseFeatures(message+8, cps);
6270 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6274 * If the move is illegal, cancel it and redraw the board.
6275 * Also deal with other error cases. Matching is rather loose
6276 * here to accommodate engines written before the spec.
6278 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6279 strncmp(message, "Error", 5) == 0) {
6280 if (StrStr(message, "name") ||
6281 StrStr(message, "rating") || StrStr(message, "?") ||
6282 StrStr(message, "result") || StrStr(message, "board") ||
6283 StrStr(message, "bk") || StrStr(message, "computer") ||
6284 StrStr(message, "variant") || StrStr(message, "hint") ||
6285 StrStr(message, "random") || StrStr(message, "depth") ||
6286 StrStr(message, "accepted")) {
6289 if (StrStr(message, "protover")) {
6290 /* Program is responding to input, so it's apparently done
6291 initializing, and this error message indicates it is
6292 protocol version 1. So we don't need to wait any longer
6293 for it to initialize and send feature commands. */
6294 FeatureDone(cps, 1);
6295 cps->protocolVersion = 1;
6298 cps->maybeThinking = FALSE;
6300 if (StrStr(message, "draw")) {
6301 /* Program doesn't have "draw" command */
6302 cps->sendDrawOffers = 0;
6305 if (cps->sendTime != 1 &&
6306 (StrStr(message, "time") || StrStr(message, "otim"))) {
6307 /* Program apparently doesn't have "time" or "otim" command */
6311 if (StrStr(message, "analyze")) {
6312 cps->analysisSupport = FALSE;
6313 cps->analyzing = FALSE;
6315 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6316 DisplayError(buf2, 0);
6319 if (StrStr(message, "(no matching move)st")) {
6320 /* Special kludge for GNU Chess 4 only */
6321 cps->stKludge = TRUE;
6322 SendTimeControl(cps, movesPerSession, timeControl,
6323 timeIncrement, appData.searchDepth,
6327 if (StrStr(message, "(no matching move)sd")) {
6328 /* Special kludge for GNU Chess 4 only */
6329 cps->sdKludge = TRUE;
6330 SendTimeControl(cps, movesPerSession, timeControl,
6331 timeIncrement, appData.searchDepth,
6335 if (!StrStr(message, "llegal")) {
6338 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6339 gameMode == IcsIdle) return;
6340 if (forwardMostMove <= backwardMostMove) return;
6342 /* Following removed: it caused a bug where a real illegal move
6343 message in analyze mored would be ignored. */
6344 if (cps == &first && programStats.ok_to_send == 0) {
6345 /* Bogus message from Crafty responding to "." This filtering
6346 can miss some of the bad messages, but fortunately the bug
6347 is fixed in current Crafty versions, so it doesn't matter. */
6351 if (pausing) PauseEvent();
6352 if (gameMode == PlayFromGameFile) {
6353 /* Stop reading this game file */
6354 gameMode = EditGame;
6357 currentMove = --forwardMostMove;
6358 DisplayMove(currentMove-1); /* before DisplayMoveError */
6360 DisplayBothClocks();
6361 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6362 parseList[currentMove], cps->which);
6363 DisplayMoveError(buf1);
6364 DrawPosition(FALSE, boards[currentMove]);
6366 /* [HGM] illegal-move claim should forfeit game when Xboard */
6367 /* only passes fully legal moves */
6368 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6369 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6370 "False illegal-move claim", GE_XBOARD );
6374 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6375 /* Program has a broken "time" command that
6376 outputs a string not ending in newline.
6382 * If chess program startup fails, exit with an error message.
6383 * Attempts to recover here are futile.
6385 if ((StrStr(message, "unknown host") != NULL)
6386 || (StrStr(message, "No remote directory") != NULL)
6387 || (StrStr(message, "not found") != NULL)
6388 || (StrStr(message, "No such file") != NULL)
6389 || (StrStr(message, "can't alloc") != NULL)
6390 || (StrStr(message, "Permission denied") != NULL)) {
6392 cps->maybeThinking = FALSE;
6393 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6394 cps->which, cps->program, cps->host, message);
6395 RemoveInputSource(cps->isr);
6396 DisplayFatalError(buf1, 0, 1);
6401 * Look for hint output
6403 if (sscanf(message, "Hint: %s", buf1) == 1) {
6404 if (cps == &first && hintRequested) {
6405 hintRequested = FALSE;
6406 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6407 &fromX, &fromY, &toX, &toY, &promoChar)) {
6408 (void) CoordsToAlgebraic(boards[forwardMostMove],
6409 PosFlags(forwardMostMove), EP_UNKNOWN,
6410 fromY, fromX, toY, toX, promoChar, buf1);
6411 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6412 DisplayInformation(buf2);
6414 /* Hint move could not be parsed!? */
6415 snprintf(buf2, sizeof(buf2),
6416 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6418 DisplayError(buf2, 0);
6421 strcpy(lastHint, buf1);
6427 * Ignore other messages if game is not in progress
6429 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6430 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6433 * look for win, lose, draw, or draw offer
6435 if (strncmp(message, "1-0", 3) == 0) {
6436 char *p, *q, *r = "";
6437 p = strchr(message, '{');
6445 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6447 } else if (strncmp(message, "0-1", 3) == 0) {
6448 char *p, *q, *r = "";
6449 p = strchr(message, '{');
6457 /* Kludge for Arasan 4.1 bug */
6458 if (strcmp(r, "Black resigns") == 0) {
6459 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6462 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6464 } else if (strncmp(message, "1/2", 3) == 0) {
6465 char *p, *q, *r = "";
6466 p = strchr(message, '{');
6475 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6478 } else if (strncmp(message, "White resign", 12) == 0) {
6479 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6481 } else if (strncmp(message, "Black resign", 12) == 0) {
6482 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6484 } else if (strncmp(message, "White matches", 13) == 0 ||
6485 strncmp(message, "Black matches", 13) == 0 ) {
6486 /* [HGM] ignore GNUShogi noises */
6488 } else if (strncmp(message, "White", 5) == 0 &&
6489 message[5] != '(' &&
6490 StrStr(message, "Black") == NULL) {
6491 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6493 } else if (strncmp(message, "Black", 5) == 0 &&
6494 message[5] != '(') {
6495 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6497 } else if (strcmp(message, "resign") == 0 ||
6498 strcmp(message, "computer resigns") == 0) {
6500 case MachinePlaysBlack:
6501 case IcsPlayingBlack:
6502 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6504 case MachinePlaysWhite:
6505 case IcsPlayingWhite:
6506 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6508 case TwoMachinesPlay:
6509 if (cps->twoMachinesColor[0] == 'w')
6510 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6512 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6519 } else if (strncmp(message, "opponent mates", 14) == 0) {
6521 case MachinePlaysBlack:
6522 case IcsPlayingBlack:
6523 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6525 case MachinePlaysWhite:
6526 case IcsPlayingWhite:
6527 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6529 case TwoMachinesPlay:
6530 if (cps->twoMachinesColor[0] == 'w')
6531 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6533 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6540 } else if (strncmp(message, "computer mates", 14) == 0) {
6542 case MachinePlaysBlack:
6543 case IcsPlayingBlack:
6544 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6546 case MachinePlaysWhite:
6547 case IcsPlayingWhite:
6548 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6550 case TwoMachinesPlay:
6551 if (cps->twoMachinesColor[0] == 'w')
6552 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6554 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6561 } else if (strncmp(message, "checkmate", 9) == 0) {
6562 if (WhiteOnMove(forwardMostMove)) {
6563 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6565 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6568 } else if (strstr(message, "Draw") != NULL ||
6569 strstr(message, "game is a draw") != NULL) {
6570 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6572 } else if (strstr(message, "offer") != NULL &&
6573 strstr(message, "draw") != NULL) {
6575 if (appData.zippyPlay && first.initDone) {
6576 /* Relay offer to ICS */
6577 SendToICS(ics_prefix);
6578 SendToICS("draw\n");
6581 cps->offeredDraw = 2; /* valid until this engine moves twice */
6582 if (gameMode == TwoMachinesPlay) {
6583 if (cps->other->offeredDraw) {
6584 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6585 /* [HGM] in two-machine mode we delay relaying draw offer */
6586 /* until after we also have move, to see if it is really claim */
6590 if (cps->other->sendDrawOffers) {
6591 SendToProgram("draw\n", cps->other);
6595 } else if (gameMode == MachinePlaysWhite ||
6596 gameMode == MachinePlaysBlack) {
6597 if (userOfferedDraw) {
6598 DisplayInformation(_("Machine accepts your draw offer"));
6599 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6601 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6608 * Look for thinking output
6610 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6611 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6613 int plylev, mvleft, mvtot, curscore, time;
6614 char mvname[MOVE_LEN];
6618 int prefixHint = FALSE;
6619 mvname[0] = NULLCHAR;
6622 case MachinePlaysBlack:
6623 case IcsPlayingBlack:
6624 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6626 case MachinePlaysWhite:
6627 case IcsPlayingWhite:
6628 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6633 case IcsObserving: /* [DM] icsEngineAnalyze */
6634 if (!appData.icsEngineAnalyze) ignore = TRUE;
6636 case TwoMachinesPlay:
6637 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6648 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6649 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6651 if (plyext != ' ' && plyext != '\t') {
6655 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6656 if( cps->scoreIsAbsolute &&
6657 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6659 curscore = -curscore;
6663 programStats.depth = plylev;
6664 programStats.nodes = nodes;
6665 programStats.time = time;
6666 programStats.score = curscore;
6667 programStats.got_only_move = 0;
6669 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6672 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6673 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6674 if(WhiteOnMove(forwardMostMove))
6675 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6676 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6679 /* Buffer overflow protection */
6680 if (buf1[0] != NULLCHAR) {
6681 if (strlen(buf1) >= sizeof(programStats.movelist)
6682 && appData.debugMode) {
6684 "PV is too long; using the first %d bytes.\n",
6685 sizeof(programStats.movelist) - 1);
6688 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6690 sprintf(programStats.movelist, " no PV\n");
6693 if (programStats.seen_stat) {
6694 programStats.ok_to_send = 1;
6697 if (strchr(programStats.movelist, '(') != NULL) {
6698 programStats.line_is_book = 1;
6699 programStats.nr_moves = 0;
6700 programStats.moves_left = 0;
6702 programStats.line_is_book = 0;
6705 SendProgramStatsToFrontend( cps, &programStats );
6708 [AS] Protect the thinkOutput buffer from overflow... this
6709 is only useful if buf1 hasn't overflowed first!
6711 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6713 (gameMode == TwoMachinesPlay ?
6714 ToUpper(cps->twoMachinesColor[0]) : ' '),
6715 ((double) curscore) / 100.0,
6716 prefixHint ? lastHint : "",
6717 prefixHint ? " " : "" );
6719 if( buf1[0] != NULLCHAR ) {
6720 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6722 if( strlen(buf1) > max_len ) {
6723 if( appData.debugMode) {
6724 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6726 buf1[max_len+1] = '\0';
6729 strcat( thinkOutput, buf1 );
6732 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6733 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6734 DisplayMove(currentMove - 1);
6739 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6740 /* crafty (9.25+) says "(only move) <move>"
6741 * if there is only 1 legal move
6743 sscanf(p, "(only move) %s", buf1);
6744 sprintf(thinkOutput, "%s (only move)", buf1);
6745 sprintf(programStats.movelist, "%s (only move)", buf1);
6746 programStats.depth = 1;
6747 programStats.nr_moves = 1;
6748 programStats.moves_left = 1;
6749 programStats.nodes = 1;
6750 programStats.time = 1;
6751 programStats.got_only_move = 1;
6753 /* Not really, but we also use this member to
6754 mean "line isn't going to change" (Crafty
6755 isn't searching, so stats won't change) */
6756 programStats.line_is_book = 1;
6758 SendProgramStatsToFrontend( cps, &programStats );
6760 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6761 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6762 DisplayMove(currentMove - 1);
6766 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6767 &time, &nodes, &plylev, &mvleft,
6768 &mvtot, mvname) >= 5) {
6769 /* The stat01: line is from Crafty (9.29+) in response
6770 to the "." command */
6771 programStats.seen_stat = 1;
6772 cps->maybeThinking = TRUE;
6774 if (programStats.got_only_move || !appData.periodicUpdates)
6777 programStats.depth = plylev;
6778 programStats.time = time;
6779 programStats.nodes = nodes;
6780 programStats.moves_left = mvleft;
6781 programStats.nr_moves = mvtot;
6782 strcpy(programStats.move_name, mvname);
6783 programStats.ok_to_send = 1;
6784 programStats.movelist[0] = '\0';
6786 SendProgramStatsToFrontend( cps, &programStats );
6791 } else if (strncmp(message,"++",2) == 0) {
6792 /* Crafty 9.29+ outputs this */
6793 programStats.got_fail = 2;
6796 } else if (strncmp(message,"--",2) == 0) {
6797 /* Crafty 9.29+ outputs this */
6798 programStats.got_fail = 1;
6801 } else if (thinkOutput[0] != NULLCHAR &&
6802 strncmp(message, " ", 4) == 0) {
6803 unsigned message_len;
6806 while (*p && *p == ' ') p++;
6808 message_len = strlen( p );
6810 /* [AS] Avoid buffer overflow */
6811 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6812 strcat(thinkOutput, " ");
6813 strcat(thinkOutput, p);
6816 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6817 strcat(programStats.movelist, " ");
6818 strcat(programStats.movelist, p);
6821 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6822 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6823 DisplayMove(currentMove - 1);
6832 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6833 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6835 ChessProgramStats cpstats;
6837 if (plyext != ' ' && plyext != '\t') {
6841 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6842 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6843 curscore = -curscore;
6846 cpstats.depth = plylev;
6847 cpstats.nodes = nodes;
6848 cpstats.time = time;
6849 cpstats.score = curscore;
6850 cpstats.got_only_move = 0;
6851 cpstats.movelist[0] = '\0';
6853 if (buf1[0] != NULLCHAR) {
6854 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6857 cpstats.ok_to_send = 0;
6858 cpstats.line_is_book = 0;
6859 cpstats.nr_moves = 0;
6860 cpstats.moves_left = 0;
6862 SendProgramStatsToFrontend( cps, &cpstats );
6869 /* Parse a game score from the character string "game", and
6870 record it as the history of the current game. The game
6871 score is NOT assumed to start from the standard position.
6872 The display is not updated in any way.
6875 ParseGameHistory(game)
6879 int fromX, fromY, toX, toY, boardIndex;
6884 if (appData.debugMode)
6885 fprintf(debugFP, "Parsing game history: %s\n", game);
6887 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6888 gameInfo.site = StrSave(appData.icsHost);
6889 gameInfo.date = PGNDate();
6890 gameInfo.round = StrSave("-");
6892 /* Parse out names of players */
6893 while (*game == ' ') game++;
6895 while (*game != ' ') *p++ = *game++;
6897 gameInfo.white = StrSave(buf);
6898 while (*game == ' ') game++;
6900 while (*game != ' ' && *game != '\n') *p++ = *game++;
6902 gameInfo.black = StrSave(buf);
6905 boardIndex = blackPlaysFirst ? 1 : 0;
6908 yyboardindex = boardIndex;
6909 moveType = (ChessMove) yylex();
6911 case IllegalMove: /* maybe suicide chess, etc. */
6912 if (appData.debugMode) {
6913 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6914 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6915 setbuf(debugFP, NULL);
6917 case WhitePromotionChancellor:
6918 case BlackPromotionChancellor:
6919 case WhitePromotionArchbishop:
6920 case BlackPromotionArchbishop:
6921 case WhitePromotionQueen:
6922 case BlackPromotionQueen:
6923 case WhitePromotionRook:
6924 case BlackPromotionRook:
6925 case WhitePromotionBishop:
6926 case BlackPromotionBishop:
6927 case WhitePromotionKnight:
6928 case BlackPromotionKnight:
6929 case WhitePromotionKing:
6930 case BlackPromotionKing:
6932 case WhiteCapturesEnPassant:
6933 case BlackCapturesEnPassant:
6934 case WhiteKingSideCastle:
6935 case WhiteQueenSideCastle:
6936 case BlackKingSideCastle:
6937 case BlackQueenSideCastle:
6938 case WhiteKingSideCastleWild:
6939 case WhiteQueenSideCastleWild:
6940 case BlackKingSideCastleWild:
6941 case BlackQueenSideCastleWild:
6943 case WhiteHSideCastleFR:
6944 case WhiteASideCastleFR:
6945 case BlackHSideCastleFR:
6946 case BlackASideCastleFR:
6948 fromX = currentMoveString[0] - AAA;
6949 fromY = currentMoveString[1] - ONE;
6950 toX = currentMoveString[2] - AAA;
6951 toY = currentMoveString[3] - ONE;
6952 promoChar = currentMoveString[4];
6956 fromX = moveType == WhiteDrop ?
6957 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6958 (int) CharToPiece(ToLower(currentMoveString[0]));
6960 toX = currentMoveString[2] - AAA;
6961 toY = currentMoveString[3] - ONE;
6962 promoChar = NULLCHAR;
6966 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6967 if (appData.debugMode) {
6968 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6969 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6970 setbuf(debugFP, NULL);
6972 DisplayError(buf, 0);
6974 case ImpossibleMove:
6976 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6977 if (appData.debugMode) {
6978 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6979 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6980 setbuf(debugFP, NULL);
6982 DisplayError(buf, 0);
6984 case (ChessMove) 0: /* end of file */
6985 if (boardIndex < backwardMostMove) {
6986 /* Oops, gap. How did that happen? */
6987 DisplayError(_("Gap in move list"), 0);
6990 backwardMostMove = blackPlaysFirst ? 1 : 0;
6991 if (boardIndex > forwardMostMove) {
6992 forwardMostMove = boardIndex;
6996 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6997 strcat(parseList[boardIndex-1], " ");
6998 strcat(parseList[boardIndex-1], yy_text);
7010 case GameUnfinished:
7011 if (gameMode == IcsExamining) {
7012 if (boardIndex < backwardMostMove) {
7013 /* Oops, gap. How did that happen? */
7016 backwardMostMove = blackPlaysFirst ? 1 : 0;
7019 gameInfo.result = moveType;
7020 p = strchr(yy_text, '{');
7021 if (p == NULL) p = strchr(yy_text, '(');
7024 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7026 q = strchr(p, *p == '{' ? '}' : ')');
7027 if (q != NULL) *q = NULLCHAR;
7030 gameInfo.resultDetails = StrSave(p);
7033 if (boardIndex >= forwardMostMove &&
7034 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7035 backwardMostMove = blackPlaysFirst ? 1 : 0;
7038 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7039 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7040 parseList[boardIndex]);
7041 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7042 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7043 /* currentMoveString is set as a side-effect of yylex */
7044 strcpy(moveList[boardIndex], currentMoveString);
7045 strcat(moveList[boardIndex], "\n");
7047 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7048 castlingRights[boardIndex], &epStatus[boardIndex]);
7049 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7050 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7056 if(gameInfo.variant != VariantShogi)
7057 strcat(parseList[boardIndex - 1], "+");
7061 strcat(parseList[boardIndex - 1], "#");
7068 /* Apply a move to the given board */
7070 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7071 int fromX, fromY, toX, toY;
7077 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7079 /* [HGM] compute & store e.p. status and castling rights for new position */
7080 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7083 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7087 if( board[toY][toX] != EmptySquare )
7090 if( board[fromY][fromX] == WhitePawn ) {
7091 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7094 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7095 gameInfo.variant != VariantBerolina || toX < fromX)
7096 *ep = toX | berolina;
7097 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7098 gameInfo.variant != VariantBerolina || toX > fromX)
7102 if( board[fromY][fromX] == BlackPawn ) {
7103 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7105 if( toY-fromY== -2) {
7106 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7107 gameInfo.variant != VariantBerolina || toX < fromX)
7108 *ep = toX | berolina;
7109 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7110 gameInfo.variant != VariantBerolina || toX > fromX)
7115 for(i=0; i<nrCastlingRights; i++) {
7116 if(castling[i] == fromX && castlingRank[i] == fromY ||
7117 castling[i] == toX && castlingRank[i] == toY
7118 ) castling[i] = -1; // revoke for moved or captured piece
7123 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7124 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7125 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7127 if (fromX == toX && fromY == toY) return;
7129 if (fromY == DROP_RANK) {
7131 piece = board[toY][toX] = (ChessSquare) fromX;
7133 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7134 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7135 if(gameInfo.variant == VariantKnightmate)
7136 king += (int) WhiteUnicorn - (int) WhiteKing;
7138 /* Code added by Tord: */
7139 /* FRC castling assumed when king captures friendly rook. */
7140 if (board[fromY][fromX] == WhiteKing &&
7141 board[toY][toX] == WhiteRook) {
7142 board[fromY][fromX] = EmptySquare;
7143 board[toY][toX] = EmptySquare;
7145 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7147 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7149 } else if (board[fromY][fromX] == BlackKing &&
7150 board[toY][toX] == BlackRook) {
7151 board[fromY][fromX] = EmptySquare;
7152 board[toY][toX] = EmptySquare;
7154 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7156 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7158 /* End of code added by Tord */
7160 } else if (board[fromY][fromX] == king
7161 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7162 && toY == fromY && toX > fromX+1) {
7163 board[fromY][fromX] = EmptySquare;
7164 board[toY][toX] = king;
7165 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7166 board[fromY][BOARD_RGHT-1] = EmptySquare;
7167 } else if (board[fromY][fromX] == king
7168 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7169 && toY == fromY && toX < fromX-1) {
7170 board[fromY][fromX] = EmptySquare;
7171 board[toY][toX] = king;
7172 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7173 board[fromY][BOARD_LEFT] = EmptySquare;
7174 } else if (board[fromY][fromX] == WhitePawn
7175 && toY == BOARD_HEIGHT-1
7176 && gameInfo.variant != VariantXiangqi
7178 /* white pawn promotion */
7179 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7180 if (board[toY][toX] == EmptySquare) {
7181 board[toY][toX] = WhiteQueen;
7183 if(gameInfo.variant==VariantBughouse ||
7184 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7185 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7186 board[fromY][fromX] = EmptySquare;
7187 } else if ((fromY == BOARD_HEIGHT-4)
7189 && gameInfo.variant != VariantXiangqi
7190 && gameInfo.variant != VariantBerolina
7191 && (board[fromY][fromX] == WhitePawn)
7192 && (board[toY][toX] == EmptySquare)) {
7193 board[fromY][fromX] = EmptySquare;
7194 board[toY][toX] = WhitePawn;
7195 captured = board[toY - 1][toX];
7196 board[toY - 1][toX] = EmptySquare;
7197 } else if ((fromY == BOARD_HEIGHT-4)
7199 && gameInfo.variant == VariantBerolina
7200 && (board[fromY][fromX] == WhitePawn)
7201 && (board[toY][toX] == EmptySquare)) {
7202 board[fromY][fromX] = EmptySquare;
7203 board[toY][toX] = WhitePawn;
7204 if(oldEP & EP_BEROLIN_A) {
7205 captured = board[fromY][fromX-1];
7206 board[fromY][fromX-1] = EmptySquare;
7207 }else{ captured = board[fromY][fromX+1];
7208 board[fromY][fromX+1] = EmptySquare;
7210 } else if (board[fromY][fromX] == king
7211 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7212 && toY == fromY && toX > fromX+1) {
7213 board[fromY][fromX] = EmptySquare;
7214 board[toY][toX] = king;
7215 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7216 board[fromY][BOARD_RGHT-1] = EmptySquare;
7217 } else if (board[fromY][fromX] == king
7218 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219 && toY == fromY && toX < fromX-1) {
7220 board[fromY][fromX] = EmptySquare;
7221 board[toY][toX] = king;
7222 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7223 board[fromY][BOARD_LEFT] = EmptySquare;
7224 } else if (fromY == 7 && fromX == 3
7225 && board[fromY][fromX] == BlackKing
7226 && toY == 7 && toX == 5) {
7227 board[fromY][fromX] = EmptySquare;
7228 board[toY][toX] = BlackKing;
7229 board[fromY][7] = EmptySquare;
7230 board[toY][4] = BlackRook;
7231 } else if (fromY == 7 && fromX == 3
7232 && board[fromY][fromX] == BlackKing
7233 && toY == 7 && toX == 1) {
7234 board[fromY][fromX] = EmptySquare;
7235 board[toY][toX] = BlackKing;
7236 board[fromY][0] = EmptySquare;
7237 board[toY][2] = BlackRook;
7238 } else if (board[fromY][fromX] == BlackPawn
7240 && gameInfo.variant != VariantXiangqi
7242 /* black pawn promotion */
7243 board[0][toX] = CharToPiece(ToLower(promoChar));
7244 if (board[0][toX] == EmptySquare) {
7245 board[0][toX] = BlackQueen;
7247 if(gameInfo.variant==VariantBughouse ||
7248 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7249 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7250 board[fromY][fromX] = EmptySquare;
7251 } else if ((fromY == 3)
7253 && gameInfo.variant != VariantXiangqi
7254 && gameInfo.variant != VariantBerolina
7255 && (board[fromY][fromX] == BlackPawn)
7256 && (board[toY][toX] == EmptySquare)) {
7257 board[fromY][fromX] = EmptySquare;
7258 board[toY][toX] = BlackPawn;
7259 captured = board[toY + 1][toX];
7260 board[toY + 1][toX] = EmptySquare;
7261 } else if ((fromY == 3)
7263 && gameInfo.variant == VariantBerolina
7264 && (board[fromY][fromX] == BlackPawn)
7265 && (board[toY][toX] == EmptySquare)) {
7266 board[fromY][fromX] = EmptySquare;
7267 board[toY][toX] = BlackPawn;
7268 if(oldEP & EP_BEROLIN_A) {
7269 captured = board[fromY][fromX-1];
7270 board[fromY][fromX-1] = EmptySquare;
7271 }else{ captured = board[fromY][fromX+1];
7272 board[fromY][fromX+1] = EmptySquare;
7275 board[toY][toX] = board[fromY][fromX];
7276 board[fromY][fromX] = EmptySquare;
7279 /* [HGM] now we promote for Shogi, if needed */
7280 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7281 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7284 if (gameInfo.holdingsWidth != 0) {
7286 /* !!A lot more code needs to be written to support holdings */
7287 /* [HGM] OK, so I have written it. Holdings are stored in the */
7288 /* penultimate board files, so they are automaticlly stored */
7289 /* in the game history. */
7290 if (fromY == DROP_RANK) {
7291 /* Delete from holdings, by decreasing count */
7292 /* and erasing image if necessary */
7294 if(p < (int) BlackPawn) { /* white drop */
7295 p -= (int)WhitePawn;
7296 if(p >= gameInfo.holdingsSize) p = 0;
7297 if(--board[p][BOARD_WIDTH-2] == 0)
7298 board[p][BOARD_WIDTH-1] = EmptySquare;
7299 } else { /* black drop */
7300 p -= (int)BlackPawn;
7301 if(p >= gameInfo.holdingsSize) p = 0;
7302 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7303 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7306 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7307 && gameInfo.variant != VariantBughouse ) {
7308 /* [HGM] holdings: Add to holdings, if holdings exist */
7309 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7310 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7311 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7314 if (p >= (int) BlackPawn) {
7315 p -= (int)BlackPawn;
7316 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7317 /* in Shogi restore piece to its original first */
7318 captured = (ChessSquare) (DEMOTED captured);
7321 p = PieceToNumber((ChessSquare)p);
7322 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7323 board[p][BOARD_WIDTH-2]++;
7324 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7326 p -= (int)WhitePawn;
7327 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7328 captured = (ChessSquare) (DEMOTED captured);
7331 p = PieceToNumber((ChessSquare)p);
7332 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7333 board[BOARD_HEIGHT-1-p][1]++;
7334 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7338 } else if (gameInfo.variant == VariantAtomic) {
7339 if (captured != EmptySquare) {
7341 for (y = toY-1; y <= toY+1; y++) {
7342 for (x = toX-1; x <= toX+1; x++) {
7343 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7344 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7345 board[y][x] = EmptySquare;
7349 board[toY][toX] = EmptySquare;
7352 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7353 /* [HGM] Shogi promotions */
7354 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7357 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7358 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7359 // [HGM] superchess: take promotion piece out of holdings
7360 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7361 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7362 if(!--board[k][BOARD_WIDTH-2])
7363 board[k][BOARD_WIDTH-1] = EmptySquare;
7365 if(!--board[BOARD_HEIGHT-1-k][1])
7366 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7372 /* Updates forwardMostMove */
7374 MakeMove(fromX, fromY, toX, toY, promoChar)
7375 int fromX, fromY, toX, toY;
7378 // forwardMostMove++; // [HGM] bare: moved downstream
7380 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7381 int timeLeft; static int lastLoadFlag=0; int king, piece;
7382 piece = boards[forwardMostMove][fromY][fromX];
7383 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7384 if(gameInfo.variant == VariantKnightmate)
7385 king += (int) WhiteUnicorn - (int) WhiteKing;
7386 if(forwardMostMove == 0) {
7388 fprintf(serverMoves, "%s;", second.tidy);
7389 fprintf(serverMoves, "%s;", first.tidy);
7390 if(!blackPlaysFirst)
7391 fprintf(serverMoves, "%s;", second.tidy);
7392 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7393 lastLoadFlag = loadFlag;
7395 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7396 // print castling suffix
7397 if( toY == fromY && piece == king ) {
7399 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7401 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7404 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7405 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7406 boards[forwardMostMove][toY][toX] == EmptySquare
7408 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7410 if(promoChar != NULLCHAR)
7411 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7413 fprintf(serverMoves, "/%d/%d",
7414 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7415 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7416 else timeLeft = blackTimeRemaining/1000;
7417 fprintf(serverMoves, "/%d", timeLeft);
7419 fflush(serverMoves);
7422 if (forwardMostMove+1 >= MAX_MOVES) {
7423 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7428 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7429 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7430 if (commentList[forwardMostMove+1] != NULL) {
7431 free(commentList[forwardMostMove+1]);
7432 commentList[forwardMostMove+1] = NULL;
7434 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7435 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7436 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7437 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7438 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7439 gameInfo.result = GameUnfinished;
7440 if (gameInfo.resultDetails != NULL) {
7441 free(gameInfo.resultDetails);
7442 gameInfo.resultDetails = NULL;
7444 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7445 moveList[forwardMostMove - 1]);
7446 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7447 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7448 fromY, fromX, toY, toX, promoChar,
7449 parseList[forwardMostMove - 1]);
7450 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7451 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7452 castlingRights[forwardMostMove]) ) {
7458 if(gameInfo.variant != VariantShogi)
7459 strcat(parseList[forwardMostMove - 1], "+");
7463 strcat(parseList[forwardMostMove - 1], "#");
7466 if (appData.debugMode) {
7467 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7472 /* Updates currentMove if not pausing */
7474 ShowMove(fromX, fromY, toX, toY)
7476 int instant = (gameMode == PlayFromGameFile) ?
7477 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7479 if(appData.noGUI) return;
7481 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7485 if (forwardMostMove == currentMove + 1)
7488 // AnimateMove(boards[forwardMostMove - 1],
7489 // fromX, fromY, toX, toY);
7491 if (appData.highlightLastMove)
7493 SetHighlights(fromX, fromY, toX, toY);
7496 currentMove = forwardMostMove;
7499 if (instant) return;
7501 DisplayMove(currentMove - 1);
7502 DrawPosition(FALSE, boards[currentMove]);
7503 DisplayBothClocks();
7504 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7509 void SendEgtPath(ChessProgramState *cps)
7510 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7511 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7513 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7516 char c, *q = name+1, *r, *s;
7518 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7519 while(*p && *p != ',') *q++ = *p++;
7521 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7522 strcmp(name, ",nalimov:") == 0 ) {
7523 // take nalimov path from the menu-changeable option first, if it is defined
7524 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7525 SendToProgram(buf,cps); // send egtbpath command for nalimov
7527 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7528 (s = StrStr(appData.egtFormats, name)) != NULL) {
7529 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7530 s = r = StrStr(s, ":") + 1; // beginning of path info
7531 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7532 c = *r; *r = 0; // temporarily null-terminate path info
7533 *--q = 0; // strip of trailig ':' from name
7534 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7536 SendToProgram(buf,cps); // send egtbpath command for this format
7538 if(*p == ',') p++; // read away comma to position for next format name
7543 InitChessProgram(cps, setup)
7544 ChessProgramState *cps;
7545 int setup; /* [HGM] needed to setup FRC opening position */
7547 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7548 if (appData.noChessProgram) return;
7549 hintRequested = FALSE;
7550 bookRequested = FALSE;
7552 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7553 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7554 if(cps->memSize) { /* [HGM] memory */
7555 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7556 SendToProgram(buf, cps);
7558 SendEgtPath(cps); /* [HGM] EGT */
7559 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7560 sprintf(buf, "cores %d\n", appData.smpCores);
7561 SendToProgram(buf, cps);
7564 SendToProgram(cps->initString, cps);
7565 if (gameInfo.variant != VariantNormal &&
7566 gameInfo.variant != VariantLoadable
7567 /* [HGM] also send variant if board size non-standard */
7568 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7570 char *v = VariantName(gameInfo.variant);
7571 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7572 /* [HGM] in protocol 1 we have to assume all variants valid */
7573 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7574 DisplayFatalError(buf, 0, 1);
7578 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7579 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7580 if( gameInfo.variant == VariantXiangqi )
7581 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7582 if( gameInfo.variant == VariantShogi )
7583 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7584 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7585 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7586 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7587 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7588 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7589 if( gameInfo.variant == VariantCourier )
7590 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7591 if( gameInfo.variant == VariantSuper )
7592 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7593 if( gameInfo.variant == VariantGreat )
7594 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7597 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7598 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7599 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7600 if(StrStr(cps->variants, b) == NULL) {
7601 // specific sized variant not known, check if general sizing allowed
7602 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7603 if(StrStr(cps->variants, "boardsize") == NULL) {
7604 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7605 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7606 DisplayFatalError(buf, 0, 1);
7609 /* [HGM] here we really should compare with the maximum supported board size */
7612 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7613 sprintf(buf, "variant %s\n", b);
7614 SendToProgram(buf, cps);
7616 currentlyInitializedVariant = gameInfo.variant;
7618 /* [HGM] send opening position in FRC to first engine */
7620 SendToProgram("force\n", cps);
7622 /* engine is now in force mode! Set flag to wake it up after first move. */
7623 setboardSpoiledMachineBlack = 1;
7627 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7628 SendToProgram(buf, cps);
7630 cps->maybeThinking = FALSE;
7631 cps->offeredDraw = 0;
7632 if (!appData.icsActive) {
7633 SendTimeControl(cps, movesPerSession, timeControl,
7634 timeIncrement, appData.searchDepth,
7637 if (appData.showThinking
7638 // [HGM] thinking: four options require thinking output to be sent
7639 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7641 SendToProgram("post\n", cps);
7643 SendToProgram("hard\n", cps);
7644 if (!appData.ponderNextMove) {
7645 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7646 it without being sure what state we are in first. "hard"
7647 is not a toggle, so that one is OK.
7649 SendToProgram("easy\n", cps);
7652 sprintf(buf, "ping %d\n", ++cps->lastPing);
7653 SendToProgram(buf, cps);
7655 cps->initDone = TRUE;
7660 StartChessProgram(cps)
7661 ChessProgramState *cps;
7666 if (appData.noChessProgram) return;
7667 cps->initDone = FALSE;
7669 if (strcmp(cps->host, "localhost") == 0) {
7670 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7671 } else if (*appData.remoteShell == NULLCHAR) {
7672 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7674 if (*appData.remoteUser == NULLCHAR) {
7675 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7678 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7679 cps->host, appData.remoteUser, cps->program);
7681 err = StartChildProcess(buf, "", &cps->pr);
7685 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7686 DisplayFatalError(buf, err, 1);
7692 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7693 if (cps->protocolVersion > 1) {
7694 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7695 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7696 cps->comboCnt = 0; // and values of combo boxes
7697 SendToProgram(buf, cps);
7699 SendToProgram("xboard\n", cps);
7705 TwoMachinesEventIfReady P((void))
7707 if (first.lastPing != first.lastPong) {
7708 DisplayMessage("", _("Waiting for first chess program"));
7709 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7712 if (second.lastPing != second.lastPong) {
7713 DisplayMessage("", _("Waiting for second chess program"));
7714 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7722 NextMatchGame P((void))
7724 int index; /* [HGM] autoinc: step lod index during match */
7726 if (*appData.loadGameFile != NULLCHAR) {
7727 index = appData.loadGameIndex;
7728 if(index < 0) { // [HGM] autoinc
7729 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7730 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7732 LoadGameFromFile(appData.loadGameFile,
7734 appData.loadGameFile, FALSE);
7735 } else if (*appData.loadPositionFile != NULLCHAR) {
7736 index = appData.loadPositionIndex;
7737 if(index < 0) { // [HGM] autoinc
7738 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7739 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7741 LoadPositionFromFile(appData.loadPositionFile,
7743 appData.loadPositionFile);
7745 TwoMachinesEventIfReady();
7748 void UserAdjudicationEvent( int result )
7750 ChessMove gameResult = GameIsDrawn;
7753 gameResult = WhiteWins;
7755 else if( result < 0 ) {
7756 gameResult = BlackWins;
7759 if( gameMode == TwoMachinesPlay ) {
7760 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7766 GameEnds(result, resultDetails, whosays)
7768 char *resultDetails;
7771 GameMode nextGameMode;
7775 if(endingGame) return; /* [HGM] crash: forbid recursion */
7778 if (appData.debugMode) {
7779 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7780 result, resultDetails ? resultDetails : "(null)", whosays);
7783 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7784 /* If we are playing on ICS, the server decides when the
7785 game is over, but the engine can offer to draw, claim
7789 if (appData.zippyPlay && first.initDone) {
7790 if (result == GameIsDrawn) {
7791 /* In case draw still needs to be claimed */
7792 SendToICS(ics_prefix);
7793 SendToICS("draw\n");
7794 } else if (StrCaseStr(resultDetails, "resign")) {
7795 SendToICS(ics_prefix);
7796 SendToICS("resign\n");
7800 endingGame = 0; /* [HGM] crash */
7804 /* If we're loading the game from a file, stop */
7805 if (whosays == GE_FILE) {
7806 (void) StopLoadGameTimer();
7810 /* Cancel draw offers */
7811 first.offeredDraw = second.offeredDraw = 0;
7813 /* If this is an ICS game, only ICS can really say it's done;
7814 if not, anyone can. */
7815 isIcsGame = (gameMode == IcsPlayingWhite ||
7816 gameMode == IcsPlayingBlack ||
7817 gameMode == IcsObserving ||
7818 gameMode == IcsExamining);
7820 if (!isIcsGame || whosays == GE_ICS) {
7821 /* OK -- not an ICS game, or ICS said it was done */
7823 if (!isIcsGame && !appData.noChessProgram)
7824 SetUserThinkingEnables();
7826 /* [HGM] if a machine claims the game end we verify this claim */
7827 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7828 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7830 ChessMove trueResult = (ChessMove) -1;
7832 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7833 first.twoMachinesColor[0] :
7834 second.twoMachinesColor[0] ;
7836 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7837 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7838 /* [HGM] verify: engine mate claims accepted if they were flagged */
7839 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7841 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7842 /* [HGM] verify: engine mate claims accepted if they were flagged */
7843 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7845 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7846 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7849 // now verify win claims, but not in drop games, as we don't understand those yet
7850 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7851 || gameInfo.variant == VariantGreat) &&
7852 (result == WhiteWins && claimer == 'w' ||
7853 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7854 if (appData.debugMode) {
7855 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7856 result, epStatus[forwardMostMove], forwardMostMove);
7858 if(result != trueResult) {
7859 sprintf(buf, "False win claim: '%s'", resultDetails);
7860 result = claimer == 'w' ? BlackWins : WhiteWins;
7861 resultDetails = buf;
7864 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7865 && (forwardMostMove <= backwardMostMove ||
7866 epStatus[forwardMostMove-1] > EP_DRAWS ||
7867 (claimer=='b')==(forwardMostMove&1))
7869 /* [HGM] verify: draws that were not flagged are false claims */
7870 sprintf(buf, "False draw claim: '%s'", resultDetails);
7871 result = claimer == 'w' ? BlackWins : WhiteWins;
7872 resultDetails = buf;
7874 /* (Claiming a loss is accepted no questions asked!) */
7877 /* [HGM] bare: don't allow bare King to win */
7878 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7879 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7880 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7881 && result != GameIsDrawn)
7882 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7883 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7884 int p = (int)boards[forwardMostMove][i][j] - color;
7885 if(p >= 0 && p <= (int)WhiteKing) k++;
7887 if (appData.debugMode) {
7888 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7889 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7892 result = GameIsDrawn;
7893 sprintf(buf, "%s but bare king", resultDetails);
7894 resultDetails = buf;
7899 if(serverMoves != NULL && !loadFlag) { char c = '=';
7900 if(result==WhiteWins) c = '+';
7901 if(result==BlackWins) c = '-';
7902 if(resultDetails != NULL)
7903 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7905 if (resultDetails != NULL) {
7906 gameInfo.result = result;
7907 gameInfo.resultDetails = StrSave(resultDetails);
7909 /* display last move only if game was not loaded from file */
7910 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7911 DisplayMove(currentMove - 1);
7913 if (forwardMostMove != 0) {
7914 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
7915 if (*appData.saveGameFile != NULLCHAR) {
7916 SaveGameToFile(appData.saveGameFile, TRUE);
7917 } else if (appData.autoSaveGames) {
7920 if (*appData.savePositionFile != NULLCHAR) {
7921 SavePositionToFile(appData.savePositionFile);
7926 /* Tell program how game ended in case it is learning */
7927 /* [HGM] Moved this to after saving the PGN, just in case */
7928 /* engine died and we got here through time loss. In that */
7929 /* case we will get a fatal error writing the pipe, which */
7930 /* would otherwise lose us the PGN. */
7931 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7932 /* output during GameEnds should never be fatal anymore */
7933 if (gameMode == MachinePlaysWhite ||
7934 gameMode == MachinePlaysBlack ||
7935 gameMode == TwoMachinesPlay ||
7936 gameMode == IcsPlayingWhite ||
7937 gameMode == IcsPlayingBlack ||
7938 gameMode == BeginningOfGame) {
7940 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7942 if (first.pr != NoProc) {
7943 SendToProgram(buf, &first);
7945 if (second.pr != NoProc &&
7946 gameMode == TwoMachinesPlay) {
7947 SendToProgram(buf, &second);
7952 if (appData.icsActive) {
7953 if (appData.quietPlay &&
7954 (gameMode == IcsPlayingWhite ||
7955 gameMode == IcsPlayingBlack)) {
7956 SendToICS(ics_prefix);
7957 SendToICS("set shout 1\n");
7959 nextGameMode = IcsIdle;
7960 ics_user_moved = FALSE;
7961 /* clean up premove. It's ugly when the game has ended and the
7962 * premove highlights are still on the board.
7966 ClearPremoveHighlights();
7967 DrawPosition(FALSE, boards[currentMove]);
7969 if (whosays == GE_ICS) {
7972 if (gameMode == IcsPlayingWhite)
7974 else if(gameMode == IcsPlayingBlack)
7978 if (gameMode == IcsPlayingBlack)
7980 else if(gameMode == IcsPlayingWhite)
7987 PlayIcsUnfinishedSound();
7990 } else if (gameMode == EditGame ||
7991 gameMode == PlayFromGameFile ||
7992 gameMode == AnalyzeMode ||
7993 gameMode == AnalyzeFile) {
7994 nextGameMode = gameMode;
7996 nextGameMode = EndOfGame;
8001 nextGameMode = gameMode;
8004 if (appData.noChessProgram) {
8005 gameMode = nextGameMode;
8007 endingGame = 0; /* [HGM] crash */
8012 /* Put first chess program into idle state */
8013 if (first.pr != NoProc &&
8014 (gameMode == MachinePlaysWhite ||
8015 gameMode == MachinePlaysBlack ||
8016 gameMode == TwoMachinesPlay ||
8017 gameMode == IcsPlayingWhite ||
8018 gameMode == IcsPlayingBlack ||
8019 gameMode == BeginningOfGame)) {
8020 SendToProgram("force\n", &first);
8021 if (first.usePing) {
8023 sprintf(buf, "ping %d\n", ++first.lastPing);
8024 SendToProgram(buf, &first);
8027 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8028 /* Kill off first chess program */
8029 if (first.isr != NULL)
8030 RemoveInputSource(first.isr);
8033 if (first.pr != NoProc) {
8035 DoSleep( appData.delayBeforeQuit );
8036 SendToProgram("quit\n", &first);
8037 DoSleep( appData.delayAfterQuit );
8038 DestroyChildProcess(first.pr, first.useSigterm);
8043 /* Put second chess program into idle state */
8044 if (second.pr != NoProc &&
8045 gameMode == TwoMachinesPlay) {
8046 SendToProgram("force\n", &second);
8047 if (second.usePing) {
8049 sprintf(buf, "ping %d\n", ++second.lastPing);
8050 SendToProgram(buf, &second);
8053 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8054 /* Kill off second chess program */
8055 if (second.isr != NULL)
8056 RemoveInputSource(second.isr);
8059 if (second.pr != NoProc) {
8060 DoSleep( appData.delayBeforeQuit );
8061 SendToProgram("quit\n", &second);
8062 DoSleep( appData.delayAfterQuit );
8063 DestroyChildProcess(second.pr, second.useSigterm);
8068 if (matchMode && gameMode == TwoMachinesPlay) {
8071 if (first.twoMachinesColor[0] == 'w') {
8078 if (first.twoMachinesColor[0] == 'b') {
8087 if (matchGame < appData.matchGames) {
8089 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8090 tmp = first.twoMachinesColor;
8091 first.twoMachinesColor = second.twoMachinesColor;
8092 second.twoMachinesColor = tmp;
8094 gameMode = nextGameMode;
8096 if(appData.matchPause>10000 || appData.matchPause<10)
8097 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8098 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8099 endingGame = 0; /* [HGM] crash */
8103 gameMode = nextGameMode;
8104 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8105 first.tidy, second.tidy,
8106 first.matchWins, second.matchWins,
8107 appData.matchGames - (first.matchWins + second.matchWins));
8108 DisplayFatalError(buf, 0, 0);
8111 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8112 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8114 gameMode = nextGameMode;
8116 endingGame = 0; /* [HGM] crash */
8119 /* Assumes program was just initialized (initString sent).
8120 Leaves program in force mode. */
8122 FeedMovesToProgram(cps, upto)
8123 ChessProgramState *cps;
8128 if (appData.debugMode)
8129 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8130 startedFromSetupPosition ? "position and " : "",
8131 backwardMostMove, upto, cps->which);
8132 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8133 // [HGM] variantswitch: make engine aware of new variant
8134 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8135 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8136 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8137 SendToProgram(buf, cps);
8138 currentlyInitializedVariant = gameInfo.variant;
8140 SendToProgram("force\n", cps);
8141 if (startedFromSetupPosition) {
8142 SendBoard(cps, backwardMostMove);
8143 if (appData.debugMode) {
8144 fprintf(debugFP, "feedMoves\n");
8147 for (i = backwardMostMove; i < upto; i++) {
8148 SendMoveToProgram(i, cps);
8154 ResurrectChessProgram()
8156 /* The chess program may have exited.
8157 If so, restart it and feed it all the moves made so far. */
8159 if (appData.noChessProgram || first.pr != NoProc) return;
8161 StartChessProgram(&first);
8162 InitChessProgram(&first, FALSE);
8163 FeedMovesToProgram(&first, currentMove);
8165 if (!first.sendTime) {
8166 /* can't tell gnuchess what its clock should read,
8167 so we bow to its notion. */
8169 timeRemaining[0][currentMove] = whiteTimeRemaining;
8170 timeRemaining[1][currentMove] = blackTimeRemaining;
8173 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8174 appData.icsEngineAnalyze) && first.analysisSupport) {
8175 SendToProgram("analyze\n", &first);
8176 first.analyzing = TRUE;
8189 if (appData.debugMode) {
8190 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8191 redraw, init, gameMode);
8193 pausing = pauseExamInvalid = FALSE;
8194 startedFromSetupPosition = blackPlaysFirst = FALSE;
8196 whiteFlag = blackFlag = FALSE;
8197 userOfferedDraw = FALSE;
8198 hintRequested = bookRequested = FALSE;
8199 first.maybeThinking = FALSE;
8200 second.maybeThinking = FALSE;
8201 first.bookSuspend = FALSE; // [HGM] book
8202 second.bookSuspend = FALSE;
8203 thinkOutput[0] = NULLCHAR;
8204 lastHint[0] = NULLCHAR;
8205 ClearGameInfo(&gameInfo);
8206 gameInfo.variant = StringToVariant(appData.variant);
8207 ics_user_moved = ics_clock_paused = FALSE;
8208 ics_getting_history = H_FALSE;
8210 white_holding[0] = black_holding[0] = NULLCHAR;
8211 ClearProgramStats();
8212 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8216 flipView = appData.flipView;
8217 ClearPremoveHighlights();
8219 alarmSounded = FALSE;
8221 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8222 if(appData.serverMovesName != NULL) {
8223 /* [HGM] prepare to make moves file for broadcasting */
8224 clock_t t = clock();
8225 if(serverMoves != NULL) fclose(serverMoves);
8226 serverMoves = fopen(appData.serverMovesName, "r");
8227 if(serverMoves != NULL) {
8228 fclose(serverMoves);
8229 /* delay 15 sec before overwriting, so all clients can see end */
8230 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8232 serverMoves = fopen(appData.serverMovesName, "w");
8236 gameMode = BeginningOfGame;
8239 if(appData.icsActive) gameInfo.variant = VariantNormal;
8240 InitPosition(redraw);
8241 for (i = 0; i < MAX_MOVES; i++) {
8242 if (commentList[i] != NULL) {
8243 free(commentList[i]);
8244 commentList[i] = NULL;
8249 timeRemaining[0][0] = whiteTimeRemaining;
8250 timeRemaining[1][0] = blackTimeRemaining;
8251 if (first.pr == NULL) {
8252 StartChessProgram(&first);
8255 InitChessProgram(&first, startedFromSetupPosition);
8259 DisplayMessage("", "");
8260 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8269 if (!AutoPlayOneMove())
8271 if (matchMode || appData.timeDelay == 0)
8273 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8275 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8284 int fromX, fromY, toX, toY;
8286 if (appData.debugMode) {
8287 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8290 if (gameMode != PlayFromGameFile)
8293 if (currentMove >= forwardMostMove) {
8294 gameMode = EditGame;
8297 /* [AS] Clear current move marker at the end of a game */
8298 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8303 toX = moveList[currentMove][2] - AAA;
8304 toY = moveList[currentMove][3] - ONE;
8306 if (moveList[currentMove][1] == '@') {
8307 if (appData.highlightLastMove) {
8308 SetHighlights(-1, -1, toX, toY);
8311 fromX = moveList[currentMove][0] - AAA;
8312 fromY = moveList[currentMove][1] - ONE;
8314 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8316 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8318 if (appData.highlightLastMove) {
8319 SetHighlights(fromX, fromY, toX, toY);
8322 DisplayMove(currentMove);
8323 SendMoveToProgram(currentMove++, &first);
8324 DisplayBothClocks();
8325 DrawPosition(FALSE, boards[currentMove]);
8326 // [HGM] PV info: always display, routine tests if empty
8327 DisplayComment(currentMove - 1, commentList[currentMove]);
8333 LoadGameOneMove(readAhead)
8334 ChessMove readAhead;
8336 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8337 char promoChar = NULLCHAR;
8342 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8343 gameMode != AnalyzeMode && gameMode != Training) {
8348 yyboardindex = forwardMostMove;
8349 if (readAhead != (ChessMove)0) {
8350 moveType = readAhead;
8352 if (gameFileFP == NULL)
8354 moveType = (ChessMove) yylex();
8360 if (appData.debugMode)
8361 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8363 if (*p == '{' || *p == '[' || *p == '(') {
8364 p[strlen(p) - 1] = NULLCHAR;
8368 /* append the comment but don't display it */
8369 while (*p == '\n') p++;
8370 AppendComment(currentMove, p);
8373 case WhiteCapturesEnPassant:
8374 case BlackCapturesEnPassant:
8375 case WhitePromotionChancellor:
8376 case BlackPromotionChancellor:
8377 case WhitePromotionArchbishop:
8378 case BlackPromotionArchbishop:
8379 case WhitePromotionCentaur:
8380 case BlackPromotionCentaur:
8381 case WhitePromotionQueen:
8382 case BlackPromotionQueen:
8383 case WhitePromotionRook:
8384 case BlackPromotionRook:
8385 case WhitePromotionBishop:
8386 case BlackPromotionBishop:
8387 case WhitePromotionKnight:
8388 case BlackPromotionKnight:
8389 case WhitePromotionKing:
8390 case BlackPromotionKing:
8392 case WhiteKingSideCastle:
8393 case WhiteQueenSideCastle:
8394 case BlackKingSideCastle:
8395 case BlackQueenSideCastle:
8396 case WhiteKingSideCastleWild:
8397 case WhiteQueenSideCastleWild:
8398 case BlackKingSideCastleWild:
8399 case BlackQueenSideCastleWild:
8401 case WhiteHSideCastleFR:
8402 case WhiteASideCastleFR:
8403 case BlackHSideCastleFR:
8404 case BlackASideCastleFR:
8406 if (appData.debugMode)
8407 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8408 fromX = currentMoveString[0] - AAA;
8409 fromY = currentMoveString[1] - ONE;
8410 toX = currentMoveString[2] - AAA;
8411 toY = currentMoveString[3] - ONE;
8412 promoChar = currentMoveString[4];
8417 if (appData.debugMode)
8418 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8419 fromX = moveType == WhiteDrop ?
8420 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8421 (int) CharToPiece(ToLower(currentMoveString[0]));
8423 toX = currentMoveString[2] - AAA;
8424 toY = currentMoveString[3] - ONE;
8430 case GameUnfinished:
8431 if (appData.debugMode)
8432 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8433 p = strchr(yy_text, '{');
8434 if (p == NULL) p = strchr(yy_text, '(');
8437 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8439 q = strchr(p, *p == '{' ? '}' : ')');
8440 if (q != NULL) *q = NULLCHAR;
8443 GameEnds(moveType, p, GE_FILE);
8445 if (cmailMsgLoaded) {
8447 flipView = WhiteOnMove(currentMove);
8448 if (moveType == GameUnfinished) flipView = !flipView;
8449 if (appData.debugMode)
8450 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8454 case (ChessMove) 0: /* end of file */
8455 if (appData.debugMode)
8456 fprintf(debugFP, "Parser hit end of file\n");
8457 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8458 EP_UNKNOWN, castlingRights[currentMove]) ) {
8464 if (WhiteOnMove(currentMove)) {
8465 GameEnds(BlackWins, "Black mates", GE_FILE);
8467 GameEnds(WhiteWins, "White mates", GE_FILE);
8471 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8478 if (lastLoadGameStart == GNUChessGame) {
8479 /* GNUChessGames have numbers, but they aren't move numbers */
8480 if (appData.debugMode)
8481 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8482 yy_text, (int) moveType);
8483 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8485 /* else fall thru */
8490 /* Reached start of next game in file */
8491 if (appData.debugMode)
8492 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8493 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8494 EP_UNKNOWN, castlingRights[currentMove]) ) {
8500 if (WhiteOnMove(currentMove)) {
8501 GameEnds(BlackWins, "Black mates", GE_FILE);
8503 GameEnds(WhiteWins, "White mates", GE_FILE);
8507 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8513 case PositionDiagram: /* should not happen; ignore */
8514 case ElapsedTime: /* ignore */
8515 case NAG: /* ignore */
8516 if (appData.debugMode)
8517 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8518 yy_text, (int) moveType);
8519 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8522 if (appData.testLegality) {
8523 if (appData.debugMode)
8524 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8525 sprintf(move, _("Illegal move: %d.%s%s"),
8526 (forwardMostMove / 2) + 1,
8527 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8528 DisplayError(move, 0);
8531 if (appData.debugMode)
8532 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8533 yy_text, currentMoveString);
8534 fromX = currentMoveString[0] - AAA;
8535 fromY = currentMoveString[1] - ONE;
8536 toX = currentMoveString[2] - AAA;
8537 toY = currentMoveString[3] - ONE;
8538 promoChar = currentMoveString[4];
8543 if (appData.debugMode)
8544 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8545 sprintf(move, _("Ambiguous move: %d.%s%s"),
8546 (forwardMostMove / 2) + 1,
8547 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8548 DisplayError(move, 0);
8553 case ImpossibleMove:
8554 if (appData.debugMode)
8555 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8556 sprintf(move, _("Illegal move: %d.%s%s"),
8557 (forwardMostMove / 2) + 1,
8558 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8559 DisplayError(move, 0);
8565 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8566 DrawPosition(FALSE, boards[currentMove]);
8567 DisplayBothClocks();
8568 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8569 DisplayComment(currentMove - 1, commentList[currentMove]);
8571 (void) StopLoadGameTimer();
8573 cmailOldMove = forwardMostMove;
8576 /* currentMoveString is set as a side-effect of yylex */
8577 strcat(currentMoveString, "\n");
8578 strcpy(moveList[forwardMostMove], currentMoveString);
8580 thinkOutput[0] = NULLCHAR;
8581 MakeMove(fromX, fromY, toX, toY, promoChar);
8582 currentMove = forwardMostMove;
8587 /* Load the nth game from the given file */
8589 LoadGameFromFile(filename, n, title, useList)
8593 /*Boolean*/ int useList;
8598 if (strcmp(filename, "-") == 0) {
8602 f = fopen(filename, "rb");
8604 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8605 DisplayError(buf, errno);
8609 if (fseek(f, 0, 0) == -1) {
8610 /* f is not seekable; probably a pipe */
8613 if (useList && n == 0) {
8614 int error = GameListBuild(f);
8616 DisplayError(_("Cannot build game list"), error);
8617 } else if (!ListEmpty(&gameList) &&
8618 ((ListGame *) gameList.tailPred)->number > 1) {
8619 GameListPopUp(f, title);
8626 return LoadGame(f, n, title, FALSE);
8631 MakeRegisteredMove()
8633 int fromX, fromY, toX, toY;
8635 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8636 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8639 if (appData.debugMode)
8640 fprintf(debugFP, "Restoring %s for game %d\n",
8641 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8643 thinkOutput[0] = NULLCHAR;
8644 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8645 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8646 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8647 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8648 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8649 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8650 MakeMove(fromX, fromY, toX, toY, promoChar);
8651 ShowMove(fromX, fromY, toX, toY);
8653 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8654 EP_UNKNOWN, castlingRights[currentMove]) ) {
8661 if (WhiteOnMove(currentMove)) {
8662 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8664 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8669 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8676 if (WhiteOnMove(currentMove)) {
8677 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8679 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8684 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8695 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8697 CmailLoadGame(f, gameNumber, title, useList)
8705 if (gameNumber > nCmailGames) {
8706 DisplayError(_("No more games in this message"), 0);
8709 if (f == lastLoadGameFP) {
8710 int offset = gameNumber - lastLoadGameNumber;
8712 cmailMsg[0] = NULLCHAR;
8713 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8714 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8715 nCmailMovesRegistered--;
8717 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8718 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8719 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8722 if (! RegisterMove()) return FALSE;
8726 retVal = LoadGame(f, gameNumber, title, useList);
8728 /* Make move registered during previous look at this game, if any */
8729 MakeRegisteredMove();
8731 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8732 commentList[currentMove]
8733 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8734 DisplayComment(currentMove - 1, commentList[currentMove]);
8740 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8745 int gameNumber = lastLoadGameNumber + offset;
8746 if (lastLoadGameFP == NULL) {
8747 DisplayError(_("No game has been loaded yet"), 0);
8750 if (gameNumber <= 0) {
8751 DisplayError(_("Can't back up any further"), 0);
8754 if (cmailMsgLoaded) {
8755 return CmailLoadGame(lastLoadGameFP, gameNumber,
8756 lastLoadGameTitle, lastLoadGameUseList);
8758 return LoadGame(lastLoadGameFP, gameNumber,
8759 lastLoadGameTitle, lastLoadGameUseList);
8765 /* Load the nth game from open file f */
8767 LoadGame(f, gameNumber, title, useList)
8775 int gn = gameNumber;
8776 ListGame *lg = NULL;
8779 GameMode oldGameMode;
8780 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8782 if (appData.debugMode)
8783 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8785 if (gameMode == Training )
8786 SetTrainingModeOff();
8788 oldGameMode = gameMode;
8789 if (gameMode != BeginningOfGame) {
8794 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8795 fclose(lastLoadGameFP);
8799 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8802 fseek(f, lg->offset, 0);
8803 GameListHighlight(gameNumber);
8807 DisplayError(_("Game number out of range"), 0);
8812 if (fseek(f, 0, 0) == -1) {
8813 if (f == lastLoadGameFP ?
8814 gameNumber == lastLoadGameNumber + 1 :
8818 DisplayError(_("Can't seek on game file"), 0);
8824 lastLoadGameNumber = gameNumber;
8825 strcpy(lastLoadGameTitle, title);
8826 lastLoadGameUseList = useList;
8830 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8831 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8832 lg->gameInfo.black);
8834 } else if (*title != NULLCHAR) {
8835 if (gameNumber > 1) {
8836 sprintf(buf, "%s %d", title, gameNumber);
8839 DisplayTitle(title);
8843 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8844 gameMode = PlayFromGameFile;
8848 currentMove = forwardMostMove = backwardMostMove = 0;
8849 CopyBoard(boards[0], initialPosition);
8853 * Skip the first gn-1 games in the file.
8854 * Also skip over anything that precedes an identifiable
8855 * start of game marker, to avoid being confused by
8856 * garbage at the start of the file. Currently
8857 * recognized start of game markers are the move number "1",
8858 * the pattern "gnuchess .* game", the pattern
8859 * "^[#;%] [^ ]* game file", and a PGN tag block.
8860 * A game that starts with one of the latter two patterns
8861 * will also have a move number 1, possibly
8862 * following a position diagram.
8863 * 5-4-02: Let's try being more lenient and allowing a game to
8864 * start with an unnumbered move. Does that break anything?
8866 cm = lastLoadGameStart = (ChessMove) 0;
8868 yyboardindex = forwardMostMove;
8869 cm = (ChessMove) yylex();
8872 if (cmailMsgLoaded) {
8873 nCmailGames = CMAIL_MAX_GAMES - gn;
8876 DisplayError(_("Game not found in file"), 0);
8883 lastLoadGameStart = cm;
8887 switch (lastLoadGameStart) {
8894 gn--; /* count this game */
8895 lastLoadGameStart = cm;
8904 switch (lastLoadGameStart) {
8909 gn--; /* count this game */
8910 lastLoadGameStart = cm;
8913 lastLoadGameStart = cm; /* game counted already */
8921 yyboardindex = forwardMostMove;
8922 cm = (ChessMove) yylex();
8923 } while (cm == PGNTag || cm == Comment);
8930 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8931 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8932 != CMAIL_OLD_RESULT) {
8934 cmailResult[ CMAIL_MAX_GAMES
8935 - gn - 1] = CMAIL_OLD_RESULT;
8941 /* Only a NormalMove can be at the start of a game
8942 * without a position diagram. */
8943 if (lastLoadGameStart == (ChessMove) 0) {
8945 lastLoadGameStart = MoveNumberOne;
8954 if (appData.debugMode)
8955 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8957 if (cm == XBoardGame) {
8958 /* Skip any header junk before position diagram and/or move 1 */
8960 yyboardindex = forwardMostMove;
8961 cm = (ChessMove) yylex();
8963 if (cm == (ChessMove) 0 ||
8964 cm == GNUChessGame || cm == XBoardGame) {
8965 /* Empty game; pretend end-of-file and handle later */
8970 if (cm == MoveNumberOne || cm == PositionDiagram ||
8971 cm == PGNTag || cm == Comment)
8974 } else if (cm == GNUChessGame) {
8975 if (gameInfo.event != NULL) {
8976 free(gameInfo.event);
8978 gameInfo.event = StrSave(yy_text);
8981 startedFromSetupPosition = FALSE;
8982 while (cm == PGNTag) {
8983 if (appData.debugMode)
8984 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8985 err = ParsePGNTag(yy_text, &gameInfo);
8986 if (!err) numPGNTags++;
8988 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8989 if(gameInfo.variant != oldVariant) {
8990 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8992 oldVariant = gameInfo.variant;
8993 if (appData.debugMode)
8994 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8998 if (gameInfo.fen != NULL) {
8999 Board initial_position;
9000 startedFromSetupPosition = TRUE;
9001 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9003 DisplayError(_("Bad FEN position in file"), 0);
9006 CopyBoard(boards[0], initial_position);
9007 if (blackPlaysFirst) {
9008 currentMove = forwardMostMove = backwardMostMove = 1;
9009 CopyBoard(boards[1], initial_position);
9010 strcpy(moveList[0], "");
9011 strcpy(parseList[0], "");
9012 timeRemaining[0][1] = whiteTimeRemaining;
9013 timeRemaining[1][1] = blackTimeRemaining;
9014 if (commentList[0] != NULL) {
9015 commentList[1] = commentList[0];
9016 commentList[0] = NULL;
9019 currentMove = forwardMostMove = backwardMostMove = 0;
9021 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9023 initialRulePlies = FENrulePlies;
9024 epStatus[forwardMostMove] = FENepStatus;
9025 for( i=0; i< nrCastlingRights; i++ )
9026 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9028 yyboardindex = forwardMostMove;
9030 gameInfo.fen = NULL;
9033 yyboardindex = forwardMostMove;
9034 cm = (ChessMove) yylex();
9036 /* Handle comments interspersed among the tags */
9037 while (cm == Comment) {
9039 if (appData.debugMode)
9040 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9042 if (*p == '{' || *p == '[' || *p == '(') {
9043 p[strlen(p) - 1] = NULLCHAR;
9046 while (*p == '\n') p++;
9047 AppendComment(currentMove, p);
9048 yyboardindex = forwardMostMove;
9049 cm = (ChessMove) yylex();
9053 /* don't rely on existence of Event tag since if game was
9054 * pasted from clipboard the Event tag may not exist
9056 if (numPGNTags > 0){
9058 if (gameInfo.variant == VariantNormal) {
9059 gameInfo.variant = StringToVariant(gameInfo.event);
9062 if( appData.autoDisplayTags ) {
9063 tags = PGNTags(&gameInfo);
9064 TagsPopUp(tags, CmailMsg());
9069 /* Make something up, but don't display it now */
9074 if (cm == PositionDiagram) {
9077 Board initial_position;
9079 if (appData.debugMode)
9080 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9082 if (!startedFromSetupPosition) {
9084 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9085 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9095 initial_position[i][j++] = CharToPiece(*p);
9098 while (*p == ' ' || *p == '\t' ||
9099 *p == '\n' || *p == '\r') p++;
9101 if (strncmp(p, "black", strlen("black"))==0)
9102 blackPlaysFirst = TRUE;
9104 blackPlaysFirst = FALSE;
9105 startedFromSetupPosition = TRUE;
9107 CopyBoard(boards[0], initial_position);
9108 if (blackPlaysFirst) {
9109 currentMove = forwardMostMove = backwardMostMove = 1;
9110 CopyBoard(boards[1], initial_position);
9111 strcpy(moveList[0], "");
9112 strcpy(parseList[0], "");
9113 timeRemaining[0][1] = whiteTimeRemaining;
9114 timeRemaining[1][1] = blackTimeRemaining;
9115 if (commentList[0] != NULL) {
9116 commentList[1] = commentList[0];
9117 commentList[0] = NULL;
9120 currentMove = forwardMostMove = backwardMostMove = 0;
9123 yyboardindex = forwardMostMove;
9124 cm = (ChessMove) yylex();
9127 if (first.pr == NoProc) {
9128 StartChessProgram(&first);
9130 InitChessProgram(&first, FALSE);
9131 SendToProgram("force\n", &first);
9132 if (startedFromSetupPosition) {
9133 SendBoard(&first, forwardMostMove);
9134 if (appData.debugMode) {
9135 fprintf(debugFP, "Load Game\n");
9137 DisplayBothClocks();
9140 /* [HGM] server: flag to write setup moves in broadcast file as one */
9141 loadFlag = appData.suppressLoadMoves;
9143 while (cm == Comment) {
9145 if (appData.debugMode)
9146 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9148 if (*p == '{' || *p == '[' || *p == '(') {
9149 p[strlen(p) - 1] = NULLCHAR;
9152 while (*p == '\n') p++;
9153 AppendComment(currentMove, p);
9154 yyboardindex = forwardMostMove;
9155 cm = (ChessMove) yylex();
9158 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9159 cm == WhiteWins || cm == BlackWins ||
9160 cm == GameIsDrawn || cm == GameUnfinished) {
9161 DisplayMessage("", _("No moves in game"));
9162 if (cmailMsgLoaded) {
9163 if (appData.debugMode)
9164 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9168 DrawPosition(FALSE, boards[currentMove]);
9169 DisplayBothClocks();
9170 gameMode = EditGame;
9177 // [HGM] PV info: routine tests if comment empty
9178 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9179 DisplayComment(currentMove - 1, commentList[currentMove]);
9181 if (!matchMode && appData.timeDelay != 0)
9182 DrawPosition(FALSE, boards[currentMove]);
9184 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9185 programStats.ok_to_send = 1;
9188 /* if the first token after the PGN tags is a move
9189 * and not move number 1, retrieve it from the parser
9191 if (cm != MoveNumberOne)
9192 LoadGameOneMove(cm);
9194 /* load the remaining moves from the file */
9195 while (LoadGameOneMove((ChessMove)0)) {
9196 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9197 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9200 /* rewind to the start of the game */
9201 currentMove = backwardMostMove;
9203 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9205 if (oldGameMode == AnalyzeFile ||
9206 oldGameMode == AnalyzeMode) {
9210 if (matchMode || appData.timeDelay == 0) {
9212 gameMode = EditGame;
9214 } else if (appData.timeDelay > 0) {
9218 if (appData.debugMode)
9219 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9221 loadFlag = 0; /* [HGM] true game starts */
9225 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9227 ReloadPosition(offset)
9230 int positionNumber = lastLoadPositionNumber + offset;
9231 if (lastLoadPositionFP == NULL) {
9232 DisplayError(_("No position has been loaded yet"), 0);
9235 if (positionNumber <= 0) {
9236 DisplayError(_("Can't back up any further"), 0);
9239 return LoadPosition(lastLoadPositionFP, positionNumber,
9240 lastLoadPositionTitle);
9243 /* Load the nth position from the given file */
9245 LoadPositionFromFile(filename, n, title)
9253 if (strcmp(filename, "-") == 0) {
9254 return LoadPosition(stdin, n, "stdin");
9256 f = fopen(filename, "rb");
9258 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9259 DisplayError(buf, errno);
9262 return LoadPosition(f, n, title);
9267 /* Load the nth position from the given open file, and close it */
9269 LoadPosition(f, positionNumber, title)
9274 char *p, line[MSG_SIZ];
9275 Board initial_position;
9276 int i, j, fenMode, pn;
9278 if (gameMode == Training )
9279 SetTrainingModeOff();
9281 if (gameMode != BeginningOfGame) {
9284 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9285 fclose(lastLoadPositionFP);
9287 if (positionNumber == 0) positionNumber = 1;
9288 lastLoadPositionFP = f;
9289 lastLoadPositionNumber = positionNumber;
9290 strcpy(lastLoadPositionTitle, title);
9291 if (first.pr == NoProc) {
9292 StartChessProgram(&first);
9293 InitChessProgram(&first, FALSE);
9295 pn = positionNumber;
9296 if (positionNumber < 0) {
9297 /* Negative position number means to seek to that byte offset */
9298 if (fseek(f, -positionNumber, 0) == -1) {
9299 DisplayError(_("Can't seek on position file"), 0);
9304 if (fseek(f, 0, 0) == -1) {
9305 if (f == lastLoadPositionFP ?
9306 positionNumber == lastLoadPositionNumber + 1 :
9307 positionNumber == 1) {
9310 DisplayError(_("Can't seek on position file"), 0);
9315 /* See if this file is FEN or old-style xboard */
9316 if (fgets(line, MSG_SIZ, f) == NULL) {
9317 DisplayError(_("Position not found in file"), 0);
9326 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9327 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9328 case '1': case '2': case '3': case '4': case '5': case '6':
9329 case '7': case '8': case '9':
9330 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9331 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9332 case 'C': case 'W': case 'c': case 'w':
9337 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9338 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9342 if (fenMode || line[0] == '#') pn--;
9344 /* skip positions before number pn */
9345 if (fgets(line, MSG_SIZ, f) == NULL) {
9347 DisplayError(_("Position not found in file"), 0);
9350 if (fenMode || line[0] == '#') pn--;
9355 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9356 DisplayError(_("Bad FEN position in file"), 0);
9360 (void) fgets(line, MSG_SIZ, f);
9361 (void) fgets(line, MSG_SIZ, f);
9363 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9364 (void) fgets(line, MSG_SIZ, f);
9365 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9368 initial_position[i][j++] = CharToPiece(*p);
9372 blackPlaysFirst = FALSE;
9374 (void) fgets(line, MSG_SIZ, f);
9375 if (strncmp(line, "black", strlen("black"))==0)
9376 blackPlaysFirst = TRUE;
9379 startedFromSetupPosition = TRUE;
9381 SendToProgram("force\n", &first);
9382 CopyBoard(boards[0], initial_position);
9383 if (blackPlaysFirst) {
9384 currentMove = forwardMostMove = backwardMostMove = 1;
9385 strcpy(moveList[0], "");
9386 strcpy(parseList[0], "");
9387 CopyBoard(boards[1], initial_position);
9388 DisplayMessage("", _("Black to play"));
9390 currentMove = forwardMostMove = backwardMostMove = 0;
9391 DisplayMessage("", _("White to play"));
9393 /* [HGM] copy FEN attributes as well */
9395 initialRulePlies = FENrulePlies;
9396 epStatus[forwardMostMove] = FENepStatus;
9397 for( i=0; i< nrCastlingRights; i++ )
9398 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9400 SendBoard(&first, forwardMostMove);
9401 if (appData.debugMode) {
9403 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9404 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9405 fprintf(debugFP, "Load Position\n");
9408 if (positionNumber > 1) {
9409 sprintf(line, "%s %d", title, positionNumber);
9412 DisplayTitle(title);
9414 gameMode = EditGame;
9417 timeRemaining[0][1] = whiteTimeRemaining;
9418 timeRemaining[1][1] = blackTimeRemaining;
9419 DrawPosition(FALSE, boards[currentMove]);
9426 CopyPlayerNameIntoFileName(dest, src)
9429 while (*src != NULLCHAR && *src != ',') {
9434 *(*dest)++ = *src++;
9439 char *DefaultFileName(ext)
9442 static char def[MSG_SIZ];
9445 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9447 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9449 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9458 /* Save the current game to the given file */
9460 SaveGameToFile(filename, append)
9467 if (strcmp(filename, "-") == 0) {
9468 return SaveGame(stdout, 0, NULL);
9470 f = fopen(filename, append ? "a" : "w");
9472 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9473 DisplayError(buf, errno);
9476 return SaveGame(f, 0, NULL);
9485 static char buf[MSG_SIZ];
9488 p = strchr(str, ' ');
9489 if (p == NULL) return str;
9490 strncpy(buf, str, p - str);
9491 buf[p - str] = NULLCHAR;
9495 #define PGN_MAX_LINE 75
9497 #define PGN_SIDE_WHITE 0
9498 #define PGN_SIDE_BLACK 1
9501 static int FindFirstMoveOutOfBook( int side )
9505 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9506 int index = backwardMostMove;
9507 int has_book_hit = 0;
9509 if( (index % 2) != side ) {
9513 while( index < forwardMostMove ) {
9514 /* Check to see if engine is in book */
9515 int depth = pvInfoList[index].depth;
9516 int score = pvInfoList[index].score;
9522 else if( score == 0 && depth == 63 ) {
9523 in_book = 1; /* Zappa */
9525 else if( score == 2 && depth == 99 ) {
9526 in_book = 1; /* Abrok */
9529 has_book_hit += in_book;
9545 void GetOutOfBookInfo( char * buf )
9549 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9551 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9552 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9556 if( oob[0] >= 0 || oob[1] >= 0 ) {
9557 for( i=0; i<2; i++ ) {
9561 if( i > 0 && oob[0] >= 0 ) {
9565 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9566 sprintf( buf+strlen(buf), "%s%.2f",
9567 pvInfoList[idx].score >= 0 ? "+" : "",
9568 pvInfoList[idx].score / 100.0 );
9574 /* Save game in PGN style and close the file */
9579 int i, offset, linelen, newblock;
9583 int movelen, numlen, blank;
9584 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9586 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9588 tm = time((time_t *) NULL);
9590 PrintPGNTags(f, &gameInfo);
9592 if (backwardMostMove > 0 || startedFromSetupPosition) {
9593 char *fen = PositionToFEN(backwardMostMove, NULL);
9594 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9595 fprintf(f, "\n{--------------\n");
9596 PrintPosition(f, backwardMostMove);
9597 fprintf(f, "--------------}\n");
9601 /* [AS] Out of book annotation */
9602 if( appData.saveOutOfBookInfo ) {
9605 GetOutOfBookInfo( buf );
9607 if( buf[0] != '\0' ) {
9608 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9615 i = backwardMostMove;
9619 while (i < forwardMostMove) {
9620 /* Print comments preceding this move */
9621 if (commentList[i] != NULL) {
9622 if (linelen > 0) fprintf(f, "\n");
9623 fprintf(f, "{\n%s}\n", commentList[i]);
9628 /* Format move number */
9630 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9633 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9635 numtext[0] = NULLCHAR;
9638 numlen = strlen(numtext);
9641 /* Print move number */
9642 blank = linelen > 0 && numlen > 0;
9643 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9652 fprintf(f, numtext);
9656 strcpy(move_buffer, parseList[i]); // [HGM] pgn: print move via buffer, so it can be edited
9657 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9658 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9659 int p = movelen - 1;
9660 if(move_buffer[p] == ' ') p--;
9661 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9662 while(p && move_buffer[--p] != '(');
9663 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9668 blank = linelen > 0 && movelen > 0;
9669 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9678 fprintf(f, move_buffer);
9681 /* [AS] Add PV info if present */
9682 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9683 /* [HGM] add time */
9684 char buf[MSG_SIZ]; int seconds = 0;
9687 if(i >= backwardMostMove) {
9689 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9690 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9692 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9693 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9695 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9697 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9700 if( seconds <= 0) buf[0] = 0; else
9701 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9702 seconds = (seconds + 4)/10; // round to full seconds
9703 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9704 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9707 sprintf( move_buffer, "{%s%.2f/%d%s}",
9708 pvInfoList[i].score >= 0 ? "+" : "",
9709 pvInfoList[i].score / 100.0,
9710 pvInfoList[i].depth,
9713 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9715 /* Print score/depth */
9716 blank = linelen > 0 && movelen > 0;
9717 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9726 fprintf(f, move_buffer);
9733 /* Start a new line */
9734 if (linelen > 0) fprintf(f, "\n");
9736 /* Print comments after last move */
9737 if (commentList[i] != NULL) {
9738 fprintf(f, "{\n%s}\n", commentList[i]);
9742 if (gameInfo.resultDetails != NULL &&
9743 gameInfo.resultDetails[0] != NULLCHAR) {
9744 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9745 PGNResult(gameInfo.result));
9747 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9754 /* Save game in old style and close the file */
9762 tm = time((time_t *) NULL);
9764 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9767 if (backwardMostMove > 0 || startedFromSetupPosition) {
9768 fprintf(f, "\n[--------------\n");
9769 PrintPosition(f, backwardMostMove);
9770 fprintf(f, "--------------]\n");
9775 i = backwardMostMove;
9776 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9778 while (i < forwardMostMove) {
9779 if (commentList[i] != NULL) {
9780 fprintf(f, "[%s]\n", commentList[i]);
9784 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9787 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9789 if (commentList[i] != NULL) {
9793 if (i >= forwardMostMove) {
9797 fprintf(f, "%s\n", parseList[i]);
9802 if (commentList[i] != NULL) {
9803 fprintf(f, "[%s]\n", commentList[i]);
9806 /* This isn't really the old style, but it's close enough */
9807 if (gameInfo.resultDetails != NULL &&
9808 gameInfo.resultDetails[0] != NULLCHAR) {
9809 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9810 gameInfo.resultDetails);
9812 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9819 /* Save the current game to open file f and close the file */
9821 SaveGame(f, dummy, dummy2)
9826 if (gameMode == EditPosition) EditPositionDone();
9827 if (appData.oldSaveStyle)
9828 return SaveGameOldStyle(f);
9830 return SaveGamePGN(f);
9833 /* Save the current position to the given file */
9835 SavePositionToFile(filename)
9841 if (strcmp(filename, "-") == 0) {
9842 return SavePosition(stdout, 0, NULL);
9844 f = fopen(filename, "a");
9846 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9847 DisplayError(buf, errno);
9850 SavePosition(f, 0, NULL);
9856 /* Save the current position to the given open file and close the file */
9858 SavePosition(f, dummy, dummy2)
9866 if (appData.oldSaveStyle) {
9867 tm = time((time_t *) NULL);
9869 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9871 fprintf(f, "[--------------\n");
9872 PrintPosition(f, currentMove);
9873 fprintf(f, "--------------]\n");
9875 fen = PositionToFEN(currentMove, NULL);
9876 fprintf(f, "%s\n", fen);
9884 ReloadCmailMsgEvent(unregister)
9888 static char *inFilename = NULL;
9889 static char *outFilename;
9891 struct stat inbuf, outbuf;
9894 /* Any registered moves are unregistered if unregister is set, */
9895 /* i.e. invoked by the signal handler */
9897 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9898 cmailMoveRegistered[i] = FALSE;
9899 if (cmailCommentList[i] != NULL) {
9900 free(cmailCommentList[i]);
9901 cmailCommentList[i] = NULL;
9904 nCmailMovesRegistered = 0;
9907 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9908 cmailResult[i] = CMAIL_NOT_RESULT;
9912 if (inFilename == NULL) {
9913 /* Because the filenames are static they only get malloced once */
9914 /* and they never get freed */
9915 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9916 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9918 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9919 sprintf(outFilename, "%s.out", appData.cmailGameName);
9922 status = stat(outFilename, &outbuf);
9924 cmailMailedMove = FALSE;
9926 status = stat(inFilename, &inbuf);
9927 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9930 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9931 counts the games, notes how each one terminated, etc.
9933 It would be nice to remove this kludge and instead gather all
9934 the information while building the game list. (And to keep it
9935 in the game list nodes instead of having a bunch of fixed-size
9936 parallel arrays.) Note this will require getting each game's
9937 termination from the PGN tags, as the game list builder does
9938 not process the game moves. --mann
9940 cmailMsgLoaded = TRUE;
9941 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9943 /* Load first game in the file or popup game menu */
9944 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9954 char string[MSG_SIZ];
9956 if ( cmailMailedMove
9957 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9958 return TRUE; /* Allow free viewing */
9961 /* Unregister move to ensure that we don't leave RegisterMove */
9962 /* with the move registered when the conditions for registering no */
9964 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9965 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9966 nCmailMovesRegistered --;
9968 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9970 free(cmailCommentList[lastLoadGameNumber - 1]);
9971 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9975 if (cmailOldMove == -1) {
9976 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9980 if (currentMove > cmailOldMove + 1) {
9981 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9985 if (currentMove < cmailOldMove) {
9986 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9990 if (forwardMostMove > currentMove) {
9991 /* Silently truncate extra moves */
9995 if ( (currentMove == cmailOldMove + 1)
9996 || ( (currentMove == cmailOldMove)
9997 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9998 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9999 if (gameInfo.result != GameUnfinished) {
10000 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10003 if (commentList[currentMove] != NULL) {
10004 cmailCommentList[lastLoadGameNumber - 1]
10005 = StrSave(commentList[currentMove]);
10007 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10009 if (appData.debugMode)
10010 fprintf(debugFP, "Saving %s for game %d\n",
10011 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10014 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10016 f = fopen(string, "w");
10017 if (appData.oldSaveStyle) {
10018 SaveGameOldStyle(f); /* also closes the file */
10020 sprintf(string, "%s.pos.out", appData.cmailGameName);
10021 f = fopen(string, "w");
10022 SavePosition(f, 0, NULL); /* also closes the file */
10024 fprintf(f, "{--------------\n");
10025 PrintPosition(f, currentMove);
10026 fprintf(f, "--------------}\n\n");
10028 SaveGame(f, 0, NULL); /* also closes the file*/
10031 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10032 nCmailMovesRegistered ++;
10033 } else if (nCmailGames == 1) {
10034 DisplayError(_("You have not made a move yet"), 0);
10045 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10046 FILE *commandOutput;
10047 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10048 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10054 if (! cmailMsgLoaded) {
10055 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10059 if (nCmailGames == nCmailResults) {
10060 DisplayError(_("No unfinished games"), 0);
10064 #if CMAIL_PROHIBIT_REMAIL
10065 if (cmailMailedMove) {
10066 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);
10067 DisplayError(msg, 0);
10072 if (! (cmailMailedMove || RegisterMove())) return;
10074 if ( cmailMailedMove
10075 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10076 sprintf(string, partCommandString,
10077 appData.debugMode ? " -v" : "", appData.cmailGameName);
10078 commandOutput = popen(string, "r");
10080 if (commandOutput == NULL) {
10081 DisplayError(_("Failed to invoke cmail"), 0);
10083 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10084 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10086 if (nBuffers > 1) {
10087 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10088 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10089 nBytes = MSG_SIZ - 1;
10091 (void) memcpy(msg, buffer, nBytes);
10093 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10095 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10096 cmailMailedMove = TRUE; /* Prevent >1 moves */
10099 for (i = 0; i < nCmailGames; i ++) {
10100 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10105 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10107 sprintf(buffer, "%s/%s.%s.archive",
10109 appData.cmailGameName,
10111 LoadGameFromFile(buffer, 1, buffer, FALSE);
10112 cmailMsgLoaded = FALSE;
10116 DisplayInformation(msg);
10117 pclose(commandOutput);
10120 if ((*cmailMsg) != '\0') {
10121 DisplayInformation(cmailMsg);
10126 #endif /* !WIN32 */
10135 int prependComma = 0;
10137 char string[MSG_SIZ]; /* Space for game-list */
10140 if (!cmailMsgLoaded) return "";
10142 if (cmailMailedMove) {
10143 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10145 /* Create a list of games left */
10146 sprintf(string, "[");
10147 for (i = 0; i < nCmailGames; i ++) {
10148 if (! ( cmailMoveRegistered[i]
10149 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10150 if (prependComma) {
10151 sprintf(number, ",%d", i + 1);
10153 sprintf(number, "%d", i + 1);
10157 strcat(string, number);
10160 strcat(string, "]");
10162 if (nCmailMovesRegistered + nCmailResults == 0) {
10163 switch (nCmailGames) {
10166 _("Still need to make move for game\n"));
10171 _("Still need to make moves for both games\n"));
10176 _("Still need to make moves for all %d games\n"),
10181 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10184 _("Still need to make a move for game %s\n"),
10189 if (nCmailResults == nCmailGames) {
10190 sprintf(cmailMsg, _("No unfinished games\n"));
10192 sprintf(cmailMsg, _("Ready to send mail\n"));
10198 _("Still need to make moves for games %s\n"),
10210 if (gameMode == Training)
10211 SetTrainingModeOff();
10214 cmailMsgLoaded = FALSE;
10215 if (appData.icsActive) {
10216 SendToICS(ics_prefix);
10217 SendToICS("refresh\n");
10227 /* Give up on clean exit */
10231 /* Keep trying for clean exit */
10235 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10237 if (telnetISR != NULL) {
10238 RemoveInputSource(telnetISR);
10240 if (icsPR != NoProc) {
10241 DestroyChildProcess(icsPR, TRUE);
10244 /* Save game if resource set and not already saved by GameEnds() */
10245 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10246 && forwardMostMove > 0) {
10247 if (*appData.saveGameFile != NULLCHAR) {
10248 SaveGameToFile(appData.saveGameFile, TRUE);
10249 } else if (appData.autoSaveGames) {
10252 if (*appData.savePositionFile != NULLCHAR) {
10253 SavePositionToFile(appData.savePositionFile);
10256 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10258 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10259 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10261 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10262 /* make sure this other one finishes before killing it! */
10263 if(endingGame) { int count = 0;
10264 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10265 while(endingGame && count++ < 10) DoSleep(1);
10266 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10269 /* Kill off chess programs */
10270 if (first.pr != NoProc) {
10273 DoSleep( appData.delayBeforeQuit );
10274 SendToProgram("quit\n", &first);
10275 DoSleep( appData.delayAfterQuit );
10276 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10278 if (second.pr != NoProc) {
10279 DoSleep( appData.delayBeforeQuit );
10280 SendToProgram("quit\n", &second);
10281 DoSleep( appData.delayAfterQuit );
10282 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10284 if (first.isr != NULL) {
10285 RemoveInputSource(first.isr);
10287 if (second.isr != NULL) {
10288 RemoveInputSource(second.isr);
10291 ShutDownFrontEnd();
10298 if (appData.debugMode)
10299 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10303 if (gameMode == MachinePlaysWhite ||
10304 gameMode == MachinePlaysBlack) {
10307 DisplayBothClocks();
10309 if (gameMode == PlayFromGameFile) {
10310 if (appData.timeDelay >= 0)
10311 AutoPlayGameLoop();
10312 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10313 Reset(FALSE, TRUE);
10314 SendToICS(ics_prefix);
10315 SendToICS("refresh\n");
10316 } else if (currentMove < forwardMostMove) {
10317 ForwardInner(forwardMostMove);
10319 pauseExamInvalid = FALSE;
10321 switch (gameMode) {
10325 pauseExamForwardMostMove = forwardMostMove;
10326 pauseExamInvalid = FALSE;
10329 case IcsPlayingWhite:
10330 case IcsPlayingBlack:
10334 case PlayFromGameFile:
10335 (void) StopLoadGameTimer();
10339 case BeginningOfGame:
10340 if (appData.icsActive) return;
10341 /* else fall through */
10342 case MachinePlaysWhite:
10343 case MachinePlaysBlack:
10344 case TwoMachinesPlay:
10345 if (forwardMostMove == 0)
10346 return; /* don't pause if no one has moved */
10347 if ((gameMode == MachinePlaysWhite &&
10348 !WhiteOnMove(forwardMostMove)) ||
10349 (gameMode == MachinePlaysBlack &&
10350 WhiteOnMove(forwardMostMove))) {
10363 char title[MSG_SIZ];
10365 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10366 strcpy(title, _("Edit comment"));
10368 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10369 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10370 parseList[currentMove - 1]);
10373 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10380 char *tags = PGNTags(&gameInfo);
10381 EditTagsPopUp(tags);
10388 if (appData.noChessProgram || gameMode == AnalyzeMode)
10391 if (gameMode != AnalyzeFile) {
10392 if (!appData.icsEngineAnalyze) {
10394 if (gameMode != EditGame) return;
10396 ResurrectChessProgram();
10397 SendToProgram("analyze\n", &first);
10398 first.analyzing = TRUE;
10399 /*first.maybeThinking = TRUE;*/
10400 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10401 AnalysisPopUp(_("Analysis"),
10402 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10404 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10409 StartAnalysisClock();
10410 GetTimeMark(&lastNodeCountTime);
10417 if (appData.noChessProgram || gameMode == AnalyzeFile)
10420 if (gameMode != AnalyzeMode) {
10422 if (gameMode != EditGame) return;
10423 ResurrectChessProgram();
10424 SendToProgram("analyze\n", &first);
10425 first.analyzing = TRUE;
10426 /*first.maybeThinking = TRUE;*/
10427 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10428 AnalysisPopUp(_("Analysis"),
10429 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10431 gameMode = AnalyzeFile;
10436 StartAnalysisClock();
10437 GetTimeMark(&lastNodeCountTime);
10442 MachineWhiteEvent()
10445 char *bookHit = NULL;
10447 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10451 if (gameMode == PlayFromGameFile ||
10452 gameMode == TwoMachinesPlay ||
10453 gameMode == Training ||
10454 gameMode == AnalyzeMode ||
10455 gameMode == EndOfGame)
10458 if (gameMode == EditPosition)
10459 EditPositionDone();
10461 if (!WhiteOnMove(currentMove)) {
10462 DisplayError(_("It is not White's turn"), 0);
10466 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10469 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10470 gameMode == AnalyzeFile)
10473 ResurrectChessProgram(); /* in case it isn't running */
10474 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10475 gameMode = MachinePlaysWhite;
10478 gameMode = MachinePlaysWhite;
10482 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10484 if (first.sendName) {
10485 sprintf(buf, "name %s\n", gameInfo.black);
10486 SendToProgram(buf, &first);
10488 if (first.sendTime) {
10489 if (first.useColors) {
10490 SendToProgram("black\n", &first); /*gnu kludge*/
10492 SendTimeRemaining(&first, TRUE);
10494 if (first.useColors) {
10495 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10497 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10498 SetMachineThinkingEnables();
10499 first.maybeThinking = TRUE;
10502 if (appData.autoFlipView && !flipView) {
10503 flipView = !flipView;
10504 DrawPosition(FALSE, NULL);
10505 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10508 if(bookHit) { // [HGM] book: simulate book reply
10509 static char bookMove[MSG_SIZ]; // a bit generous?
10511 programStats.nodes = programStats.depth = programStats.time =
10512 programStats.score = programStats.got_only_move = 0;
10513 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10515 strcpy(bookMove, "move ");
10516 strcat(bookMove, bookHit);
10517 HandleMachineMove(bookMove, &first);
10522 MachineBlackEvent()
10525 char *bookHit = NULL;
10527 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10531 if (gameMode == PlayFromGameFile ||
10532 gameMode == TwoMachinesPlay ||
10533 gameMode == Training ||
10534 gameMode == AnalyzeMode ||
10535 gameMode == EndOfGame)
10538 if (gameMode == EditPosition)
10539 EditPositionDone();
10541 if (WhiteOnMove(currentMove)) {
10542 DisplayError(_("It is not Black's turn"), 0);
10546 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10549 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10550 gameMode == AnalyzeFile)
10553 ResurrectChessProgram(); /* in case it isn't running */
10554 gameMode = MachinePlaysBlack;
10558 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10560 if (first.sendName) {
10561 sprintf(buf, "name %s\n", gameInfo.white);
10562 SendToProgram(buf, &first);
10564 if (first.sendTime) {
10565 if (first.useColors) {
10566 SendToProgram("white\n", &first); /*gnu kludge*/
10568 SendTimeRemaining(&first, FALSE);
10570 if (first.useColors) {
10571 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10573 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10574 SetMachineThinkingEnables();
10575 first.maybeThinking = TRUE;
10578 if (appData.autoFlipView && flipView) {
10579 flipView = !flipView;
10580 DrawPosition(FALSE, NULL);
10581 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10583 if(bookHit) { // [HGM] book: simulate book reply
10584 static char bookMove[MSG_SIZ]; // a bit generous?
10586 programStats.nodes = programStats.depth = programStats.time =
10587 programStats.score = programStats.got_only_move = 0;
10588 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10590 strcpy(bookMove, "move ");
10591 strcat(bookMove, bookHit);
10592 HandleMachineMove(bookMove, &first);
10598 DisplayTwoMachinesTitle()
10601 if (appData.matchGames > 0) {
10602 if (first.twoMachinesColor[0] == 'w') {
10603 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10604 gameInfo.white, gameInfo.black,
10605 first.matchWins, second.matchWins,
10606 matchGame - 1 - (first.matchWins + second.matchWins));
10608 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10609 gameInfo.white, gameInfo.black,
10610 second.matchWins, first.matchWins,
10611 matchGame - 1 - (first.matchWins + second.matchWins));
10614 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10620 TwoMachinesEvent P((void))
10624 ChessProgramState *onmove;
10625 char *bookHit = NULL;
10627 if (appData.noChessProgram) return;
10629 switch (gameMode) {
10630 case TwoMachinesPlay:
10632 case MachinePlaysWhite:
10633 case MachinePlaysBlack:
10634 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10635 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10639 case BeginningOfGame:
10640 case PlayFromGameFile:
10643 if (gameMode != EditGame) return;
10646 EditPositionDone();
10657 forwardMostMove = currentMove;
10658 ResurrectChessProgram(); /* in case first program isn't running */
10660 if (second.pr == NULL) {
10661 StartChessProgram(&second);
10662 if (second.protocolVersion == 1) {
10663 TwoMachinesEventIfReady();
10665 /* kludge: allow timeout for initial "feature" command */
10667 DisplayMessage("", _("Starting second chess program"));
10668 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10672 DisplayMessage("", "");
10673 InitChessProgram(&second, FALSE);
10674 SendToProgram("force\n", &second);
10675 if (startedFromSetupPosition) {
10676 SendBoard(&second, backwardMostMove);
10677 if (appData.debugMode) {
10678 fprintf(debugFP, "Two Machines\n");
10681 for (i = backwardMostMove; i < forwardMostMove; i++) {
10682 SendMoveToProgram(i, &second);
10685 gameMode = TwoMachinesPlay;
10689 DisplayTwoMachinesTitle();
10691 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10697 SendToProgram(first.computerString, &first);
10698 if (first.sendName) {
10699 sprintf(buf, "name %s\n", second.tidy);
10700 SendToProgram(buf, &first);
10702 SendToProgram(second.computerString, &second);
10703 if (second.sendName) {
10704 sprintf(buf, "name %s\n", first.tidy);
10705 SendToProgram(buf, &second);
10709 if (!first.sendTime || !second.sendTime) {
10710 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10711 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10713 if (onmove->sendTime) {
10714 if (onmove->useColors) {
10715 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10717 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10719 if (onmove->useColors) {
10720 SendToProgram(onmove->twoMachinesColor, onmove);
10722 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10723 // SendToProgram("go\n", onmove);
10724 onmove->maybeThinking = TRUE;
10725 SetMachineThinkingEnables();
10729 if(bookHit) { // [HGM] book: simulate book reply
10730 static char bookMove[MSG_SIZ]; // a bit generous?
10732 programStats.nodes = programStats.depth = programStats.time =
10733 programStats.score = programStats.got_only_move = 0;
10734 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10736 strcpy(bookMove, "move ");
10737 strcat(bookMove, bookHit);
10738 HandleMachineMove(bookMove, &first);
10745 if (gameMode == Training) {
10746 SetTrainingModeOff();
10747 gameMode = PlayFromGameFile;
10748 DisplayMessage("", _("Training mode off"));
10750 gameMode = Training;
10751 animateTraining = appData.animate;
10753 /* make sure we are not already at the end of the game */
10754 if (currentMove < forwardMostMove) {
10755 SetTrainingModeOn();
10756 DisplayMessage("", _("Training mode on"));
10758 gameMode = PlayFromGameFile;
10759 DisplayError(_("Already at end of game"), 0);
10768 if (!appData.icsActive) return;
10769 switch (gameMode) {
10770 case IcsPlayingWhite:
10771 case IcsPlayingBlack:
10774 case BeginningOfGame:
10782 EditPositionDone();
10795 gameMode = IcsIdle;
10806 switch (gameMode) {
10808 SetTrainingModeOff();
10810 case MachinePlaysWhite:
10811 case MachinePlaysBlack:
10812 case BeginningOfGame:
10813 SendToProgram("force\n", &first);
10814 SetUserThinkingEnables();
10816 case PlayFromGameFile:
10817 (void) StopLoadGameTimer();
10818 if (gameFileFP != NULL) {
10823 EditPositionDone();
10828 SendToProgram("force\n", &first);
10830 case TwoMachinesPlay:
10831 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10832 ResurrectChessProgram();
10833 SetUserThinkingEnables();
10836 ResurrectChessProgram();
10838 case IcsPlayingBlack:
10839 case IcsPlayingWhite:
10840 DisplayError(_("Warning: You are still playing a game"), 0);
10843 DisplayError(_("Warning: You are still observing a game"), 0);
10846 DisplayError(_("Warning: You are still examining a game"), 0);
10857 first.offeredDraw = second.offeredDraw = 0;
10859 if (gameMode == PlayFromGameFile) {
10860 whiteTimeRemaining = timeRemaining[0][currentMove];
10861 blackTimeRemaining = timeRemaining[1][currentMove];
10865 if (gameMode == MachinePlaysWhite ||
10866 gameMode == MachinePlaysBlack ||
10867 gameMode == TwoMachinesPlay ||
10868 gameMode == EndOfGame) {
10869 i = forwardMostMove;
10870 while (i > currentMove) {
10871 SendToProgram("undo\n", &first);
10874 whiteTimeRemaining = timeRemaining[0][currentMove];
10875 blackTimeRemaining = timeRemaining[1][currentMove];
10876 DisplayBothClocks();
10877 if (whiteFlag || blackFlag) {
10878 whiteFlag = blackFlag = 0;
10883 gameMode = EditGame;
10890 EditPositionEvent()
10892 if (gameMode == EditPosition) {
10898 if (gameMode != EditGame) return;
10900 gameMode = EditPosition;
10903 if (currentMove > 0)
10904 CopyBoard(boards[0], boards[currentMove]);
10906 blackPlaysFirst = !WhiteOnMove(currentMove);
10908 currentMove = forwardMostMove = backwardMostMove = 0;
10909 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10916 /* [DM] icsEngineAnalyze - possible call from other functions */
10917 if (appData.icsEngineAnalyze) {
10918 appData.icsEngineAnalyze = FALSE;
10920 DisplayMessage("",_("Close ICS engine analyze..."));
10922 if (first.analysisSupport && first.analyzing) {
10923 SendToProgram("exit\n", &first);
10924 first.analyzing = FALSE;
10927 thinkOutput[0] = NULLCHAR;
10933 startedFromSetupPosition = TRUE;
10934 InitChessProgram(&first, FALSE);
10935 SendToProgram("force\n", &first);
10936 if (blackPlaysFirst) {
10937 strcpy(moveList[0], "");
10938 strcpy(parseList[0], "");
10939 currentMove = forwardMostMove = backwardMostMove = 1;
10940 CopyBoard(boards[1], boards[0]);
10941 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10943 epStatus[1] = epStatus[0];
10944 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10947 currentMove = forwardMostMove = backwardMostMove = 0;
10949 SendBoard(&first, forwardMostMove);
10950 if (appData.debugMode) {
10951 fprintf(debugFP, "EditPosDone\n");
10954 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10955 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10956 gameMode = EditGame;
10958 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10959 ClearHighlights(); /* [AS] */
10962 /* Pause for `ms' milliseconds */
10963 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10973 } while (SubtractTimeMarks(&m2, &m1) < ms);
10976 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10978 SendMultiLineToICS(buf)
10981 char temp[MSG_SIZ+1], *p;
10988 strncpy(temp, buf, len);
10993 if (*p == '\n' || *p == '\r')
10998 strcat(temp, "\n");
11000 SendToPlayer(temp, strlen(temp));
11004 SetWhiteToPlayEvent()
11006 if (gameMode == EditPosition) {
11007 blackPlaysFirst = FALSE;
11008 DisplayBothClocks(); /* works because currentMove is 0 */
11009 } else if (gameMode == IcsExamining) {
11010 SendToICS(ics_prefix);
11011 SendToICS("tomove white\n");
11016 SetBlackToPlayEvent()
11018 if (gameMode == EditPosition) {
11019 blackPlaysFirst = TRUE;
11020 currentMove = 1; /* kludge */
11021 DisplayBothClocks();
11023 } else if (gameMode == IcsExamining) {
11024 SendToICS(ics_prefix);
11025 SendToICS("tomove black\n");
11030 EditPositionMenuEvent(selection, x, y)
11031 ChessSquare selection;
11035 ChessSquare piece = boards[0][y][x];
11037 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11039 switch (selection) {
11041 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11042 SendToICS(ics_prefix);
11043 SendToICS("bsetup clear\n");
11044 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11045 SendToICS(ics_prefix);
11046 SendToICS("clearboard\n");
11048 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11049 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11050 for (y = 0; y < BOARD_HEIGHT; y++) {
11051 if (gameMode == IcsExamining) {
11052 if (boards[currentMove][y][x] != EmptySquare) {
11053 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11058 boards[0][y][x] = p;
11063 if (gameMode == EditPosition) {
11064 DrawPosition(FALSE, boards[0]);
11069 SetWhiteToPlayEvent();
11073 SetBlackToPlayEvent();
11077 if (gameMode == IcsExamining) {
11078 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11081 boards[0][y][x] = EmptySquare;
11082 DrawPosition(FALSE, boards[0]);
11087 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11088 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11089 selection = (ChessSquare) (PROMOTED piece);
11090 } else if(piece == EmptySquare) selection = WhiteSilver;
11091 else selection = (ChessSquare)((int)piece - 1);
11095 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11096 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11097 selection = (ChessSquare) (DEMOTED piece);
11098 } else if(piece == EmptySquare) selection = BlackSilver;
11099 else selection = (ChessSquare)((int)piece + 1);
11104 if(gameInfo.variant == VariantShatranj ||
11105 gameInfo.variant == VariantXiangqi ||
11106 gameInfo.variant == VariantCourier )
11107 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11112 if(gameInfo.variant == VariantXiangqi)
11113 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11114 if(gameInfo.variant == VariantKnightmate)
11115 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11118 if (gameMode == IcsExamining) {
11119 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11120 PieceToChar(selection), AAA + x, ONE + y);
11123 boards[0][y][x] = selection;
11124 DrawPosition(FALSE, boards[0]);
11132 DropMenuEvent(selection, x, y)
11133 ChessSquare selection;
11136 ChessMove moveType;
11138 switch (gameMode) {
11139 case IcsPlayingWhite:
11140 case MachinePlaysBlack:
11141 if (!WhiteOnMove(currentMove)) {
11142 DisplayMoveError(_("It is Black's turn"));
11145 moveType = WhiteDrop;
11147 case IcsPlayingBlack:
11148 case MachinePlaysWhite:
11149 if (WhiteOnMove(currentMove)) {
11150 DisplayMoveError(_("It is White's turn"));
11153 moveType = BlackDrop;
11156 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11162 if (moveType == BlackDrop && selection < BlackPawn) {
11163 selection = (ChessSquare) ((int) selection
11164 + (int) BlackPawn - (int) WhitePawn);
11166 if (boards[currentMove][y][x] != EmptySquare) {
11167 DisplayMoveError(_("That square is occupied"));
11171 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11177 /* Accept a pending offer of any kind from opponent */
11179 if (appData.icsActive) {
11180 SendToICS(ics_prefix);
11181 SendToICS("accept\n");
11182 } else if (cmailMsgLoaded) {
11183 if (currentMove == cmailOldMove &&
11184 commentList[cmailOldMove] != NULL &&
11185 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11186 "Black offers a draw" : "White offers a draw")) {
11188 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11189 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11191 DisplayError(_("There is no pending offer on this move"), 0);
11192 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11195 /* Not used for offers from chess program */
11202 /* Decline a pending offer of any kind from opponent */
11204 if (appData.icsActive) {
11205 SendToICS(ics_prefix);
11206 SendToICS("decline\n");
11207 } else if (cmailMsgLoaded) {
11208 if (currentMove == cmailOldMove &&
11209 commentList[cmailOldMove] != NULL &&
11210 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11211 "Black offers a draw" : "White offers a draw")) {
11213 AppendComment(cmailOldMove, "Draw declined");
11214 DisplayComment(cmailOldMove - 1, "Draw declined");
11217 DisplayError(_("There is no pending offer on this move"), 0);
11220 /* Not used for offers from chess program */
11227 /* Issue ICS rematch command */
11228 if (appData.icsActive) {
11229 SendToICS(ics_prefix);
11230 SendToICS("rematch\n");
11237 /* Call your opponent's flag (claim a win on time) */
11238 if (appData.icsActive) {
11239 SendToICS(ics_prefix);
11240 SendToICS("flag\n");
11242 switch (gameMode) {
11245 case MachinePlaysWhite:
11248 GameEnds(GameIsDrawn, "Both players ran out of time",
11251 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11253 DisplayError(_("Your opponent is not out of time"), 0);
11256 case MachinePlaysBlack:
11259 GameEnds(GameIsDrawn, "Both players ran out of time",
11262 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11264 DisplayError(_("Your opponent is not out of time"), 0);
11274 /* Offer draw or accept pending draw offer from opponent */
11276 if (appData.icsActive) {
11277 /* Note: tournament rules require draw offers to be
11278 made after you make your move but before you punch
11279 your clock. Currently ICS doesn't let you do that;
11280 instead, you immediately punch your clock after making
11281 a move, but you can offer a draw at any time. */
11283 SendToICS(ics_prefix);
11284 SendToICS("draw\n");
11285 } else if (cmailMsgLoaded) {
11286 if (currentMove == cmailOldMove &&
11287 commentList[cmailOldMove] != NULL &&
11288 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11289 "Black offers a draw" : "White offers a draw")) {
11290 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11291 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11292 } else if (currentMove == cmailOldMove + 1) {
11293 char *offer = WhiteOnMove(cmailOldMove) ?
11294 "White offers a draw" : "Black offers a draw";
11295 AppendComment(currentMove, offer);
11296 DisplayComment(currentMove - 1, offer);
11297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11299 DisplayError(_("You must make your move before offering a draw"), 0);
11300 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11302 } else if (first.offeredDraw) {
11303 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11305 if (first.sendDrawOffers) {
11306 SendToProgram("draw\n", &first);
11307 userOfferedDraw = TRUE;
11315 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11317 if (appData.icsActive) {
11318 SendToICS(ics_prefix);
11319 SendToICS("adjourn\n");
11321 /* Currently GNU Chess doesn't offer or accept Adjourns */
11329 /* Offer Abort or accept pending Abort offer from opponent */
11331 if (appData.icsActive) {
11332 SendToICS(ics_prefix);
11333 SendToICS("abort\n");
11335 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11342 /* Resign. You can do this even if it's not your turn. */
11344 if (appData.icsActive) {
11345 SendToICS(ics_prefix);
11346 SendToICS("resign\n");
11348 switch (gameMode) {
11349 case MachinePlaysWhite:
11350 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11352 case MachinePlaysBlack:
11353 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11356 if (cmailMsgLoaded) {
11358 if (WhiteOnMove(cmailOldMove)) {
11359 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11361 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11363 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11374 StopObservingEvent()
11376 /* Stop observing current games */
11377 SendToICS(ics_prefix);
11378 SendToICS("unobserve\n");
11382 StopExaminingEvent()
11384 /* Stop observing current game */
11385 SendToICS(ics_prefix);
11386 SendToICS("unexamine\n");
11390 ForwardInner(target)
11395 if (appData.debugMode)
11396 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11397 target, currentMove, forwardMostMove);
11399 if (gameMode == EditPosition)
11402 if (gameMode == PlayFromGameFile && !pausing)
11405 if (gameMode == IcsExamining && pausing)
11406 limit = pauseExamForwardMostMove;
11408 limit = forwardMostMove;
11410 if (target > limit) target = limit;
11412 if (target > 0 && moveList[target - 1][0]) {
11413 int fromX, fromY, toX, toY;
11414 toX = moveList[target - 1][2] - AAA;
11415 toY = moveList[target - 1][3] - ONE;
11416 if (moveList[target - 1][1] == '@') {
11417 if (appData.highlightLastMove) {
11418 SetHighlights(-1, -1, toX, toY);
11421 fromX = moveList[target - 1][0] - AAA;
11422 fromY = moveList[target - 1][1] - ONE;
11423 if (target == currentMove + 1) {
11424 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11426 if (appData.highlightLastMove) {
11427 SetHighlights(fromX, fromY, toX, toY);
11431 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11432 gameMode == Training || gameMode == PlayFromGameFile ||
11433 gameMode == AnalyzeFile) {
11434 while (currentMove < target) {
11435 SendMoveToProgram(currentMove++, &first);
11438 currentMove = target;
11441 if (gameMode == EditGame || gameMode == EndOfGame) {
11442 whiteTimeRemaining = timeRemaining[0][currentMove];
11443 blackTimeRemaining = timeRemaining[1][currentMove];
11445 DisplayBothClocks();
11446 DisplayMove(currentMove - 1);
11447 DrawPosition(FALSE, boards[currentMove]);
11448 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11449 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11450 DisplayComment(currentMove - 1, commentList[currentMove]);
11458 if (gameMode == IcsExamining && !pausing) {
11459 SendToICS(ics_prefix);
11460 SendToICS("forward\n");
11462 ForwardInner(currentMove + 1);
11469 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11470 /* to optimze, we temporarily turn off analysis mode while we feed
11471 * the remaining moves to the engine. Otherwise we get analysis output
11474 if (first.analysisSupport) {
11475 SendToProgram("exit\nforce\n", &first);
11476 first.analyzing = FALSE;
11480 if (gameMode == IcsExamining && !pausing) {
11481 SendToICS(ics_prefix);
11482 SendToICS("forward 999999\n");
11484 ForwardInner(forwardMostMove);
11487 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11488 /* we have fed all the moves, so reactivate analysis mode */
11489 SendToProgram("analyze\n", &first);
11490 first.analyzing = TRUE;
11491 /*first.maybeThinking = TRUE;*/
11492 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11497 BackwardInner(target)
11500 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11502 if (appData.debugMode)
11503 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11504 target, currentMove, forwardMostMove);
11506 if (gameMode == EditPosition) return;
11507 if (currentMove <= backwardMostMove) {
11509 DrawPosition(full_redraw, boards[currentMove]);
11512 if (gameMode == PlayFromGameFile && !pausing)
11515 if (moveList[target][0]) {
11516 int fromX, fromY, toX, toY;
11517 toX = moveList[target][2] - AAA;
11518 toY = moveList[target][3] - ONE;
11519 if (moveList[target][1] == '@') {
11520 if (appData.highlightLastMove) {
11521 SetHighlights(-1, -1, toX, toY);
11524 fromX = moveList[target][0] - AAA;
11525 fromY = moveList[target][1] - ONE;
11526 if (target == currentMove - 1) {
11527 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11529 if (appData.highlightLastMove) {
11530 SetHighlights(fromX, fromY, toX, toY);
11534 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11535 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11536 while (currentMove > target) {
11537 SendToProgram("undo\n", &first);
11541 currentMove = target;
11544 if (gameMode == EditGame || gameMode == EndOfGame) {
11545 whiteTimeRemaining = timeRemaining[0][currentMove];
11546 blackTimeRemaining = timeRemaining[1][currentMove];
11548 DisplayBothClocks();
11549 DisplayMove(currentMove - 1);
11550 DrawPosition(full_redraw, boards[currentMove]);
11551 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11552 // [HGM] PV info: routine tests if comment empty
11553 DisplayComment(currentMove - 1, commentList[currentMove]);
11559 if (gameMode == IcsExamining && !pausing) {
11560 SendToICS(ics_prefix);
11561 SendToICS("backward\n");
11563 BackwardInner(currentMove - 1);
11570 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11571 /* to optimze, we temporarily turn off analysis mode while we undo
11572 * all the moves. Otherwise we get analysis output after each undo.
11574 if (first.analysisSupport) {
11575 SendToProgram("exit\nforce\n", &first);
11576 first.analyzing = FALSE;
11580 if (gameMode == IcsExamining && !pausing) {
11581 SendToICS(ics_prefix);
11582 SendToICS("backward 999999\n");
11584 BackwardInner(backwardMostMove);
11587 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11588 /* we have fed all the moves, so reactivate analysis mode */
11589 SendToProgram("analyze\n", &first);
11590 first.analyzing = TRUE;
11591 /*first.maybeThinking = TRUE;*/
11592 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11599 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11600 if (to >= forwardMostMove) to = forwardMostMove;
11601 if (to <= backwardMostMove) to = backwardMostMove;
11602 if (to < currentMove) {
11612 if (gameMode != IcsExamining) {
11613 DisplayError(_("You are not examining a game"), 0);
11617 DisplayError(_("You can't revert while pausing"), 0);
11620 SendToICS(ics_prefix);
11621 SendToICS("revert\n");
11627 switch (gameMode) {
11628 case MachinePlaysWhite:
11629 case MachinePlaysBlack:
11630 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11631 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11634 if (forwardMostMove < 2) return;
11635 currentMove = forwardMostMove = forwardMostMove - 2;
11636 whiteTimeRemaining = timeRemaining[0][currentMove];
11637 blackTimeRemaining = timeRemaining[1][currentMove];
11638 DisplayBothClocks();
11639 DisplayMove(currentMove - 1);
11640 ClearHighlights();/*!! could figure this out*/
11641 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11642 SendToProgram("remove\n", &first);
11643 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11646 case BeginningOfGame:
11650 case IcsPlayingWhite:
11651 case IcsPlayingBlack:
11652 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11653 SendToICS(ics_prefix);
11654 SendToICS("takeback 2\n");
11656 SendToICS(ics_prefix);
11657 SendToICS("takeback 1\n");
11666 ChessProgramState *cps;
11668 switch (gameMode) {
11669 case MachinePlaysWhite:
11670 if (!WhiteOnMove(forwardMostMove)) {
11671 DisplayError(_("It is your turn"), 0);
11676 case MachinePlaysBlack:
11677 if (WhiteOnMove(forwardMostMove)) {
11678 DisplayError(_("It is your turn"), 0);
11683 case TwoMachinesPlay:
11684 if (WhiteOnMove(forwardMostMove) ==
11685 (first.twoMachinesColor[0] == 'w')) {
11691 case BeginningOfGame:
11695 SendToProgram("?\n", cps);
11699 TruncateGameEvent()
11702 if (gameMode != EditGame) return;
11709 if (forwardMostMove > currentMove) {
11710 if (gameInfo.resultDetails != NULL) {
11711 free(gameInfo.resultDetails);
11712 gameInfo.resultDetails = NULL;
11713 gameInfo.result = GameUnfinished;
11715 forwardMostMove = currentMove;
11716 HistorySet(parseList, backwardMostMove, forwardMostMove,
11724 if (appData.noChessProgram) return;
11725 switch (gameMode) {
11726 case MachinePlaysWhite:
11727 if (WhiteOnMove(forwardMostMove)) {
11728 DisplayError(_("Wait until your turn"), 0);
11732 case BeginningOfGame:
11733 case MachinePlaysBlack:
11734 if (!WhiteOnMove(forwardMostMove)) {
11735 DisplayError(_("Wait until your turn"), 0);
11740 DisplayError(_("No hint available"), 0);
11743 SendToProgram("hint\n", &first);
11744 hintRequested = TRUE;
11750 if (appData.noChessProgram) return;
11751 switch (gameMode) {
11752 case MachinePlaysWhite:
11753 if (WhiteOnMove(forwardMostMove)) {
11754 DisplayError(_("Wait until your turn"), 0);
11758 case BeginningOfGame:
11759 case MachinePlaysBlack:
11760 if (!WhiteOnMove(forwardMostMove)) {
11761 DisplayError(_("Wait until your turn"), 0);
11766 EditPositionDone();
11768 case TwoMachinesPlay:
11773 SendToProgram("bk\n", &first);
11774 bookOutput[0] = NULLCHAR;
11775 bookRequested = TRUE;
11781 char *tags = PGNTags(&gameInfo);
11782 TagsPopUp(tags, CmailMsg());
11786 /* end button procedures */
11789 PrintPosition(fp, move)
11795 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11796 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11797 char c = PieceToChar(boards[move][i][j]);
11798 fputc(c == 'x' ? '.' : c, fp);
11799 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11802 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11803 fprintf(fp, "white to play\n");
11805 fprintf(fp, "black to play\n");
11812 if (gameInfo.white != NULL) {
11813 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11819 /* Find last component of program's own name, using some heuristics */
11821 TidyProgramName(prog, host, buf)
11822 char *prog, *host, buf[MSG_SIZ];
11825 int local = (strcmp(host, "localhost") == 0);
11826 while (!local && (p = strchr(prog, ';')) != NULL) {
11828 while (*p == ' ') p++;
11831 if (*prog == '"' || *prog == '\'') {
11832 q = strchr(prog + 1, *prog);
11834 q = strchr(prog, ' ');
11836 if (q == NULL) q = prog + strlen(prog);
11838 while (p >= prog && *p != '/' && *p != '\\') p--;
11840 if(p == prog && *p == '"') p++;
11841 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11842 memcpy(buf, p, q - p);
11843 buf[q - p] = NULLCHAR;
11851 TimeControlTagValue()
11854 if (!appData.clockMode) {
11856 } else if (movesPerSession > 0) {
11857 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11858 } else if (timeIncrement == 0) {
11859 sprintf(buf, "%ld", timeControl/1000);
11861 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11863 return StrSave(buf);
11869 /* This routine is used only for certain modes */
11870 VariantClass v = gameInfo.variant;
11871 ClearGameInfo(&gameInfo);
11872 gameInfo.variant = v;
11874 switch (gameMode) {
11875 case MachinePlaysWhite:
11876 gameInfo.event = StrSave( appData.pgnEventHeader );
11877 gameInfo.site = StrSave(HostName());
11878 gameInfo.date = PGNDate();
11879 gameInfo.round = StrSave("-");
11880 gameInfo.white = StrSave(first.tidy);
11881 gameInfo.black = StrSave(UserName());
11882 gameInfo.timeControl = TimeControlTagValue();
11885 case MachinePlaysBlack:
11886 gameInfo.event = StrSave( appData.pgnEventHeader );
11887 gameInfo.site = StrSave(HostName());
11888 gameInfo.date = PGNDate();
11889 gameInfo.round = StrSave("-");
11890 gameInfo.white = StrSave(UserName());
11891 gameInfo.black = StrSave(first.tidy);
11892 gameInfo.timeControl = TimeControlTagValue();
11895 case TwoMachinesPlay:
11896 gameInfo.event = StrSave( appData.pgnEventHeader );
11897 gameInfo.site = StrSave(HostName());
11898 gameInfo.date = PGNDate();
11899 if (matchGame > 0) {
11901 sprintf(buf, "%d", matchGame);
11902 gameInfo.round = StrSave(buf);
11904 gameInfo.round = StrSave("-");
11906 if (first.twoMachinesColor[0] == 'w') {
11907 gameInfo.white = StrSave(first.tidy);
11908 gameInfo.black = StrSave(second.tidy);
11910 gameInfo.white = StrSave(second.tidy);
11911 gameInfo.black = StrSave(first.tidy);
11913 gameInfo.timeControl = TimeControlTagValue();
11917 gameInfo.event = StrSave("Edited game");
11918 gameInfo.site = StrSave(HostName());
11919 gameInfo.date = PGNDate();
11920 gameInfo.round = StrSave("-");
11921 gameInfo.white = StrSave("-");
11922 gameInfo.black = StrSave("-");
11926 gameInfo.event = StrSave("Edited position");
11927 gameInfo.site = StrSave(HostName());
11928 gameInfo.date = PGNDate();
11929 gameInfo.round = StrSave("-");
11930 gameInfo.white = StrSave("-");
11931 gameInfo.black = StrSave("-");
11934 case IcsPlayingWhite:
11935 case IcsPlayingBlack:
11940 case PlayFromGameFile:
11941 gameInfo.event = StrSave("Game from non-PGN file");
11942 gameInfo.site = StrSave(HostName());
11943 gameInfo.date = PGNDate();
11944 gameInfo.round = StrSave("-");
11945 gameInfo.white = StrSave("?");
11946 gameInfo.black = StrSave("?");
11955 ReplaceComment(index, text)
11961 while (*text == '\n') text++;
11962 len = strlen(text);
11963 while (len > 0 && text[len - 1] == '\n') len--;
11965 if (commentList[index] != NULL)
11966 free(commentList[index]);
11969 commentList[index] = NULL;
11972 commentList[index] = (char *) malloc(len + 2);
11973 strncpy(commentList[index], text, len);
11974 commentList[index][len] = '\n';
11975 commentList[index][len + 1] = NULLCHAR;
11988 if (ch == '\r') continue;
11990 } while (ch != '\0');
11994 AppendComment(index, text)
12001 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12004 while (*text == '\n') text++;
12005 len = strlen(text);
12006 while (len > 0 && text[len - 1] == '\n') len--;
12008 if (len == 0) return;
12010 if (commentList[index] != NULL) {
12011 old = commentList[index];
12012 oldlen = strlen(old);
12013 commentList[index] = (char *) malloc(oldlen + len + 2);
12014 strcpy(commentList[index], old);
12016 strncpy(&commentList[index][oldlen], text, len);
12017 commentList[index][oldlen + len] = '\n';
12018 commentList[index][oldlen + len + 1] = NULLCHAR;
12020 commentList[index] = (char *) malloc(len + 2);
12021 strncpy(commentList[index], text, len);
12022 commentList[index][len] = '\n';
12023 commentList[index][len + 1] = NULLCHAR;
12027 static char * FindStr( char * text, char * sub_text )
12029 char * result = strstr( text, sub_text );
12031 if( result != NULL ) {
12032 result += strlen( sub_text );
12038 /* [AS] Try to extract PV info from PGN comment */
12039 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12040 char *GetInfoFromComment( int index, char * text )
12044 if( text != NULL && index > 0 ) {
12047 int time = -1, sec = 0, deci;
12048 char * s_eval = FindStr( text, "[%eval " );
12049 char * s_emt = FindStr( text, "[%emt " );
12051 if( s_eval != NULL || s_emt != NULL ) {
12055 if( s_eval != NULL ) {
12056 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12060 if( delim != ']' ) {
12065 if( s_emt != NULL ) {
12069 /* We expect something like: [+|-]nnn.nn/dd */
12072 sep = strchr( text, '/' );
12073 if( sep == NULL || sep < (text+4) ) {
12077 time = -1; sec = -1; deci = -1;
12078 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12079 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12080 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12081 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12085 if( score_lo < 0 || score_lo >= 100 ) {
12089 if(sec >= 0) time = 600*time + 10*sec; else
12090 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12092 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12094 /* [HGM] PV time: now locate end of PV info */
12095 while( *++sep >= '0' && *sep <= '9'); // strip depth
12097 while( *++sep >= '0' && *sep <= '9'); // strip time
12099 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12101 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12102 while(*sep == ' ') sep++;
12113 pvInfoList[index-1].depth = depth;
12114 pvInfoList[index-1].score = score;
12115 pvInfoList[index-1].time = 10*time; // centi-sec
12121 SendToProgram(message, cps)
12123 ChessProgramState *cps;
12125 int count, outCount, error;
12128 if (cps->pr == NULL) return;
12131 if (appData.debugMode) {
12134 fprintf(debugFP, "%ld >%-6s: %s",
12135 SubtractTimeMarks(&now, &programStartTime),
12136 cps->which, message);
12139 count = strlen(message);
12140 outCount = OutputToProcess(cps->pr, message, count, &error);
12141 if (outCount < count && !exiting
12142 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12143 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12144 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12145 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12146 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12147 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12149 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12151 gameInfo.resultDetails = buf;
12153 DisplayFatalError(buf, error, 1);
12158 ReceiveFromProgram(isr, closure, message, count, error)
12159 InputSourceRef isr;
12167 ChessProgramState *cps = (ChessProgramState *)closure;
12169 if (isr != cps->isr) return; /* Killed intentionally */
12173 _("Error: %s chess program (%s) exited unexpectedly"),
12174 cps->which, cps->program);
12175 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12176 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12177 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12178 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12180 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12182 gameInfo.resultDetails = buf;
12184 RemoveInputSource(cps->isr);
12185 DisplayFatalError(buf, 0, 1);
12188 _("Error reading from %s chess program (%s)"),
12189 cps->which, cps->program);
12190 RemoveInputSource(cps->isr);
12192 /* [AS] Program is misbehaving badly... kill it */
12193 if( count == -2 ) {
12194 DestroyChildProcess( cps->pr, 9 );
12198 DisplayFatalError(buf, error, 1);
12203 if ((end_str = strchr(message, '\r')) != NULL)
12204 *end_str = NULLCHAR;
12205 if ((end_str = strchr(message, '\n')) != NULL)
12206 *end_str = NULLCHAR;
12208 if (appData.debugMode) {
12209 TimeMark now; int print = 1;
12210 char *quote = ""; char c; int i;
12212 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12213 char start = message[0];
12214 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12215 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12216 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12217 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12218 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12219 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12220 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12221 { quote = "# "; print = (appData.engineComments == 2); }
12222 message[0] = start; // restore original message
12226 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12227 SubtractTimeMarks(&now, &programStartTime), cps->which,
12233 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12234 if (appData.icsEngineAnalyze) {
12235 if (strstr(message, "whisper") != NULL ||
12236 strstr(message, "kibitz") != NULL ||
12237 strstr(message, "tellics") != NULL) return;
12240 HandleMachineMove(message, cps);
12245 SendTimeControl(cps, mps, tc, inc, sd, st)
12246 ChessProgramState *cps;
12247 int mps, inc, sd, st;
12253 if( timeControl_2 > 0 ) {
12254 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12255 tc = timeControl_2;
12258 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12259 inc /= cps->timeOdds;
12260 st /= cps->timeOdds;
12262 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12265 /* Set exact time per move, normally using st command */
12266 if (cps->stKludge) {
12267 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12269 if (seconds == 0) {
12270 sprintf(buf, "level 1 %d\n", st/60);
12272 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12275 sprintf(buf, "st %d\n", st);
12278 /* Set conventional or incremental time control, using level command */
12279 if (seconds == 0) {
12280 /* Note old gnuchess bug -- minutes:seconds used to not work.
12281 Fixed in later versions, but still avoid :seconds
12282 when seconds is 0. */
12283 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12285 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12286 seconds, inc/1000);
12289 SendToProgram(buf, cps);
12291 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12292 /* Orthogonally, limit search to given depth */
12294 if (cps->sdKludge) {
12295 sprintf(buf, "depth\n%d\n", sd);
12297 sprintf(buf, "sd %d\n", sd);
12299 SendToProgram(buf, cps);
12302 if(cps->nps > 0) { /* [HGM] nps */
12303 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12305 sprintf(buf, "nps %d\n", cps->nps);
12306 SendToProgram(buf, cps);
12311 ChessProgramState *WhitePlayer()
12312 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12314 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12315 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12321 SendTimeRemaining(cps, machineWhite)
12322 ChessProgramState *cps;
12323 int /*boolean*/ machineWhite;
12325 char message[MSG_SIZ];
12328 /* Note: this routine must be called when the clocks are stopped
12329 or when they have *just* been set or switched; otherwise
12330 it will be off by the time since the current tick started.
12332 if (machineWhite) {
12333 time = whiteTimeRemaining / 10;
12334 otime = blackTimeRemaining / 10;
12336 time = blackTimeRemaining / 10;
12337 otime = whiteTimeRemaining / 10;
12339 /* [HGM] translate opponent's time by time-odds factor */
12340 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12341 if (appData.debugMode) {
12342 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12345 if (time <= 0) time = 1;
12346 if (otime <= 0) otime = 1;
12348 sprintf(message, "time %ld\n", time);
12349 SendToProgram(message, cps);
12351 sprintf(message, "otim %ld\n", otime);
12352 SendToProgram(message, cps);
12356 BoolFeature(p, name, loc, cps)
12360 ChessProgramState *cps;
12363 int len = strlen(name);
12365 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12367 sscanf(*p, "%d", &val);
12369 while (**p && **p != ' ') (*p)++;
12370 sprintf(buf, "accepted %s\n", name);
12371 SendToProgram(buf, cps);
12378 IntFeature(p, name, loc, cps)
12382 ChessProgramState *cps;
12385 int len = strlen(name);
12386 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12388 sscanf(*p, "%d", loc);
12389 while (**p && **p != ' ') (*p)++;
12390 sprintf(buf, "accepted %s\n", name);
12391 SendToProgram(buf, cps);
12398 StringFeature(p, name, loc, cps)
12402 ChessProgramState *cps;
12405 int len = strlen(name);
12406 if (strncmp((*p), name, len) == 0
12407 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12409 sscanf(*p, "%[^\"]", loc);
12410 while (**p && **p != '\"') (*p)++;
12411 if (**p == '\"') (*p)++;
12412 sprintf(buf, "accepted %s\n", name);
12413 SendToProgram(buf, cps);
12420 ParseOption(Option *opt, ChessProgramState *cps)
12421 // [HGM] options: process the string that defines an engine option, and determine
12422 // name, type, default value, and allowed value range
12424 char *p, *q, buf[MSG_SIZ];
12425 int n, min = (-1)<<31, max = 1<<31, def;
12427 if(p = strstr(opt->name, " -spin ")) {
12428 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12429 if(max < min) max = min; // enforce consistency
12430 if(def < min) def = min;
12431 if(def > max) def = max;
12436 } else if(p = strstr(opt->name, " -string ")) {
12437 opt->textValue = p+9;
12438 opt->type = TextBox;
12439 } else if(p = strstr(opt->name, " -check ")) {
12440 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12441 opt->value = (def != 0);
12442 opt->type = CheckBox;
12443 } else if(p = strstr(opt->name, " -combo ")) {
12444 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12445 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12446 opt->value = n = 0;
12447 while(q = StrStr(q, " /// ")) {
12448 n++; *q = 0; // count choices, and null-terminate each of them
12450 if(*q == '*') { // remember default, which is marked with * prefix
12454 cps->comboList[cps->comboCnt++] = q;
12456 cps->comboList[cps->comboCnt++] = NULL;
12458 opt->type = ComboBox;
12459 } else if(p = strstr(opt->name, " -button")) {
12460 opt->type = Button;
12461 } else if(p = strstr(opt->name, " -save")) {
12462 opt->type = SaveButton;
12463 } else return FALSE;
12464 *p = 0; // terminate option name
12465 // now look if the command-line options define a setting for this engine option.
12466 if(cps->optionSettings && cps->optionSettings[0])
12467 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12468 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12469 sprintf(buf, "option %s", p);
12470 if(p = strstr(buf, ",")) *p = 0;
12472 SendToProgram(buf, cps);
12478 FeatureDone(cps, val)
12479 ChessProgramState* cps;
12482 DelayedEventCallback cb = GetDelayedEvent();
12483 if ((cb == InitBackEnd3 && cps == &first) ||
12484 (cb == TwoMachinesEventIfReady && cps == &second)) {
12485 CancelDelayedEvent();
12486 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12488 cps->initDone = val;
12491 /* Parse feature command from engine */
12493 ParseFeatures(args, cps)
12495 ChessProgramState *cps;
12503 while (*p == ' ') p++;
12504 if (*p == NULLCHAR) return;
12506 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12507 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12508 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12509 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12510 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12511 if (BoolFeature(&p, "reuse", &val, cps)) {
12512 /* Engine can disable reuse, but can't enable it if user said no */
12513 if (!val) cps->reuse = FALSE;
12516 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12517 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12518 if (gameMode == TwoMachinesPlay) {
12519 DisplayTwoMachinesTitle();
12525 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12526 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12527 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12528 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12529 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12530 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12531 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12532 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12533 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12534 if (IntFeature(&p, "done", &val, cps)) {
12535 FeatureDone(cps, val);
12538 /* Added by Tord: */
12539 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12540 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12541 /* End of additions by Tord */
12543 /* [HGM] added features: */
12544 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12545 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12546 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12547 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12548 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12549 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12550 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12551 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12552 if(cps->nrOptions >= MAX_OPTIONS) {
12554 sprintf(buf, "%s engine has too many options\n", cps->which);
12555 DisplayError(buf, 0);
12559 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12560 /* End of additions by HGM */
12562 /* unknown feature: complain and skip */
12564 while (*q && *q != '=') q++;
12565 sprintf(buf, "rejected %.*s\n", q-p, p);
12566 SendToProgram(buf, cps);
12572 while (*p && *p != '\"') p++;
12573 if (*p == '\"') p++;
12575 while (*p && *p != ' ') p++;
12583 PeriodicUpdatesEvent(newState)
12586 if (newState == appData.periodicUpdates)
12589 appData.periodicUpdates=newState;
12591 /* Display type changes, so update it now */
12594 /* Get the ball rolling again... */
12596 AnalysisPeriodicEvent(1);
12597 StartAnalysisClock();
12602 PonderNextMoveEvent(newState)
12605 if (newState == appData.ponderNextMove) return;
12606 if (gameMode == EditPosition) EditPositionDone();
12608 SendToProgram("hard\n", &first);
12609 if (gameMode == TwoMachinesPlay) {
12610 SendToProgram("hard\n", &second);
12613 SendToProgram("easy\n", &first);
12614 thinkOutput[0] = NULLCHAR;
12615 if (gameMode == TwoMachinesPlay) {
12616 SendToProgram("easy\n", &second);
12619 appData.ponderNextMove = newState;
12623 NewSettingEvent(option, command, value)
12629 if (gameMode == EditPosition) EditPositionDone();
12630 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12631 SendToProgram(buf, &first);
12632 if (gameMode == TwoMachinesPlay) {
12633 SendToProgram(buf, &second);
12638 ShowThinkingEvent()
12639 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12641 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12642 int newState = appData.showThinking
12643 // [HGM] thinking: other features now need thinking output as well
12644 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12646 if (oldState == newState) return;
12647 oldState = newState;
12648 if (gameMode == EditPosition) EditPositionDone();
12650 SendToProgram("post\n", &first);
12651 if (gameMode == TwoMachinesPlay) {
12652 SendToProgram("post\n", &second);
12655 SendToProgram("nopost\n", &first);
12656 thinkOutput[0] = NULLCHAR;
12657 if (gameMode == TwoMachinesPlay) {
12658 SendToProgram("nopost\n", &second);
12661 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12665 AskQuestionEvent(title, question, replyPrefix, which)
12666 char *title; char *question; char *replyPrefix; char *which;
12668 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12669 if (pr == NoProc) return;
12670 AskQuestion(title, question, replyPrefix, pr);
12674 DisplayMove(moveNumber)
12677 char message[MSG_SIZ];
12679 char cpThinkOutput[MSG_SIZ];
12681 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12683 if (moveNumber == forwardMostMove - 1 ||
12684 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12686 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12688 if (strchr(cpThinkOutput, '\n')) {
12689 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12692 *cpThinkOutput = NULLCHAR;
12695 /* [AS] Hide thinking from human user */
12696 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12697 *cpThinkOutput = NULLCHAR;
12698 if( thinkOutput[0] != NULLCHAR ) {
12701 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12702 cpThinkOutput[i] = '.';
12704 cpThinkOutput[i] = NULLCHAR;
12705 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12709 if (moveNumber == forwardMostMove - 1 &&
12710 gameInfo.resultDetails != NULL) {
12711 if (gameInfo.resultDetails[0] == NULLCHAR) {
12712 sprintf(res, " %s", PGNResult(gameInfo.result));
12714 sprintf(res, " {%s} %s",
12715 gameInfo.resultDetails, PGNResult(gameInfo.result));
12721 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12722 DisplayMessage(res, cpThinkOutput);
12724 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12725 WhiteOnMove(moveNumber) ? " " : ".. ",
12726 parseList[moveNumber], res);
12727 DisplayMessage(message, cpThinkOutput);
12732 DisplayAnalysisText(text)
12737 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12738 || appData.icsEngineAnalyze) {
12739 sprintf(buf, "Analysis (%s)", first.tidy);
12740 AnalysisPopUp(buf, text);
12748 while (*str && isspace(*str)) ++str;
12749 while (*str && !isspace(*str)) ++str;
12750 if (!*str) return 1;
12751 while (*str && isspace(*str)) ++str;
12752 if (!*str) return 1;
12760 char lst[MSG_SIZ / 2];
12762 static char *xtra[] = { "", " (--)", " (++)" };
12765 if (programStats.time == 0) {
12766 programStats.time = 1;
12769 if (programStats.got_only_move) {
12770 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12772 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12774 nps = (u64ToDouble(programStats.nodes) /
12775 ((double)programStats.time /100.0));
12777 cs = programStats.time % 100;
12778 s = programStats.time / 100;
12784 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12785 if (programStats.move_name[0] != NULLCHAR) {
12786 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12787 programStats.depth,
12788 programStats.nr_moves-programStats.moves_left,
12789 programStats.nr_moves, programStats.move_name,
12790 ((float)programStats.score)/100.0, lst,
12791 only_one_move(lst)?
12792 xtra[programStats.got_fail] : "",
12793 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12795 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12796 programStats.depth,
12797 programStats.nr_moves-programStats.moves_left,
12798 programStats.nr_moves, ((float)programStats.score)/100.0,
12800 only_one_move(lst)?
12801 xtra[programStats.got_fail] : "",
12802 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12805 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12806 programStats.depth,
12807 ((float)programStats.score)/100.0,
12809 only_one_move(lst)?
12810 xtra[programStats.got_fail] : "",
12811 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12814 DisplayAnalysisText(buf);
12818 DisplayComment(moveNumber, text)
12822 char title[MSG_SIZ];
12823 char buf[8000]; // comment can be long!
12826 if( appData.autoDisplayComment ) {
12827 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12828 strcpy(title, "Comment");
12830 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12831 WhiteOnMove(moveNumber) ? " " : ".. ",
12832 parseList[moveNumber]);
12834 // [HGM] PV info: display PV info together with (or as) comment
12835 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12836 if(text == NULL) text = "";
12837 score = pvInfoList[moveNumber].score;
12838 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12839 depth, (pvInfoList[moveNumber].time+50)/100, text);
12842 } else title[0] = 0;
12845 CommentPopUp(title, text);
12848 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12849 * might be busy thinking or pondering. It can be omitted if your
12850 * gnuchess is configured to stop thinking immediately on any user
12851 * input. However, that gnuchess feature depends on the FIONREAD
12852 * ioctl, which does not work properly on some flavors of Unix.
12856 ChessProgramState *cps;
12859 if (!cps->useSigint) return;
12860 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12861 switch (gameMode) {
12862 case MachinePlaysWhite:
12863 case MachinePlaysBlack:
12864 case TwoMachinesPlay:
12865 case IcsPlayingWhite:
12866 case IcsPlayingBlack:
12869 /* Skip if we know it isn't thinking */
12870 if (!cps->maybeThinking) return;
12871 if (appData.debugMode)
12872 fprintf(debugFP, "Interrupting %s\n", cps->which);
12873 InterruptChildProcess(cps->pr);
12874 cps->maybeThinking = FALSE;
12879 #endif /*ATTENTION*/
12885 if (whiteTimeRemaining <= 0) {
12888 if (appData.icsActive) {
12889 if (appData.autoCallFlag &&
12890 gameMode == IcsPlayingBlack && !blackFlag) {
12891 SendToICS(ics_prefix);
12892 SendToICS("flag\n");
12896 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12898 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12899 if (appData.autoCallFlag) {
12900 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12907 if (blackTimeRemaining <= 0) {
12910 if (appData.icsActive) {
12911 if (appData.autoCallFlag &&
12912 gameMode == IcsPlayingWhite && !whiteFlag) {
12913 SendToICS(ics_prefix);
12914 SendToICS("flag\n");
12918 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12920 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12921 if (appData.autoCallFlag) {
12922 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12935 if (!appData.clockMode || appData.icsActive ||
12936 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12939 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12941 if ( !WhiteOnMove(forwardMostMove) )
12942 /* White made time control */
12943 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12944 /* [HGM] time odds: correct new time quota for time odds! */
12945 / WhitePlayer()->timeOdds;
12947 /* Black made time control */
12948 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12949 / WhitePlayer()->other->timeOdds;
12953 DisplayBothClocks()
12955 int wom = gameMode == EditPosition ?
12956 !blackPlaysFirst : WhiteOnMove(currentMove);
12957 DisplayWhiteClock(whiteTimeRemaining, wom);
12958 DisplayBlackClock(blackTimeRemaining, !wom);
12962 /* Timekeeping seems to be a portability nightmare. I think everyone
12963 has ftime(), but I'm really not sure, so I'm including some ifdefs
12964 to use other calls if you don't. Clocks will be less accurate if
12965 you have neither ftime nor gettimeofday.
12968 /* VS 2008 requires the #include outside of the function */
12969 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12970 #include <sys/timeb.h>
12973 /* Get the current time as a TimeMark */
12978 #if HAVE_GETTIMEOFDAY
12980 struct timeval timeVal;
12981 struct timezone timeZone;
12983 gettimeofday(&timeVal, &timeZone);
12984 tm->sec = (long) timeVal.tv_sec;
12985 tm->ms = (int) (timeVal.tv_usec / 1000L);
12987 #else /*!HAVE_GETTIMEOFDAY*/
12990 // include <sys/timeb.h> / moved to just above start of function
12991 struct timeb timeB;
12994 tm->sec = (long) timeB.time;
12995 tm->ms = (int) timeB.millitm;
12997 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12998 tm->sec = (long) time(NULL);
13004 /* Return the difference in milliseconds between two
13005 time marks. We assume the difference will fit in a long!
13008 SubtractTimeMarks(tm2, tm1)
13009 TimeMark *tm2, *tm1;
13011 return 1000L*(tm2->sec - tm1->sec) +
13012 (long) (tm2->ms - tm1->ms);
13017 * Code to manage the game clocks.
13019 * In tournament play, black starts the clock and then white makes a move.
13020 * We give the human user a slight advantage if he is playing white---the
13021 * clocks don't run until he makes his first move, so it takes zero time.
13022 * Also, we don't account for network lag, so we could get out of sync
13023 * with GNU Chess's clock -- but then, referees are always right.
13026 static TimeMark tickStartTM;
13027 static long intendedTickLength;
13030 NextTickLength(timeRemaining)
13031 long timeRemaining;
13033 long nominalTickLength, nextTickLength;
13035 if (timeRemaining > 0L && timeRemaining <= 10000L)
13036 nominalTickLength = 100L;
13038 nominalTickLength = 1000L;
13039 nextTickLength = timeRemaining % nominalTickLength;
13040 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13042 return nextTickLength;
13045 /* Adjust clock one minute up or down */
13047 AdjustClock(Boolean which, int dir)
13049 if(which) blackTimeRemaining += 60000*dir;
13050 else whiteTimeRemaining += 60000*dir;
13051 DisplayBothClocks();
13054 /* Stop clocks and reset to a fresh time control */
13058 (void) StopClockTimer();
13059 if (appData.icsActive) {
13060 whiteTimeRemaining = blackTimeRemaining = 0;
13061 } else { /* [HGM] correct new time quote for time odds */
13062 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13063 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13065 if (whiteFlag || blackFlag) {
13067 whiteFlag = blackFlag = FALSE;
13069 DisplayBothClocks();
13072 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13074 /* Decrement running clock by amount of time that has passed */
13078 long timeRemaining;
13079 long lastTickLength, fudge;
13082 if (!appData.clockMode) return;
13083 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13087 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13089 /* Fudge if we woke up a little too soon */
13090 fudge = intendedTickLength - lastTickLength;
13091 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13093 if (WhiteOnMove(forwardMostMove)) {
13094 if(whiteNPS >= 0) lastTickLength = 0;
13095 timeRemaining = whiteTimeRemaining -= lastTickLength;
13096 DisplayWhiteClock(whiteTimeRemaining - fudge,
13097 WhiteOnMove(currentMove));
13099 if(blackNPS >= 0) lastTickLength = 0;
13100 timeRemaining = blackTimeRemaining -= lastTickLength;
13101 DisplayBlackClock(blackTimeRemaining - fudge,
13102 !WhiteOnMove(currentMove));
13105 if (CheckFlags()) return;
13108 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13109 StartClockTimer(intendedTickLength);
13111 /* if the time remaining has fallen below the alarm threshold, sound the
13112 * alarm. if the alarm has sounded and (due to a takeback or time control
13113 * with increment) the time remaining has increased to a level above the
13114 * threshold, reset the alarm so it can sound again.
13117 if (appData.icsActive && appData.icsAlarm) {
13119 /* make sure we are dealing with the user's clock */
13120 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13121 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13124 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13125 alarmSounded = FALSE;
13126 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13128 alarmSounded = TRUE;
13134 /* A player has just moved, so stop the previously running
13135 clock and (if in clock mode) start the other one.
13136 We redisplay both clocks in case we're in ICS mode, because
13137 ICS gives us an update to both clocks after every move.
13138 Note that this routine is called *after* forwardMostMove
13139 is updated, so the last fractional tick must be subtracted
13140 from the color that is *not* on move now.
13145 long lastTickLength;
13147 int flagged = FALSE;
13151 if (StopClockTimer() && appData.clockMode) {
13152 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13153 if (WhiteOnMove(forwardMostMove)) {
13154 if(blackNPS >= 0) lastTickLength = 0;
13155 blackTimeRemaining -= lastTickLength;
13156 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13157 // if(pvInfoList[forwardMostMove-1].time == -1)
13158 pvInfoList[forwardMostMove-1].time = // use GUI time
13159 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13161 if(whiteNPS >= 0) lastTickLength = 0;
13162 whiteTimeRemaining -= lastTickLength;
13163 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13164 // if(pvInfoList[forwardMostMove-1].time == -1)
13165 pvInfoList[forwardMostMove-1].time =
13166 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13168 flagged = CheckFlags();
13170 CheckTimeControl();
13172 if (flagged || !appData.clockMode) return;
13174 switch (gameMode) {
13175 case MachinePlaysBlack:
13176 case MachinePlaysWhite:
13177 case BeginningOfGame:
13178 if (pausing) return;
13182 case PlayFromGameFile:
13191 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13192 whiteTimeRemaining : blackTimeRemaining);
13193 StartClockTimer(intendedTickLength);
13197 /* Stop both clocks */
13201 long lastTickLength;
13204 if (!StopClockTimer()) return;
13205 if (!appData.clockMode) return;
13207 printf("Debug: in stop clocks\n");
13211 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13212 if (WhiteOnMove(forwardMostMove)) {
13213 if(whiteNPS >= 0) lastTickLength = 0;
13214 whiteTimeRemaining -= lastTickLength;
13215 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13217 if(blackNPS >= 0) lastTickLength = 0;
13218 blackTimeRemaining -= lastTickLength;
13219 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13222 printf("Debug: end stop clocks\n");
13225 /* Start clock of player on move. Time may have been reset, so
13226 if clock is already running, stop and restart it. */
13230 (void) StopClockTimer(); /* in case it was running already */
13231 DisplayBothClocks();
13232 if (CheckFlags()) return;
13234 if (!appData.clockMode) return;
13235 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13237 GetTimeMark(&tickStartTM);
13238 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13239 whiteTimeRemaining : blackTimeRemaining);
13241 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13242 whiteNPS = blackNPS = -1;
13243 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13244 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13245 whiteNPS = first.nps;
13246 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13247 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13248 blackNPS = first.nps;
13249 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13250 whiteNPS = second.nps;
13251 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13252 blackNPS = second.nps;
13253 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13255 StartClockTimer(intendedTickLength);
13262 long second, minute, hour, day;
13264 static char buf[32];
13266 if (ms > 0 && ms <= 9900) {
13267 /* convert milliseconds to tenths, rounding up */
13268 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13270 sprintf(buf, " %03.1f ", tenths/10.0);
13274 /* convert milliseconds to seconds, rounding up */
13275 /* use floating point to avoid strangeness of integer division
13276 with negative dividends on many machines */
13277 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13284 day = second / (60 * 60 * 24);
13285 second = second % (60 * 60 * 24);
13286 hour = second / (60 * 60);
13287 second = second % (60 * 60);
13288 minute = second / 60;
13289 second = second % 60;
13292 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13293 sign, day, hour, minute, second);
13295 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13297 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13304 * This is necessary because some C libraries aren't ANSI C compliant yet.
13307 StrStr(string, match)
13308 char *string, *match;
13312 length = strlen(match);
13314 for (i = strlen(string) - length; i >= 0; i--, string++)
13315 if (!strncmp(match, string, length))
13322 StrCaseStr(string, match)
13323 char *string, *match;
13327 length = strlen(match);
13329 for (i = strlen(string) - length; i >= 0; i--, string++) {
13330 for (j = 0; j < length; j++) {
13331 if (ToLower(match[j]) != ToLower(string[j]))
13334 if (j == length) return string;
13348 c1 = ToLower(*s1++);
13349 c2 = ToLower(*s2++);
13350 if (c1 > c2) return 1;
13351 if (c1 < c2) return -1;
13352 if (c1 == NULLCHAR) return 0;
13361 return isupper(c) ? tolower(c) : c;
13369 return islower(c) ? toupper(c) : c;
13371 #endif /* !_amigados */
13379 if ((ret = (char *) malloc(strlen(s) + 1))) {
13386 StrSavePtr(s, savePtr)
13387 char *s, **savePtr;
13392 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13393 strcpy(*savePtr, s);
13405 clock = time((time_t *)NULL);
13406 tm = localtime(&clock);
13407 sprintf(buf, "%04d.%02d.%02d",
13408 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13409 return StrSave(buf);
13414 PositionToFEN(move, overrideCastling)
13416 char *overrideCastling;
13418 int i, j, fromX, fromY, toX, toY;
13425 whiteToPlay = (gameMode == EditPosition) ?
13426 !blackPlaysFirst : (move % 2 == 0);
13429 /* Piece placement data */
13430 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13432 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13433 if (boards[move][i][j] == EmptySquare) {
13435 } else { ChessSquare piece = boards[move][i][j];
13436 if (emptycount > 0) {
13437 if(emptycount<10) /* [HGM] can be >= 10 */
13438 *p++ = '0' + emptycount;
13439 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13442 if(PieceToChar(piece) == '+') {
13443 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13445 piece = (ChessSquare)(DEMOTED piece);
13447 *p++ = PieceToChar(piece);
13449 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13450 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13455 if (emptycount > 0) {
13456 if(emptycount<10) /* [HGM] can be >= 10 */
13457 *p++ = '0' + emptycount;
13458 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13465 /* [HGM] print Crazyhouse or Shogi holdings */
13466 if( gameInfo.holdingsWidth ) {
13467 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13469 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13470 piece = boards[move][i][BOARD_WIDTH-1];
13471 if( piece != EmptySquare )
13472 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13473 *p++ = PieceToChar(piece);
13475 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13476 piece = boards[move][BOARD_HEIGHT-i-1][0];
13477 if( piece != EmptySquare )
13478 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13479 *p++ = PieceToChar(piece);
13482 if( q == p ) *p++ = '-';
13488 *p++ = whiteToPlay ? 'w' : 'b';
13491 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13492 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13494 if(nrCastlingRights) {
13496 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13497 /* [HGM] write directly from rights */
13498 if(castlingRights[move][2] >= 0 &&
13499 castlingRights[move][0] >= 0 )
13500 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13501 if(castlingRights[move][2] >= 0 &&
13502 castlingRights[move][1] >= 0 )
13503 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13504 if(castlingRights[move][5] >= 0 &&
13505 castlingRights[move][3] >= 0 )
13506 *p++ = castlingRights[move][3] + AAA;
13507 if(castlingRights[move][5] >= 0 &&
13508 castlingRights[move][4] >= 0 )
13509 *p++ = castlingRights[move][4] + AAA;
13512 /* [HGM] write true castling rights */
13513 if( nrCastlingRights == 6 ) {
13514 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13515 castlingRights[move][2] >= 0 ) *p++ = 'K';
13516 if(castlingRights[move][1] == BOARD_LEFT &&
13517 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13518 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13519 castlingRights[move][5] >= 0 ) *p++ = 'k';
13520 if(castlingRights[move][4] == BOARD_LEFT &&
13521 castlingRights[move][5] >= 0 ) *p++ = 'q';
13524 if (q == p) *p++ = '-'; /* No castling rights */
13528 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13529 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13530 /* En passant target square */
13531 if (move > backwardMostMove) {
13532 fromX = moveList[move - 1][0] - AAA;
13533 fromY = moveList[move - 1][1] - ONE;
13534 toX = moveList[move - 1][2] - AAA;
13535 toY = moveList[move - 1][3] - ONE;
13536 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13537 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13538 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13540 /* 2-square pawn move just happened */
13542 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13553 /* [HGM] find reversible plies */
13554 { int i = 0, j=move;
13556 if (appData.debugMode) { int k;
13557 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13558 for(k=backwardMostMove; k<=forwardMostMove; k++)
13559 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13563 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13564 if( j == backwardMostMove ) i += initialRulePlies;
13565 sprintf(p, "%d ", i);
13566 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13568 /* Fullmove number */
13569 sprintf(p, "%d", (move / 2) + 1);
13571 return StrSave(buf);
13575 ParseFEN(board, blackPlaysFirst, fen)
13577 int *blackPlaysFirst;
13587 /* [HGM] by default clear Crazyhouse holdings, if present */
13588 if(gameInfo.holdingsWidth) {
13589 for(i=0; i<BOARD_HEIGHT; i++) {
13590 board[i][0] = EmptySquare; /* black holdings */
13591 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13592 board[i][1] = (ChessSquare) 0; /* black counts */
13593 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13597 /* Piece placement data */
13598 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13601 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13602 if (*p == '/') p++;
13603 emptycount = gameInfo.boardWidth - j;
13604 while (emptycount--)
13605 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13607 #if(BOARD_SIZE >= 10)
13608 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13609 p++; emptycount=10;
13610 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13611 while (emptycount--)
13612 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13614 } else if (isdigit(*p)) {
13615 emptycount = *p++ - '0';
13616 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13617 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13618 while (emptycount--)
13619 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13620 } else if (*p == '+' || isalpha(*p)) {
13621 if (j >= gameInfo.boardWidth) return FALSE;
13623 piece = CharToPiece(*++p);
13624 if(piece == EmptySquare) return FALSE; /* unknown piece */
13625 piece = (ChessSquare) (PROMOTED piece ); p++;
13626 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13627 } else piece = CharToPiece(*p++);
13629 if(piece==EmptySquare) return FALSE; /* unknown piece */
13630 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13631 piece = (ChessSquare) (PROMOTED piece);
13632 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13635 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13641 while (*p == '/' || *p == ' ') p++;
13643 /* [HGM] look for Crazyhouse holdings here */
13644 while(*p==' ') p++;
13645 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13647 if(*p == '-' ) *p++; /* empty holdings */ else {
13648 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13649 /* if we would allow FEN reading to set board size, we would */
13650 /* have to add holdings and shift the board read so far here */
13651 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13653 if((int) piece >= (int) BlackPawn ) {
13654 i = (int)piece - (int)BlackPawn;
13655 i = PieceToNumber((ChessSquare)i);
13656 if( i >= gameInfo.holdingsSize ) return FALSE;
13657 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13658 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13660 i = (int)piece - (int)WhitePawn;
13661 i = PieceToNumber((ChessSquare)i);
13662 if( i >= gameInfo.holdingsSize ) return FALSE;
13663 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13664 board[i][BOARD_WIDTH-2]++; /* black holdings */
13668 if(*p == ']') *p++;
13671 while(*p == ' ') p++;
13676 *blackPlaysFirst = FALSE;
13679 *blackPlaysFirst = TRUE;
13685 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13686 /* return the extra info in global variiables */
13688 /* set defaults in case FEN is incomplete */
13689 FENepStatus = EP_UNKNOWN;
13690 for(i=0; i<nrCastlingRights; i++ ) {
13691 FENcastlingRights[i] =
13692 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13693 } /* assume possible unless obviously impossible */
13694 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13695 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13696 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13697 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13698 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13699 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13702 while(*p==' ') p++;
13703 if(nrCastlingRights) {
13704 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13705 /* castling indicator present, so default becomes no castlings */
13706 for(i=0; i<nrCastlingRights; i++ ) {
13707 FENcastlingRights[i] = -1;
13710 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13711 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13712 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13713 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13714 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13716 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13717 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13718 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13722 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13723 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13724 FENcastlingRights[2] = whiteKingFile;
13727 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13728 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13729 FENcastlingRights[2] = whiteKingFile;
13732 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13733 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13734 FENcastlingRights[5] = blackKingFile;
13737 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13738 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13739 FENcastlingRights[5] = blackKingFile;
13742 default: /* FRC castlings */
13743 if(c >= 'a') { /* black rights */
13744 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13745 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13746 if(i == BOARD_RGHT) break;
13747 FENcastlingRights[5] = i;
13749 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13750 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13752 FENcastlingRights[3] = c;
13754 FENcastlingRights[4] = c;
13755 } else { /* white rights */
13756 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13757 if(board[0][i] == WhiteKing) break;
13758 if(i == BOARD_RGHT) break;
13759 FENcastlingRights[2] = i;
13760 c -= AAA - 'a' + 'A';
13761 if(board[0][c] >= WhiteKing) break;
13763 FENcastlingRights[0] = c;
13765 FENcastlingRights[1] = c;
13769 if (appData.debugMode) {
13770 fprintf(debugFP, "FEN castling rights:");
13771 for(i=0; i<nrCastlingRights; i++)
13772 fprintf(debugFP, " %d", FENcastlingRights[i]);
13773 fprintf(debugFP, "\n");
13776 while(*p==' ') p++;
13779 /* read e.p. field in games that know e.p. capture */
13780 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13781 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13783 p++; FENepStatus = EP_NONE;
13785 char c = *p++ - AAA;
13787 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13788 if(*p >= '0' && *p <='9') *p++;
13794 if(sscanf(p, "%d", &i) == 1) {
13795 FENrulePlies = i; /* 50-move ply counter */
13796 /* (The move number is still ignored) */
13803 EditPositionPasteFEN(char *fen)
13806 Board initial_position;
13808 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13809 DisplayError(_("Bad FEN position in clipboard"), 0);
13812 int savedBlackPlaysFirst = blackPlaysFirst;
13813 EditPositionEvent();
13814 blackPlaysFirst = savedBlackPlaysFirst;
13815 CopyBoard(boards[0], initial_position);
13816 /* [HGM] copy FEN attributes as well */
13818 initialRulePlies = FENrulePlies;
13819 epStatus[0] = FENepStatus;
13820 for( i=0; i<nrCastlingRights; i++ )
13821 castlingRights[0][i] = FENcastlingRights[i];
13823 EditPositionDone();
13824 DisplayBothClocks();
13825 DrawPosition(FALSE, boards[currentMove]);