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;
245 int lastSavedGame; /* [HGM] save: ID of game */
247 /* States for ics_getting_history */
249 #define H_REQUESTED 1
250 #define H_GOT_REQ_HEADER 2
251 #define H_GOT_UNREQ_HEADER 3
252 #define H_GETTING_MOVES 4
253 #define H_GOT_UNWANTED_HEADER 5
255 /* whosays values for GameEnds */
264 /* Maximum number of games in a cmail message */
265 #define CMAIL_MAX_GAMES 20
267 /* Different types of move when calling RegisterMove */
269 #define CMAIL_RESIGN 1
271 #define CMAIL_ACCEPT 3
273 /* Different types of result to remember for each game */
274 #define CMAIL_NOT_RESULT 0
275 #define CMAIL_OLD_RESULT 1
276 #define CMAIL_NEW_RESULT 2
278 /* Telnet protocol constants */
289 static char * safeStrCpy( char * dst, const char * src, size_t count )
291 assert( dst != NULL );
292 assert( src != NULL );
295 strncpy( dst, src, count );
296 dst[ count-1 ] = '\0';
301 //[HGM] for future use? Conditioned out for now to suppress warning.
302 static char * safeStrCat( char * dst, const char * src, size_t count )
306 assert( dst != NULL );
307 assert( src != NULL );
310 dst_len = strlen(dst);
312 assert( count > dst_len ); /* Buffer size must be greater than current length */
314 safeStrCpy( dst + dst_len, src, count - dst_len );
320 /* Some compiler can't cast u64 to double
321 * This function do the job for us:
323 * We use the highest bit for cast, this only
324 * works if the highest bit is not
325 * in use (This should not happen)
327 * We used this for all compiler
330 u64ToDouble(u64 value)
333 u64 tmp = value & u64Const(0x7fffffffffffffff);
334 r = (double)(s64)tmp;
335 if (value & u64Const(0x8000000000000000))
336 r += 9.2233720368547758080e18; /* 2^63 */
340 /* Fake up flags for now, as we aren't keeping track of castling
341 availability yet. [HGM] Change of logic: the flag now only
342 indicates the type of castlings allowed by the rule of the game.
343 The actual rights themselves are maintained in the array
344 castlingRights, as part of the game history, and are not probed
350 int flags = F_ALL_CASTLE_OK;
351 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
352 switch (gameInfo.variant) {
354 flags &= ~F_ALL_CASTLE_OK;
355 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
356 flags |= F_IGNORE_CHECK;
358 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
361 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
363 case VariantKriegspiel:
364 flags |= F_KRIEGSPIEL_CAPTURE;
366 case VariantCapaRandom:
367 case VariantFischeRandom:
368 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
369 case VariantNoCastle:
370 case VariantShatranj:
372 flags &= ~F_ALL_CASTLE_OK;
380 FILE *gameFileFP, *debugFP;
383 [AS] Note: sometimes, the sscanf() function is used to parse the input
384 into a fixed-size buffer. Because of this, we must be prepared to
385 receive strings as long as the size of the input buffer, which is currently
386 set to 4K for Windows and 8K for the rest.
387 So, we must either allocate sufficiently large buffers here, or
388 reduce the size of the input buffer in the input reading part.
391 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
392 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
393 char thinkOutput1[MSG_SIZ*10];
395 ChessProgramState first, second;
397 /* premove variables */
400 int premoveFromX = 0;
401 int premoveFromY = 0;
402 int premovePromoChar = 0;
404 Boolean alarmSounded;
405 /* end premove variables */
407 char *ics_prefix = "$";
408 int ics_type = ICS_GENERIC;
410 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
411 int pauseExamForwardMostMove = 0;
412 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
413 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
414 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
415 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
416 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
417 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
418 int whiteFlag = FALSE, blackFlag = FALSE;
419 int userOfferedDraw = FALSE;
420 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
421 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
422 int cmailMoveType[CMAIL_MAX_GAMES];
423 long ics_clock_paused = 0;
424 ProcRef icsPR = NoProc, cmailPR = NoProc;
425 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
426 GameMode gameMode = BeginningOfGame;
427 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
428 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
429 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
430 int hiddenThinkOutputState = 0; /* [AS] */
431 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
432 int adjudicateLossPlies = 6;
433 char white_holding[64], black_holding[64];
434 TimeMark lastNodeCountTime;
435 long lastNodeCount=0;
436 int have_sent_ICS_logon = 0;
438 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
439 long timeControl_2; /* [AS] Allow separate time controls */
440 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
441 long timeRemaining[2][MAX_MOVES];
443 TimeMark programStartTime;
444 char ics_handle[MSG_SIZ];
445 int have_set_title = 0;
447 /* animateTraining preserves the state of appData.animate
448 * when Training mode is activated. This allows the
449 * response to be animated when appData.animate == TRUE and
450 * appData.animateDragging == TRUE.
452 Boolean animateTraining;
458 Board boards[MAX_MOVES];
459 /* [HGM] Following 7 needed for accurate legality tests: */
460 char epStatus[MAX_MOVES];
461 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
462 char castlingRank[BOARD_SIZE]; // and corresponding ranks
463 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
464 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
465 int initialRulePlies, FENrulePlies;
467 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 ChessSquare FIDEArray[2][BOARD_SIZE] = {
472 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
473 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
474 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
475 BlackKing, BlackBishop, BlackKnight, BlackRook }
478 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
479 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
480 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
481 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
482 BlackKing, BlackKing, BlackKnight, BlackRook }
485 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
486 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
487 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
488 { BlackRook, BlackMan, BlackBishop, BlackQueen,
489 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
492 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
493 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
494 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
495 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
496 BlackKing, BlackBishop, BlackKnight, BlackRook }
499 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
500 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
501 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
502 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
503 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare ShogiArray[2][BOARD_SIZE] = {
509 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
510 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
511 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
512 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
515 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
516 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
517 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
519 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
523 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
526 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
529 ChessSquare GreatArray[2][BOARD_SIZE] = {
530 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
531 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
532 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
533 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
536 ChessSquare JanusArray[2][BOARD_SIZE] = {
537 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
538 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
539 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
540 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
544 ChessSquare GothicArray[2][BOARD_SIZE] = {
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
546 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
548 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
551 #define GothicArray CapablancaArray
555 ChessSquare FalconArray[2][BOARD_SIZE] = {
556 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
557 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
559 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
562 #define FalconArray CapablancaArray
565 #else // !(BOARD_SIZE>=10)
566 #define XiangqiPosition FIDEArray
567 #define CapablancaArray FIDEArray
568 #define GothicArray FIDEArray
569 #define GreatArray FIDEArray
570 #endif // !(BOARD_SIZE>=10)
573 ChessSquare CourierArray[2][BOARD_SIZE] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
575 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
577 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
579 #else // !(BOARD_SIZE>=12)
580 #define CourierArray CapablancaArray
581 #endif // !(BOARD_SIZE>=12)
584 Board initialPosition;
587 /* Convert str to a rating. Checks for special cases of "----",
589 "++++", etc. Also strips ()'s */
591 string_to_rating(str)
594 while(*str && !isdigit(*str)) ++str;
596 return 0; /* One of the special "no rating" cases */
604 /* Init programStats */
605 programStats.movelist[0] = 0;
606 programStats.depth = 0;
607 programStats.nr_moves = 0;
608 programStats.moves_left = 0;
609 programStats.nodes = 0;
610 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
611 programStats.score = 0;
612 programStats.got_only_move = 0;
613 programStats.got_fail = 0;
614 programStats.line_is_book = 0;
620 int matched, min, sec;
622 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
624 GetTimeMark(&programStartTime);
625 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
628 programStats.ok_to_send = 1;
629 programStats.seen_stat = 0;
632 * Initialize game list
638 * Internet chess server status
640 if (appData.icsActive) {
641 appData.matchMode = FALSE;
642 appData.matchGames = 0;
644 appData.noChessProgram = !appData.zippyPlay;
646 appData.zippyPlay = FALSE;
647 appData.zippyTalk = FALSE;
648 appData.noChessProgram = TRUE;
650 if (*appData.icsHelper != NULLCHAR) {
651 appData.useTelnet = TRUE;
652 appData.telnetProgram = appData.icsHelper;
655 appData.zippyTalk = appData.zippyPlay = FALSE;
658 /* [AS] Initialize pv info list [HGM] and game state */
662 for( i=0; i<MAX_MOVES; i++ ) {
663 pvInfoList[i].depth = -1;
665 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
670 * Parse timeControl resource
672 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
673 appData.movesPerSession)) {
675 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
676 DisplayFatalError(buf, 0, 2);
680 * Parse searchTime resource
682 if (*appData.searchTime != NULLCHAR) {
683 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
685 searchTime = min * 60;
686 } else if (matched == 2) {
687 searchTime = min * 60 + sec;
690 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
691 DisplayFatalError(buf, 0, 2);
695 /* [AS] Adjudication threshold */
696 adjudicateLossThreshold = appData.adjudicateLossThreshold;
698 first.which = "first";
699 second.which = "second";
700 first.maybeThinking = second.maybeThinking = FALSE;
701 first.pr = second.pr = NoProc;
702 first.isr = second.isr = NULL;
703 first.sendTime = second.sendTime = 2;
704 first.sendDrawOffers = 1;
705 if (appData.firstPlaysBlack) {
706 first.twoMachinesColor = "black\n";
707 second.twoMachinesColor = "white\n";
709 first.twoMachinesColor = "white\n";
710 second.twoMachinesColor = "black\n";
712 first.program = appData.firstChessProgram;
713 second.program = appData.secondChessProgram;
714 first.host = appData.firstHost;
715 second.host = appData.secondHost;
716 first.dir = appData.firstDirectory;
717 second.dir = appData.secondDirectory;
718 first.other = &second;
719 second.other = &first;
720 first.initString = appData.initString;
721 second.initString = appData.secondInitString;
722 first.computerString = appData.firstComputerString;
723 second.computerString = appData.secondComputerString;
724 first.useSigint = second.useSigint = TRUE;
725 first.useSigterm = second.useSigterm = TRUE;
726 first.reuse = appData.reuseFirst;
727 second.reuse = appData.reuseSecond;
728 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
729 second.nps = appData.secondNPS;
730 first.useSetboard = second.useSetboard = FALSE;
731 first.useSAN = second.useSAN = FALSE;
732 first.usePing = second.usePing = FALSE;
733 first.lastPing = second.lastPing = 0;
734 first.lastPong = second.lastPong = 0;
735 first.usePlayother = second.usePlayother = FALSE;
736 first.useColors = second.useColors = TRUE;
737 first.useUsermove = second.useUsermove = FALSE;
738 first.sendICS = second.sendICS = FALSE;
739 first.sendName = second.sendName = appData.icsActive;
740 first.sdKludge = second.sdKludge = FALSE;
741 first.stKludge = second.stKludge = FALSE;
742 TidyProgramName(first.program, first.host, first.tidy);
743 TidyProgramName(second.program, second.host, second.tidy);
744 first.matchWins = second.matchWins = 0;
745 strcpy(first.variants, appData.variant);
746 strcpy(second.variants, appData.variant);
747 first.analysisSupport = second.analysisSupport = 2; /* detect */
748 first.analyzing = second.analyzing = FALSE;
749 first.initDone = second.initDone = FALSE;
751 /* New features added by Tord: */
752 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
753 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
754 /* End of new features added by Tord. */
755 first.fenOverride = appData.fenOverride1;
756 second.fenOverride = appData.fenOverride2;
758 /* [HGM] time odds: set factor for each machine */
759 first.timeOdds = appData.firstTimeOdds;
760 second.timeOdds = appData.secondTimeOdds;
762 if(appData.timeOddsMode) {
763 norm = first.timeOdds;
764 if(norm > second.timeOdds) norm = second.timeOdds;
766 first.timeOdds /= norm;
767 second.timeOdds /= norm;
770 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
771 first.accumulateTC = appData.firstAccumulateTC;
772 second.accumulateTC = appData.secondAccumulateTC;
773 first.maxNrOfSessions = second.maxNrOfSessions = 1;
776 first.debug = second.debug = FALSE;
777 first.supportsNPS = second.supportsNPS = UNKNOWN;
780 first.optionSettings = appData.firstOptions;
781 second.optionSettings = appData.secondOptions;
783 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
784 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
785 first.isUCI = appData.firstIsUCI; /* [AS] */
786 second.isUCI = appData.secondIsUCI; /* [AS] */
787 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
788 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
790 if (appData.firstProtocolVersion > PROTOVER ||
791 appData.firstProtocolVersion < 1) {
793 sprintf(buf, _("protocol version %d not supported"),
794 appData.firstProtocolVersion);
795 DisplayFatalError(buf, 0, 2);
797 first.protocolVersion = appData.firstProtocolVersion;
800 if (appData.secondProtocolVersion > PROTOVER ||
801 appData.secondProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.secondProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 second.protocolVersion = appData.secondProtocolVersion;
810 if (appData.icsActive) {
811 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
812 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
834 while (*q != ' ' && *q != NULLCHAR) q++;
836 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
837 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
838 sprintf(programVersion, "%s + ", PACKAGE_STRING);
839 strncat(programVersion, p, q - p);
841 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantBerolina: /* might work if TestLegality is off */
903 case VariantCapaRandom: /* should work */
904 case VariantJanus: /* should work */
905 case VariantSuper: /* experimental */
906 case VariantGreat: /* experimental, requires legality testing to be off */
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
912 InitEngineUCI( installDir, &second );
915 int NextIntegerFromString( char ** str, long * value )
920 while( *s == ' ' || *s == '\t' ) {
926 if( *s >= '0' && *s <= '9' ) {
927 while( *s >= '0' && *s <= '9' ) {
928 *value = *value * 10 + (*s - '0');
940 int NextTimeControlFromString( char ** str, long * value )
943 int result = NextIntegerFromString( str, &temp );
946 *value = temp * 60; /* Minutes */
949 result = NextIntegerFromString( str, &temp );
950 *value += temp; /* Seconds */
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
959 int result = -1; long temp, temp2;
961 if(**str != '+') return -1; // old params remain in force!
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
966 /* time only: incremental or sudden-death time control */
967 if(**str == '+') { /* increment follows; read it */
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
972 *moves = 0; *tc = temp * 1000;
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
976 (*str)++; /* classical time control */
977 result = NextTimeControlFromString( str, &temp2);
986 int GetTimeQuota(int movenr)
987 { /* [HGM] get time to add from the multi-session time-control string */
988 int moves=1; /* kludge to force reading of first session */
989 long time, increment;
990 char *s = fullTimeControlString;
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996 if(movenr == -1) return time; /* last move before new session */
997 if(!moves) return increment; /* current session is incremental */
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
999 } while(movenr >= -1); /* try again for next session */
1001 return 0; // no new time quota on this move
1005 ParseTimeControl(tc, ti, mps)
1011 int matched, min, sec;
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
1015 timeControl = min * 60 * 1000;
1016 } else if (matched == 2) {
1017 timeControl = (min * 60 + sec) * 1000;
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1030 else sprintf(buf, "+%s+%d", tc, ti);
1033 sprintf(buf, "+%d/%s", mps, tc);
1034 else sprintf(buf, "+%s", tc);
1036 fullTimeControlString = StrSave(buf);
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1043 /* Parse second time control */
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1054 timeControl_2 = tc2 * 1000;
1064 timeControl = tc1 * 1000;
1068 timeIncrement = ti * 1000; /* convert to ms */
1069 movesPerSession = 0;
1072 movesPerSession = mps;
1080 if (appData.debugMode) {
1081 fprintf(debugFP, "%s\n", programVersion);
1084 if (appData.matchGames > 0) {
1085 appData.matchMode = TRUE;
1086 } else if (appData.matchMode) {
1087 appData.matchGames = 1;
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1090 appData.matchGames = appData.sameColorGames;
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
1099 /* kludge: allow timeout for initial "feature" commands */
1101 DisplayMessage("", _("Starting chess program"));
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1107 InitBackEnd3 P((void))
1109 GameMode initialMode;
1113 InitChessProgram(&first, startedFromSetupPosition);
1116 if (appData.icsActive) {
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
1123 if (*appData.icsCommPort != NULLCHAR) {
1124 sprintf(buf, _("Could not open comm port %s"),
1125 appData.icsCommPort);
1127 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1128 appData.icsHost, appData.icsPort);
1130 DisplayFatalError(buf, err, 1);
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1138 } else if (appData.noChessProgram) {
1144 if (*appData.cmailGameName != NULLCHAR) {
1146 OpenLoopback(&cmailPR);
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1152 DisplayMessage("", "");
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
1154 initialMode = BeginningOfGame;
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1156 initialMode = TwoMachinesPlay;
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1158 initialMode = AnalyzeFile;
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1160 initialMode = AnalyzeMode;
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1162 initialMode = MachinePlaysWhite;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1164 initialMode = MachinePlaysBlack;
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1166 initialMode = EditGame;
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1168 initialMode = EditPosition;
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1170 initialMode = Training;
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1173 DisplayFatalError(buf, 0, 2);
1177 if (appData.matchMode) {
1178 /* Set up machine vs. machine match */
1179 if (appData.noChessProgram) {
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
1186 if (*appData.loadGameFile != NULLCHAR) {
1187 int index = appData.loadGameIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadGameFromFile(appData.loadGameFile,
1191 appData.loadGameFile, FALSE)) {
1192 DisplayFatalError(_("Bad game file"), 0, 1);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
1197 if(index<0) lastIndex = index = 1;
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionFile)) {
1201 DisplayFatalError(_("Bad position file"), 0, 1);
1206 } else if (*appData.cmailGameName != NULLCHAR) {
1207 /* Set up cmail mode */
1208 ReloadCmailMsgEvent(TRUE);
1210 /* Set up other modes */
1211 if (initialMode == AnalyzeFile) {
1212 if (*appData.loadGameFile == NULLCHAR) {
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1217 if (*appData.loadGameFile != NULLCHAR) {
1218 (void) LoadGameFromFile(appData.loadGameFile,
1219 appData.loadGameIndex,
1220 appData.loadGameFile, TRUE);
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
1223 appData.loadPositionIndex,
1224 appData.loadPositionFile);
1225 /* [HGM] try to make self-starting even after FEN load */
1226 /* to allow automatic setup of fairy variants with wtm */
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1228 gameMode = BeginningOfGame;
1229 setboardSpoiledMachineBlack = 1;
1231 /* [HGM] loadPos: make that every new game uses the setup */
1232 /* from file as long as we do not switch variant */
1233 if(!blackPlaysFirst) { int i;
1234 startedFromPositionFile = TRUE;
1235 CopyBoard(filePosition, boards[0]);
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1239 if (initialMode == AnalyzeMode) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1244 if (appData.icsActive) {
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1249 } else if (initialMode == AnalyzeFile) {
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1251 ShowThinkingEvent();
1253 AnalysisPeriodicEvent(1);
1254 } else if (initialMode == MachinePlaysWhite) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1265 MachineWhiteEvent();
1266 } else if (initialMode == MachinePlaysBlack) {
1267 if (appData.noChessProgram) {
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1272 if (appData.icsActive) {
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1277 MachineBlackEvent();
1278 } else if (initialMode == TwoMachinesPlay) {
1279 if (appData.noChessProgram) {
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1284 if (appData.icsActive) {
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1290 } else if (initialMode == EditGame) {
1292 } else if (initialMode == EditPosition) {
1293 EditPositionEvent();
1294 } else if (initialMode == Training) {
1295 if (*appData.loadGameFile == NULLCHAR) {
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1305 * Establish will establish a contact to a remote host.port.
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1307 * used to talk to the host.
1308 * Returns 0 if okay, error code if not.
1315 if (*appData.icsCommPort != NULLCHAR) {
1316 /* Talk to the host through a serial comm port */
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
1319 } else if (*appData.gateway != NULLCHAR) {
1320 if (*appData.remoteShell == NULLCHAR) {
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
1322 snprintf(buf, sizeof(buf), "%s %s %s",
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1327 /* Use the rsh program to run telnet program on a gateway host */
1328 if (*appData.remoteUser == NULLCHAR) {
1329 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1330 appData.gateway, appData.telnetProgram,
1331 appData.icsHost, appData.icsPort);
1333 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1334 appData.remoteShell, appData.gateway,
1335 appData.remoteUser, appData.telnetProgram,
1336 appData.icsHost, appData.icsPort);
1338 return StartChildProcess(buf, "", &icsPR);
1341 } else if (appData.useTelnet) {
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1345 /* TCP socket interface differs somewhat between
1346 Unix and NT; handle details in the front end.
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1353 show_bytes(fp, buf, count)
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1360 fprintf(fp, "\\%03o", *buf & 0xff);
1369 /* Returns an errno value */
1371 OutputMaybeTelnet(pr, message, count, outError)
1377 char buf[8192], *p, *q, *buflim;
1378 int left, newcount, outcount;
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1381 *appData.gateway != NULLCHAR) {
1382 if (appData.debugMode) {
1383 fprintf(debugFP, ">ICS: ");
1384 show_bytes(debugFP, message, count);
1385 fprintf(debugFP, "\n");
1387 return OutputToProcess(pr, message, count, outError);
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1397 if (appData.debugMode) {
1398 fprintf(debugFP, ">ICS: ");
1399 show_bytes(debugFP, buf, newcount);
1400 fprintf(debugFP, "\n");
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
1403 if (outcount < newcount) return -1; /* to be sure */
1410 } else if (((unsigned char) *p) == TN_IAC) {
1411 *q++ = (char) TN_IAC;
1418 if (appData.debugMode) {
1419 fprintf(debugFP, ">ICS: ");
1420 show_bytes(debugFP, buf, newcount);
1421 fprintf(debugFP, "\n");
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
1424 if (outcount < newcount) return -1; /* to be sure */
1429 read_from_player(isr, closure, message, count, error)
1436 int outError, outCount;
1437 static int gotEof = 0;
1439 /* Pass data read from player on to ICS */
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1443 if (outCount < count) {
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1446 } else if (count < 0) {
1447 RemoveInputSource(isr);
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1449 } else if (gotEof++ > 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 int count, outCount, outError;
1461 if (icsPR == NULL) return;
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1465 if (outCount < count) {
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1470 /* This is used for sending logon scripts to the ICS. Sending
1471 without a delay causes problems when using timestamp on ICC
1472 (at least on my machine). */
1474 SendToICSDelayed(s,msdelay)
1478 int count, outCount, outError;
1480 if (icsPR == NULL) return;
1483 if (appData.debugMode) {
1484 fprintf(debugFP, ">ICS: ");
1485 show_bytes(debugFP, s, count);
1486 fprintf(debugFP, "\n");
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1490 if (outCount < count) {
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1496 /* Remove all highlighting escape sequences in s
1497 Also deletes any suffix starting with '('
1500 StripHighlightAndTitle(s)
1503 static char retbuf[MSG_SIZ];
1506 while (*s != NULLCHAR) {
1507 while (*s == '\033') {
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
1509 if (*s != NULLCHAR) s++;
1511 while (*s != NULLCHAR && *s != '\033') {
1512 if (*s == '(' || *s == '[') {
1523 /* Remove all highlighting escape sequences in s */
1528 static char retbuf[MSG_SIZ];
1531 while (*s != NULLCHAR) {
1532 while (*s == '\033') {
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
1534 if (*s != NULLCHAR) s++;
1536 while (*s != NULLCHAR && *s != '\033') {
1544 char *variantNames[] = VARIANT_NAMES;
1549 return variantNames[v];
1553 /* Identify a variant from the strings the chess servers use or the
1554 PGN Variant tag names we use. */
1561 VariantClass v = VariantNormal;
1562 int i, found = FALSE;
1567 /* [HGM] skip over optional board-size prefixes */
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1570 while( *e++ != '_');
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1922 if( (int)lowestPiece >= BlackPawn ) {
1925 holdingsStartRow = BOARD_HEIGHT-1;
1928 holdingsColumn = BOARD_WIDTH-1;
1929 countsColumn = BOARD_WIDTH-2;
1930 holdingsStartRow = 0;
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1935 board[i][holdingsColumn] = EmptySquare;
1936 board[i][countsColumn] = (ChessSquare) 0;
1938 while( (p=*holdings++) != NULLCHAR ) {
1939 piece = CharToPiece( ToUpper(p) );
1940 if(piece == EmptySquare) continue;
1941 /*j = (int) piece - (int) WhitePawn;*/
1942 j = PieceToNumber(piece);
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1944 if(j < 0) continue; /* should not happen */
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1947 board[holdingsStartRow+j*direction][countsColumn]++;
1954 VariantSwitch(Board board, VariantClass newVariant)
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1983 switch(newVariant) {
1985 newWidth = 9; newHeight = 9;
1986 gameInfo.holdingsSize = 7;
1987 case VariantBughouse:
1988 case VariantCrazyhouse:
1989 newHoldingsWidth = 2; break;
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
1994 if(newWidth != gameInfo.boardWidth ||
1995 newHeight != gameInfo.boardHeight ||
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
1998 /* shift position to new playing area, if needed */
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2000 for(i=0; i<BOARD_HEIGHT; i++)
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2004 for(i=0; i<newHeight; i++) {
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 gameInfo.boardWidth = newWidth;
2016 gameInfo.boardHeight = newHeight;
2017 gameInfo.holdingsWidth = newHoldingsWidth;
2018 gameInfo.variant = newVariant;
2019 InitDrawingSizes(-2, 0);
2021 /* [HGM] The following should definitely be solved in a better way */
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2025 saveEP = epStatus[0];
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 epStatus[0] = saveEP;
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2035 forwardMostMove = oldForwardMostMove;
2036 backwardMostMove = oldBackwardMostMove;
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2040 static int loggedOn = FALSE;
2042 /*-- Game start info cache: --*/
2044 char gs_kind[MSG_SIZ];
2045 static char player1Name[128] = "";
2046 static char player2Name[128] = "";
2047 static int player1Rating = -1;
2048 static int player2Rating = -1;
2049 /*----------------------------*/
2051 ColorClass curColor = ColorNormal;
2052 int suppressKibitz = 0;
2055 read_from_ics(isr, closure, data, count, error)
2062 #define BUF_SIZE 8192
2063 #define STARTED_NONE 0
2064 #define STARTED_MOVES 1
2065 #define STARTED_BOARD 2
2066 #define STARTED_OBSERVE 3
2067 #define STARTED_HOLDINGS 4
2068 #define STARTED_CHATTER 5
2069 #define STARTED_COMMENT 6
2070 #define STARTED_MOVES_NOHIDE 7
2072 static int started = STARTED_NONE;
2073 static char parse[20000];
2074 static int parse_pos = 0;
2075 static char buf[BUF_SIZE + 1];
2076 static int firstTime = TRUE, intfSet = FALSE;
2077 static ColorClass prevColor = ColorNormal;
2078 static int savingComment = FALSE;
2084 int backup; /* [DM] For zippy color lines */
2087 if (appData.debugMode) {
2089 fprintf(debugFP, "<ICS: ");
2090 show_bytes(debugFP, data, count);
2091 fprintf(debugFP, "\n");
2095 if (appData.debugMode) { int f = forwardMostMove;
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2100 /* If last read ended with a partial line that we couldn't parse,
2101 prepend it to the new read and try again. */
2102 if (leftover_len > 0) {
2103 for (i=0; i<leftover_len; i++)
2104 buf[i] = buf[leftover_start + i];
2107 /* Copy in new characters, removing nulls and \r's */
2108 buf_len = leftover_len;
2109 for (i = 0; i < count; i++) {
2110 if (data[i] != NULLCHAR && data[i] != '\r')
2111 buf[buf_len++] = data[i];
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2115 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2119 buf[buf_len] = NULLCHAR;
2120 next_out = leftover_len;
2124 while (i < buf_len) {
2125 /* Deal with part of the TELNET option negotiation
2126 protocol. We refuse to do anything beyond the
2127 defaults, except that we allow the WILL ECHO option,
2128 which ICS uses to turn off password echoing when we are
2129 directly connected to it. We reject this option
2130 if localLineEditing mode is on (always on in xboard)
2131 and we are talking to port 23, which might be a real
2132 telnet server that will try to keep WILL ECHO on permanently.
2134 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2135 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2136 unsigned char option;
2138 switch ((unsigned char) buf[++i]) {
2140 if (appData.debugMode)
2141 fprintf(debugFP, "\n<WILL ");
2142 switch (option = (unsigned char) buf[++i]) {
2144 if (appData.debugMode)
2145 fprintf(debugFP, "ECHO ");
2146 /* Reply only if this is a change, according
2147 to the protocol rules. */
2148 if (remoteEchoOption) break;
2149 if (appData.localLineEditing &&
2150 atoi(appData.icsPort) == TN_PORT) {
2151 TelnetRequest(TN_DONT, TN_ECHO);
2154 TelnetRequest(TN_DO, TN_ECHO);
2155 remoteEchoOption = TRUE;
2159 if (appData.debugMode)
2160 fprintf(debugFP, "%d ", option);
2161 /* Whatever this is, we don't want it. */
2162 TelnetRequest(TN_DONT, option);
2167 if (appData.debugMode)
2168 fprintf(debugFP, "\n<WONT ");
2169 switch (option = (unsigned char) buf[++i]) {
2171 if (appData.debugMode)
2172 fprintf(debugFP, "ECHO ");
2173 /* Reply only if this is a change, according
2174 to the protocol rules. */
2175 if (!remoteEchoOption) break;
2177 TelnetRequest(TN_DONT, TN_ECHO);
2178 remoteEchoOption = FALSE;
2181 if (appData.debugMode)
2182 fprintf(debugFP, "%d ", (unsigned char) option);
2183 /* Whatever this is, it must already be turned
2184 off, because we never agree to turn on
2185 anything non-default, so according to the
2186 protocol rules, we don't reply. */
2191 if (appData.debugMode)
2192 fprintf(debugFP, "\n<DO ");
2193 switch (option = (unsigned char) buf[++i]) {
2195 /* Whatever this is, we refuse to do it. */
2196 if (appData.debugMode)
2197 fprintf(debugFP, "%d ", option);
2198 TelnetRequest(TN_WONT, option);
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DONT ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 /* Whatever this is, we are already not doing
2210 it, because we never agree to do anything
2211 non-default, so according to the protocol
2212 rules, we don't reply. */
2217 if (appData.debugMode)
2218 fprintf(debugFP, "\n<IAC ");
2219 /* Doubled IAC; pass it through */
2223 if (appData.debugMode)
2224 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2225 /* Drop all other telnet commands on the floor */
2228 if (oldi > next_out)
2229 SendToPlayer(&buf[next_out], oldi - next_out);
2235 /* OK, this at least will *usually* work */
2236 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2240 if (loggedOn && !intfSet) {
2241 if (ics_type == ICS_ICC) {
2243 "/set-quietly interface %s\n/set-quietly style 12\n",
2246 } else if (ics_type == ICS_CHESSNET) {
2247 sprintf(str, "/style 12\n");
2249 strcpy(str, "alias $ @\n$set interface ");
2250 strcat(str, programVersion);
2251 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2253 strcat(str, "$iset nohighlight 1\n");
2255 strcat(str, "$iset lock 1\n$style 12\n");
2261 if (started == STARTED_COMMENT) {
2262 /* Accumulate characters in comment */
2263 parse[parse_pos++] = buf[i];
2264 if (buf[i] == '\n') {
2265 parse[parse_pos] = NULLCHAR;
2266 if(!suppressKibitz) // [HGM] kibitz
2267 AppendComment(forwardMostMove, StripHighlight(parse));
2268 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2269 int nrDigit = 0, nrAlph = 0, i;
2270 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2271 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2272 parse[parse_pos] = NULLCHAR;
2273 // try to be smart: if it does not look like search info, it should go to
2274 // ICS interaction window after all, not to engine-output window.
2275 for(i=0; i<parse_pos; i++) { // count letters and digits
2276 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2277 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2278 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2280 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2281 int depth=0; float score;
2282 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2283 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2284 pvInfoList[forwardMostMove-1].depth = depth;
2285 pvInfoList[forwardMostMove-1].score = 100*score;
2287 OutputKibitz(suppressKibitz, parse);
2290 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2291 SendToPlayer(tmp, strlen(tmp));
2294 started = STARTED_NONE;
2296 /* Don't match patterns against characters in chatter */
2301 if (started == STARTED_CHATTER) {
2302 if (buf[i] != '\n') {
2303 /* Don't match patterns against characters in chatter */
2307 started = STARTED_NONE;
2310 /* Kludge to deal with rcmd protocol */
2311 if (firstTime && looking_at(buf, &i, "\001*")) {
2312 DisplayFatalError(&buf[1], 0, 1);
2318 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2321 if (appData.debugMode)
2322 fprintf(debugFP, "ics_type %d\n", ics_type);
2325 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2326 ics_type = ICS_FICS;
2328 if (appData.debugMode)
2329 fprintf(debugFP, "ics_type %d\n", ics_type);
2332 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2333 ics_type = ICS_CHESSNET;
2335 if (appData.debugMode)
2336 fprintf(debugFP, "ics_type %d\n", ics_type);
2341 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2342 looking_at(buf, &i, "Logging you in as \"*\"") ||
2343 looking_at(buf, &i, "will be \"*\""))) {
2344 strcpy(ics_handle, star_match[0]);
2348 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2350 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2351 DisplayIcsInteractionTitle(buf);
2352 have_set_title = TRUE;
2355 /* skip finger notes */
2356 if (started == STARTED_NONE &&
2357 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2358 (buf[i] == '1' && buf[i+1] == '0')) &&
2359 buf[i+2] == ':' && buf[i+3] == ' ') {
2360 started = STARTED_CHATTER;
2365 /* skip formula vars */
2366 if (started == STARTED_NONE &&
2367 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2368 started = STARTED_CHATTER;
2374 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2375 if (appData.autoKibitz && started == STARTED_NONE &&
2376 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2377 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2378 if(looking_at(buf, &i, "* kibitzes: ") &&
2379 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2380 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2381 suppressKibitz = TRUE;
2382 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2383 && (gameMode == IcsPlayingWhite)) ||
2384 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2385 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2386 started = STARTED_CHATTER; // own kibitz we simply discard
2388 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2389 parse_pos = 0; parse[0] = NULLCHAR;
2390 savingComment = TRUE;
2391 suppressKibitz = gameMode != IcsObserving ? 2 :
2392 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2396 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2397 started = STARTED_CHATTER;
2398 suppressKibitz = TRUE;
2400 } // [HGM] kibitz: end of patch
2402 if (appData.zippyTalk || appData.zippyPlay) {
2403 /* [DM] Backup address for color zippy lines */
2407 if (loggedOn == TRUE)
2408 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2409 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2411 if (ZippyControl(buf, &i) ||
2412 ZippyConverse(buf, &i) ||
2413 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2415 if (!appData.colorize) continue;
2419 } // [DM] 'else { ' deleted
2420 if (/* Don't color "message" or "messages" output */
2421 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2422 looking_at(buf, &i, "*. * at *:*: ") ||
2423 looking_at(buf, &i, "--* (*:*): ") ||
2424 /* Regular tells and says */
2425 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2426 looking_at(buf, &i, "* (your partner) tells you: ") ||
2427 looking_at(buf, &i, "* says: ") ||
2428 /* Message notifications (same color as tells) */
2429 looking_at(buf, &i, "* has left a message ") ||
2430 looking_at(buf, &i, "* just sent you a message:\n") ||
2431 /* Whispers and kibitzes */
2432 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2433 looking_at(buf, &i, "* kibitzes: ") ||
2435 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2437 if (tkind == 1 && strchr(star_match[0], ':')) {
2438 /* Avoid "tells you:" spoofs in channels */
2441 if (star_match[0][0] == NULLCHAR ||
2442 strchr(star_match[0], ' ') ||
2443 (tkind == 3 && strchr(star_match[1], ' '))) {
2444 /* Reject bogus matches */
2447 if (appData.colorize) {
2448 if (oldi > next_out) {
2449 SendToPlayer(&buf[next_out], oldi - next_out);
2454 Colorize(ColorTell, FALSE);
2455 curColor = ColorTell;
2458 Colorize(ColorKibitz, FALSE);
2459 curColor = ColorKibitz;
2462 p = strrchr(star_match[1], '(');
2469 Colorize(ColorChannel1, FALSE);
2470 curColor = ColorChannel1;
2472 Colorize(ColorChannel, FALSE);
2473 curColor = ColorChannel;
2477 curColor = ColorNormal;
2481 if (started == STARTED_NONE && appData.autoComment &&
2482 (gameMode == IcsObserving ||
2483 gameMode == IcsPlayingWhite ||
2484 gameMode == IcsPlayingBlack)) {
2485 parse_pos = i - oldi;
2486 memcpy(parse, &buf[oldi], parse_pos);
2487 parse[parse_pos] = NULLCHAR;
2488 started = STARTED_COMMENT;
2489 savingComment = TRUE;
2491 started = STARTED_CHATTER;
2492 savingComment = FALSE;
2499 if (looking_at(buf, &i, "* s-shouts: ") ||
2500 looking_at(buf, &i, "* c-shouts: ")) {
2501 if (appData.colorize) {
2502 if (oldi > next_out) {
2503 SendToPlayer(&buf[next_out], oldi - next_out);
2506 Colorize(ColorSShout, FALSE);
2507 curColor = ColorSShout;
2510 started = STARTED_CHATTER;
2514 if (looking_at(buf, &i, "--->")) {
2519 if (looking_at(buf, &i, "* shouts: ") ||
2520 looking_at(buf, &i, "--> ")) {
2521 if (appData.colorize) {
2522 if (oldi > next_out) {
2523 SendToPlayer(&buf[next_out], oldi - next_out);
2526 Colorize(ColorShout, FALSE);
2527 curColor = ColorShout;
2530 started = STARTED_CHATTER;
2534 if (looking_at( buf, &i, "Challenge:")) {
2535 if (appData.colorize) {
2536 if (oldi > next_out) {
2537 SendToPlayer(&buf[next_out], oldi - next_out);
2540 Colorize(ColorChallenge, FALSE);
2541 curColor = ColorChallenge;
2547 if (looking_at(buf, &i, "* offers you") ||
2548 looking_at(buf, &i, "* offers to be") ||
2549 looking_at(buf, &i, "* would like to") ||
2550 looking_at(buf, &i, "* requests to") ||
2551 looking_at(buf, &i, "Your opponent offers") ||
2552 looking_at(buf, &i, "Your opponent requests")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorRequest, FALSE);
2560 curColor = ColorRequest;
2565 if (looking_at(buf, &i, "* (*) seeking")) {
2566 if (appData.colorize) {
2567 if (oldi > next_out) {
2568 SendToPlayer(&buf[next_out], oldi - next_out);
2571 Colorize(ColorSeek, FALSE);
2572 curColor = ColorSeek;
2577 if (looking_at(buf, &i, "\\ ")) {
2578 if (prevColor != ColorNormal) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(prevColor, TRUE);
2584 curColor = prevColor;
2586 if (savingComment) {
2587 parse_pos = i - oldi;
2588 memcpy(parse, &buf[oldi], parse_pos);
2589 parse[parse_pos] = NULLCHAR;
2590 started = STARTED_COMMENT;
2592 started = STARTED_CHATTER;
2597 if (looking_at(buf, &i, "Black Strength :") ||
2598 looking_at(buf, &i, "<<< style 10 board >>>") ||
2599 looking_at(buf, &i, "<10>") ||
2600 looking_at(buf, &i, "#@#")) {
2601 /* Wrong board style */
2603 SendToICS(ics_prefix);
2604 SendToICS("set style 12\n");
2605 SendToICS(ics_prefix);
2606 SendToICS("refresh\n");
2610 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2612 have_sent_ICS_logon = 1;
2616 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2617 (looking_at(buf, &i, "\n<12> ") ||
2618 looking_at(buf, &i, "<12> "))) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 started = STARTED_BOARD;
2629 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2630 looking_at(buf, &i, "<b1> ")) {
2631 if (oldi > next_out) {
2632 SendToPlayer(&buf[next_out], oldi - next_out);
2635 started = STARTED_HOLDINGS;
2640 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2642 /* Header for a move list -- first line */
2644 switch (ics_getting_history) {
2648 case BeginningOfGame:
2649 /* User typed "moves" or "oldmoves" while we
2650 were idle. Pretend we asked for these
2651 moves and soak them up so user can step
2652 through them and/or save them.
2655 gameMode = IcsObserving;
2658 ics_getting_history = H_GOT_UNREQ_HEADER;
2660 case EditGame: /*?*/
2661 case EditPosition: /*?*/
2662 /* Should above feature work in these modes too? */
2663 /* For now it doesn't */
2664 ics_getting_history = H_GOT_UNWANTED_HEADER;
2667 ics_getting_history = H_GOT_UNWANTED_HEADER;
2672 /* Is this the right one? */
2673 if (gameInfo.white && gameInfo.black &&
2674 strcmp(gameInfo.white, star_match[0]) == 0 &&
2675 strcmp(gameInfo.black, star_match[2]) == 0) {
2677 ics_getting_history = H_GOT_REQ_HEADER;
2680 case H_GOT_REQ_HEADER:
2681 case H_GOT_UNREQ_HEADER:
2682 case H_GOT_UNWANTED_HEADER:
2683 case H_GETTING_MOVES:
2684 /* Should not happen */
2685 DisplayError(_("Error gathering move list: two headers"), 0);
2686 ics_getting_history = H_FALSE;
2690 /* Save player ratings into gameInfo if needed */
2691 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2692 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2693 (gameInfo.whiteRating == -1 ||
2694 gameInfo.blackRating == -1)) {
2696 gameInfo.whiteRating = string_to_rating(star_match[1]);
2697 gameInfo.blackRating = string_to_rating(star_match[3]);
2698 if (appData.debugMode)
2699 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2700 gameInfo.whiteRating, gameInfo.blackRating);
2705 if (looking_at(buf, &i,
2706 "* * match, initial time: * minute*, increment: * second")) {
2707 /* Header for a move list -- second line */
2708 /* Initial board will follow if this is a wild game */
2709 if (gameInfo.event != NULL) free(gameInfo.event);
2710 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2711 gameInfo.event = StrSave(str);
2712 /* [HGM] we switched variant. Translate boards if needed. */
2713 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2717 if (looking_at(buf, &i, "Move ")) {
2718 /* Beginning of a move list */
2719 switch (ics_getting_history) {
2721 /* Normally should not happen */
2722 /* Maybe user hit reset while we were parsing */
2725 /* Happens if we are ignoring a move list that is not
2726 * the one we just requested. Common if the user
2727 * tries to observe two games without turning off
2730 case H_GETTING_MOVES:
2731 /* Should not happen */
2732 DisplayError(_("Error gathering move list: nested"), 0);
2733 ics_getting_history = H_FALSE;
2735 case H_GOT_REQ_HEADER:
2736 ics_getting_history = H_GETTING_MOVES;
2737 started = STARTED_MOVES;
2739 if (oldi > next_out) {
2740 SendToPlayer(&buf[next_out], oldi - next_out);
2743 case H_GOT_UNREQ_HEADER:
2744 ics_getting_history = H_GETTING_MOVES;
2745 started = STARTED_MOVES_NOHIDE;
2748 case H_GOT_UNWANTED_HEADER:
2749 ics_getting_history = H_FALSE;
2755 if (looking_at(buf, &i, "% ") ||
2756 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2757 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2758 savingComment = FALSE;
2761 case STARTED_MOVES_NOHIDE:
2762 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2763 parse[parse_pos + i - oldi] = NULLCHAR;
2764 ParseGameHistory(parse);
2766 if (appData.zippyPlay && first.initDone) {
2767 FeedMovesToProgram(&first, forwardMostMove);
2768 if (gameMode == IcsPlayingWhite) {
2769 if (WhiteOnMove(forwardMostMove)) {
2770 if (first.sendTime) {
2771 if (first.useColors) {
2772 SendToProgram("black\n", &first);
2774 SendTimeRemaining(&first, TRUE);
2777 if (first.useColors) {
2778 SendToProgram("white\ngo\n", &first);
2780 SendToProgram("go\n", &first);
2783 if (first.useColors) {
2784 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2786 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2788 first.maybeThinking = TRUE;
2790 if (first.usePlayother) {
2791 if (first.sendTime) {
2792 SendTimeRemaining(&first, TRUE);
2794 SendToProgram("playother\n", &first);
2800 } else if (gameMode == IcsPlayingBlack) {
2801 if (!WhiteOnMove(forwardMostMove)) {
2802 if (first.sendTime) {
2803 if (first.useColors) {
2804 SendToProgram("white\n", &first);
2806 SendTimeRemaining(&first, FALSE);
2809 if (first.useColors) {
2810 SendToProgram("black\ngo\n", &first);
2812 SendToProgram("go\n", &first);
2815 if (first.useColors) {
2816 SendToProgram("black\n", &first);
2818 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2820 first.maybeThinking = TRUE;
2822 if (first.usePlayother) {
2823 if (first.sendTime) {
2824 SendTimeRemaining(&first, FALSE);
2826 SendToProgram("playother\n", &first);
2835 if (gameMode == IcsObserving && ics_gamenum == -1) {
2836 /* Moves came from oldmoves or moves command
2837 while we weren't doing anything else.
2839 currentMove = forwardMostMove;
2840 ClearHighlights();/*!!could figure this out*/
2841 flipView = appData.flipView;
2842 DrawPosition(FALSE, boards[currentMove]);
2843 DisplayBothClocks();
2844 sprintf(str, "%s vs. %s",
2845 gameInfo.white, gameInfo.black);
2849 /* Moves were history of an active game */
2850 if (gameInfo.resultDetails != NULL) {
2851 free(gameInfo.resultDetails);
2852 gameInfo.resultDetails = NULL;
2855 HistorySet(parseList, backwardMostMove,
2856 forwardMostMove, currentMove-1);
2857 DisplayMove(currentMove - 1);
2858 if (started == STARTED_MOVES) next_out = i;
2859 started = STARTED_NONE;
2860 ics_getting_history = H_FALSE;
2863 case STARTED_OBSERVE:
2864 started = STARTED_NONE;
2865 SendToICS(ics_prefix);
2866 SendToICS("refresh\n");
2872 if(bookHit) { // [HGM] book: simulate book reply
2873 static char bookMove[MSG_SIZ]; // a bit generous?
2875 programStats.nodes = programStats.depth = programStats.time =
2876 programStats.score = programStats.got_only_move = 0;
2877 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2879 strcpy(bookMove, "move ");
2880 strcat(bookMove, bookHit);
2881 HandleMachineMove(bookMove, &first);
2886 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2887 started == STARTED_HOLDINGS ||
2888 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2889 /* Accumulate characters in move list or board */
2890 parse[parse_pos++] = buf[i];
2893 /* Start of game messages. Mostly we detect start of game
2894 when the first board image arrives. On some versions
2895 of the ICS, though, we need to do a "refresh" after starting
2896 to observe in order to get the current board right away. */
2897 if (looking_at(buf, &i, "Adding game * to observation list")) {
2898 started = STARTED_OBSERVE;
2902 /* Handle auto-observe */
2903 if (appData.autoObserve &&
2904 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2905 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2907 /* Choose the player that was highlighted, if any. */
2908 if (star_match[0][0] == '\033' ||
2909 star_match[1][0] != '\033') {
2910 player = star_match[0];
2912 player = star_match[2];
2914 sprintf(str, "%sobserve %s\n",
2915 ics_prefix, StripHighlightAndTitle(player));
2918 /* Save ratings from notify string */
2919 strcpy(player1Name, star_match[0]);
2920 player1Rating = string_to_rating(star_match[1]);
2921 strcpy(player2Name, star_match[2]);
2922 player2Rating = string_to_rating(star_match[3]);
2924 if (appData.debugMode)
2926 "Ratings from 'Game notification:' %s %d, %s %d\n",
2927 player1Name, player1Rating,
2928 player2Name, player2Rating);
2933 /* Deal with automatic examine mode after a game,
2934 and with IcsObserving -> IcsExamining transition */
2935 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2936 looking_at(buf, &i, "has made you an examiner of game *")) {
2938 int gamenum = atoi(star_match[0]);
2939 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2940 gamenum == ics_gamenum) {
2941 /* We were already playing or observing this game;
2942 no need to refetch history */
2943 gameMode = IcsExamining;
2945 pauseExamForwardMostMove = forwardMostMove;
2946 } else if (currentMove < forwardMostMove) {
2947 ForwardInner(forwardMostMove);
2950 /* I don't think this case really can happen */
2951 SendToICS(ics_prefix);
2952 SendToICS("refresh\n");
2957 /* Error messages */
2958 // if (ics_user_moved) {
2959 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2960 if (looking_at(buf, &i, "Illegal move") ||
2961 looking_at(buf, &i, "Not a legal move") ||
2962 looking_at(buf, &i, "Your king is in check") ||
2963 looking_at(buf, &i, "It isn't your turn") ||
2964 looking_at(buf, &i, "It is not your move")) {
2966 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2967 currentMove = --forwardMostMove;
2968 DisplayMove(currentMove - 1); /* before DMError */
2969 DrawPosition(FALSE, boards[currentMove]);
2971 DisplayBothClocks();
2973 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2979 if (looking_at(buf, &i, "still have time") ||
2980 looking_at(buf, &i, "not out of time") ||
2981 looking_at(buf, &i, "either player is out of time") ||
2982 looking_at(buf, &i, "has timeseal; checking")) {
2983 /* We must have called his flag a little too soon */
2984 whiteFlag = blackFlag = FALSE;
2988 if (looking_at(buf, &i, "added * seconds to") ||
2989 looking_at(buf, &i, "seconds were added to")) {
2990 /* Update the clocks */
2991 SendToICS(ics_prefix);
2992 SendToICS("refresh\n");
2996 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2997 ics_clock_paused = TRUE;
3002 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3003 ics_clock_paused = FALSE;
3008 /* Grab player ratings from the Creating: message.
3009 Note we have to check for the special case when
3010 the ICS inserts things like [white] or [black]. */
3011 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3012 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3014 0 player 1 name (not necessarily white)
3016 2 empty, white, or black (IGNORED)
3017 3 player 2 name (not necessarily black)
3020 The names/ratings are sorted out when the game
3021 actually starts (below).
3023 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3024 player1Rating = string_to_rating(star_match[1]);
3025 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3026 player2Rating = string_to_rating(star_match[4]);
3028 if (appData.debugMode)
3030 "Ratings from 'Creating:' %s %d, %s %d\n",
3031 player1Name, player1Rating,
3032 player2Name, player2Rating);
3037 /* Improved generic start/end-of-game messages */
3038 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3039 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3040 /* If tkind == 0: */
3041 /* star_match[0] is the game number */
3042 /* [1] is the white player's name */
3043 /* [2] is the black player's name */
3044 /* For end-of-game: */
3045 /* [3] is the reason for the game end */
3046 /* [4] is a PGN end game-token, preceded by " " */
3047 /* For start-of-game: */
3048 /* [3] begins with "Creating" or "Continuing" */
3049 /* [4] is " *" or empty (don't care). */
3050 int gamenum = atoi(star_match[0]);
3051 char *whitename, *blackname, *why, *endtoken;
3052 ChessMove endtype = (ChessMove) 0;
3055 whitename = star_match[1];
3056 blackname = star_match[2];
3057 why = star_match[3];
3058 endtoken = star_match[4];
3060 whitename = star_match[1];
3061 blackname = star_match[3];
3062 why = star_match[5];
3063 endtoken = star_match[6];
3066 /* Game start messages */
3067 if (strncmp(why, "Creating ", 9) == 0 ||
3068 strncmp(why, "Continuing ", 11) == 0) {
3069 gs_gamenum = gamenum;
3070 strcpy(gs_kind, strchr(why, ' ') + 1);
3072 if (appData.zippyPlay) {
3073 ZippyGameStart(whitename, blackname);
3079 /* Game end messages */
3080 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3081 ics_gamenum != gamenum) {
3084 while (endtoken[0] == ' ') endtoken++;
3085 switch (endtoken[0]) {
3088 endtype = GameUnfinished;
3091 endtype = BlackWins;
3094 if (endtoken[1] == '/')
3095 endtype = GameIsDrawn;
3097 endtype = WhiteWins;
3100 GameEnds(endtype, why, GE_ICS);
3102 if (appData.zippyPlay && first.initDone) {
3103 ZippyGameEnd(endtype, why);
3104 if (first.pr == NULL) {
3105 /* Start the next process early so that we'll
3106 be ready for the next challenge */
3107 StartChessProgram(&first);
3109 /* Send "new" early, in case this command takes
3110 a long time to finish, so that we'll be ready
3111 for the next challenge. */
3112 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3119 if (looking_at(buf, &i, "Removing game * from observation") ||
3120 looking_at(buf, &i, "no longer observing game *") ||
3121 looking_at(buf, &i, "Game * (*) has no examiners")) {
3122 if (gameMode == IcsObserving &&
3123 atoi(star_match[0]) == ics_gamenum)
3125 /* icsEngineAnalyze */
3126 if (appData.icsEngineAnalyze) {
3133 ics_user_moved = FALSE;
3138 if (looking_at(buf, &i, "no longer examining game *")) {
3139 if (gameMode == IcsExamining &&
3140 atoi(star_match[0]) == ics_gamenum)
3144 ics_user_moved = FALSE;
3149 /* Advance leftover_start past any newlines we find,
3150 so only partial lines can get reparsed */
3151 if (looking_at(buf, &i, "\n")) {
3152 prevColor = curColor;
3153 if (curColor != ColorNormal) {
3154 if (oldi > next_out) {
3155 SendToPlayer(&buf[next_out], oldi - next_out);
3158 Colorize(ColorNormal, FALSE);
3159 curColor = ColorNormal;
3161 if (started == STARTED_BOARD) {
3162 started = STARTED_NONE;
3163 parse[parse_pos] = NULLCHAR;
3164 ParseBoard12(parse);
3167 /* Send premove here */
3168 if (appData.premove) {
3170 if (currentMove == 0 &&
3171 gameMode == IcsPlayingWhite &&
3172 appData.premoveWhite) {
3173 sprintf(str, "%s%s\n", ics_prefix,
3174 appData.premoveWhiteText);
3175 if (appData.debugMode)
3176 fprintf(debugFP, "Sending premove:\n");
3178 } else if (currentMove == 1 &&
3179 gameMode == IcsPlayingBlack &&
3180 appData.premoveBlack) {
3181 sprintf(str, "%s%s\n", ics_prefix,
3182 appData.premoveBlackText);
3183 if (appData.debugMode)
3184 fprintf(debugFP, "Sending premove:\n");
3186 } else if (gotPremove) {
3188 ClearPremoveHighlights();
3189 if (appData.debugMode)
3190 fprintf(debugFP, "Sending premove:\n");
3191 UserMoveEvent(premoveFromX, premoveFromY,
3192 premoveToX, premoveToY,
3197 /* Usually suppress following prompt */
3198 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3199 if (looking_at(buf, &i, "*% ")) {
3200 savingComment = FALSE;
3204 } else if (started == STARTED_HOLDINGS) {
3206 char new_piece[MSG_SIZ];
3207 started = STARTED_NONE;
3208 parse[parse_pos] = NULLCHAR;
3209 if (appData.debugMode)
3210 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3211 parse, currentMove);
3212 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3213 gamenum == ics_gamenum) {
3214 if (gameInfo.variant == VariantNormal) {
3215 /* [HGM] We seem to switch variant during a game!
3216 * Presumably no holdings were displayed, so we have
3217 * to move the position two files to the right to
3218 * create room for them!
3220 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3221 /* Get a move list just to see the header, which
3222 will tell us whether this is really bug or zh */
3223 if (ics_getting_history == H_FALSE) {
3224 ics_getting_history = H_REQUESTED;
3225 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3229 new_piece[0] = NULLCHAR;
3230 sscanf(parse, "game %d white [%s black [%s <- %s",
3231 &gamenum, white_holding, black_holding,
3233 white_holding[strlen(white_holding)-1] = NULLCHAR;
3234 black_holding[strlen(black_holding)-1] = NULLCHAR;
3235 /* [HGM] copy holdings to board holdings area */
3236 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3237 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3239 if (appData.zippyPlay && first.initDone) {
3240 ZippyHoldings(white_holding, black_holding,
3244 if (tinyLayout || smallLayout) {
3245 char wh[16], bh[16];
3246 PackHolding(wh, white_holding);
3247 PackHolding(bh, black_holding);
3248 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3249 gameInfo.white, gameInfo.black);
3251 sprintf(str, "%s [%s] vs. %s [%s]",
3252 gameInfo.white, white_holding,
3253 gameInfo.black, black_holding);
3256 DrawPosition(FALSE, boards[currentMove]);
3259 /* Suppress following prompt */
3260 if (looking_at(buf, &i, "*% ")) {
3261 savingComment = FALSE;
3268 i++; /* skip unparsed character and loop back */
3271 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3272 started != STARTED_HOLDINGS && i > next_out) {
3273 SendToPlayer(&buf[next_out], i - next_out);
3276 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3278 leftover_len = buf_len - leftover_start;
3279 /* if buffer ends with something we couldn't parse,
3280 reparse it after appending the next read */
3282 } else if (count == 0) {
3283 RemoveInputSource(isr);
3284 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3286 DisplayFatalError(_("Error reading from ICS"), error, 1);
3291 /* Board style 12 looks like this:
3293 <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
3295 * The "<12> " is stripped before it gets to this routine. The two
3296 * trailing 0's (flip state and clock ticking) are later addition, and
3297 * some chess servers may not have them, or may have only the first.
3298 * Additional trailing fields may be added in the future.
3301 #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"
3303 #define RELATION_OBSERVING_PLAYED 0
3304 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3305 #define RELATION_PLAYING_MYMOVE 1
3306 #define RELATION_PLAYING_NOTMYMOVE -1
3307 #define RELATION_EXAMINING 2
3308 #define RELATION_ISOLATED_BOARD -3
3309 #define RELATION_STARTING_POSITION -4 /* FICS only */
3312 ParseBoard12(string)
3315 GameMode newGameMode;
3316 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3317 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3318 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3319 char to_play, board_chars[200];
3320 char move_str[500], str[500], elapsed_time[500];
3321 char black[32], white[32];
3323 int prevMove = currentMove;
3326 int fromX, fromY, toX, toY;
3328 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3329 char *bookHit = NULL; // [HGM] book
3331 fromX = fromY = toX = toY = -1;
3335 if (appData.debugMode)
3336 fprintf(debugFP, _("Parsing board: %s\n"), string);
3338 move_str[0] = NULLCHAR;
3339 elapsed_time[0] = NULLCHAR;
3340 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3342 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3343 if(string[i] == ' ') { ranks++; files = 0; }
3347 for(j = 0; j <i; j++) board_chars[j] = string[j];
3348 board_chars[i] = '\0';
3351 n = sscanf(string, PATTERN, &to_play, &double_push,
3352 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3353 &gamenum, white, black, &relation, &basetime, &increment,
3354 &white_stren, &black_stren, &white_time, &black_time,
3355 &moveNum, str, elapsed_time, move_str, &ics_flip,
3359 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3360 DisplayError(str, 0);
3364 /* Convert the move number to internal form */
3365 moveNum = (moveNum - 1) * 2;
3366 if (to_play == 'B') moveNum++;
3367 if (moveNum >= MAX_MOVES) {
3368 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3374 case RELATION_OBSERVING_PLAYED:
3375 case RELATION_OBSERVING_STATIC:
3376 if (gamenum == -1) {
3377 /* Old ICC buglet */
3378 relation = RELATION_OBSERVING_STATIC;
3380 newGameMode = IcsObserving;
3382 case RELATION_PLAYING_MYMOVE:
3383 case RELATION_PLAYING_NOTMYMOVE:
3385 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3386 IcsPlayingWhite : IcsPlayingBlack;
3388 case RELATION_EXAMINING:
3389 newGameMode = IcsExamining;
3391 case RELATION_ISOLATED_BOARD:
3393 /* Just display this board. If user was doing something else,
3394 we will forget about it until the next board comes. */
3395 newGameMode = IcsIdle;
3397 case RELATION_STARTING_POSITION:
3398 newGameMode = gameMode;
3402 /* Modify behavior for initial board display on move listing
3405 switch (ics_getting_history) {
3409 case H_GOT_REQ_HEADER:
3410 case H_GOT_UNREQ_HEADER:
3411 /* This is the initial position of the current game */
3412 gamenum = ics_gamenum;
3413 moveNum = 0; /* old ICS bug workaround */
3414 if (to_play == 'B') {
3415 startedFromSetupPosition = TRUE;
3416 blackPlaysFirst = TRUE;
3418 if (forwardMostMove == 0) forwardMostMove = 1;
3419 if (backwardMostMove == 0) backwardMostMove = 1;
3420 if (currentMove == 0) currentMove = 1;
3422 newGameMode = gameMode;
3423 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3425 case H_GOT_UNWANTED_HEADER:
3426 /* This is an initial board that we don't want */
3428 case H_GETTING_MOVES:
3429 /* Should not happen */
3430 DisplayError(_("Error gathering move list: extra board"), 0);
3431 ics_getting_history = H_FALSE;
3435 /* Take action if this is the first board of a new game, or of a
3436 different game than is currently being displayed. */
3437 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3438 relation == RELATION_ISOLATED_BOARD) {
3440 /* Forget the old game and get the history (if any) of the new one */
3441 if (gameMode != BeginningOfGame) {
3445 if (appData.autoRaiseBoard) BoardToTop();
3447 if (gamenum == -1) {
3448 newGameMode = IcsIdle;
3449 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3450 appData.getMoveList) {
3451 /* Need to get game history */
3452 ics_getting_history = H_REQUESTED;
3453 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3457 /* Initially flip the board to have black on the bottom if playing
3458 black or if the ICS flip flag is set, but let the user change
3459 it with the Flip View button. */
3460 flipView = appData.autoFlipView ?
3461 (newGameMode == IcsPlayingBlack) || ics_flip :
3464 /* Done with values from previous mode; copy in new ones */
3465 gameMode = newGameMode;
3467 ics_gamenum = gamenum;
3468 if (gamenum == gs_gamenum) {
3469 int klen = strlen(gs_kind);
3470 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3471 sprintf(str, "ICS %s", gs_kind);
3472 gameInfo.event = StrSave(str);
3474 gameInfo.event = StrSave("ICS game");
3476 gameInfo.site = StrSave(appData.icsHost);
3477 gameInfo.date = PGNDate();
3478 gameInfo.round = StrSave("-");
3479 gameInfo.white = StrSave(white);
3480 gameInfo.black = StrSave(black);
3481 timeControl = basetime * 60 * 1000;
3483 timeIncrement = increment * 1000;
3484 movesPerSession = 0;
3485 gameInfo.timeControl = TimeControlTagValue();
3486 VariantSwitch(board, StringToVariant(gameInfo.event) );
3487 if (appData.debugMode) {
3488 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3489 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3490 setbuf(debugFP, NULL);
3493 gameInfo.outOfBook = NULL;
3495 /* Do we have the ratings? */
3496 if (strcmp(player1Name, white) == 0 &&
3497 strcmp(player2Name, black) == 0) {
3498 if (appData.debugMode)
3499 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3500 player1Rating, player2Rating);
3501 gameInfo.whiteRating = player1Rating;
3502 gameInfo.blackRating = player2Rating;
3503 } else if (strcmp(player2Name, white) == 0 &&
3504 strcmp(player1Name, black) == 0) {
3505 if (appData.debugMode)
3506 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3507 player2Rating, player1Rating);
3508 gameInfo.whiteRating = player2Rating;
3509 gameInfo.blackRating = player1Rating;
3511 player1Name[0] = player2Name[0] = NULLCHAR;
3513 /* Silence shouts if requested */
3514 if (appData.quietPlay &&
3515 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3516 SendToICS(ics_prefix);
3517 SendToICS("set shout 0\n");
3521 /* Deal with midgame name changes */
3523 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3524 if (gameInfo.white) free(gameInfo.white);
3525 gameInfo.white = StrSave(white);
3527 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3528 if (gameInfo.black) free(gameInfo.black);
3529 gameInfo.black = StrSave(black);
3533 /* Throw away game result if anything actually changes in examine mode */
3534 if (gameMode == IcsExamining && !newGame) {
3535 gameInfo.result = GameUnfinished;
3536 if (gameInfo.resultDetails != NULL) {
3537 free(gameInfo.resultDetails);
3538 gameInfo.resultDetails = NULL;
3542 /* In pausing && IcsExamining mode, we ignore boards coming
3543 in if they are in a different variation than we are. */
3544 if (pauseExamInvalid) return;
3545 if (pausing && gameMode == IcsExamining) {
3546 if (moveNum <= pauseExamForwardMostMove) {
3547 pauseExamInvalid = TRUE;
3548 forwardMostMove = pauseExamForwardMostMove;
3553 if (appData.debugMode) {
3554 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3556 /* Parse the board */
3557 for (k = 0; k < ranks; k++) {
3558 for (j = 0; j < files; j++)
3559 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3560 if(gameInfo.holdingsWidth > 1) {
3561 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3562 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3565 CopyBoard(boards[moveNum], board);
3567 startedFromSetupPosition =
3568 !CompareBoards(board, initialPosition);
3569 if(startedFromSetupPosition)
3570 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3573 /* [HGM] Set castling rights. Take the outermost Rooks,
3574 to make it also work for FRC opening positions. Note that board12
3575 is really defective for later FRC positions, as it has no way to
3576 indicate which Rook can castle if they are on the same side of King.
3577 For the initial position we grant rights to the outermost Rooks,
3578 and remember thos rights, and we then copy them on positions
3579 later in an FRC game. This means WB might not recognize castlings with
3580 Rooks that have moved back to their original position as illegal,
3581 but in ICS mode that is not its job anyway.
3583 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3584 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3586 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590 if(board[0][i] == WhiteRook) j = i;
3591 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3599 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3600 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3602 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603 if(board[BOARD_HEIGHT-1][k] == bKing)
3604 initialRights[5] = castlingRights[moveNum][5] = k;
3606 r = castlingRights[moveNum][0] = initialRights[0];
3607 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3608 r = castlingRights[moveNum][1] = initialRights[1];
3609 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3610 r = castlingRights[moveNum][3] = initialRights[3];
3611 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3612 r = castlingRights[moveNum][4] = initialRights[4];
3613 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3614 /* wildcastle kludge: always assume King has rights */
3615 r = castlingRights[moveNum][2] = initialRights[2];
3616 r = castlingRights[moveNum][5] = initialRights[5];
3618 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3619 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3622 if (ics_getting_history == H_GOT_REQ_HEADER ||
3623 ics_getting_history == H_GOT_UNREQ_HEADER) {
3624 /* This was an initial position from a move list, not
3625 the current position */
3629 /* Update currentMove and known move number limits */
3630 newMove = newGame || moveNum > forwardMostMove;
3632 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3633 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3634 takeback = forwardMostMove - moveNum;
3635 for (i = 0; i < takeback; i++) {
3636 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3637 SendToProgram("undo\n", &first);
3642 forwardMostMove = backwardMostMove = currentMove = moveNum;
3643 if (gameMode == IcsExamining && moveNum == 0) {
3644 /* Workaround for ICS limitation: we are not told the wild
3645 type when starting to examine a game. But if we ask for
3646 the move list, the move list header will tell us */
3647 ics_getting_history = H_REQUESTED;
3648 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3651 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3652 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3653 forwardMostMove = moveNum;
3654 if (!pausing || currentMove > forwardMostMove)
3655 currentMove = forwardMostMove;
3657 /* New part of history that is not contiguous with old part */
3658 if (pausing && gameMode == IcsExamining) {
3659 pauseExamInvalid = TRUE;
3660 forwardMostMove = pauseExamForwardMostMove;
3663 forwardMostMove = backwardMostMove = currentMove = moveNum;
3664 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3665 ics_getting_history = H_REQUESTED;
3666 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3671 /* Update the clocks */
3672 if (strchr(elapsed_time, '.')) {
3674 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3675 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3677 /* Time is in seconds */
3678 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3679 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3684 if (appData.zippyPlay && newGame &&
3685 gameMode != IcsObserving && gameMode != IcsIdle &&
3686 gameMode != IcsExamining)
3687 ZippyFirstBoard(moveNum, basetime, increment);
3690 /* Put the move on the move list, first converting
3691 to canonical algebraic form. */
3693 if (appData.debugMode) {
3694 if (appData.debugMode) { int f = forwardMostMove;
3695 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3696 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3698 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3699 fprintf(debugFP, "moveNum = %d\n", moveNum);
3700 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3701 setbuf(debugFP, NULL);
3703 if (moveNum <= backwardMostMove) {
3704 /* We don't know what the board looked like before
3706 strcpy(parseList[moveNum - 1], move_str);
3707 strcat(parseList[moveNum - 1], " ");
3708 strcat(parseList[moveNum - 1], elapsed_time);
3709 moveList[moveNum - 1][0] = NULLCHAR;
3710 } else if (strcmp(move_str, "none") == 0) {
3711 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3712 /* Again, we don't know what the board looked like;
3713 this is really the start of the game. */
3714 parseList[moveNum - 1][0] = NULLCHAR;
3715 moveList[moveNum - 1][0] = NULLCHAR;
3716 backwardMostMove = moveNum;
3717 startedFromSetupPosition = TRUE;
3718 fromX = fromY = toX = toY = -1;
3720 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3721 // So we parse the long-algebraic move string in stead of the SAN move
3722 int valid; char buf[MSG_SIZ], *prom;
3724 // str looks something like "Q/a1-a2"; kill the slash
3726 sprintf(buf, "%c%s", str[0], str+2);
3727 else strcpy(buf, str); // might be castling
3728 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3729 strcat(buf, prom); // long move lacks promo specification!
3730 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3731 if(appData.debugMode)
3732 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3733 strcpy(move_str, buf);
3735 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3736 &fromX, &fromY, &toX, &toY, &promoChar)
3737 || ParseOneMove(buf, moveNum - 1, &moveType,
3738 &fromX, &fromY, &toX, &toY, &promoChar);
3739 // end of long SAN patch
3741 (void) CoordsToAlgebraic(boards[moveNum - 1],
3742 PosFlags(moveNum - 1), EP_UNKNOWN,
3743 fromY, fromX, toY, toX, promoChar,
3744 parseList[moveNum-1]);
3745 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3746 castlingRights[moveNum]) ) {
3752 if(gameInfo.variant != VariantShogi)
3753 strcat(parseList[moveNum - 1], "+");
3756 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3757 strcat(parseList[moveNum - 1], "#");
3760 strcat(parseList[moveNum - 1], " ");
3761 strcat(parseList[moveNum - 1], elapsed_time);
3762 /* currentMoveString is set as a side-effect of ParseOneMove */
3763 strcpy(moveList[moveNum - 1], currentMoveString);
3764 strcat(moveList[moveNum - 1], "\n");
3766 /* Move from ICS was illegal!? Punt. */
3767 if (appData.debugMode) {
3768 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3769 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3772 if (appData.testLegality && appData.debugMode) {
3773 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3774 DisplayError(str, 0);
3777 strcpy(parseList[moveNum - 1], move_str);
3778 strcat(parseList[moveNum - 1], " ");
3779 strcat(parseList[moveNum - 1], elapsed_time);
3780 moveList[moveNum - 1][0] = NULLCHAR;
3781 fromX = fromY = toX = toY = -1;
3784 if (appData.debugMode) {
3785 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3786 setbuf(debugFP, NULL);
3790 /* Send move to chess program (BEFORE animating it). */
3791 if (appData.zippyPlay && !newGame && newMove &&
3792 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3794 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3795 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3796 if (moveList[moveNum - 1][0] == NULLCHAR) {
3797 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3799 DisplayError(str, 0);
3801 if (first.sendTime) {
3802 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3804 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3805 if (firstMove && !bookHit) {
3807 if (first.useColors) {
3808 SendToProgram(gameMode == IcsPlayingWhite ?
3810 "black\ngo\n", &first);
3812 SendToProgram("go\n", &first);
3814 first.maybeThinking = TRUE;
3817 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3818 if (moveList[moveNum - 1][0] == NULLCHAR) {
3819 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3820 DisplayError(str, 0);
3822 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3823 SendMoveToProgram(moveNum - 1, &first);
3830 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3831 /* If move comes from a remote source, animate it. If it
3832 isn't remote, it will have already been animated. */
3833 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3834 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3836 if (!pausing && appData.highlightLastMove) {
3837 SetHighlights(fromX, fromY, toX, toY);
3841 /* Start the clocks */
3842 whiteFlag = blackFlag = FALSE;
3843 appData.clockMode = !(basetime == 0 && increment == 0);
3845 ics_clock_paused = TRUE;
3847 } else if (ticking == 1) {
3848 ics_clock_paused = FALSE;
3850 if (gameMode == IcsIdle ||
3851 relation == RELATION_OBSERVING_STATIC ||
3852 relation == RELATION_EXAMINING ||
3854 DisplayBothClocks();
3858 /* Display opponents and material strengths */
3859 if (gameInfo.variant != VariantBughouse &&
3860 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3861 if (tinyLayout || smallLayout) {
3862 if(gameInfo.variant == VariantNormal)
3863 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3864 gameInfo.white, white_stren, gameInfo.black, black_stren,
3865 basetime, increment);
3867 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3868 gameInfo.white, white_stren, gameInfo.black, black_stren,
3869 basetime, increment, (int) gameInfo.variant);
3871 if(gameInfo.variant == VariantNormal)
3872 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3873 gameInfo.white, white_stren, gameInfo.black, black_stren,
3874 basetime, increment);
3876 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3877 gameInfo.white, white_stren, gameInfo.black, black_stren,
3878 basetime, increment, VariantName(gameInfo.variant));
3881 if (appData.debugMode) {
3882 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3887 /* Display the board */
3888 if (!pausing && !appData.noGUI) {
3890 if (appData.premove)
3892 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3893 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3894 ClearPremoveHighlights();
3896 DrawPosition(FALSE, boards[currentMove]);
3897 DisplayMove(moveNum - 1);
3898 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3899 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3900 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3901 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3905 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3907 if(bookHit) { // [HGM] book: simulate book reply
3908 static char bookMove[MSG_SIZ]; // a bit generous?
3910 programStats.nodes = programStats.depth = programStats.time =
3911 programStats.score = programStats.got_only_move = 0;
3912 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3914 strcpy(bookMove, "move ");
3915 strcat(bookMove, bookHit);
3916 HandleMachineMove(bookMove, &first);
3925 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3926 ics_getting_history = H_REQUESTED;
3927 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3933 AnalysisPeriodicEvent(force)
3936 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3937 && !force) || !appData.periodicUpdates)
3940 /* Send . command to Crafty to collect stats */
3941 SendToProgram(".\n", &first);
3943 /* Don't send another until we get a response (this makes
3944 us stop sending to old Crafty's which don't understand
3945 the "." command (sending illegal cmds resets node count & time,
3946 which looks bad)) */
3947 programStats.ok_to_send = 0;
3951 SendMoveToProgram(moveNum, cps)
3953 ChessProgramState *cps;
3957 if (cps->useUsermove) {
3958 SendToProgram("usermove ", cps);
3962 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3963 int len = space - parseList[moveNum];
3964 memcpy(buf, parseList[moveNum], len);
3966 buf[len] = NULLCHAR;
3968 sprintf(buf, "%s\n", parseList[moveNum]);
3970 SendToProgram(buf, cps);
3972 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3973 AlphaRank(moveList[moveNum], 4);
3974 SendToProgram(moveList[moveNum], cps);
3975 AlphaRank(moveList[moveNum], 4); // and back
3977 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3978 * the engine. It would be nice to have a better way to identify castle
3980 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3981 && cps->useOOCastle) {
3982 int fromX = moveList[moveNum][0] - AAA;
3983 int fromY = moveList[moveNum][1] - ONE;
3984 int toX = moveList[moveNum][2] - AAA;
3985 int toY = moveList[moveNum][3] - ONE;
3986 if((boards[moveNum][fromY][fromX] == WhiteKing
3987 && boards[moveNum][toY][toX] == WhiteRook)
3988 || (boards[moveNum][fromY][fromX] == BlackKing
3989 && boards[moveNum][toY][toX] == BlackRook)) {
3990 if(toX > fromX) SendToProgram("O-O\n", cps);
3991 else SendToProgram("O-O-O\n", cps);
3993 else SendToProgram(moveList[moveNum], cps);
3995 else SendToProgram(moveList[moveNum], cps);
3996 /* End of additions by Tord */
3999 /* [HGM] setting up the opening has brought engine in force mode! */
4000 /* Send 'go' if we are in a mode where machine should play. */
4001 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4002 (gameMode == TwoMachinesPlay ||
4004 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4006 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4007 SendToProgram("go\n", cps);
4008 if (appData.debugMode) {
4009 fprintf(debugFP, "(extra)\n");
4012 setboardSpoiledMachineBlack = 0;
4016 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4018 int fromX, fromY, toX, toY;
4020 char user_move[MSG_SIZ];
4024 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4025 (int)moveType, fromX, fromY, toX, toY);
4026 DisplayError(user_move + strlen("say "), 0);
4028 case WhiteKingSideCastle:
4029 case BlackKingSideCastle:
4030 case WhiteQueenSideCastleWild:
4031 case BlackQueenSideCastleWild:
4033 case WhiteHSideCastleFR:
4034 case BlackHSideCastleFR:
4036 sprintf(user_move, "o-o\n");
4038 case WhiteQueenSideCastle:
4039 case BlackQueenSideCastle:
4040 case WhiteKingSideCastleWild:
4041 case BlackKingSideCastleWild:
4043 case WhiteASideCastleFR:
4044 case BlackASideCastleFR:
4046 sprintf(user_move, "o-o-o\n");
4048 case WhitePromotionQueen:
4049 case BlackPromotionQueen:
4050 case WhitePromotionRook:
4051 case BlackPromotionRook:
4052 case WhitePromotionBishop:
4053 case BlackPromotionBishop:
4054 case WhitePromotionKnight:
4055 case BlackPromotionKnight:
4056 case WhitePromotionKing:
4057 case BlackPromotionKing:
4058 case WhitePromotionChancellor:
4059 case BlackPromotionChancellor:
4060 case WhitePromotionArchbishop:
4061 case BlackPromotionArchbishop:
4062 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4063 sprintf(user_move, "%c%c%c%c=%c\n",
4064 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4065 PieceToChar(WhiteFerz));
4066 else if(gameInfo.variant == VariantGreat)
4067 sprintf(user_move, "%c%c%c%c=%c\n",
4068 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4069 PieceToChar(WhiteMan));
4071 sprintf(user_move, "%c%c%c%c=%c\n",
4072 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4073 PieceToChar(PromoPiece(moveType)));
4077 sprintf(user_move, "%c@%c%c\n",
4078 ToUpper(PieceToChar((ChessSquare) fromX)),
4079 AAA + toX, ONE + toY);
4082 case WhiteCapturesEnPassant:
4083 case BlackCapturesEnPassant:
4084 case IllegalMove: /* could be a variant we don't quite understand */
4085 sprintf(user_move, "%c%c%c%c\n",
4086 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4089 SendToICS(user_move);
4093 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4098 if (rf == DROP_RANK) {
4099 sprintf(move, "%c@%c%c\n",
4100 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4102 if (promoChar == 'x' || promoChar == NULLCHAR) {
4103 sprintf(move, "%c%c%c%c\n",
4104 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4106 sprintf(move, "%c%c%c%c%c\n",
4107 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4113 ProcessICSInitScript(f)
4118 while (fgets(buf, MSG_SIZ, f)) {
4119 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4126 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4128 AlphaRank(char *move, int n)
4130 // char *p = move, c; int x, y;
4132 if (appData.debugMode) {
4133 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4137 move[2]>='0' && move[2]<='9' &&
4138 move[3]>='a' && move[3]<='x' ) {
4140 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4141 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4143 if(move[0]>='0' && move[0]<='9' &&
4144 move[1]>='a' && move[1]<='x' &&
4145 move[2]>='0' && move[2]<='9' &&
4146 move[3]>='a' && move[3]<='x' ) {
4147 /* input move, Shogi -> normal */
4148 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4149 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4150 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4151 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4154 move[3]>='0' && move[3]<='9' &&
4155 move[2]>='a' && move[2]<='x' ) {
4157 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4158 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4161 move[0]>='a' && move[0]<='x' &&
4162 move[3]>='0' && move[3]<='9' &&
4163 move[2]>='a' && move[2]<='x' ) {
4164 /* output move, normal -> Shogi */
4165 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4166 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4167 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4168 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4169 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4171 if (appData.debugMode) {
4172 fprintf(debugFP, " out = '%s'\n", move);
4176 /* Parser for moves from gnuchess, ICS, or user typein box */
4178 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4181 ChessMove *moveType;
4182 int *fromX, *fromY, *toX, *toY;
4185 if (appData.debugMode) {
4186 fprintf(debugFP, "move to parse: %s\n", move);
4188 *moveType = yylexstr(moveNum, move);
4190 switch (*moveType) {
4191 case WhitePromotionChancellor:
4192 case BlackPromotionChancellor:
4193 case WhitePromotionArchbishop:
4194 case BlackPromotionArchbishop:
4195 case WhitePromotionQueen:
4196 case BlackPromotionQueen:
4197 case WhitePromotionRook:
4198 case BlackPromotionRook:
4199 case WhitePromotionBishop:
4200 case BlackPromotionBishop:
4201 case WhitePromotionKnight:
4202 case BlackPromotionKnight:
4203 case WhitePromotionKing:
4204 case BlackPromotionKing:
4206 case WhiteCapturesEnPassant:
4207 case BlackCapturesEnPassant:
4208 case WhiteKingSideCastle:
4209 case WhiteQueenSideCastle:
4210 case BlackKingSideCastle:
4211 case BlackQueenSideCastle:
4212 case WhiteKingSideCastleWild:
4213 case WhiteQueenSideCastleWild:
4214 case BlackKingSideCastleWild:
4215 case BlackQueenSideCastleWild:
4216 /* Code added by Tord: */
4217 case WhiteHSideCastleFR:
4218 case WhiteASideCastleFR:
4219 case BlackHSideCastleFR:
4220 case BlackASideCastleFR:
4221 /* End of code added by Tord */
4222 case IllegalMove: /* bug or odd chess variant */
4223 *fromX = currentMoveString[0] - AAA;
4224 *fromY = currentMoveString[1] - ONE;
4225 *toX = currentMoveString[2] - AAA;
4226 *toY = currentMoveString[3] - ONE;
4227 *promoChar = currentMoveString[4];
4228 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4229 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4230 if (appData.debugMode) {
4231 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4233 *fromX = *fromY = *toX = *toY = 0;
4236 if (appData.testLegality) {
4237 return (*moveType != IllegalMove);
4239 return !(fromX == fromY && toX == toY);
4244 *fromX = *moveType == WhiteDrop ?
4245 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4246 (int) CharToPiece(ToLower(currentMoveString[0]));
4248 *toX = currentMoveString[2] - AAA;
4249 *toY = currentMoveString[3] - ONE;
4250 *promoChar = NULLCHAR;
4254 case ImpossibleMove:
4255 case (ChessMove) 0: /* end of file */
4264 if (appData.debugMode) {
4265 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4268 *fromX = *fromY = *toX = *toY = 0;
4269 *promoChar = NULLCHAR;
4274 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4275 // All positions will have equal probability, but the current method will not provide a unique
4276 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4282 int piecesLeft[(int)BlackPawn];
4283 int seed, nrOfShuffles;
4285 void GetPositionNumber()
4286 { // sets global variable seed
4289 seed = appData.defaultFrcPosition;
4290 if(seed < 0) { // randomize based on time for negative FRC position numbers
4291 for(i=0; i<50; i++) seed += random();
4292 seed = random() ^ random() >> 8 ^ random() << 8;
4293 if(seed<0) seed = -seed;
4297 int put(Board board, int pieceType, int rank, int n, int shade)
4298 // put the piece on the (n-1)-th empty squares of the given shade
4302 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4303 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4304 board[rank][i] = (ChessSquare) pieceType;
4305 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4307 piecesLeft[pieceType]--;
4315 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4316 // calculate where the next piece goes, (any empty square), and put it there
4320 i = seed % squaresLeft[shade];
4321 nrOfShuffles *= squaresLeft[shade];
4322 seed /= squaresLeft[shade];
4323 put(board, pieceType, rank, i, shade);
4326 void AddTwoPieces(Board board, int pieceType, int rank)
4327 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4329 int i, n=squaresLeft[ANY], j=n-1, k;
4331 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4332 i = seed % k; // pick one
4335 while(i >= j) i -= j--;
4336 j = n - 1 - j; i += j;
4337 put(board, pieceType, rank, j, ANY);
4338 put(board, pieceType, rank, i, ANY);
4341 void SetUpShuffle(Board board, int number)
4345 GetPositionNumber(); nrOfShuffles = 1;
4347 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4348 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4349 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4351 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4353 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4354 p = (int) board[0][i];
4355 if(p < (int) BlackPawn) piecesLeft[p] ++;
4356 board[0][i] = EmptySquare;
4359 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4360 // shuffles restricted to allow normal castling put KRR first
4361 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4362 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4363 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4364 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4365 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4366 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4367 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4368 put(board, WhiteRook, 0, 0, ANY);
4369 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4372 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4373 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4374 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4375 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4376 while(piecesLeft[p] >= 2) {
4377 AddOnePiece(board, p, 0, LITE);
4378 AddOnePiece(board, p, 0, DARK);
4380 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4383 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4384 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4385 // but we leave King and Rooks for last, to possibly obey FRC restriction
4386 if(p == (int)WhiteRook) continue;
4387 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4388 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4391 // now everything is placed, except perhaps King (Unicorn) and Rooks
4393 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4394 // Last King gets castling rights
4395 while(piecesLeft[(int)WhiteUnicorn]) {
4396 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4397 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4400 while(piecesLeft[(int)WhiteKing]) {
4401 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4402 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4407 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4408 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4411 // Only Rooks can be left; simply place them all
4412 while(piecesLeft[(int)WhiteRook]) {
4413 i = put(board, WhiteRook, 0, 0, ANY);
4414 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4417 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4419 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4422 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4423 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4426 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4429 int SetCharTable( char *table, const char * map )
4430 /* [HGM] moved here from winboard.c because of its general usefulness */
4431 /* Basically a safe strcpy that uses the last character as King */
4433 int result = FALSE; int NrPieces;
4435 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4436 && NrPieces >= 12 && !(NrPieces&1)) {
4437 int i; /* [HGM] Accept even length from 12 to 34 */
4439 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4440 for( i=0; i<NrPieces/2-1; i++ ) {
4442 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4444 table[(int) WhiteKing] = map[NrPieces/2-1];
4445 table[(int) BlackKing] = map[NrPieces-1];
4453 void Prelude(Board board)
4454 { // [HGM] superchess: random selection of exo-pieces
4455 int i, j, k; ChessSquare p;
4456 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4458 GetPositionNumber(); // use FRC position number
4460 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4461 SetCharTable(pieceToChar, appData.pieceToCharTable);
4462 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4463 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4466 j = seed%4; seed /= 4;
4467 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4468 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4469 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4470 j = seed%3 + (seed%3 >= j); seed /= 3;
4471 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4472 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4473 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4474 j = seed%3; seed /= 3;
4475 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4476 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4477 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4478 j = seed%2 + (seed%2 >= j); seed /= 2;
4479 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4480 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4481 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4482 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4483 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4484 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4485 put(board, exoPieces[0], 0, 0, ANY);
4486 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4490 InitPosition(redraw)
4493 ChessSquare (* pieces)[BOARD_SIZE];
4494 int i, j, pawnRow, overrule,
4495 oldx = gameInfo.boardWidth,
4496 oldy = gameInfo.boardHeight,
4497 oldh = gameInfo.holdingsWidth,
4498 oldv = gameInfo.variant;
4500 currentMove = forwardMostMove = backwardMostMove = 0;
4501 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4503 /* [AS] Initialize pv info list [HGM] and game status */
4505 for( i=0; i<MAX_MOVES; i++ ) {
4506 pvInfoList[i].depth = 0;
4507 epStatus[i]=EP_NONE;
4508 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4511 initialRulePlies = 0; /* 50-move counter start */
4513 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4514 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4518 /* [HGM] logic here is completely changed. In stead of full positions */
4519 /* the initialized data only consist of the two backranks. The switch */
4520 /* selects which one we will use, which is than copied to the Board */
4521 /* initialPosition, which for the rest is initialized by Pawns and */
4522 /* empty squares. This initial position is then copied to boards[0], */
4523 /* possibly after shuffling, so that it remains available. */
4525 gameInfo.holdingsWidth = 0; /* default board sizes */
4526 gameInfo.boardWidth = 8;
4527 gameInfo.boardHeight = 8;
4528 gameInfo.holdingsSize = 0;
4529 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4530 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4531 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4533 switch (gameInfo.variant) {
4534 case VariantFischeRandom:
4535 shuffleOpenings = TRUE;
4539 case VariantShatranj:
4540 pieces = ShatranjArray;
4541 nrCastlingRights = 0;
4542 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4544 case VariantTwoKings:
4545 pieces = twoKingsArray;
4547 case VariantCapaRandom:
4548 shuffleOpenings = TRUE;
4549 case VariantCapablanca:
4550 pieces = CapablancaArray;
4551 gameInfo.boardWidth = 10;
4552 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4555 pieces = GothicArray;
4556 gameInfo.boardWidth = 10;
4557 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4560 pieces = JanusArray;
4561 gameInfo.boardWidth = 10;
4562 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4563 nrCastlingRights = 6;
4564 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4565 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4566 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4567 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4568 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4569 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4572 pieces = FalconArray;
4573 gameInfo.boardWidth = 10;
4574 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4576 case VariantXiangqi:
4577 pieces = XiangqiArray;
4578 gameInfo.boardWidth = 9;
4579 gameInfo.boardHeight = 10;
4580 nrCastlingRights = 0;
4581 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4584 pieces = ShogiArray;
4585 gameInfo.boardWidth = 9;
4586 gameInfo.boardHeight = 9;
4587 gameInfo.holdingsSize = 7;
4588 nrCastlingRights = 0;
4589 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4591 case VariantCourier:
4592 pieces = CourierArray;
4593 gameInfo.boardWidth = 12;
4594 nrCastlingRights = 0;
4595 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4596 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4598 case VariantKnightmate:
4599 pieces = KnightmateArray;
4600 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4603 pieces = fairyArray;
4604 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4607 pieces = GreatArray;
4608 gameInfo.boardWidth = 10;
4609 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4610 gameInfo.holdingsSize = 8;
4614 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4615 gameInfo.holdingsSize = 8;
4616 startedFromSetupPosition = TRUE;
4618 case VariantCrazyhouse:
4619 case VariantBughouse:
4621 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4622 gameInfo.holdingsSize = 5;
4624 case VariantWildCastle:
4626 /* !!?shuffle with kings guaranteed to be on d or e file */
4627 shuffleOpenings = 1;
4629 case VariantNoCastle:
4631 nrCastlingRights = 0;
4632 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4633 /* !!?unconstrained back-rank shuffle */
4634 shuffleOpenings = 1;
4639 if(appData.NrFiles >= 0) {
4640 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4641 gameInfo.boardWidth = appData.NrFiles;
4643 if(appData.NrRanks >= 0) {
4644 gameInfo.boardHeight = appData.NrRanks;
4646 if(appData.holdingsSize >= 0) {
4647 i = appData.holdingsSize;
4648 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4649 gameInfo.holdingsSize = i;
4651 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4652 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4653 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4655 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4656 if(pawnRow < 1) pawnRow = 1;
4658 /* User pieceToChar list overrules defaults */
4659 if(appData.pieceToCharTable != NULL)
4660 SetCharTable(pieceToChar, appData.pieceToCharTable);
4662 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4664 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4665 s = (ChessSquare) 0; /* account holding counts in guard band */
4666 for( i=0; i<BOARD_HEIGHT; i++ )
4667 initialPosition[i][j] = s;
4669 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4670 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4671 initialPosition[pawnRow][j] = WhitePawn;
4672 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4673 if(gameInfo.variant == VariantXiangqi) {
4675 initialPosition[pawnRow][j] =
4676 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4677 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4678 initialPosition[2][j] = WhiteCannon;
4679 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4683 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4685 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4688 initialPosition[1][j] = WhiteBishop;
4689 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4691 initialPosition[1][j] = WhiteRook;
4692 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4695 if( nrCastlingRights == -1) {
4696 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4697 /* This sets default castling rights from none to normal corners */
4698 /* Variants with other castling rights must set them themselves above */
4699 nrCastlingRights = 6;
4701 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4702 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4703 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4704 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4705 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4706 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4709 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4710 if(gameInfo.variant == VariantGreat) { // promotion commoners
4711 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4712 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4713 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4714 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4717 if(gameInfo.variant == VariantFischeRandom) {
4718 if( appData.defaultFrcPosition < 0 ) {
4719 ShuffleFRC( initialPosition );
4722 SetupFRC( initialPosition, appData.defaultFrcPosition );
4724 startedFromSetupPosition = TRUE;
4727 if (appData.debugMode) {
4728 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4730 if(shuffleOpenings) {
4731 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4732 startedFromSetupPosition = TRUE;
4735 if(startedFromPositionFile) {
4736 /* [HGM] loadPos: use PositionFile for every new game */
4737 CopyBoard(initialPosition, filePosition);
4738 for(i=0; i<nrCastlingRights; i++)
4739 castlingRights[0][i] = initialRights[i] = fileRights[i];
4740 startedFromSetupPosition = TRUE;
4743 CopyBoard(boards[0], initialPosition);
4745 if(oldx != gameInfo.boardWidth ||
4746 oldy != gameInfo.boardHeight ||
4747 oldh != gameInfo.holdingsWidth
4749 || oldv == VariantGothic || // For licensing popups
4750 gameInfo.variant == VariantGothic
4753 || oldv == VariantFalcon ||
4754 gameInfo.variant == VariantFalcon
4757 InitDrawingSizes(-2 ,0);
4760 DrawPosition(TRUE, boards[currentMove]);
4764 SendBoard(cps, moveNum)
4765 ChessProgramState *cps;
4768 char message[MSG_SIZ];
4770 if (cps->useSetboard) {
4771 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4772 sprintf(message, "setboard %s\n", fen);
4773 SendToProgram(message, cps);
4779 /* Kludge to set black to move, avoiding the troublesome and now
4780 * deprecated "black" command.
4782 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4784 SendToProgram("edit\n", cps);
4785 SendToProgram("#\n", cps);
4786 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4787 bp = &boards[moveNum][i][BOARD_LEFT];
4788 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4789 if ((int) *bp < (int) BlackPawn) {
4790 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4792 if(message[0] == '+' || message[0] == '~') {
4793 sprintf(message, "%c%c%c+\n",
4794 PieceToChar((ChessSquare)(DEMOTED *bp)),
4797 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4798 message[1] = BOARD_RGHT - 1 - j + '1';
4799 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4801 SendToProgram(message, cps);
4806 SendToProgram("c\n", cps);
4807 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4808 bp = &boards[moveNum][i][BOARD_LEFT];
4809 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4810 if (((int) *bp != (int) EmptySquare)
4811 && ((int) *bp >= (int) BlackPawn)) {
4812 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4814 if(message[0] == '+' || message[0] == '~') {
4815 sprintf(message, "%c%c%c+\n",
4816 PieceToChar((ChessSquare)(DEMOTED *bp)),
4819 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4820 message[1] = BOARD_RGHT - 1 - j + '1';
4821 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4823 SendToProgram(message, cps);
4828 SendToProgram(".\n", cps);
4830 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4834 IsPromotion(fromX, fromY, toX, toY)
4835 int fromX, fromY, toX, toY;
4837 /* [HGM] add Shogi promotions */
4838 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4841 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4842 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4843 /* [HGM] Note to self: line above also weeds out drops */
4844 piece = boards[currentMove][fromY][fromX];
4845 if(gameInfo.variant == VariantShogi) {
4846 promotionZoneSize = 3;
4847 highestPromotingPiece = (int)WhiteKing;
4848 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4849 and if in normal chess we then allow promotion to King, why not
4850 allow promotion of other piece in Shogi? */
4852 if((int)piece >= BlackPawn) {
4853 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4855 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4857 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4858 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4860 return ( (int)piece <= highestPromotingPiece );
4864 InPalace(row, column)
4866 { /* [HGM] for Xiangqi */
4867 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4868 column < (BOARD_WIDTH + 4)/2 &&
4869 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4874 PieceForSquare (x, y)
4878 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4881 return boards[currentMove][y][x];
4885 OKToStartUserMove(x, y)
4888 ChessSquare from_piece;
4891 if (matchMode) return FALSE;
4892 if (gameMode == EditPosition) return TRUE;
4894 if (x >= 0 && y >= 0)
4895 from_piece = boards[currentMove][y][x];
4897 from_piece = EmptySquare;
4899 if (from_piece == EmptySquare) return FALSE;
4901 white_piece = (int)from_piece >= (int)WhitePawn &&
4902 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4905 case PlayFromGameFile:
4907 case TwoMachinesPlay:
4915 case MachinePlaysWhite:
4916 case IcsPlayingBlack:
4917 if (appData.zippyPlay) return FALSE;
4919 DisplayMoveError(_("You are playing Black"));
4924 case MachinePlaysBlack:
4925 case IcsPlayingWhite:
4926 if (appData.zippyPlay) return FALSE;
4928 DisplayMoveError(_("You are playing White"));
4934 if (!white_piece && WhiteOnMove(currentMove)) {
4935 DisplayMoveError(_("It is White's turn"));
4938 if (white_piece && !WhiteOnMove(currentMove)) {
4939 DisplayMoveError(_("It is Black's turn"));
4942 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4943 /* Editing correspondence game history */
4944 /* Could disallow this or prompt for confirmation */
4947 if (currentMove < forwardMostMove) {
4948 /* Discarding moves */
4949 /* Could prompt for confirmation here,
4950 but I don't think that's such a good idea */
4951 forwardMostMove = currentMove;
4955 case BeginningOfGame:
4956 if (appData.icsActive) return FALSE;
4957 if (!appData.noChessProgram) {
4959 DisplayMoveError(_("You are playing White"));
4966 if (!white_piece && WhiteOnMove(currentMove)) {
4967 DisplayMoveError(_("It is White's turn"));
4970 if (white_piece && !WhiteOnMove(currentMove)) {
4971 DisplayMoveError(_("It is Black's turn"));
4980 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4981 && gameMode != AnalyzeFile && gameMode != Training) {
4982 DisplayMoveError(_("Displayed position is not current"));
4988 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4989 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4990 int lastLoadGameUseList = FALSE;
4991 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4992 ChessMove lastLoadGameStart = (ChessMove) 0;
4996 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4997 int fromX, fromY, toX, toY;
5001 ChessSquare pdown, pup;
5003 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5004 if ((fromX == toX) && (fromY == toY)) {
5005 return ImpossibleMove;
5008 /* [HGM] suppress all moves into holdings area and guard band */
5009 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5010 return ImpossibleMove;
5012 /* [HGM] <sameColor> moved to here from winboard.c */
5013 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5014 pdown = boards[currentMove][fromY][fromX];
5015 pup = boards[currentMove][toY][toX];
5016 if ( gameMode != EditPosition &&
5017 (WhitePawn <= pdown && pdown < BlackPawn &&
5018 WhitePawn <= pup && pup < BlackPawn ||
5019 BlackPawn <= pdown && pdown < EmptySquare &&
5020 BlackPawn <= pup && pup < EmptySquare
5021 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5022 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5023 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5025 return ImpossibleMove;
5027 /* Check if the user is playing in turn. This is complicated because we
5028 let the user "pick up" a piece before it is his turn. So the piece he
5029 tried to pick up may have been captured by the time he puts it down!
5030 Therefore we use the color the user is supposed to be playing in this
5031 test, not the color of the piece that is currently on the starting
5032 square---except in EditGame mode, where the user is playing both
5033 sides; fortunately there the capture race can't happen. (It can
5034 now happen in IcsExamining mode, but that's just too bad. The user
5035 will get a somewhat confusing message in that case.)
5039 case PlayFromGameFile:
5041 case TwoMachinesPlay:
5045 /* We switched into a game mode where moves are not accepted,
5046 perhaps while the mouse button was down. */
5047 return ImpossibleMove;
5049 case MachinePlaysWhite:
5050 /* User is moving for Black */
5051 if (WhiteOnMove(currentMove)) {
5052 DisplayMoveError(_("It is White's turn"));
5053 return ImpossibleMove;
5057 case MachinePlaysBlack:
5058 /* User is moving for White */
5059 if (!WhiteOnMove(currentMove)) {
5060 DisplayMoveError(_("It is Black's turn"));
5061 return ImpossibleMove;
5067 case BeginningOfGame:
5070 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5071 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5072 /* User is moving for Black */
5073 if (WhiteOnMove(currentMove)) {
5074 DisplayMoveError(_("It is White's turn"));
5075 return ImpossibleMove;
5078 /* User is moving for White */
5079 if (!WhiteOnMove(currentMove)) {
5080 DisplayMoveError(_("It is Black's turn"));
5081 return ImpossibleMove;
5086 case IcsPlayingBlack:
5087 /* User is moving for Black */
5088 if (WhiteOnMove(currentMove)) {
5089 if (!appData.premove) {
5090 DisplayMoveError(_("It is White's turn"));
5091 } else if (toX >= 0 && toY >= 0) {
5094 premoveFromX = fromX;
5095 premoveFromY = fromY;
5096 premovePromoChar = promoChar;
5098 if (appData.debugMode)
5099 fprintf(debugFP, "Got premove: fromX %d,"
5100 "fromY %d, toX %d, toY %d\n",
5101 fromX, fromY, toX, toY);
5103 return ImpossibleMove;
5107 case IcsPlayingWhite:
5108 /* User is moving for White */
5109 if (!WhiteOnMove(currentMove)) {
5110 if (!appData.premove) {
5111 DisplayMoveError(_("It is Black's turn"));
5112 } else if (toX >= 0 && toY >= 0) {
5115 premoveFromX = fromX;
5116 premoveFromY = fromY;
5117 premovePromoChar = promoChar;
5119 if (appData.debugMode)
5120 fprintf(debugFP, "Got premove: fromX %d,"
5121 "fromY %d, toX %d, toY %d\n",
5122 fromX, fromY, toX, toY);
5124 return ImpossibleMove;
5132 /* EditPosition, empty square, or different color piece;
5133 click-click move is possible */
5134 if (toX == -2 || toY == -2) {
5135 boards[0][fromY][fromX] = EmptySquare;
5136 return AmbiguousMove;
5137 } else if (toX >= 0 && toY >= 0) {
5138 boards[0][toY][toX] = boards[0][fromY][fromX];
5139 boards[0][fromY][fromX] = EmptySquare;
5140 return AmbiguousMove;
5142 return ImpossibleMove;
5145 /* [HGM] If move started in holdings, it means a drop */
5146 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5147 if( pup != EmptySquare ) return ImpossibleMove;
5148 if(appData.testLegality) {
5149 /* it would be more logical if LegalityTest() also figured out
5150 * which drops are legal. For now we forbid pawns on back rank.
5151 * Shogi is on its own here...
5153 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5154 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5155 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5157 return WhiteDrop; /* Not needed to specify white or black yet */
5160 userOfferedDraw = FALSE;
5162 /* [HGM] always test for legality, to get promotion info */
5163 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5164 epStatus[currentMove], castlingRights[currentMove],
5165 fromY, fromX, toY, toX, promoChar);
5167 /* [HGM] but possibly ignore an IllegalMove result */
5168 if (appData.testLegality) {
5169 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5170 DisplayMoveError(_("Illegal move"));
5171 return ImpossibleMove;
5174 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5176 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5177 function is made into one that returns an OK move type if FinishMove
5178 should be called. This to give the calling driver routine the
5179 opportunity to finish the userMove input with a promotion popup,
5180 without bothering the user with this for invalid or illegal moves */
5182 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5185 /* Common tail of UserMoveEvent and DropMenuEvent */
5187 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5189 int fromX, fromY, toX, toY;
5190 /*char*/int promoChar;
5193 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5194 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5195 // [HGM] superchess: suppress promotions to non-available piece
5196 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5197 if(WhiteOnMove(currentMove)) {
5198 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5200 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5204 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5205 move type in caller when we know the move is a legal promotion */
5206 if(moveType == NormalMove && promoChar)
5207 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5208 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5209 /* [HGM] convert drag-and-drop piece drops to standard form */
5210 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5211 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5212 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5213 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5214 // fromX = boards[currentMove][fromY][fromX];
5215 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5216 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5217 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5218 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5222 /* [HGM] <popupFix> The following if has been moved here from
5223 UserMoveEvent(). Because it seemed to belon here (why not allow
5224 piece drops in training games?), and because it can only be
5225 performed after it is known to what we promote. */
5226 if (gameMode == Training) {
5227 /* compare the move played on the board to the next move in the
5228 * game. If they match, display the move and the opponent's response.
5229 * If they don't match, display an error message.
5232 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5233 CopyBoard(testBoard, boards[currentMove]);
5234 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5236 if (CompareBoards(testBoard, boards[currentMove+1])) {
5237 ForwardInner(currentMove+1);
5239 /* Autoplay the opponent's response.
5240 * if appData.animate was TRUE when Training mode was entered,
5241 * the response will be animated.
5243 saveAnimate = appData.animate;
5244 appData.animate = animateTraining;
5245 ForwardInner(currentMove+1);
5246 appData.animate = saveAnimate;
5248 /* check for the end of the game */
5249 if (currentMove >= forwardMostMove) {
5250 gameMode = PlayFromGameFile;
5252 SetTrainingModeOff();
5253 DisplayInformation(_("End of game"));
5256 DisplayError(_("Incorrect move"), 0);
5261 /* Ok, now we know that the move is good, so we can kill
5262 the previous line in Analysis Mode */
5263 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5264 forwardMostMove = currentMove;
5267 /* If we need the chess program but it's dead, restart it */
5268 ResurrectChessProgram();
5270 /* A user move restarts a paused game*/
5274 thinkOutput[0] = NULLCHAR;
5276 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5278 if (gameMode == BeginningOfGame) {
5279 if (appData.noChessProgram) {
5280 gameMode = EditGame;
5284 gameMode = MachinePlaysBlack;
5287 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5289 if (first.sendName) {
5290 sprintf(buf, "name %s\n", gameInfo.white);
5291 SendToProgram(buf, &first);
5297 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5298 /* Relay move to ICS or chess engine */
5299 if (appData.icsActive) {
5300 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5301 gameMode == IcsExamining) {
5302 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5306 if (first.sendTime && (gameMode == BeginningOfGame ||
5307 gameMode == MachinePlaysWhite ||
5308 gameMode == MachinePlaysBlack)) {
5309 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5311 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5312 // [HGM] book: if program might be playing, let it use book
5313 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5314 first.maybeThinking = TRUE;
5315 } else SendMoveToProgram(forwardMostMove-1, &first);
5316 if (currentMove == cmailOldMove + 1) {
5317 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5321 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5325 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5326 EP_UNKNOWN, castlingRights[currentMove]) ) {
5332 if (WhiteOnMove(currentMove)) {
5333 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5335 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5339 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5344 case MachinePlaysBlack:
5345 case MachinePlaysWhite:
5346 /* disable certain menu options while machine is thinking */
5347 SetMachineThinkingEnables();
5354 if(bookHit) { // [HGM] book: simulate book reply
5355 static char bookMove[MSG_SIZ]; // a bit generous?
5357 programStats.nodes = programStats.depth = programStats.time =
5358 programStats.score = programStats.got_only_move = 0;
5359 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5361 strcpy(bookMove, "move ");
5362 strcat(bookMove, bookHit);
5363 HandleMachineMove(bookMove, &first);
5369 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5370 int fromX, fromY, toX, toY;
5373 /* [HGM] This routine was added to allow calling of its two logical
5374 parts from other modules in the old way. Before, UserMoveEvent()
5375 automatically called FinishMove() if the move was OK, and returned
5376 otherwise. I separated the two, in order to make it possible to
5377 slip a promotion popup in between. But that it always needs two
5378 calls, to the first part, (now called UserMoveTest() ), and to
5379 FinishMove if the first part succeeded. Calls that do not need
5380 to do anything in between, can call this routine the old way.
5382 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5383 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5384 if(moveType != ImpossibleMove)
5385 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5388 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5390 // char * hint = lastHint;
5391 FrontEndProgramStats stats;
5393 stats.which = cps == &first ? 0 : 1;
5394 stats.depth = cpstats->depth;
5395 stats.nodes = cpstats->nodes;
5396 stats.score = cpstats->score;
5397 stats.time = cpstats->time;
5398 stats.pv = cpstats->movelist;
5399 stats.hint = lastHint;
5400 stats.an_move_index = 0;
5401 stats.an_move_count = 0;
5403 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5404 stats.hint = cpstats->move_name;
5405 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5406 stats.an_move_count = cpstats->nr_moves;
5409 SetProgramStats( &stats );
5412 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5413 { // [HGM] book: this routine intercepts moves to simulate book replies
5414 char *bookHit = NULL;
5416 //first determine if the incoming move brings opponent into his book
5417 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5418 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5419 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5420 if(bookHit != NULL && !cps->bookSuspend) {
5421 // make sure opponent is not going to reply after receiving move to book position
5422 SendToProgram("force\n", cps);
5423 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5425 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5426 // now arrange restart after book miss
5428 // after a book hit we never send 'go', and the code after the call to this routine
5429 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5431 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5432 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5433 SendToProgram(buf, cps);
5434 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5435 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5436 SendToProgram("go\n", cps);
5437 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5438 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5439 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5440 SendToProgram("go\n", cps);
5441 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5443 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5447 ChessProgramState *savedState;
5448 void DeferredBookMove(void)
5450 if(savedState->lastPing != savedState->lastPong)
5451 ScheduleDelayedEvent(DeferredBookMove, 10);
5453 HandleMachineMove(savedMessage, savedState);
5457 HandleMachineMove(message, cps)
5459 ChessProgramState *cps;
5461 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5462 char realname[MSG_SIZ];
5463 int fromX, fromY, toX, toY;
5470 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5472 * Kludge to ignore BEL characters
5474 while (*message == '\007') message++;
5477 * [HGM] engine debug message: ignore lines starting with '#' character
5479 if(cps->debug && *message == '#') return;
5482 * Look for book output
5484 if (cps == &first && bookRequested) {
5485 if (message[0] == '\t' || message[0] == ' ') {
5486 /* Part of the book output is here; append it */
5487 strcat(bookOutput, message);
5488 strcat(bookOutput, " \n");
5490 } else if (bookOutput[0] != NULLCHAR) {
5491 /* All of book output has arrived; display it */
5492 char *p = bookOutput;
5493 while (*p != NULLCHAR) {
5494 if (*p == '\t') *p = ' ';
5497 DisplayInformation(bookOutput);
5498 bookRequested = FALSE;
5499 /* Fall through to parse the current output */
5504 * Look for machine move.
5506 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5507 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5509 /* This method is only useful on engines that support ping */
5510 if (cps->lastPing != cps->lastPong) {
5511 if (gameMode == BeginningOfGame) {
5512 /* Extra move from before last new; ignore */
5513 if (appData.debugMode) {
5514 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5517 if (appData.debugMode) {
5518 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5519 cps->which, gameMode);
5522 SendToProgram("undo\n", cps);
5528 case BeginningOfGame:
5529 /* Extra move from before last reset; ignore */
5530 if (appData.debugMode) {
5531 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5538 /* Extra move after we tried to stop. The mode test is
5539 not a reliable way of detecting this problem, but it's
5540 the best we can do on engines that don't support ping.
5542 if (appData.debugMode) {
5543 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5544 cps->which, gameMode);
5546 SendToProgram("undo\n", cps);
5549 case MachinePlaysWhite:
5550 case IcsPlayingWhite:
5551 machineWhite = TRUE;
5554 case MachinePlaysBlack:
5555 case IcsPlayingBlack:
5556 machineWhite = FALSE;
5559 case TwoMachinesPlay:
5560 machineWhite = (cps->twoMachinesColor[0] == 'w');
5563 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5564 if (appData.debugMode) {
5566 "Ignoring move out of turn by %s, gameMode %d"
5567 ", forwardMost %d\n",
5568 cps->which, gameMode, forwardMostMove);
5573 if (appData.debugMode) { int f = forwardMostMove;
5574 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5575 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5577 if(cps->alphaRank) AlphaRank(machineMove, 4);
5578 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5579 &fromX, &fromY, &toX, &toY, &promoChar)) {
5580 /* Machine move could not be parsed; ignore it. */
5581 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5582 machineMove, cps->which);
5583 DisplayError(buf1, 0);
5584 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5585 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5586 if (gameMode == TwoMachinesPlay) {
5587 GameEnds(machineWhite ? BlackWins : WhiteWins,
5593 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5594 /* So we have to redo legality test with true e.p. status here, */
5595 /* to make sure an illegal e.p. capture does not slip through, */
5596 /* to cause a forfeit on a justified illegal-move complaint */
5597 /* of the opponent. */
5598 if( gameMode==TwoMachinesPlay && appData.testLegality
5599 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5602 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5603 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5604 fromY, fromX, toY, toX, promoChar);
5605 if (appData.debugMode) {
5607 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5608 castlingRights[forwardMostMove][i], castlingRank[i]);
5609 fprintf(debugFP, "castling rights\n");
5611 if(moveType == IllegalMove) {
5612 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5613 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5614 GameEnds(machineWhite ? BlackWins : WhiteWins,
5617 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5618 /* [HGM] Kludge to handle engines that send FRC-style castling
5619 when they shouldn't (like TSCP-Gothic) */
5621 case WhiteASideCastleFR:
5622 case BlackASideCastleFR:
5624 currentMoveString[2]++;
5626 case WhiteHSideCastleFR:
5627 case BlackHSideCastleFR:
5629 currentMoveString[2]--;
5631 default: ; // nothing to do, but suppresses warning of pedantic compilers
5634 hintRequested = FALSE;
5635 lastHint[0] = NULLCHAR;
5636 bookRequested = FALSE;
5637 /* Program may be pondering now */
5638 cps->maybeThinking = TRUE;
5639 if (cps->sendTime == 2) cps->sendTime = 1;
5640 if (cps->offeredDraw) cps->offeredDraw--;
5643 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5645 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5647 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5648 char buf[3*MSG_SIZ];
5650 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5651 programStats.score / 100.,
5653 programStats.time / 100.,
5654 (unsigned int)programStats.nodes,
5655 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5656 programStats.movelist);
5661 /* currentMoveString is set as a side-effect of ParseOneMove */
5662 strcpy(machineMove, currentMoveString);
5663 strcat(machineMove, "\n");
5664 strcpy(moveList[forwardMostMove], machineMove);
5666 /* [AS] Save move info and clear stats for next move */
5667 pvInfoList[ forwardMostMove ].score = programStats.score;
5668 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5669 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5670 ClearProgramStats();
5671 thinkOutput[0] = NULLCHAR;
5672 hiddenThinkOutputState = 0;
5674 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5676 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5677 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5680 while( count < adjudicateLossPlies ) {
5681 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5684 score = -score; /* Flip score for winning side */
5687 if( score > adjudicateLossThreshold ) {
5694 if( count >= adjudicateLossPlies ) {
5695 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5697 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5698 "Xboard adjudication",
5705 if( gameMode == TwoMachinesPlay ) {
5706 // [HGM] some adjudications useful with buggy engines
5707 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5708 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5711 if( appData.testLegality )
5712 { /* [HGM] Some more adjudications for obstinate engines */
5713 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5714 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5715 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5716 static int moveCount = 6;
5718 char *reason = NULL;
5720 /* Count what is on board. */
5721 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5722 { ChessSquare p = boards[forwardMostMove][i][j];
5726 { /* count B,N,R and other of each side */
5729 NrK++; break; // [HGM] atomic: count Kings
5733 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5734 bishopsColor |= 1 << ((i^j)&1);
5739 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5740 bishopsColor |= 1 << ((i^j)&1);
5755 PawnAdvance += m; NrPawns++;
5757 NrPieces += (p != EmptySquare);
5758 NrW += ((int)p < (int)BlackPawn);
5759 if(gameInfo.variant == VariantXiangqi &&
5760 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5761 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5762 NrW -= ((int)p < (int)BlackPawn);
5766 /* Some material-based adjudications that have to be made before stalemate test */
5767 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5768 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5769 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5770 if(appData.checkMates) {
5771 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5772 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5773 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5774 "Xboard adjudication: King destroyed", GE_XBOARD );
5779 /* Bare King in Shatranj (loses) or Losers (wins) */
5780 if( NrW == 1 || NrPieces - NrW == 1) {
5781 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5782 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5783 if(appData.checkMates) {
5784 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5785 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5787 "Xboard adjudication: Bare king", GE_XBOARD );
5791 if( gameInfo.variant == VariantShatranj && --bare < 0)
5793 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5794 if(appData.checkMates) {
5795 /* but only adjudicate if adjudication enabled */
5796 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5797 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5798 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5799 "Xboard adjudication: Bare king", GE_XBOARD );
5806 // don't wait for engine to announce game end if we can judge ourselves
5807 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5808 castlingRights[forwardMostMove]) ) {
5810 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5811 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5812 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5813 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5816 reason = "Xboard adjudication: 3rd check";
5817 epStatus[forwardMostMove] = EP_CHECKMATE;
5827 reason = "Xboard adjudication: Stalemate";
5828 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5829 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5830 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5831 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5832 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5833 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5834 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5835 EP_CHECKMATE : EP_WINS);
5836 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5837 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5841 reason = "Xboard adjudication: Checkmate";
5842 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5846 switch(i = epStatus[forwardMostMove]) {
5848 result = GameIsDrawn; break;
5850 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5852 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5854 result = (ChessMove) 0;
5856 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5857 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5858 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859 GameEnds( result, reason, GE_XBOARD );
5863 /* Next absolutely insufficient mating material. */
5864 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5865 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5866 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5867 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5868 { /* KBK, KNK, KK of KBKB with like Bishops */
5870 /* always flag draws, for judging claims */
5871 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5873 if(appData.materialDraws) {
5874 /* but only adjudicate them if adjudication enabled */
5875 SendToProgram("force\n", cps->other); // suppress reply
5876 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5877 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5878 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5883 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5885 ( NrWR == 1 && NrBR == 1 /* KRKR */
5886 || NrWQ==1 && NrBQ==1 /* KQKQ */
5887 || NrWN==2 || NrBN==2 /* KNNK */
5888 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5890 if(--moveCount < 0 && appData.trivialDraws)
5891 { /* if the first 3 moves do not show a tactical win, declare draw */
5892 SendToProgram("force\n", cps->other); // suppress reply
5893 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5894 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5895 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5898 } else moveCount = 6;
5902 if (appData.debugMode) { int i;
5903 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5904 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5905 appData.drawRepeats);
5906 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5907 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5911 /* Check for rep-draws */
5913 for(k = forwardMostMove-2;
5914 k>=backwardMostMove && k>=forwardMostMove-100 &&
5915 epStatus[k] < EP_UNKNOWN &&
5916 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5920 if (appData.debugMode) {
5921 fprintf(debugFP, " loop\n");
5924 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5926 if (appData.debugMode) {
5927 fprintf(debugFP, "match\n");
5930 /* compare castling rights */
5931 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5932 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5933 rights++; /* King lost rights, while rook still had them */
5934 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5935 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5936 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5937 rights++; /* but at least one rook lost them */
5939 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5940 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5942 if( castlingRights[forwardMostMove][5] >= 0 ) {
5943 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5944 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5948 if (appData.debugMode) {
5949 for(i=0; i<nrCastlingRights; i++)
5950 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
5953 if (appData.debugMode) {
5954 fprintf(debugFP, " %d %d\n", rights, k);
5957 if( rights == 0 && ++count > appData.drawRepeats-2
5958 && appData.drawRepeats > 1) {
5959 /* adjudicate after user-specified nr of repeats */
5960 SendToProgram("force\n", cps->other); // suppress reply
5961 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5962 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5963 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5964 // [HGM] xiangqi: check for forbidden perpetuals
5965 int m, ourPerpetual = 1, hisPerpetual = 1;
5966 for(m=forwardMostMove; m>k; m-=2) {
5967 if(MateTest(boards[m], PosFlags(m),
5968 EP_NONE, castlingRights[m]) != MT_CHECK)
5969 ourPerpetual = 0; // the current mover did not always check
5970 if(MateTest(boards[m-1], PosFlags(m-1),
5971 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5972 hisPerpetual = 0; // the opponent did not always check
5974 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5975 ourPerpetual, hisPerpetual);
5976 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5977 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5978 "Xboard adjudication: perpetual checking", GE_XBOARD );
5981 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5982 break; // (or we would have caught him before). Abort repetition-checking loop.
5983 // Now check for perpetual chases
5984 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5985 hisPerpetual = PerpetualChase(k, forwardMostMove);
5986 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5987 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5988 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5989 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5992 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5993 break; // Abort repetition-checking loop.
5995 // if neither of us is checking or chasing all the time, or both are, it is draw
5997 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6000 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6001 epStatus[forwardMostMove] = EP_REP_DRAW;
6005 /* Now we test for 50-move draws. Determine ply count */
6006 count = forwardMostMove;
6007 /* look for last irreversble move */
6008 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6010 /* if we hit starting position, add initial plies */
6011 if( count == backwardMostMove )
6012 count -= initialRulePlies;
6013 count = forwardMostMove - count;
6015 epStatus[forwardMostMove] = EP_RULE_DRAW;
6016 /* this is used to judge if draw claims are legal */
6017 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6018 SendToProgram("force\n", cps->other); // suppress reply
6019 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6020 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6021 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6025 /* if draw offer is pending, treat it as a draw claim
6026 * when draw condition present, to allow engines a way to
6027 * claim draws before making their move to avoid a race
6028 * condition occurring after their move
6030 if( cps->other->offeredDraw || cps->offeredDraw ) {
6032 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6033 p = "Draw claim: 50-move rule";
6034 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6035 p = "Draw claim: 3-fold repetition";
6036 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6037 p = "Draw claim: insufficient mating material";
6039 SendToProgram("force\n", cps->other); // suppress reply
6040 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6041 GameEnds( GameIsDrawn, p, GE_XBOARD );
6042 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6048 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6049 SendToProgram("force\n", cps->other); // suppress reply
6050 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6051 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6053 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6060 if (gameMode == TwoMachinesPlay) {
6061 /* [HGM] relaying draw offers moved to after reception of move */
6062 /* and interpreting offer as claim if it brings draw condition */
6063 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6064 SendToProgram("draw\n", cps->other);
6066 if (cps->other->sendTime) {
6067 SendTimeRemaining(cps->other,
6068 cps->other->twoMachinesColor[0] == 'w');
6070 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6071 if (firstMove && !bookHit) {
6073 if (cps->other->useColors) {
6074 SendToProgram(cps->other->twoMachinesColor, cps->other);
6076 SendToProgram("go\n", cps->other);
6078 cps->other->maybeThinking = TRUE;
6081 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6083 if (!pausing && appData.ringBellAfterMoves) {
6088 * Reenable menu items that were disabled while
6089 * machine was thinking
6091 if (gameMode != TwoMachinesPlay)
6092 SetUserThinkingEnables();
6094 // [HGM] book: after book hit opponent has received move and is now in force mode
6095 // force the book reply into it, and then fake that it outputted this move by jumping
6096 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6098 static char bookMove[MSG_SIZ]; // a bit generous?
6100 strcpy(bookMove, "move ");
6101 strcat(bookMove, bookHit);
6104 programStats.nodes = programStats.depth = programStats.time =
6105 programStats.score = programStats.got_only_move = 0;
6106 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6108 if(cps->lastPing != cps->lastPong) {
6109 savedMessage = message; // args for deferred call
6111 ScheduleDelayedEvent(DeferredBookMove, 10);
6120 /* Set special modes for chess engines. Later something general
6121 * could be added here; for now there is just one kludge feature,
6122 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6123 * when "xboard" is given as an interactive command.
6125 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6126 cps->useSigint = FALSE;
6127 cps->useSigterm = FALSE;
6129 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6130 ParseFeatures(message+8, cps);
6131 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6134 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6135 * want this, I was asked to put it in, and obliged.
6137 if (!strncmp(message, "setboard ", 9)) {
6138 Board initial_position; int i;
6140 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6142 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6143 DisplayError(_("Bad FEN received from engine"), 0);
6146 Reset(FALSE, FALSE);
6147 CopyBoard(boards[0], initial_position);
6148 initialRulePlies = FENrulePlies;
6149 epStatus[0] = FENepStatus;
6150 for( i=0; i<nrCastlingRights; i++ )
6151 castlingRights[0][i] = FENcastlingRights[i];
6152 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6153 else gameMode = MachinePlaysBlack;
6154 DrawPosition(FALSE, boards[currentMove]);
6160 * Look for communication commands
6162 if (!strncmp(message, "telluser ", 9)) {
6163 DisplayNote(message + 9);
6166 if (!strncmp(message, "tellusererror ", 14)) {
6167 DisplayError(message + 14, 0);
6170 if (!strncmp(message, "tellopponent ", 13)) {
6171 if (appData.icsActive) {
6173 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6177 DisplayNote(message + 13);
6181 if (!strncmp(message, "tellothers ", 11)) {
6182 if (appData.icsActive) {
6184 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6190 if (!strncmp(message, "tellall ", 8)) {
6191 if (appData.icsActive) {
6193 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6197 DisplayNote(message + 8);
6201 if (strncmp(message, "warning", 7) == 0) {
6202 /* Undocumented feature, use tellusererror in new code */
6203 DisplayError(message, 0);
6206 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6207 strcpy(realname, cps->tidy);
6208 strcat(realname, " query");
6209 AskQuestion(realname, buf2, buf1, cps->pr);
6212 /* Commands from the engine directly to ICS. We don't allow these to be
6213 * sent until we are logged on. Crafty kibitzes have been known to
6214 * interfere with the login process.
6217 if (!strncmp(message, "tellics ", 8)) {
6218 SendToICS(message + 8);
6222 if (!strncmp(message, "tellicsnoalias ", 15)) {
6223 SendToICS(ics_prefix);
6224 SendToICS(message + 15);
6228 /* The following are for backward compatibility only */
6229 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6230 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6231 SendToICS(ics_prefix);
6237 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6241 * If the move is illegal, cancel it and redraw the board.
6242 * Also deal with other error cases. Matching is rather loose
6243 * here to accommodate engines written before the spec.
6245 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6246 strncmp(message, "Error", 5) == 0) {
6247 if (StrStr(message, "name") ||
6248 StrStr(message, "rating") || StrStr(message, "?") ||
6249 StrStr(message, "result") || StrStr(message, "board") ||
6250 StrStr(message, "bk") || StrStr(message, "computer") ||
6251 StrStr(message, "variant") || StrStr(message, "hint") ||
6252 StrStr(message, "random") || StrStr(message, "depth") ||
6253 StrStr(message, "accepted")) {
6256 if (StrStr(message, "protover")) {
6257 /* Program is responding to input, so it's apparently done
6258 initializing, and this error message indicates it is
6259 protocol version 1. So we don't need to wait any longer
6260 for it to initialize and send feature commands. */
6261 FeatureDone(cps, 1);
6262 cps->protocolVersion = 1;
6265 cps->maybeThinking = FALSE;
6267 if (StrStr(message, "draw")) {
6268 /* Program doesn't have "draw" command */
6269 cps->sendDrawOffers = 0;
6272 if (cps->sendTime != 1 &&
6273 (StrStr(message, "time") || StrStr(message, "otim"))) {
6274 /* Program apparently doesn't have "time" or "otim" command */
6278 if (StrStr(message, "analyze")) {
6279 cps->analysisSupport = FALSE;
6280 cps->analyzing = FALSE;
6282 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6283 DisplayError(buf2, 0);
6286 if (StrStr(message, "(no matching move)st")) {
6287 /* Special kludge for GNU Chess 4 only */
6288 cps->stKludge = TRUE;
6289 SendTimeControl(cps, movesPerSession, timeControl,
6290 timeIncrement, appData.searchDepth,
6294 if (StrStr(message, "(no matching move)sd")) {
6295 /* Special kludge for GNU Chess 4 only */
6296 cps->sdKludge = TRUE;
6297 SendTimeControl(cps, movesPerSession, timeControl,
6298 timeIncrement, appData.searchDepth,
6302 if (!StrStr(message, "llegal")) {
6305 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6306 gameMode == IcsIdle) return;
6307 if (forwardMostMove <= backwardMostMove) return;
6309 /* Following removed: it caused a bug where a real illegal move
6310 message in analyze mored would be ignored. */
6311 if (cps == &first && programStats.ok_to_send == 0) {
6312 /* Bogus message from Crafty responding to "." This filtering
6313 can miss some of the bad messages, but fortunately the bug
6314 is fixed in current Crafty versions, so it doesn't matter. */
6318 if (pausing) PauseEvent();
6319 if (gameMode == PlayFromGameFile) {
6320 /* Stop reading this game file */
6321 gameMode = EditGame;
6324 currentMove = --forwardMostMove;
6325 DisplayMove(currentMove-1); /* before DisplayMoveError */
6327 DisplayBothClocks();
6328 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6329 parseList[currentMove], cps->which);
6330 DisplayMoveError(buf1);
6331 DrawPosition(FALSE, boards[currentMove]);
6333 /* [HGM] illegal-move claim should forfeit game when Xboard */
6334 /* only passes fully legal moves */
6335 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6336 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6337 "False illegal-move claim", GE_XBOARD );
6341 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6342 /* Program has a broken "time" command that
6343 outputs a string not ending in newline.
6349 * If chess program startup fails, exit with an error message.
6350 * Attempts to recover here are futile.
6352 if ((StrStr(message, "unknown host") != NULL)
6353 || (StrStr(message, "No remote directory") != NULL)
6354 || (StrStr(message, "not found") != NULL)
6355 || (StrStr(message, "No such file") != NULL)
6356 || (StrStr(message, "can't alloc") != NULL)
6357 || (StrStr(message, "Permission denied") != NULL)) {
6359 cps->maybeThinking = FALSE;
6360 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6361 cps->which, cps->program, cps->host, message);
6362 RemoveInputSource(cps->isr);
6363 DisplayFatalError(buf1, 0, 1);
6368 * Look for hint output
6370 if (sscanf(message, "Hint: %s", buf1) == 1) {
6371 if (cps == &first && hintRequested) {
6372 hintRequested = FALSE;
6373 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6374 &fromX, &fromY, &toX, &toY, &promoChar)) {
6375 (void) CoordsToAlgebraic(boards[forwardMostMove],
6376 PosFlags(forwardMostMove), EP_UNKNOWN,
6377 fromY, fromX, toY, toX, promoChar, buf1);
6378 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6379 DisplayInformation(buf2);
6381 /* Hint move could not be parsed!? */
6382 snprintf(buf2, sizeof(buf2),
6383 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6385 DisplayError(buf2, 0);
6388 strcpy(lastHint, buf1);
6394 * Ignore other messages if game is not in progress
6396 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6397 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6400 * look for win, lose, draw, or draw offer
6402 if (strncmp(message, "1-0", 3) == 0) {
6403 char *p, *q, *r = "";
6404 p = strchr(message, '{');
6412 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6414 } else if (strncmp(message, "0-1", 3) == 0) {
6415 char *p, *q, *r = "";
6416 p = strchr(message, '{');
6424 /* Kludge for Arasan 4.1 bug */
6425 if (strcmp(r, "Black resigns") == 0) {
6426 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6429 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6431 } else if (strncmp(message, "1/2", 3) == 0) {
6432 char *p, *q, *r = "";
6433 p = strchr(message, '{');
6442 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6445 } else if (strncmp(message, "White resign", 12) == 0) {
6446 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6448 } else if (strncmp(message, "Black resign", 12) == 0) {
6449 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6451 } else if (strncmp(message, "White matches", 13) == 0 ||
6452 strncmp(message, "Black matches", 13) == 0 ) {
6453 /* [HGM] ignore GNUShogi noises */
6455 } else if (strncmp(message, "White", 5) == 0 &&
6456 message[5] != '(' &&
6457 StrStr(message, "Black") == NULL) {
6458 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6460 } else if (strncmp(message, "Black", 5) == 0 &&
6461 message[5] != '(') {
6462 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6464 } else if (strcmp(message, "resign") == 0 ||
6465 strcmp(message, "computer resigns") == 0) {
6467 case MachinePlaysBlack:
6468 case IcsPlayingBlack:
6469 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6471 case MachinePlaysWhite:
6472 case IcsPlayingWhite:
6473 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6475 case TwoMachinesPlay:
6476 if (cps->twoMachinesColor[0] == 'w')
6477 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6479 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6486 } else if (strncmp(message, "opponent mates", 14) == 0) {
6488 case MachinePlaysBlack:
6489 case IcsPlayingBlack:
6490 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6492 case MachinePlaysWhite:
6493 case IcsPlayingWhite:
6494 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6496 case TwoMachinesPlay:
6497 if (cps->twoMachinesColor[0] == 'w')
6498 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6500 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6507 } else if (strncmp(message, "computer mates", 14) == 0) {
6509 case MachinePlaysBlack:
6510 case IcsPlayingBlack:
6511 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6513 case MachinePlaysWhite:
6514 case IcsPlayingWhite:
6515 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6517 case TwoMachinesPlay:
6518 if (cps->twoMachinesColor[0] == 'w')
6519 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6521 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6528 } else if (strncmp(message, "checkmate", 9) == 0) {
6529 if (WhiteOnMove(forwardMostMove)) {
6530 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6532 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6535 } else if (strstr(message, "Draw") != NULL ||
6536 strstr(message, "game is a draw") != NULL) {
6537 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6539 } else if (strstr(message, "offer") != NULL &&
6540 strstr(message, "draw") != NULL) {
6542 if (appData.zippyPlay && first.initDone) {
6543 /* Relay offer to ICS */
6544 SendToICS(ics_prefix);
6545 SendToICS("draw\n");
6548 cps->offeredDraw = 2; /* valid until this engine moves twice */
6549 if (gameMode == TwoMachinesPlay) {
6550 if (cps->other->offeredDraw) {
6551 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6552 /* [HGM] in two-machine mode we delay relaying draw offer */
6553 /* until after we also have move, to see if it is really claim */
6557 if (cps->other->sendDrawOffers) {
6558 SendToProgram("draw\n", cps->other);
6562 } else if (gameMode == MachinePlaysWhite ||
6563 gameMode == MachinePlaysBlack) {
6564 if (userOfferedDraw) {
6565 DisplayInformation(_("Machine accepts your draw offer"));
6566 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6568 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6575 * Look for thinking output
6577 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6578 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6580 int plylev, mvleft, mvtot, curscore, time;
6581 char mvname[MOVE_LEN];
6585 int prefixHint = FALSE;
6586 mvname[0] = NULLCHAR;
6589 case MachinePlaysBlack:
6590 case IcsPlayingBlack:
6591 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6593 case MachinePlaysWhite:
6594 case IcsPlayingWhite:
6595 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6600 case IcsObserving: /* [DM] icsEngineAnalyze */
6601 if (!appData.icsEngineAnalyze) ignore = TRUE;
6603 case TwoMachinesPlay:
6604 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6615 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6616 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6618 if (plyext != ' ' && plyext != '\t') {
6622 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6623 if( cps->scoreIsAbsolute &&
6624 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6626 curscore = -curscore;
6630 programStats.depth = plylev;
6631 programStats.nodes = nodes;
6632 programStats.time = time;
6633 programStats.score = curscore;
6634 programStats.got_only_move = 0;
6636 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6639 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6640 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6641 if(WhiteOnMove(forwardMostMove))
6642 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6643 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6646 /* Buffer overflow protection */
6647 if (buf1[0] != NULLCHAR) {
6648 if (strlen(buf1) >= sizeof(programStats.movelist)
6649 && appData.debugMode) {
6651 "PV is too long; using the first %d bytes.\n",
6652 sizeof(programStats.movelist) - 1);
6655 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6657 sprintf(programStats.movelist, " no PV\n");
6660 if (programStats.seen_stat) {
6661 programStats.ok_to_send = 1;
6664 if (strchr(programStats.movelist, '(') != NULL) {
6665 programStats.line_is_book = 1;
6666 programStats.nr_moves = 0;
6667 programStats.moves_left = 0;
6669 programStats.line_is_book = 0;
6672 SendProgramStatsToFrontend( cps, &programStats );
6675 [AS] Protect the thinkOutput buffer from overflow... this
6676 is only useful if buf1 hasn't overflowed first!
6678 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6680 (gameMode == TwoMachinesPlay ?
6681 ToUpper(cps->twoMachinesColor[0]) : ' '),
6682 ((double) curscore) / 100.0,
6683 prefixHint ? lastHint : "",
6684 prefixHint ? " " : "" );
6686 if( buf1[0] != NULLCHAR ) {
6687 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6689 if( strlen(buf1) > max_len ) {
6690 if( appData.debugMode) {
6691 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6693 buf1[max_len+1] = '\0';
6696 strcat( thinkOutput, buf1 );
6699 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6700 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6701 DisplayMove(currentMove - 1);
6706 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6707 /* crafty (9.25+) says "(only move) <move>"
6708 * if there is only 1 legal move
6710 sscanf(p, "(only move) %s", buf1);
6711 sprintf(thinkOutput, "%s (only move)", buf1);
6712 sprintf(programStats.movelist, "%s (only move)", buf1);
6713 programStats.depth = 1;
6714 programStats.nr_moves = 1;
6715 programStats.moves_left = 1;
6716 programStats.nodes = 1;
6717 programStats.time = 1;
6718 programStats.got_only_move = 1;
6720 /* Not really, but we also use this member to
6721 mean "line isn't going to change" (Crafty
6722 isn't searching, so stats won't change) */
6723 programStats.line_is_book = 1;
6725 SendProgramStatsToFrontend( cps, &programStats );
6727 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6728 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6729 DisplayMove(currentMove - 1);
6733 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6734 &time, &nodes, &plylev, &mvleft,
6735 &mvtot, mvname) >= 5) {
6736 /* The stat01: line is from Crafty (9.29+) in response
6737 to the "." command */
6738 programStats.seen_stat = 1;
6739 cps->maybeThinking = TRUE;
6741 if (programStats.got_only_move || !appData.periodicUpdates)
6744 programStats.depth = plylev;
6745 programStats.time = time;
6746 programStats.nodes = nodes;
6747 programStats.moves_left = mvleft;
6748 programStats.nr_moves = mvtot;
6749 strcpy(programStats.move_name, mvname);
6750 programStats.ok_to_send = 1;
6751 programStats.movelist[0] = '\0';
6753 SendProgramStatsToFrontend( cps, &programStats );
6758 } else if (strncmp(message,"++",2) == 0) {
6759 /* Crafty 9.29+ outputs this */
6760 programStats.got_fail = 2;
6763 } else if (strncmp(message,"--",2) == 0) {
6764 /* Crafty 9.29+ outputs this */
6765 programStats.got_fail = 1;
6768 } else if (thinkOutput[0] != NULLCHAR &&
6769 strncmp(message, " ", 4) == 0) {
6770 unsigned message_len;
6773 while (*p && *p == ' ') p++;
6775 message_len = strlen( p );
6777 /* [AS] Avoid buffer overflow */
6778 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6779 strcat(thinkOutput, " ");
6780 strcat(thinkOutput, p);
6783 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6784 strcat(programStats.movelist, " ");
6785 strcat(programStats.movelist, p);
6788 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6789 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6790 DisplayMove(currentMove - 1);
6799 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6800 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6802 ChessProgramStats cpstats;
6804 if (plyext != ' ' && plyext != '\t') {
6808 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6809 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6810 curscore = -curscore;
6813 cpstats.depth = plylev;
6814 cpstats.nodes = nodes;
6815 cpstats.time = time;
6816 cpstats.score = curscore;
6817 cpstats.got_only_move = 0;
6818 cpstats.movelist[0] = '\0';
6820 if (buf1[0] != NULLCHAR) {
6821 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6824 cpstats.ok_to_send = 0;
6825 cpstats.line_is_book = 0;
6826 cpstats.nr_moves = 0;
6827 cpstats.moves_left = 0;
6829 SendProgramStatsToFrontend( cps, &cpstats );
6836 /* Parse a game score from the character string "game", and
6837 record it as the history of the current game. The game
6838 score is NOT assumed to start from the standard position.
6839 The display is not updated in any way.
6842 ParseGameHistory(game)
6846 int fromX, fromY, toX, toY, boardIndex;
6851 if (appData.debugMode)
6852 fprintf(debugFP, "Parsing game history: %s\n", game);
6854 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6855 gameInfo.site = StrSave(appData.icsHost);
6856 gameInfo.date = PGNDate();
6857 gameInfo.round = StrSave("-");
6859 /* Parse out names of players */
6860 while (*game == ' ') game++;
6862 while (*game != ' ') *p++ = *game++;
6864 gameInfo.white = StrSave(buf);
6865 while (*game == ' ') game++;
6867 while (*game != ' ' && *game != '\n') *p++ = *game++;
6869 gameInfo.black = StrSave(buf);
6872 boardIndex = blackPlaysFirst ? 1 : 0;
6875 yyboardindex = boardIndex;
6876 moveType = (ChessMove) yylex();
6878 case IllegalMove: /* maybe suicide chess, etc. */
6879 if (appData.debugMode) {
6880 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6881 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6882 setbuf(debugFP, NULL);
6884 case WhitePromotionChancellor:
6885 case BlackPromotionChancellor:
6886 case WhitePromotionArchbishop:
6887 case BlackPromotionArchbishop:
6888 case WhitePromotionQueen:
6889 case BlackPromotionQueen:
6890 case WhitePromotionRook:
6891 case BlackPromotionRook:
6892 case WhitePromotionBishop:
6893 case BlackPromotionBishop:
6894 case WhitePromotionKnight:
6895 case BlackPromotionKnight:
6896 case WhitePromotionKing:
6897 case BlackPromotionKing:
6899 case WhiteCapturesEnPassant:
6900 case BlackCapturesEnPassant:
6901 case WhiteKingSideCastle:
6902 case WhiteQueenSideCastle:
6903 case BlackKingSideCastle:
6904 case BlackQueenSideCastle:
6905 case WhiteKingSideCastleWild:
6906 case WhiteQueenSideCastleWild:
6907 case BlackKingSideCastleWild:
6908 case BlackQueenSideCastleWild:
6910 case WhiteHSideCastleFR:
6911 case WhiteASideCastleFR:
6912 case BlackHSideCastleFR:
6913 case BlackASideCastleFR:
6915 fromX = currentMoveString[0] - AAA;
6916 fromY = currentMoveString[1] - ONE;
6917 toX = currentMoveString[2] - AAA;
6918 toY = currentMoveString[3] - ONE;
6919 promoChar = currentMoveString[4];
6923 fromX = moveType == WhiteDrop ?
6924 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6925 (int) CharToPiece(ToLower(currentMoveString[0]));
6927 toX = currentMoveString[2] - AAA;
6928 toY = currentMoveString[3] - ONE;
6929 promoChar = NULLCHAR;
6933 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6934 if (appData.debugMode) {
6935 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6936 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6937 setbuf(debugFP, NULL);
6939 DisplayError(buf, 0);
6941 case ImpossibleMove:
6943 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6944 if (appData.debugMode) {
6945 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6946 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6947 setbuf(debugFP, NULL);
6949 DisplayError(buf, 0);
6951 case (ChessMove) 0: /* end of file */
6952 if (boardIndex < backwardMostMove) {
6953 /* Oops, gap. How did that happen? */
6954 DisplayError(_("Gap in move list"), 0);
6957 backwardMostMove = blackPlaysFirst ? 1 : 0;
6958 if (boardIndex > forwardMostMove) {
6959 forwardMostMove = boardIndex;
6963 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6964 strcat(parseList[boardIndex-1], " ");
6965 strcat(parseList[boardIndex-1], yy_text);
6977 case GameUnfinished:
6978 if (gameMode == IcsExamining) {
6979 if (boardIndex < backwardMostMove) {
6980 /* Oops, gap. How did that happen? */
6983 backwardMostMove = blackPlaysFirst ? 1 : 0;
6986 gameInfo.result = moveType;
6987 p = strchr(yy_text, '{');
6988 if (p == NULL) p = strchr(yy_text, '(');
6991 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6993 q = strchr(p, *p == '{' ? '}' : ')');
6994 if (q != NULL) *q = NULLCHAR;
6997 gameInfo.resultDetails = StrSave(p);
7000 if (boardIndex >= forwardMostMove &&
7001 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7002 backwardMostMove = blackPlaysFirst ? 1 : 0;
7005 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7006 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7007 parseList[boardIndex]);
7008 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7009 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7010 /* currentMoveString is set as a side-effect of yylex */
7011 strcpy(moveList[boardIndex], currentMoveString);
7012 strcat(moveList[boardIndex], "\n");
7014 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7015 castlingRights[boardIndex], &epStatus[boardIndex]);
7016 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7017 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7023 if(gameInfo.variant != VariantShogi)
7024 strcat(parseList[boardIndex - 1], "+");
7028 strcat(parseList[boardIndex - 1], "#");
7035 /* Apply a move to the given board */
7037 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7038 int fromX, fromY, toX, toY;
7044 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7046 /* [HGM] compute & store e.p. status and castling rights for new position */
7047 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7050 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7054 if( board[toY][toX] != EmptySquare )
7057 if( board[fromY][fromX] == WhitePawn ) {
7058 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7061 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7062 gameInfo.variant != VariantBerolina || toX < fromX)
7063 *ep = toX | berolina;
7064 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7065 gameInfo.variant != VariantBerolina || toX > fromX)
7069 if( board[fromY][fromX] == BlackPawn ) {
7070 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7072 if( toY-fromY== -2) {
7073 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7074 gameInfo.variant != VariantBerolina || toX < fromX)
7075 *ep = toX | berolina;
7076 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7077 gameInfo.variant != VariantBerolina || toX > fromX)
7082 for(i=0; i<nrCastlingRights; i++) {
7083 if(castling[i] == fromX && castlingRank[i] == fromY ||
7084 castling[i] == toX && castlingRank[i] == toY
7085 ) castling[i] = -1; // revoke for moved or captured piece
7090 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7091 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7092 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7094 if (fromX == toX && fromY == toY) return;
7096 if (fromY == DROP_RANK) {
7098 piece = board[toY][toX] = (ChessSquare) fromX;
7100 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7101 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7102 if(gameInfo.variant == VariantKnightmate)
7103 king += (int) WhiteUnicorn - (int) WhiteKing;
7105 /* Code added by Tord: */
7106 /* FRC castling assumed when king captures friendly rook. */
7107 if (board[fromY][fromX] == WhiteKing &&
7108 board[toY][toX] == WhiteRook) {
7109 board[fromY][fromX] = EmptySquare;
7110 board[toY][toX] = EmptySquare;
7112 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7114 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7116 } else if (board[fromY][fromX] == BlackKing &&
7117 board[toY][toX] == BlackRook) {
7118 board[fromY][fromX] = EmptySquare;
7119 board[toY][toX] = EmptySquare;
7121 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7123 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7125 /* End of code added by Tord */
7127 } else if (board[fromY][fromX] == king
7128 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7129 && toY == fromY && toX > fromX+1) {
7130 board[fromY][fromX] = EmptySquare;
7131 board[toY][toX] = king;
7132 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7133 board[fromY][BOARD_RGHT-1] = EmptySquare;
7134 } else if (board[fromY][fromX] == king
7135 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7136 && toY == fromY && toX < fromX-1) {
7137 board[fromY][fromX] = EmptySquare;
7138 board[toY][toX] = king;
7139 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7140 board[fromY][BOARD_LEFT] = EmptySquare;
7141 } else if (board[fromY][fromX] == WhitePawn
7142 && toY == BOARD_HEIGHT-1
7143 && gameInfo.variant != VariantXiangqi
7145 /* white pawn promotion */
7146 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7147 if (board[toY][toX] == EmptySquare) {
7148 board[toY][toX] = WhiteQueen;
7150 if(gameInfo.variant==VariantBughouse ||
7151 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7152 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7153 board[fromY][fromX] = EmptySquare;
7154 } else if ((fromY == BOARD_HEIGHT-4)
7156 && gameInfo.variant != VariantXiangqi
7157 && gameInfo.variant != VariantBerolina
7158 && (board[fromY][fromX] == WhitePawn)
7159 && (board[toY][toX] == EmptySquare)) {
7160 board[fromY][fromX] = EmptySquare;
7161 board[toY][toX] = WhitePawn;
7162 captured = board[toY - 1][toX];
7163 board[toY - 1][toX] = EmptySquare;
7164 } else if ((fromY == BOARD_HEIGHT-4)
7166 && gameInfo.variant == VariantBerolina
7167 && (board[fromY][fromX] == WhitePawn)
7168 && (board[toY][toX] == EmptySquare)) {
7169 board[fromY][fromX] = EmptySquare;
7170 board[toY][toX] = WhitePawn;
7171 if(oldEP & EP_BEROLIN_A) {
7172 captured = board[fromY][fromX-1];
7173 board[fromY][fromX-1] = EmptySquare;
7174 }else{ captured = board[fromY][fromX+1];
7175 board[fromY][fromX+1] = EmptySquare;
7177 } else if (board[fromY][fromX] == king
7178 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7179 && toY == fromY && toX > fromX+1) {
7180 board[fromY][fromX] = EmptySquare;
7181 board[toY][toX] = king;
7182 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7183 board[fromY][BOARD_RGHT-1] = EmptySquare;
7184 } else if (board[fromY][fromX] == king
7185 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7186 && toY == fromY && toX < fromX-1) {
7187 board[fromY][fromX] = EmptySquare;
7188 board[toY][toX] = king;
7189 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7190 board[fromY][BOARD_LEFT] = EmptySquare;
7191 } else if (fromY == 7 && fromX == 3
7192 && board[fromY][fromX] == BlackKing
7193 && toY == 7 && toX == 5) {
7194 board[fromY][fromX] = EmptySquare;
7195 board[toY][toX] = BlackKing;
7196 board[fromY][7] = EmptySquare;
7197 board[toY][4] = BlackRook;
7198 } else if (fromY == 7 && fromX == 3
7199 && board[fromY][fromX] == BlackKing
7200 && toY == 7 && toX == 1) {
7201 board[fromY][fromX] = EmptySquare;
7202 board[toY][toX] = BlackKing;
7203 board[fromY][0] = EmptySquare;
7204 board[toY][2] = BlackRook;
7205 } else if (board[fromY][fromX] == BlackPawn
7207 && gameInfo.variant != VariantXiangqi
7209 /* black pawn promotion */
7210 board[0][toX] = CharToPiece(ToLower(promoChar));
7211 if (board[0][toX] == EmptySquare) {
7212 board[0][toX] = BlackQueen;
7214 if(gameInfo.variant==VariantBughouse ||
7215 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7216 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7217 board[fromY][fromX] = EmptySquare;
7218 } else if ((fromY == 3)
7220 && gameInfo.variant != VariantXiangqi
7221 && gameInfo.variant != VariantBerolina
7222 && (board[fromY][fromX] == BlackPawn)
7223 && (board[toY][toX] == EmptySquare)) {
7224 board[fromY][fromX] = EmptySquare;
7225 board[toY][toX] = BlackPawn;
7226 captured = board[toY + 1][toX];
7227 board[toY + 1][toX] = EmptySquare;
7228 } else if ((fromY == 3)
7230 && gameInfo.variant == VariantBerolina
7231 && (board[fromY][fromX] == BlackPawn)
7232 && (board[toY][toX] == EmptySquare)) {
7233 board[fromY][fromX] = EmptySquare;
7234 board[toY][toX] = BlackPawn;
7235 if(oldEP & EP_BEROLIN_A) {
7236 captured = board[fromY][fromX-1];
7237 board[fromY][fromX-1] = EmptySquare;
7238 }else{ captured = board[fromY][fromX+1];
7239 board[fromY][fromX+1] = EmptySquare;
7242 board[toY][toX] = board[fromY][fromX];
7243 board[fromY][fromX] = EmptySquare;
7246 /* [HGM] now we promote for Shogi, if needed */
7247 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7248 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7251 if (gameInfo.holdingsWidth != 0) {
7253 /* !!A lot more code needs to be written to support holdings */
7254 /* [HGM] OK, so I have written it. Holdings are stored in the */
7255 /* penultimate board files, so they are automaticlly stored */
7256 /* in the game history. */
7257 if (fromY == DROP_RANK) {
7258 /* Delete from holdings, by decreasing count */
7259 /* and erasing image if necessary */
7261 if(p < (int) BlackPawn) { /* white drop */
7262 p -= (int)WhitePawn;
7263 if(p >= gameInfo.holdingsSize) p = 0;
7264 if(--board[p][BOARD_WIDTH-2] == 0)
7265 board[p][BOARD_WIDTH-1] = EmptySquare;
7266 } else { /* black drop */
7267 p -= (int)BlackPawn;
7268 if(p >= gameInfo.holdingsSize) p = 0;
7269 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7270 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7273 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7274 && gameInfo.variant != VariantBughouse ) {
7275 /* [HGM] holdings: Add to holdings, if holdings exist */
7276 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7277 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7278 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7281 if (p >= (int) BlackPawn) {
7282 p -= (int)BlackPawn;
7283 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7284 /* in Shogi restore piece to its original first */
7285 captured = (ChessSquare) (DEMOTED captured);
7288 p = PieceToNumber((ChessSquare)p);
7289 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7290 board[p][BOARD_WIDTH-2]++;
7291 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7293 p -= (int)WhitePawn;
7294 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7295 captured = (ChessSquare) (DEMOTED captured);
7298 p = PieceToNumber((ChessSquare)p);
7299 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7300 board[BOARD_HEIGHT-1-p][1]++;
7301 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7305 } else if (gameInfo.variant == VariantAtomic) {
7306 if (captured != EmptySquare) {
7308 for (y = toY-1; y <= toY+1; y++) {
7309 for (x = toX-1; x <= toX+1; x++) {
7310 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7311 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7312 board[y][x] = EmptySquare;
7316 board[toY][toX] = EmptySquare;
7319 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7320 /* [HGM] Shogi promotions */
7321 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7324 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7325 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7326 // [HGM] superchess: take promotion piece out of holdings
7327 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7328 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7329 if(!--board[k][BOARD_WIDTH-2])
7330 board[k][BOARD_WIDTH-1] = EmptySquare;
7332 if(!--board[BOARD_HEIGHT-1-k][1])
7333 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7339 /* Updates forwardMostMove */
7341 MakeMove(fromX, fromY, toX, toY, promoChar)
7342 int fromX, fromY, toX, toY;
7345 // forwardMostMove++; // [HGM] bare: moved downstream
7347 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7348 int timeLeft; static int lastLoadFlag=0; int king, piece;
7349 piece = boards[forwardMostMove][fromY][fromX];
7350 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7351 if(gameInfo.variant == VariantKnightmate)
7352 king += (int) WhiteUnicorn - (int) WhiteKing;
7353 if(forwardMostMove == 0) {
7355 fprintf(serverMoves, "%s;", second.tidy);
7356 fprintf(serverMoves, "%s;", first.tidy);
7357 if(!blackPlaysFirst)
7358 fprintf(serverMoves, "%s;", second.tidy);
7359 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7360 lastLoadFlag = loadFlag;
7362 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7363 // print castling suffix
7364 if( toY == fromY && piece == king ) {
7366 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7368 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7371 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7372 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7373 boards[forwardMostMove][toY][toX] == EmptySquare
7375 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7377 if(promoChar != NULLCHAR)
7378 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7380 fprintf(serverMoves, "/%d/%d",
7381 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7382 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7383 else timeLeft = blackTimeRemaining/1000;
7384 fprintf(serverMoves, "/%d", timeLeft);
7386 fflush(serverMoves);
7389 if (forwardMostMove+1 >= MAX_MOVES) {
7390 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7395 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7396 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7397 if (commentList[forwardMostMove+1] != NULL) {
7398 free(commentList[forwardMostMove+1]);
7399 commentList[forwardMostMove+1] = NULL;
7401 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7402 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7403 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7404 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7405 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7406 gameInfo.result = GameUnfinished;
7407 if (gameInfo.resultDetails != NULL) {
7408 free(gameInfo.resultDetails);
7409 gameInfo.resultDetails = NULL;
7411 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7412 moveList[forwardMostMove - 1]);
7413 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7414 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7415 fromY, fromX, toY, toX, promoChar,
7416 parseList[forwardMostMove - 1]);
7417 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7418 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7419 castlingRights[forwardMostMove]) ) {
7425 if(gameInfo.variant != VariantShogi)
7426 strcat(parseList[forwardMostMove - 1], "+");
7430 strcat(parseList[forwardMostMove - 1], "#");
7433 if (appData.debugMode) {
7434 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7439 /* Updates currentMove if not pausing */
7441 ShowMove(fromX, fromY, toX, toY)
7443 int instant = (gameMode == PlayFromGameFile) ?
7444 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7445 if(appData.noGUI) return;
7446 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7448 if (forwardMostMove == currentMove + 1) {
7449 AnimateMove(boards[forwardMostMove - 1],
7450 fromX, fromY, toX, toY);
7452 if (appData.highlightLastMove) {
7453 SetHighlights(fromX, fromY, toX, toY);
7456 currentMove = forwardMostMove;
7459 if (instant) return;
7461 DisplayMove(currentMove - 1);
7462 DrawPosition(FALSE, boards[currentMove]);
7463 DisplayBothClocks();
7464 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7467 void SendEgtPath(ChessProgramState *cps)
7468 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7469 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7471 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7474 char c, *q = name+1, *r, *s;
7476 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7477 while(*p && *p != ',') *q++ = *p++;
7479 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7480 strcmp(name, ",nalimov:") == 0 ) {
7481 // take nalimov path from the menu-changeable option first, if it is defined
7482 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7483 SendToProgram(buf,cps); // send egtbpath command for nalimov
7485 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7486 (s = StrStr(appData.egtFormats, name)) != NULL) {
7487 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7488 s = r = StrStr(s, ":") + 1; // beginning of path info
7489 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7490 c = *r; *r = 0; // temporarily null-terminate path info
7491 *--q = 0; // strip of trailig ':' from name
7492 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7494 SendToProgram(buf,cps); // send egtbpath command for this format
7496 if(*p == ',') p++; // read away comma to position for next format name
7501 InitChessProgram(cps, setup)
7502 ChessProgramState *cps;
7503 int setup; /* [HGM] needed to setup FRC opening position */
7505 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7506 if (appData.noChessProgram) return;
7507 hintRequested = FALSE;
7508 bookRequested = FALSE;
7510 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7511 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7512 if(cps->memSize) { /* [HGM] memory */
7513 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7514 SendToProgram(buf, cps);
7516 SendEgtPath(cps); /* [HGM] EGT */
7517 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7518 sprintf(buf, "cores %d\n", appData.smpCores);
7519 SendToProgram(buf, cps);
7522 SendToProgram(cps->initString, cps);
7523 if (gameInfo.variant != VariantNormal &&
7524 gameInfo.variant != VariantLoadable
7525 /* [HGM] also send variant if board size non-standard */
7526 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7528 char *v = VariantName(gameInfo.variant);
7529 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7530 /* [HGM] in protocol 1 we have to assume all variants valid */
7531 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7532 DisplayFatalError(buf, 0, 1);
7536 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7537 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7538 if( gameInfo.variant == VariantXiangqi )
7539 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7540 if( gameInfo.variant == VariantShogi )
7541 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7542 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7543 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7544 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7545 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7546 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7547 if( gameInfo.variant == VariantCourier )
7548 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7549 if( gameInfo.variant == VariantSuper )
7550 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7551 if( gameInfo.variant == VariantGreat )
7552 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7555 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7556 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7557 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7558 if(StrStr(cps->variants, b) == NULL) {
7559 // specific sized variant not known, check if general sizing allowed
7560 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7561 if(StrStr(cps->variants, "boardsize") == NULL) {
7562 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7563 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7564 DisplayFatalError(buf, 0, 1);
7567 /* [HGM] here we really should compare with the maximum supported board size */
7570 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7571 sprintf(buf, "variant %s\n", b);
7572 SendToProgram(buf, cps);
7574 currentlyInitializedVariant = gameInfo.variant;
7576 /* [HGM] send opening position in FRC to first engine */
7578 SendToProgram("force\n", cps);
7580 /* engine is now in force mode! Set flag to wake it up after first move. */
7581 setboardSpoiledMachineBlack = 1;
7585 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7586 SendToProgram(buf, cps);
7588 cps->maybeThinking = FALSE;
7589 cps->offeredDraw = 0;
7590 if (!appData.icsActive) {
7591 SendTimeControl(cps, movesPerSession, timeControl,
7592 timeIncrement, appData.searchDepth,
7595 if (appData.showThinking
7596 // [HGM] thinking: four options require thinking output to be sent
7597 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7599 SendToProgram("post\n", cps);
7601 SendToProgram("hard\n", cps);
7602 if (!appData.ponderNextMove) {
7603 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7604 it without being sure what state we are in first. "hard"
7605 is not a toggle, so that one is OK.
7607 SendToProgram("easy\n", cps);
7610 sprintf(buf, "ping %d\n", ++cps->lastPing);
7611 SendToProgram(buf, cps);
7613 cps->initDone = TRUE;
7618 StartChessProgram(cps)
7619 ChessProgramState *cps;
7624 if (appData.noChessProgram) return;
7625 cps->initDone = FALSE;
7627 if (strcmp(cps->host, "localhost") == 0) {
7628 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7629 } else if (*appData.remoteShell == NULLCHAR) {
7630 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7632 if (*appData.remoteUser == NULLCHAR) {
7633 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7636 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7637 cps->host, appData.remoteUser, cps->program);
7639 err = StartChildProcess(buf, "", &cps->pr);
7643 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7644 DisplayFatalError(buf, err, 1);
7650 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7651 if (cps->protocolVersion > 1) {
7652 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7653 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7654 cps->comboCnt = 0; // and values of combo boxes
7655 SendToProgram(buf, cps);
7657 SendToProgram("xboard\n", cps);
7663 TwoMachinesEventIfReady P((void))
7665 if (first.lastPing != first.lastPong) {
7666 DisplayMessage("", _("Waiting for first chess program"));
7667 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7670 if (second.lastPing != second.lastPong) {
7671 DisplayMessage("", _("Waiting for second chess program"));
7672 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7680 NextMatchGame P((void))
7682 int index; /* [HGM] autoinc: step lod index during match */
7684 if (*appData.loadGameFile != NULLCHAR) {
7685 index = appData.loadGameIndex;
7686 if(index < 0) { // [HGM] autoinc
7687 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7688 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7690 LoadGameFromFile(appData.loadGameFile,
7692 appData.loadGameFile, FALSE);
7693 } else if (*appData.loadPositionFile != NULLCHAR) {
7694 index = appData.loadPositionIndex;
7695 if(index < 0) { // [HGM] autoinc
7696 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7697 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7699 LoadPositionFromFile(appData.loadPositionFile,
7701 appData.loadPositionFile);
7703 TwoMachinesEventIfReady();
7706 void UserAdjudicationEvent( int result )
7708 ChessMove gameResult = GameIsDrawn;
7711 gameResult = WhiteWins;
7713 else if( result < 0 ) {
7714 gameResult = BlackWins;
7717 if( gameMode == TwoMachinesPlay ) {
7718 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7723 // [HGM] save: calculate checksum of game to make games easily identifiable
7724 int StringCheckSum(char *s)
7727 if(s==NULL) return 0;
7728 while(*s) i = i*259 + *s++;
7735 for(i=backwardMostMove; i<forwardMostMove; i++) {
7736 sum += pvInfoList[i].depth;
7737 sum += StringCheckSum(parseList[i]);
7738 sum += StringCheckSum(commentList[i]);
7741 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7742 return sum + StringCheckSum(commentList[i]);
7743 } // end of save patch
7746 GameEnds(result, resultDetails, whosays)
7748 char *resultDetails;
7751 GameMode nextGameMode;
7755 if(endingGame) return; /* [HGM] crash: forbid recursion */
7758 if (appData.debugMode) {
7759 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7760 result, resultDetails ? resultDetails : "(null)", whosays);
7763 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7764 /* If we are playing on ICS, the server decides when the
7765 game is over, but the engine can offer to draw, claim
7769 if (appData.zippyPlay && first.initDone) {
7770 if (result == GameIsDrawn) {
7771 /* In case draw still needs to be claimed */
7772 SendToICS(ics_prefix);
7773 SendToICS("draw\n");
7774 } else if (StrCaseStr(resultDetails, "resign")) {
7775 SendToICS(ics_prefix);
7776 SendToICS("resign\n");
7780 endingGame = 0; /* [HGM] crash */
7784 /* If we're loading the game from a file, stop */
7785 if (whosays == GE_FILE) {
7786 (void) StopLoadGameTimer();
7790 /* Cancel draw offers */
7791 first.offeredDraw = second.offeredDraw = 0;
7793 /* If this is an ICS game, only ICS can really say it's done;
7794 if not, anyone can. */
7795 isIcsGame = (gameMode == IcsPlayingWhite ||
7796 gameMode == IcsPlayingBlack ||
7797 gameMode == IcsObserving ||
7798 gameMode == IcsExamining);
7800 if (!isIcsGame || whosays == GE_ICS) {
7801 /* OK -- not an ICS game, or ICS said it was done */
7803 if (!isIcsGame && !appData.noChessProgram)
7804 SetUserThinkingEnables();
7806 /* [HGM] if a machine claims the game end we verify this claim */
7807 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7808 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7810 ChessMove trueResult = (ChessMove) -1;
7812 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7813 first.twoMachinesColor[0] :
7814 second.twoMachinesColor[0] ;
7816 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7817 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7818 /* [HGM] verify: engine mate claims accepted if they were flagged */
7819 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7821 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7822 /* [HGM] verify: engine mate claims accepted if they were flagged */
7823 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7825 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7826 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7829 // now verify win claims, but not in drop games, as we don't understand those yet
7830 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7831 || gameInfo.variant == VariantGreat) &&
7832 (result == WhiteWins && claimer == 'w' ||
7833 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7834 if (appData.debugMode) {
7835 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7836 result, epStatus[forwardMostMove], forwardMostMove);
7838 if(result != trueResult) {
7839 sprintf(buf, "False win claim: '%s'", resultDetails);
7840 result = claimer == 'w' ? BlackWins : WhiteWins;
7841 resultDetails = buf;
7844 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7845 && (forwardMostMove <= backwardMostMove ||
7846 epStatus[forwardMostMove-1] > EP_DRAWS ||
7847 (claimer=='b')==(forwardMostMove&1))
7849 /* [HGM] verify: draws that were not flagged are false claims */
7850 sprintf(buf, "False draw claim: '%s'", resultDetails);
7851 result = claimer == 'w' ? BlackWins : WhiteWins;
7852 resultDetails = buf;
7854 /* (Claiming a loss is accepted no questions asked!) */
7856 /* [HGM] bare: don't allow bare King to win */
7857 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7858 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7859 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7860 && result != GameIsDrawn)
7861 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7862 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7863 int p = (int)boards[forwardMostMove][i][j] - color;
7864 if(p >= 0 && p <= (int)WhiteKing) k++;
7866 if (appData.debugMode) {
7867 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7868 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7871 result = GameIsDrawn;
7872 sprintf(buf, "%s but bare king", resultDetails);
7873 resultDetails = buf;
7879 if(serverMoves != NULL && !loadFlag) { char c = '=';
7880 if(result==WhiteWins) c = '+';
7881 if(result==BlackWins) c = '-';
7882 if(resultDetails != NULL)
7883 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7885 if (resultDetails != NULL) {
7886 gameInfo.result = result;
7887 gameInfo.resultDetails = StrSave(resultDetails);
7889 /* display last move only if game was not loaded from file */
7890 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7891 DisplayMove(currentMove - 1);
7893 if (forwardMostMove != 0) {
7894 if (gameMode != PlayFromGameFile && gameMode != EditGame
7895 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7897 if (*appData.saveGameFile != NULLCHAR) {
7898 SaveGameToFile(appData.saveGameFile, TRUE);
7899 } else if (appData.autoSaveGames) {
7902 if (*appData.savePositionFile != NULLCHAR) {
7903 SavePositionToFile(appData.savePositionFile);
7908 /* Tell program how game ended in case it is learning */
7909 /* [HGM] Moved this to after saving the PGN, just in case */
7910 /* engine died and we got here through time loss. In that */
7911 /* case we will get a fatal error writing the pipe, which */
7912 /* would otherwise lose us the PGN. */
7913 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7914 /* output during GameEnds should never be fatal anymore */
7915 if (gameMode == MachinePlaysWhite ||
7916 gameMode == MachinePlaysBlack ||
7917 gameMode == TwoMachinesPlay ||
7918 gameMode == IcsPlayingWhite ||
7919 gameMode == IcsPlayingBlack ||
7920 gameMode == BeginningOfGame) {
7922 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7924 if (first.pr != NoProc) {
7925 SendToProgram(buf, &first);
7927 if (second.pr != NoProc &&
7928 gameMode == TwoMachinesPlay) {
7929 SendToProgram(buf, &second);
7934 if (appData.icsActive) {
7935 if (appData.quietPlay &&
7936 (gameMode == IcsPlayingWhite ||
7937 gameMode == IcsPlayingBlack)) {
7938 SendToICS(ics_prefix);
7939 SendToICS("set shout 1\n");
7941 nextGameMode = IcsIdle;
7942 ics_user_moved = FALSE;
7943 /* clean up premove. It's ugly when the game has ended and the
7944 * premove highlights are still on the board.
7948 ClearPremoveHighlights();
7949 DrawPosition(FALSE, boards[currentMove]);
7951 if (whosays == GE_ICS) {
7954 if (gameMode == IcsPlayingWhite)
7956 else if(gameMode == IcsPlayingBlack)
7960 if (gameMode == IcsPlayingBlack)
7962 else if(gameMode == IcsPlayingWhite)
7969 PlayIcsUnfinishedSound();
7972 } else if (gameMode == EditGame ||
7973 gameMode == PlayFromGameFile ||
7974 gameMode == AnalyzeMode ||
7975 gameMode == AnalyzeFile) {
7976 nextGameMode = gameMode;
7978 nextGameMode = EndOfGame;
7983 nextGameMode = gameMode;
7986 if (appData.noChessProgram) {
7987 gameMode = nextGameMode;
7989 endingGame = 0; /* [HGM] crash */
7994 /* Put first chess program into idle state */
7995 if (first.pr != NoProc &&
7996 (gameMode == MachinePlaysWhite ||
7997 gameMode == MachinePlaysBlack ||
7998 gameMode == TwoMachinesPlay ||
7999 gameMode == IcsPlayingWhite ||
8000 gameMode == IcsPlayingBlack ||
8001 gameMode == BeginningOfGame)) {
8002 SendToProgram("force\n", &first);
8003 if (first.usePing) {
8005 sprintf(buf, "ping %d\n", ++first.lastPing);
8006 SendToProgram(buf, &first);
8009 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8010 /* Kill off first chess program */
8011 if (first.isr != NULL)
8012 RemoveInputSource(first.isr);
8015 if (first.pr != NoProc) {
8017 DoSleep( appData.delayBeforeQuit );
8018 SendToProgram("quit\n", &first);
8019 DoSleep( appData.delayAfterQuit );
8020 DestroyChildProcess(first.pr, first.useSigterm);
8025 /* Put second chess program into idle state */
8026 if (second.pr != NoProc &&
8027 gameMode == TwoMachinesPlay) {
8028 SendToProgram("force\n", &second);
8029 if (second.usePing) {
8031 sprintf(buf, "ping %d\n", ++second.lastPing);
8032 SendToProgram(buf, &second);
8035 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8036 /* Kill off second chess program */
8037 if (second.isr != NULL)
8038 RemoveInputSource(second.isr);
8041 if (second.pr != NoProc) {
8042 DoSleep( appData.delayBeforeQuit );
8043 SendToProgram("quit\n", &second);
8044 DoSleep( appData.delayAfterQuit );
8045 DestroyChildProcess(second.pr, second.useSigterm);
8050 if (matchMode && gameMode == TwoMachinesPlay) {
8053 if (first.twoMachinesColor[0] == 'w') {
8060 if (first.twoMachinesColor[0] == 'b') {
8069 if (matchGame < appData.matchGames) {
8071 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8072 tmp = first.twoMachinesColor;
8073 first.twoMachinesColor = second.twoMachinesColor;
8074 second.twoMachinesColor = tmp;
8076 gameMode = nextGameMode;
8078 if(appData.matchPause>10000 || appData.matchPause<10)
8079 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8080 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8081 endingGame = 0; /* [HGM] crash */
8085 gameMode = nextGameMode;
8086 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8087 first.tidy, second.tidy,
8088 first.matchWins, second.matchWins,
8089 appData.matchGames - (first.matchWins + second.matchWins));
8090 DisplayFatalError(buf, 0, 0);
8093 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8094 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8096 gameMode = nextGameMode;
8098 endingGame = 0; /* [HGM] crash */
8101 /* Assumes program was just initialized (initString sent).
8102 Leaves program in force mode. */
8104 FeedMovesToProgram(cps, upto)
8105 ChessProgramState *cps;
8110 if (appData.debugMode)
8111 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8112 startedFromSetupPosition ? "position and " : "",
8113 backwardMostMove, upto, cps->which);
8114 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8115 // [HGM] variantswitch: make engine aware of new variant
8116 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8117 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8118 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8119 SendToProgram(buf, cps);
8120 currentlyInitializedVariant = gameInfo.variant;
8122 SendToProgram("force\n", cps);
8123 if (startedFromSetupPosition) {
8124 SendBoard(cps, backwardMostMove);
8125 if (appData.debugMode) {
8126 fprintf(debugFP, "feedMoves\n");
8129 for (i = backwardMostMove; i < upto; i++) {
8130 SendMoveToProgram(i, cps);
8136 ResurrectChessProgram()
8138 /* The chess program may have exited.
8139 If so, restart it and feed it all the moves made so far. */
8141 if (appData.noChessProgram || first.pr != NoProc) return;
8143 StartChessProgram(&first);
8144 InitChessProgram(&first, FALSE);
8145 FeedMovesToProgram(&first, currentMove);
8147 if (!first.sendTime) {
8148 /* can't tell gnuchess what its clock should read,
8149 so we bow to its notion. */
8151 timeRemaining[0][currentMove] = whiteTimeRemaining;
8152 timeRemaining[1][currentMove] = blackTimeRemaining;
8155 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8156 appData.icsEngineAnalyze) && first.analysisSupport) {
8157 SendToProgram("analyze\n", &first);
8158 first.analyzing = TRUE;
8171 if (appData.debugMode) {
8172 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8173 redraw, init, gameMode);
8175 pausing = pauseExamInvalid = FALSE;
8176 startedFromSetupPosition = blackPlaysFirst = FALSE;
8178 whiteFlag = blackFlag = FALSE;
8179 userOfferedDraw = FALSE;
8180 hintRequested = bookRequested = FALSE;
8181 first.maybeThinking = FALSE;
8182 second.maybeThinking = FALSE;
8183 first.bookSuspend = FALSE; // [HGM] book
8184 second.bookSuspend = FALSE;
8185 thinkOutput[0] = NULLCHAR;
8186 lastHint[0] = NULLCHAR;
8187 ClearGameInfo(&gameInfo);
8188 gameInfo.variant = StringToVariant(appData.variant);
8189 ics_user_moved = ics_clock_paused = FALSE;
8190 ics_getting_history = H_FALSE;
8192 white_holding[0] = black_holding[0] = NULLCHAR;
8193 ClearProgramStats();
8194 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8198 flipView = appData.flipView;
8199 ClearPremoveHighlights();
8201 alarmSounded = FALSE;
8203 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8204 if(appData.serverMovesName != NULL) {
8205 /* [HGM] prepare to make moves file for broadcasting */
8206 clock_t t = clock();
8207 if(serverMoves != NULL) fclose(serverMoves);
8208 serverMoves = fopen(appData.serverMovesName, "r");
8209 if(serverMoves != NULL) {
8210 fclose(serverMoves);
8211 /* delay 15 sec before overwriting, so all clients can see end */
8212 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8214 serverMoves = fopen(appData.serverMovesName, "w");
8218 gameMode = BeginningOfGame;
8220 if(appData.icsActive) gameInfo.variant = VariantNormal;
8221 InitPosition(redraw);
8222 for (i = 0; i < MAX_MOVES; i++) {
8223 if (commentList[i] != NULL) {
8224 free(commentList[i]);
8225 commentList[i] = NULL;
8229 timeRemaining[0][0] = whiteTimeRemaining;
8230 timeRemaining[1][0] = blackTimeRemaining;
8231 if (first.pr == NULL) {
8232 StartChessProgram(&first);
8235 InitChessProgram(&first, startedFromSetupPosition);
8238 DisplayMessage("", "");
8239 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8240 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8247 if (!AutoPlayOneMove())
8249 if (matchMode || appData.timeDelay == 0)
8251 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8253 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8262 int fromX, fromY, toX, toY;
8264 if (appData.debugMode) {
8265 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8268 if (gameMode != PlayFromGameFile)
8271 if (currentMove >= forwardMostMove) {
8272 gameMode = EditGame;
8275 /* [AS] Clear current move marker at the end of a game */
8276 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8281 toX = moveList[currentMove][2] - AAA;
8282 toY = moveList[currentMove][3] - ONE;
8284 if (moveList[currentMove][1] == '@') {
8285 if (appData.highlightLastMove) {
8286 SetHighlights(-1, -1, toX, toY);
8289 fromX = moveList[currentMove][0] - AAA;
8290 fromY = moveList[currentMove][1] - ONE;
8292 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8294 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8296 if (appData.highlightLastMove) {
8297 SetHighlights(fromX, fromY, toX, toY);
8300 DisplayMove(currentMove);
8301 SendMoveToProgram(currentMove++, &first);
8302 DisplayBothClocks();
8303 DrawPosition(FALSE, boards[currentMove]);
8304 // [HGM] PV info: always display, routine tests if empty
8305 DisplayComment(currentMove - 1, commentList[currentMove]);
8311 LoadGameOneMove(readAhead)
8312 ChessMove readAhead;
8314 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8315 char promoChar = NULLCHAR;
8320 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8321 gameMode != AnalyzeMode && gameMode != Training) {
8326 yyboardindex = forwardMostMove;
8327 if (readAhead != (ChessMove)0) {
8328 moveType = readAhead;
8330 if (gameFileFP == NULL)
8332 moveType = (ChessMove) yylex();
8338 if (appData.debugMode)
8339 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8341 if (*p == '{' || *p == '[' || *p == '(') {
8342 p[strlen(p) - 1] = NULLCHAR;
8346 /* append the comment but don't display it */
8347 while (*p == '\n') p++;
8348 AppendComment(currentMove, p);
8351 case WhiteCapturesEnPassant:
8352 case BlackCapturesEnPassant:
8353 case WhitePromotionChancellor:
8354 case BlackPromotionChancellor:
8355 case WhitePromotionArchbishop:
8356 case BlackPromotionArchbishop:
8357 case WhitePromotionCentaur:
8358 case BlackPromotionCentaur:
8359 case WhitePromotionQueen:
8360 case BlackPromotionQueen:
8361 case WhitePromotionRook:
8362 case BlackPromotionRook:
8363 case WhitePromotionBishop:
8364 case BlackPromotionBishop:
8365 case WhitePromotionKnight:
8366 case BlackPromotionKnight:
8367 case WhitePromotionKing:
8368 case BlackPromotionKing:
8370 case WhiteKingSideCastle:
8371 case WhiteQueenSideCastle:
8372 case BlackKingSideCastle:
8373 case BlackQueenSideCastle:
8374 case WhiteKingSideCastleWild:
8375 case WhiteQueenSideCastleWild:
8376 case BlackKingSideCastleWild:
8377 case BlackQueenSideCastleWild:
8379 case WhiteHSideCastleFR:
8380 case WhiteASideCastleFR:
8381 case BlackHSideCastleFR:
8382 case BlackASideCastleFR:
8384 if (appData.debugMode)
8385 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8386 fromX = currentMoveString[0] - AAA;
8387 fromY = currentMoveString[1] - ONE;
8388 toX = currentMoveString[2] - AAA;
8389 toY = currentMoveString[3] - ONE;
8390 promoChar = currentMoveString[4];
8395 if (appData.debugMode)
8396 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8397 fromX = moveType == WhiteDrop ?
8398 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8399 (int) CharToPiece(ToLower(currentMoveString[0]));
8401 toX = currentMoveString[2] - AAA;
8402 toY = currentMoveString[3] - ONE;
8408 case GameUnfinished:
8409 if (appData.debugMode)
8410 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8411 p = strchr(yy_text, '{');
8412 if (p == NULL) p = strchr(yy_text, '(');
8415 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8417 q = strchr(p, *p == '{' ? '}' : ')');
8418 if (q != NULL) *q = NULLCHAR;
8421 GameEnds(moveType, p, GE_FILE);
8423 if (cmailMsgLoaded) {
8425 flipView = WhiteOnMove(currentMove);
8426 if (moveType == GameUnfinished) flipView = !flipView;
8427 if (appData.debugMode)
8428 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8432 case (ChessMove) 0: /* end of file */
8433 if (appData.debugMode)
8434 fprintf(debugFP, "Parser hit end of file\n");
8435 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8436 EP_UNKNOWN, castlingRights[currentMove]) ) {
8442 if (WhiteOnMove(currentMove)) {
8443 GameEnds(BlackWins, "Black mates", GE_FILE);
8445 GameEnds(WhiteWins, "White mates", GE_FILE);
8449 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8456 if (lastLoadGameStart == GNUChessGame) {
8457 /* GNUChessGames have numbers, but they aren't move numbers */
8458 if (appData.debugMode)
8459 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8460 yy_text, (int) moveType);
8461 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8463 /* else fall thru */
8468 /* Reached start of next game in file */
8469 if (appData.debugMode)
8470 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8471 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8472 EP_UNKNOWN, castlingRights[currentMove]) ) {
8478 if (WhiteOnMove(currentMove)) {
8479 GameEnds(BlackWins, "Black mates", GE_FILE);
8481 GameEnds(WhiteWins, "White mates", GE_FILE);
8485 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8491 case PositionDiagram: /* should not happen; ignore */
8492 case ElapsedTime: /* ignore */
8493 case NAG: /* ignore */
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8496 yy_text, (int) moveType);
8497 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8500 if (appData.testLegality) {
8501 if (appData.debugMode)
8502 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8503 sprintf(move, _("Illegal move: %d.%s%s"),
8504 (forwardMostMove / 2) + 1,
8505 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8506 DisplayError(move, 0);
8509 if (appData.debugMode)
8510 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8511 yy_text, currentMoveString);
8512 fromX = currentMoveString[0] - AAA;
8513 fromY = currentMoveString[1] - ONE;
8514 toX = currentMoveString[2] - AAA;
8515 toY = currentMoveString[3] - ONE;
8516 promoChar = currentMoveString[4];
8521 if (appData.debugMode)
8522 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8523 sprintf(move, _("Ambiguous move: %d.%s%s"),
8524 (forwardMostMove / 2) + 1,
8525 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8526 DisplayError(move, 0);
8531 case ImpossibleMove:
8532 if (appData.debugMode)
8533 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8534 sprintf(move, _("Illegal move: %d.%s%s"),
8535 (forwardMostMove / 2) + 1,
8536 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8537 DisplayError(move, 0);
8543 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8544 DrawPosition(FALSE, boards[currentMove]);
8545 DisplayBothClocks();
8546 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8547 DisplayComment(currentMove - 1, commentList[currentMove]);
8549 (void) StopLoadGameTimer();
8551 cmailOldMove = forwardMostMove;
8554 /* currentMoveString is set as a side-effect of yylex */
8555 strcat(currentMoveString, "\n");
8556 strcpy(moveList[forwardMostMove], currentMoveString);
8558 thinkOutput[0] = NULLCHAR;
8559 MakeMove(fromX, fromY, toX, toY, promoChar);
8560 currentMove = forwardMostMove;
8565 /* Load the nth game from the given file */
8567 LoadGameFromFile(filename, n, title, useList)
8571 /*Boolean*/ int useList;
8576 if (strcmp(filename, "-") == 0) {
8580 f = fopen(filename, "rb");
8582 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8583 DisplayError(buf, errno);
8587 if (fseek(f, 0, 0) == -1) {
8588 /* f is not seekable; probably a pipe */
8591 if (useList && n == 0) {
8592 int error = GameListBuild(f);
8594 DisplayError(_("Cannot build game list"), error);
8595 } else if (!ListEmpty(&gameList) &&
8596 ((ListGame *) gameList.tailPred)->number > 1) {
8597 GameListPopUp(f, title);
8604 return LoadGame(f, n, title, FALSE);
8609 MakeRegisteredMove()
8611 int fromX, fromY, toX, toY;
8613 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8614 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8617 if (appData.debugMode)
8618 fprintf(debugFP, "Restoring %s for game %d\n",
8619 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8621 thinkOutput[0] = NULLCHAR;
8622 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8623 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8624 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8625 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8626 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8627 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8628 MakeMove(fromX, fromY, toX, toY, promoChar);
8629 ShowMove(fromX, fromY, toX, toY);
8631 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8632 EP_UNKNOWN, castlingRights[currentMove]) ) {
8639 if (WhiteOnMove(currentMove)) {
8640 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8642 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8647 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8654 if (WhiteOnMove(currentMove)) {
8655 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8657 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8662 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8673 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8675 CmailLoadGame(f, gameNumber, title, useList)
8683 if (gameNumber > nCmailGames) {
8684 DisplayError(_("No more games in this message"), 0);
8687 if (f == lastLoadGameFP) {
8688 int offset = gameNumber - lastLoadGameNumber;
8690 cmailMsg[0] = NULLCHAR;
8691 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8692 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8693 nCmailMovesRegistered--;
8695 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8696 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8697 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8700 if (! RegisterMove()) return FALSE;
8704 retVal = LoadGame(f, gameNumber, title, useList);
8706 /* Make move registered during previous look at this game, if any */
8707 MakeRegisteredMove();
8709 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8710 commentList[currentMove]
8711 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8712 DisplayComment(currentMove - 1, commentList[currentMove]);
8718 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8723 int gameNumber = lastLoadGameNumber + offset;
8724 if (lastLoadGameFP == NULL) {
8725 DisplayError(_("No game has been loaded yet"), 0);
8728 if (gameNumber <= 0) {
8729 DisplayError(_("Can't back up any further"), 0);
8732 if (cmailMsgLoaded) {
8733 return CmailLoadGame(lastLoadGameFP, gameNumber,
8734 lastLoadGameTitle, lastLoadGameUseList);
8736 return LoadGame(lastLoadGameFP, gameNumber,
8737 lastLoadGameTitle, lastLoadGameUseList);
8743 /* Load the nth game from open file f */
8745 LoadGame(f, gameNumber, title, useList)
8753 int gn = gameNumber;
8754 ListGame *lg = NULL;
8757 GameMode oldGameMode;
8758 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8760 if (appData.debugMode)
8761 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8763 if (gameMode == Training )
8764 SetTrainingModeOff();
8766 oldGameMode = gameMode;
8767 if (gameMode != BeginningOfGame) {
8772 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8773 fclose(lastLoadGameFP);
8777 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8780 fseek(f, lg->offset, 0);
8781 GameListHighlight(gameNumber);
8785 DisplayError(_("Game number out of range"), 0);
8790 if (fseek(f, 0, 0) == -1) {
8791 if (f == lastLoadGameFP ?
8792 gameNumber == lastLoadGameNumber + 1 :
8796 DisplayError(_("Can't seek on game file"), 0);
8802 lastLoadGameNumber = gameNumber;
8803 strcpy(lastLoadGameTitle, title);
8804 lastLoadGameUseList = useList;
8808 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8809 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8810 lg->gameInfo.black);
8812 } else if (*title != NULLCHAR) {
8813 if (gameNumber > 1) {
8814 sprintf(buf, "%s %d", title, gameNumber);
8817 DisplayTitle(title);
8821 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8822 gameMode = PlayFromGameFile;
8826 currentMove = forwardMostMove = backwardMostMove = 0;
8827 CopyBoard(boards[0], initialPosition);
8831 * Skip the first gn-1 games in the file.
8832 * Also skip over anything that precedes an identifiable
8833 * start of game marker, to avoid being confused by
8834 * garbage at the start of the file. Currently
8835 * recognized start of game markers are the move number "1",
8836 * the pattern "gnuchess .* game", the pattern
8837 * "^[#;%] [^ ]* game file", and a PGN tag block.
8838 * A game that starts with one of the latter two patterns
8839 * will also have a move number 1, possibly
8840 * following a position diagram.
8841 * 5-4-02: Let's try being more lenient and allowing a game to
8842 * start with an unnumbered move. Does that break anything?
8844 cm = lastLoadGameStart = (ChessMove) 0;
8846 yyboardindex = forwardMostMove;
8847 cm = (ChessMove) yylex();
8850 if (cmailMsgLoaded) {
8851 nCmailGames = CMAIL_MAX_GAMES - gn;
8854 DisplayError(_("Game not found in file"), 0);
8861 lastLoadGameStart = cm;
8865 switch (lastLoadGameStart) {
8872 gn--; /* count this game */
8873 lastLoadGameStart = cm;
8882 switch (lastLoadGameStart) {
8887 gn--; /* count this game */
8888 lastLoadGameStart = cm;
8891 lastLoadGameStart = cm; /* game counted already */
8899 yyboardindex = forwardMostMove;
8900 cm = (ChessMove) yylex();
8901 } while (cm == PGNTag || cm == Comment);
8908 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8909 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8910 != CMAIL_OLD_RESULT) {
8912 cmailResult[ CMAIL_MAX_GAMES
8913 - gn - 1] = CMAIL_OLD_RESULT;
8919 /* Only a NormalMove can be at the start of a game
8920 * without a position diagram. */
8921 if (lastLoadGameStart == (ChessMove) 0) {
8923 lastLoadGameStart = MoveNumberOne;
8932 if (appData.debugMode)
8933 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8935 if (cm == XBoardGame) {
8936 /* Skip any header junk before position diagram and/or move 1 */
8938 yyboardindex = forwardMostMove;
8939 cm = (ChessMove) yylex();
8941 if (cm == (ChessMove) 0 ||
8942 cm == GNUChessGame || cm == XBoardGame) {
8943 /* Empty game; pretend end-of-file and handle later */
8948 if (cm == MoveNumberOne || cm == PositionDiagram ||
8949 cm == PGNTag || cm == Comment)
8952 } else if (cm == GNUChessGame) {
8953 if (gameInfo.event != NULL) {
8954 free(gameInfo.event);
8956 gameInfo.event = StrSave(yy_text);
8959 startedFromSetupPosition = FALSE;
8960 while (cm == PGNTag) {
8961 if (appData.debugMode)
8962 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8963 err = ParsePGNTag(yy_text, &gameInfo);
8964 if (!err) numPGNTags++;
8966 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8967 if(gameInfo.variant != oldVariant) {
8968 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8970 oldVariant = gameInfo.variant;
8971 if (appData.debugMode)
8972 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8976 if (gameInfo.fen != NULL) {
8977 Board initial_position;
8978 startedFromSetupPosition = TRUE;
8979 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8981 DisplayError(_("Bad FEN position in file"), 0);
8984 CopyBoard(boards[0], initial_position);
8985 if (blackPlaysFirst) {
8986 currentMove = forwardMostMove = backwardMostMove = 1;
8987 CopyBoard(boards[1], initial_position);
8988 strcpy(moveList[0], "");
8989 strcpy(parseList[0], "");
8990 timeRemaining[0][1] = whiteTimeRemaining;
8991 timeRemaining[1][1] = blackTimeRemaining;
8992 if (commentList[0] != NULL) {
8993 commentList[1] = commentList[0];
8994 commentList[0] = NULL;
8997 currentMove = forwardMostMove = backwardMostMove = 0;
8999 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9001 initialRulePlies = FENrulePlies;
9002 epStatus[forwardMostMove] = FENepStatus;
9003 for( i=0; i< nrCastlingRights; i++ )
9004 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9006 yyboardindex = forwardMostMove;
9008 gameInfo.fen = NULL;
9011 yyboardindex = forwardMostMove;
9012 cm = (ChessMove) yylex();
9014 /* Handle comments interspersed among the tags */
9015 while (cm == Comment) {
9017 if (appData.debugMode)
9018 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9020 if (*p == '{' || *p == '[' || *p == '(') {
9021 p[strlen(p) - 1] = NULLCHAR;
9024 while (*p == '\n') p++;
9025 AppendComment(currentMove, p);
9026 yyboardindex = forwardMostMove;
9027 cm = (ChessMove) yylex();
9031 /* don't rely on existence of Event tag since if game was
9032 * pasted from clipboard the Event tag may not exist
9034 if (numPGNTags > 0){
9036 if (gameInfo.variant == VariantNormal) {
9037 gameInfo.variant = StringToVariant(gameInfo.event);
9040 if( appData.autoDisplayTags ) {
9041 tags = PGNTags(&gameInfo);
9042 TagsPopUp(tags, CmailMsg());
9047 /* Make something up, but don't display it now */
9052 if (cm == PositionDiagram) {
9055 Board initial_position;
9057 if (appData.debugMode)
9058 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9060 if (!startedFromSetupPosition) {
9062 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9063 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9073 initial_position[i][j++] = CharToPiece(*p);
9076 while (*p == ' ' || *p == '\t' ||
9077 *p == '\n' || *p == '\r') p++;
9079 if (strncmp(p, "black", strlen("black"))==0)
9080 blackPlaysFirst = TRUE;
9082 blackPlaysFirst = FALSE;
9083 startedFromSetupPosition = TRUE;
9085 CopyBoard(boards[0], initial_position);
9086 if (blackPlaysFirst) {
9087 currentMove = forwardMostMove = backwardMostMove = 1;
9088 CopyBoard(boards[1], initial_position);
9089 strcpy(moveList[0], "");
9090 strcpy(parseList[0], "");
9091 timeRemaining[0][1] = whiteTimeRemaining;
9092 timeRemaining[1][1] = blackTimeRemaining;
9093 if (commentList[0] != NULL) {
9094 commentList[1] = commentList[0];
9095 commentList[0] = NULL;
9098 currentMove = forwardMostMove = backwardMostMove = 0;
9101 yyboardindex = forwardMostMove;
9102 cm = (ChessMove) yylex();
9105 if (first.pr == NoProc) {
9106 StartChessProgram(&first);
9108 InitChessProgram(&first, FALSE);
9109 SendToProgram("force\n", &first);
9110 if (startedFromSetupPosition) {
9111 SendBoard(&first, forwardMostMove);
9112 if (appData.debugMode) {
9113 fprintf(debugFP, "Load Game\n");
9115 DisplayBothClocks();
9118 /* [HGM] server: flag to write setup moves in broadcast file as one */
9119 loadFlag = appData.suppressLoadMoves;
9121 while (cm == Comment) {
9123 if (appData.debugMode)
9124 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9126 if (*p == '{' || *p == '[' || *p == '(') {
9127 p[strlen(p) - 1] = NULLCHAR;
9130 while (*p == '\n') p++;
9131 AppendComment(currentMove, p);
9132 yyboardindex = forwardMostMove;
9133 cm = (ChessMove) yylex();
9136 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9137 cm == WhiteWins || cm == BlackWins ||
9138 cm == GameIsDrawn || cm == GameUnfinished) {
9139 DisplayMessage("", _("No moves in game"));
9140 if (cmailMsgLoaded) {
9141 if (appData.debugMode)
9142 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9146 DrawPosition(FALSE, boards[currentMove]);
9147 DisplayBothClocks();
9148 gameMode = EditGame;
9155 // [HGM] PV info: routine tests if comment empty
9156 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9157 DisplayComment(currentMove - 1, commentList[currentMove]);
9159 if (!matchMode && appData.timeDelay != 0)
9160 DrawPosition(FALSE, boards[currentMove]);
9162 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9163 programStats.ok_to_send = 1;
9166 /* if the first token after the PGN tags is a move
9167 * and not move number 1, retrieve it from the parser
9169 if (cm != MoveNumberOne)
9170 LoadGameOneMove(cm);
9172 /* load the remaining moves from the file */
9173 while (LoadGameOneMove((ChessMove)0)) {
9174 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9175 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9178 /* rewind to the start of the game */
9179 currentMove = backwardMostMove;
9181 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9183 if (oldGameMode == AnalyzeFile ||
9184 oldGameMode == AnalyzeMode) {
9188 if (matchMode || appData.timeDelay == 0) {
9190 gameMode = EditGame;
9192 } else if (appData.timeDelay > 0) {
9196 if (appData.debugMode)
9197 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9199 loadFlag = 0; /* [HGM] true game starts */
9203 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9205 ReloadPosition(offset)
9208 int positionNumber = lastLoadPositionNumber + offset;
9209 if (lastLoadPositionFP == NULL) {
9210 DisplayError(_("No position has been loaded yet"), 0);
9213 if (positionNumber <= 0) {
9214 DisplayError(_("Can't back up any further"), 0);
9217 return LoadPosition(lastLoadPositionFP, positionNumber,
9218 lastLoadPositionTitle);
9221 /* Load the nth position from the given file */
9223 LoadPositionFromFile(filename, n, title)
9231 if (strcmp(filename, "-") == 0) {
9232 return LoadPosition(stdin, n, "stdin");
9234 f = fopen(filename, "rb");
9236 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9237 DisplayError(buf, errno);
9240 return LoadPosition(f, n, title);
9245 /* Load the nth position from the given open file, and close it */
9247 LoadPosition(f, positionNumber, title)
9252 char *p, line[MSG_SIZ];
9253 Board initial_position;
9254 int i, j, fenMode, pn;
9256 if (gameMode == Training )
9257 SetTrainingModeOff();
9259 if (gameMode != BeginningOfGame) {
9262 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9263 fclose(lastLoadPositionFP);
9265 if (positionNumber == 0) positionNumber = 1;
9266 lastLoadPositionFP = f;
9267 lastLoadPositionNumber = positionNumber;
9268 strcpy(lastLoadPositionTitle, title);
9269 if (first.pr == NoProc) {
9270 StartChessProgram(&first);
9271 InitChessProgram(&first, FALSE);
9273 pn = positionNumber;
9274 if (positionNumber < 0) {
9275 /* Negative position number means to seek to that byte offset */
9276 if (fseek(f, -positionNumber, 0) == -1) {
9277 DisplayError(_("Can't seek on position file"), 0);
9282 if (fseek(f, 0, 0) == -1) {
9283 if (f == lastLoadPositionFP ?
9284 positionNumber == lastLoadPositionNumber + 1 :
9285 positionNumber == 1) {
9288 DisplayError(_("Can't seek on position file"), 0);
9293 /* See if this file is FEN or old-style xboard */
9294 if (fgets(line, MSG_SIZ, f) == NULL) {
9295 DisplayError(_("Position not found in file"), 0);
9304 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9305 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9306 case '1': case '2': case '3': case '4': case '5': case '6':
9307 case '7': case '8': case '9':
9308 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9309 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9310 case 'C': case 'W': case 'c': case 'w':
9315 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9316 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9320 if (fenMode || line[0] == '#') pn--;
9322 /* skip positions before number pn */
9323 if (fgets(line, MSG_SIZ, f) == NULL) {
9325 DisplayError(_("Position not found in file"), 0);
9328 if (fenMode || line[0] == '#') pn--;
9333 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9334 DisplayError(_("Bad FEN position in file"), 0);
9338 (void) fgets(line, MSG_SIZ, f);
9339 (void) fgets(line, MSG_SIZ, f);
9341 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9342 (void) fgets(line, MSG_SIZ, f);
9343 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9346 initial_position[i][j++] = CharToPiece(*p);
9350 blackPlaysFirst = FALSE;
9352 (void) fgets(line, MSG_SIZ, f);
9353 if (strncmp(line, "black", strlen("black"))==0)
9354 blackPlaysFirst = TRUE;
9357 startedFromSetupPosition = TRUE;
9359 SendToProgram("force\n", &first);
9360 CopyBoard(boards[0], initial_position);
9361 if (blackPlaysFirst) {
9362 currentMove = forwardMostMove = backwardMostMove = 1;
9363 strcpy(moveList[0], "");
9364 strcpy(parseList[0], "");
9365 CopyBoard(boards[1], initial_position);
9366 DisplayMessage("", _("Black to play"));
9368 currentMove = forwardMostMove = backwardMostMove = 0;
9369 DisplayMessage("", _("White to play"));
9371 /* [HGM] copy FEN attributes as well */
9373 initialRulePlies = FENrulePlies;
9374 epStatus[forwardMostMove] = FENepStatus;
9375 for( i=0; i< nrCastlingRights; i++ )
9376 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9378 SendBoard(&first, forwardMostMove);
9379 if (appData.debugMode) {
9381 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9382 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9383 fprintf(debugFP, "Load Position\n");
9386 if (positionNumber > 1) {
9387 sprintf(line, "%s %d", title, positionNumber);
9390 DisplayTitle(title);
9392 gameMode = EditGame;
9395 timeRemaining[0][1] = whiteTimeRemaining;
9396 timeRemaining[1][1] = blackTimeRemaining;
9397 DrawPosition(FALSE, boards[currentMove]);
9404 CopyPlayerNameIntoFileName(dest, src)
9407 while (*src != NULLCHAR && *src != ',') {
9412 *(*dest)++ = *src++;
9417 char *DefaultFileName(ext)
9420 static char def[MSG_SIZ];
9423 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9425 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9427 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9436 /* Save the current game to the given file */
9438 SaveGameToFile(filename, append)
9445 if (strcmp(filename, "-") == 0) {
9446 return SaveGame(stdout, 0, NULL);
9448 f = fopen(filename, append ? "a" : "w");
9450 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9451 DisplayError(buf, errno);
9454 return SaveGame(f, 0, NULL);
9463 static char buf[MSG_SIZ];
9466 p = strchr(str, ' ');
9467 if (p == NULL) return str;
9468 strncpy(buf, str, p - str);
9469 buf[p - str] = NULLCHAR;
9473 #define PGN_MAX_LINE 75
9475 #define PGN_SIDE_WHITE 0
9476 #define PGN_SIDE_BLACK 1
9479 static int FindFirstMoveOutOfBook( int side )
9483 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9484 int index = backwardMostMove;
9485 int has_book_hit = 0;
9487 if( (index % 2) != side ) {
9491 while( index < forwardMostMove ) {
9492 /* Check to see if engine is in book */
9493 int depth = pvInfoList[index].depth;
9494 int score = pvInfoList[index].score;
9500 else if( score == 0 && depth == 63 ) {
9501 in_book = 1; /* Zappa */
9503 else if( score == 2 && depth == 99 ) {
9504 in_book = 1; /* Abrok */
9507 has_book_hit += in_book;
9523 void GetOutOfBookInfo( char * buf )
9527 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9529 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9530 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9534 if( oob[0] >= 0 || oob[1] >= 0 ) {
9535 for( i=0; i<2; i++ ) {
9539 if( i > 0 && oob[0] >= 0 ) {
9543 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9544 sprintf( buf+strlen(buf), "%s%.2f",
9545 pvInfoList[idx].score >= 0 ? "+" : "",
9546 pvInfoList[idx].score / 100.0 );
9552 /* Save game in PGN style and close the file */
9557 int i, offset, linelen, newblock;
9561 int movelen, numlen, blank;
9562 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9564 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9566 tm = time((time_t *) NULL);
9568 PrintPGNTags(f, &gameInfo);
9570 if (backwardMostMove > 0 || startedFromSetupPosition) {
9571 char *fen = PositionToFEN(backwardMostMove, NULL);
9572 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9573 fprintf(f, "\n{--------------\n");
9574 PrintPosition(f, backwardMostMove);
9575 fprintf(f, "--------------}\n");
9579 /* [AS] Out of book annotation */
9580 if( appData.saveOutOfBookInfo ) {
9583 GetOutOfBookInfo( buf );
9585 if( buf[0] != '\0' ) {
9586 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9593 i = backwardMostMove;
9597 while (i < forwardMostMove) {
9598 /* Print comments preceding this move */
9599 if (commentList[i] != NULL) {
9600 if (linelen > 0) fprintf(f, "\n");
9601 fprintf(f, "{\n%s}\n", commentList[i]);
9606 /* Format move number */
9608 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9611 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9613 numtext[0] = NULLCHAR;
9616 numlen = strlen(numtext);
9619 /* Print move number */
9620 blank = linelen > 0 && numlen > 0;
9621 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9630 fprintf(f, numtext);
9634 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9635 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9637 // SavePart already does this!
9638 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9639 int p = movelen - 1;
9640 if(move_buffer[p] == ' ') p--;
9641 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9642 while(p && move_buffer[--p] != '(');
9643 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9648 blank = linelen > 0 && movelen > 0;
9649 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9658 fprintf(f, move_buffer);
9661 /* [AS] Add PV info if present */
9662 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9663 /* [HGM] add time */
9664 char buf[MSG_SIZ]; int seconds = 0;
9667 if(i >= backwardMostMove) {
9669 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9670 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9672 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9673 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9675 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9677 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9680 if( seconds <= 0) buf[0] = 0; else
9681 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9682 seconds = (seconds + 4)/10; // round to full seconds
9683 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9684 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9687 sprintf( move_buffer, "{%s%.2f/%d%s}",
9688 pvInfoList[i].score >= 0 ? "+" : "",
9689 pvInfoList[i].score / 100.0,
9690 pvInfoList[i].depth,
9693 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9695 /* Print score/depth */
9696 blank = linelen > 0 && movelen > 0;
9697 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9706 fprintf(f, move_buffer);
9713 /* Start a new line */
9714 if (linelen > 0) fprintf(f, "\n");
9716 /* Print comments after last move */
9717 if (commentList[i] != NULL) {
9718 fprintf(f, "{\n%s}\n", commentList[i]);
9722 if (gameInfo.resultDetails != NULL &&
9723 gameInfo.resultDetails[0] != NULLCHAR) {
9724 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9725 PGNResult(gameInfo.result));
9727 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9731 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9735 /* Save game in old style and close the file */
9743 tm = time((time_t *) NULL);
9745 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9748 if (backwardMostMove > 0 || startedFromSetupPosition) {
9749 fprintf(f, "\n[--------------\n");
9750 PrintPosition(f, backwardMostMove);
9751 fprintf(f, "--------------]\n");
9756 i = backwardMostMove;
9757 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9759 while (i < forwardMostMove) {
9760 if (commentList[i] != NULL) {
9761 fprintf(f, "[%s]\n", commentList[i]);
9765 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9768 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9770 if (commentList[i] != NULL) {
9774 if (i >= forwardMostMove) {
9778 fprintf(f, "%s\n", parseList[i]);
9783 if (commentList[i] != NULL) {
9784 fprintf(f, "[%s]\n", commentList[i]);
9787 /* This isn't really the old style, but it's close enough */
9788 if (gameInfo.resultDetails != NULL &&
9789 gameInfo.resultDetails[0] != NULLCHAR) {
9790 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9791 gameInfo.resultDetails);
9793 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9800 /* Save the current game to open file f and close the file */
9802 SaveGame(f, dummy, dummy2)
9807 if (gameMode == EditPosition) EditPositionDone();
9808 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9809 if (appData.oldSaveStyle)
9810 return SaveGameOldStyle(f);
9812 return SaveGamePGN(f);
9815 /* Save the current position to the given file */
9817 SavePositionToFile(filename)
9823 if (strcmp(filename, "-") == 0) {
9824 return SavePosition(stdout, 0, NULL);
9826 f = fopen(filename, "a");
9828 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9829 DisplayError(buf, errno);
9832 SavePosition(f, 0, NULL);
9838 /* Save the current position to the given open file and close the file */
9840 SavePosition(f, dummy, dummy2)
9848 if (appData.oldSaveStyle) {
9849 tm = time((time_t *) NULL);
9851 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9853 fprintf(f, "[--------------\n");
9854 PrintPosition(f, currentMove);
9855 fprintf(f, "--------------]\n");
9857 fen = PositionToFEN(currentMove, NULL);
9858 fprintf(f, "%s\n", fen);
9866 ReloadCmailMsgEvent(unregister)
9870 static char *inFilename = NULL;
9871 static char *outFilename;
9873 struct stat inbuf, outbuf;
9876 /* Any registered moves are unregistered if unregister is set, */
9877 /* i.e. invoked by the signal handler */
9879 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9880 cmailMoveRegistered[i] = FALSE;
9881 if (cmailCommentList[i] != NULL) {
9882 free(cmailCommentList[i]);
9883 cmailCommentList[i] = NULL;
9886 nCmailMovesRegistered = 0;
9889 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9890 cmailResult[i] = CMAIL_NOT_RESULT;
9894 if (inFilename == NULL) {
9895 /* Because the filenames are static they only get malloced once */
9896 /* and they never get freed */
9897 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9898 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9900 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9901 sprintf(outFilename, "%s.out", appData.cmailGameName);
9904 status = stat(outFilename, &outbuf);
9906 cmailMailedMove = FALSE;
9908 status = stat(inFilename, &inbuf);
9909 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9912 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9913 counts the games, notes how each one terminated, etc.
9915 It would be nice to remove this kludge and instead gather all
9916 the information while building the game list. (And to keep it
9917 in the game list nodes instead of having a bunch of fixed-size
9918 parallel arrays.) Note this will require getting each game's
9919 termination from the PGN tags, as the game list builder does
9920 not process the game moves. --mann
9922 cmailMsgLoaded = TRUE;
9923 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9925 /* Load first game in the file or popup game menu */
9926 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9936 char string[MSG_SIZ];
9938 if ( cmailMailedMove
9939 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9940 return TRUE; /* Allow free viewing */
9943 /* Unregister move to ensure that we don't leave RegisterMove */
9944 /* with the move registered when the conditions for registering no */
9946 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9947 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9948 nCmailMovesRegistered --;
9950 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9952 free(cmailCommentList[lastLoadGameNumber - 1]);
9953 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9957 if (cmailOldMove == -1) {
9958 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9962 if (currentMove > cmailOldMove + 1) {
9963 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9967 if (currentMove < cmailOldMove) {
9968 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9972 if (forwardMostMove > currentMove) {
9973 /* Silently truncate extra moves */
9977 if ( (currentMove == cmailOldMove + 1)
9978 || ( (currentMove == cmailOldMove)
9979 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9980 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9981 if (gameInfo.result != GameUnfinished) {
9982 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9985 if (commentList[currentMove] != NULL) {
9986 cmailCommentList[lastLoadGameNumber - 1]
9987 = StrSave(commentList[currentMove]);
9989 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9991 if (appData.debugMode)
9992 fprintf(debugFP, "Saving %s for game %d\n",
9993 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9996 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9998 f = fopen(string, "w");
9999 if (appData.oldSaveStyle) {
10000 SaveGameOldStyle(f); /* also closes the file */
10002 sprintf(string, "%s.pos.out", appData.cmailGameName);
10003 f = fopen(string, "w");
10004 SavePosition(f, 0, NULL); /* also closes the file */
10006 fprintf(f, "{--------------\n");
10007 PrintPosition(f, currentMove);
10008 fprintf(f, "--------------}\n\n");
10010 SaveGame(f, 0, NULL); /* also closes the file*/
10013 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10014 nCmailMovesRegistered ++;
10015 } else if (nCmailGames == 1) {
10016 DisplayError(_("You have not made a move yet"), 0);
10027 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10028 FILE *commandOutput;
10029 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10030 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10036 if (! cmailMsgLoaded) {
10037 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10041 if (nCmailGames == nCmailResults) {
10042 DisplayError(_("No unfinished games"), 0);
10046 #if CMAIL_PROHIBIT_REMAIL
10047 if (cmailMailedMove) {
10048 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);
10049 DisplayError(msg, 0);
10054 if (! (cmailMailedMove || RegisterMove())) return;
10056 if ( cmailMailedMove
10057 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10058 sprintf(string, partCommandString,
10059 appData.debugMode ? " -v" : "", appData.cmailGameName);
10060 commandOutput = popen(string, "r");
10062 if (commandOutput == NULL) {
10063 DisplayError(_("Failed to invoke cmail"), 0);
10065 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10066 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10068 if (nBuffers > 1) {
10069 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10070 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10071 nBytes = MSG_SIZ - 1;
10073 (void) memcpy(msg, buffer, nBytes);
10075 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10077 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10078 cmailMailedMove = TRUE; /* Prevent >1 moves */
10081 for (i = 0; i < nCmailGames; i ++) {
10082 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10087 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10089 sprintf(buffer, "%s/%s.%s.archive",
10091 appData.cmailGameName,
10093 LoadGameFromFile(buffer, 1, buffer, FALSE);
10094 cmailMsgLoaded = FALSE;
10098 DisplayInformation(msg);
10099 pclose(commandOutput);
10102 if ((*cmailMsg) != '\0') {
10103 DisplayInformation(cmailMsg);
10108 #endif /* !WIN32 */
10117 int prependComma = 0;
10119 char string[MSG_SIZ]; /* Space for game-list */
10122 if (!cmailMsgLoaded) return "";
10124 if (cmailMailedMove) {
10125 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10127 /* Create a list of games left */
10128 sprintf(string, "[");
10129 for (i = 0; i < nCmailGames; i ++) {
10130 if (! ( cmailMoveRegistered[i]
10131 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10132 if (prependComma) {
10133 sprintf(number, ",%d", i + 1);
10135 sprintf(number, "%d", i + 1);
10139 strcat(string, number);
10142 strcat(string, "]");
10144 if (nCmailMovesRegistered + nCmailResults == 0) {
10145 switch (nCmailGames) {
10148 _("Still need to make move for game\n"));
10153 _("Still need to make moves for both games\n"));
10158 _("Still need to make moves for all %d games\n"),
10163 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10166 _("Still need to make a move for game %s\n"),
10171 if (nCmailResults == nCmailGames) {
10172 sprintf(cmailMsg, _("No unfinished games\n"));
10174 sprintf(cmailMsg, _("Ready to send mail\n"));
10180 _("Still need to make moves for games %s\n"),
10192 if (gameMode == Training)
10193 SetTrainingModeOff();
10196 cmailMsgLoaded = FALSE;
10197 if (appData.icsActive) {
10198 SendToICS(ics_prefix);
10199 SendToICS("refresh\n");
10209 /* Give up on clean exit */
10213 /* Keep trying for clean exit */
10217 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10219 if (telnetISR != NULL) {
10220 RemoveInputSource(telnetISR);
10222 if (icsPR != NoProc) {
10223 DestroyChildProcess(icsPR, TRUE);
10226 /* Save game if resource set and not already saved by GameEnds() */
10227 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10228 && forwardMostMove > 0) {
10229 if (*appData.saveGameFile != NULLCHAR) {
10230 SaveGameToFile(appData.saveGameFile, TRUE);
10231 } else if (appData.autoSaveGames) {
10234 if (*appData.savePositionFile != NULLCHAR) {
10235 SavePositionToFile(appData.savePositionFile);
10238 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10240 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10241 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10243 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10244 /* make sure this other one finishes before killing it! */
10245 if(endingGame) { int count = 0;
10246 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10247 while(endingGame && count++ < 10) DoSleep(1);
10248 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10251 /* Kill off chess programs */
10252 if (first.pr != NoProc) {
10255 DoSleep( appData.delayBeforeQuit );
10256 SendToProgram("quit\n", &first);
10257 DoSleep( appData.delayAfterQuit );
10258 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10260 if (second.pr != NoProc) {
10261 DoSleep( appData.delayBeforeQuit );
10262 SendToProgram("quit\n", &second);
10263 DoSleep( appData.delayAfterQuit );
10264 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10266 if (first.isr != NULL) {
10267 RemoveInputSource(first.isr);
10269 if (second.isr != NULL) {
10270 RemoveInputSource(second.isr);
10273 ShutDownFrontEnd();
10280 if (appData.debugMode)
10281 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10285 if (gameMode == MachinePlaysWhite ||
10286 gameMode == MachinePlaysBlack) {
10289 DisplayBothClocks();
10291 if (gameMode == PlayFromGameFile) {
10292 if (appData.timeDelay >= 0)
10293 AutoPlayGameLoop();
10294 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10295 Reset(FALSE, TRUE);
10296 SendToICS(ics_prefix);
10297 SendToICS("refresh\n");
10298 } else if (currentMove < forwardMostMove) {
10299 ForwardInner(forwardMostMove);
10301 pauseExamInvalid = FALSE;
10303 switch (gameMode) {
10307 pauseExamForwardMostMove = forwardMostMove;
10308 pauseExamInvalid = FALSE;
10311 case IcsPlayingWhite:
10312 case IcsPlayingBlack:
10316 case PlayFromGameFile:
10317 (void) StopLoadGameTimer();
10321 case BeginningOfGame:
10322 if (appData.icsActive) return;
10323 /* else fall through */
10324 case MachinePlaysWhite:
10325 case MachinePlaysBlack:
10326 case TwoMachinesPlay:
10327 if (forwardMostMove == 0)
10328 return; /* don't pause if no one has moved */
10329 if ((gameMode == MachinePlaysWhite &&
10330 !WhiteOnMove(forwardMostMove)) ||
10331 (gameMode == MachinePlaysBlack &&
10332 WhiteOnMove(forwardMostMove))) {
10345 char title[MSG_SIZ];
10347 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10348 strcpy(title, _("Edit comment"));
10350 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10351 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10352 parseList[currentMove - 1]);
10355 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10362 char *tags = PGNTags(&gameInfo);
10363 EditTagsPopUp(tags);
10370 if (appData.noChessProgram || gameMode == AnalyzeMode)
10373 if (gameMode != AnalyzeFile) {
10374 if (!appData.icsEngineAnalyze) {
10376 if (gameMode != EditGame) return;
10378 ResurrectChessProgram();
10379 SendToProgram("analyze\n", &first);
10380 first.analyzing = TRUE;
10381 /*first.maybeThinking = TRUE;*/
10382 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10383 AnalysisPopUp(_("Analysis"),
10384 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10386 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10391 StartAnalysisClock();
10392 GetTimeMark(&lastNodeCountTime);
10399 if (appData.noChessProgram || gameMode == AnalyzeFile)
10402 if (gameMode != AnalyzeMode) {
10404 if (gameMode != EditGame) return;
10405 ResurrectChessProgram();
10406 SendToProgram("analyze\n", &first);
10407 first.analyzing = TRUE;
10408 /*first.maybeThinking = TRUE;*/
10409 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10410 AnalysisPopUp(_("Analysis"),
10411 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10413 gameMode = AnalyzeFile;
10418 StartAnalysisClock();
10419 GetTimeMark(&lastNodeCountTime);
10424 MachineWhiteEvent()
10427 char *bookHit = NULL;
10429 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10433 if (gameMode == PlayFromGameFile ||
10434 gameMode == TwoMachinesPlay ||
10435 gameMode == Training ||
10436 gameMode == AnalyzeMode ||
10437 gameMode == EndOfGame)
10440 if (gameMode == EditPosition)
10441 EditPositionDone();
10443 if (!WhiteOnMove(currentMove)) {
10444 DisplayError(_("It is not White's turn"), 0);
10448 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10451 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10452 gameMode == AnalyzeFile)
10455 ResurrectChessProgram(); /* in case it isn't running */
10456 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10457 gameMode = MachinePlaysWhite;
10460 gameMode = MachinePlaysWhite;
10464 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10466 if (first.sendName) {
10467 sprintf(buf, "name %s\n", gameInfo.black);
10468 SendToProgram(buf, &first);
10470 if (first.sendTime) {
10471 if (first.useColors) {
10472 SendToProgram("black\n", &first); /*gnu kludge*/
10474 SendTimeRemaining(&first, TRUE);
10476 if (first.useColors) {
10477 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10479 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10480 SetMachineThinkingEnables();
10481 first.maybeThinking = TRUE;
10484 if (appData.autoFlipView && !flipView) {
10485 flipView = !flipView;
10486 DrawPosition(FALSE, NULL);
10487 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10490 if(bookHit) { // [HGM] book: simulate book reply
10491 static char bookMove[MSG_SIZ]; // a bit generous?
10493 programStats.nodes = programStats.depth = programStats.time =
10494 programStats.score = programStats.got_only_move = 0;
10495 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10497 strcpy(bookMove, "move ");
10498 strcat(bookMove, bookHit);
10499 HandleMachineMove(bookMove, &first);
10504 MachineBlackEvent()
10507 char *bookHit = NULL;
10509 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10513 if (gameMode == PlayFromGameFile ||
10514 gameMode == TwoMachinesPlay ||
10515 gameMode == Training ||
10516 gameMode == AnalyzeMode ||
10517 gameMode == EndOfGame)
10520 if (gameMode == EditPosition)
10521 EditPositionDone();
10523 if (WhiteOnMove(currentMove)) {
10524 DisplayError(_("It is not Black's turn"), 0);
10528 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10531 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10532 gameMode == AnalyzeFile)
10535 ResurrectChessProgram(); /* in case it isn't running */
10536 gameMode = MachinePlaysBlack;
10540 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10542 if (first.sendName) {
10543 sprintf(buf, "name %s\n", gameInfo.white);
10544 SendToProgram(buf, &first);
10546 if (first.sendTime) {
10547 if (first.useColors) {
10548 SendToProgram("white\n", &first); /*gnu kludge*/
10550 SendTimeRemaining(&first, FALSE);
10552 if (first.useColors) {
10553 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10555 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10556 SetMachineThinkingEnables();
10557 first.maybeThinking = TRUE;
10560 if (appData.autoFlipView && flipView) {
10561 flipView = !flipView;
10562 DrawPosition(FALSE, NULL);
10563 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10565 if(bookHit) { // [HGM] book: simulate book reply
10566 static char bookMove[MSG_SIZ]; // a bit generous?
10568 programStats.nodes = programStats.depth = programStats.time =
10569 programStats.score = programStats.got_only_move = 0;
10570 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10572 strcpy(bookMove, "move ");
10573 strcat(bookMove, bookHit);
10574 HandleMachineMove(bookMove, &first);
10580 DisplayTwoMachinesTitle()
10583 if (appData.matchGames > 0) {
10584 if (first.twoMachinesColor[0] == 'w') {
10585 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10586 gameInfo.white, gameInfo.black,
10587 first.matchWins, second.matchWins,
10588 matchGame - 1 - (first.matchWins + second.matchWins));
10590 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10591 gameInfo.white, gameInfo.black,
10592 second.matchWins, first.matchWins,
10593 matchGame - 1 - (first.matchWins + second.matchWins));
10596 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10602 TwoMachinesEvent P((void))
10606 ChessProgramState *onmove;
10607 char *bookHit = NULL;
10609 if (appData.noChessProgram) return;
10611 switch (gameMode) {
10612 case TwoMachinesPlay:
10614 case MachinePlaysWhite:
10615 case MachinePlaysBlack:
10616 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10617 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10621 case BeginningOfGame:
10622 case PlayFromGameFile:
10625 if (gameMode != EditGame) return;
10628 EditPositionDone();
10639 forwardMostMove = currentMove;
10640 ResurrectChessProgram(); /* in case first program isn't running */
10642 if (second.pr == NULL) {
10643 StartChessProgram(&second);
10644 if (second.protocolVersion == 1) {
10645 TwoMachinesEventIfReady();
10647 /* kludge: allow timeout for initial "feature" command */
10649 DisplayMessage("", _("Starting second chess program"));
10650 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10654 DisplayMessage("", "");
10655 InitChessProgram(&second, FALSE);
10656 SendToProgram("force\n", &second);
10657 if (startedFromSetupPosition) {
10658 SendBoard(&second, backwardMostMove);
10659 if (appData.debugMode) {
10660 fprintf(debugFP, "Two Machines\n");
10663 for (i = backwardMostMove; i < forwardMostMove; i++) {
10664 SendMoveToProgram(i, &second);
10667 gameMode = TwoMachinesPlay;
10671 DisplayTwoMachinesTitle();
10673 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10679 SendToProgram(first.computerString, &first);
10680 if (first.sendName) {
10681 sprintf(buf, "name %s\n", second.tidy);
10682 SendToProgram(buf, &first);
10684 SendToProgram(second.computerString, &second);
10685 if (second.sendName) {
10686 sprintf(buf, "name %s\n", first.tidy);
10687 SendToProgram(buf, &second);
10691 if (!first.sendTime || !second.sendTime) {
10692 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10693 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10695 if (onmove->sendTime) {
10696 if (onmove->useColors) {
10697 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10699 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10701 if (onmove->useColors) {
10702 SendToProgram(onmove->twoMachinesColor, onmove);
10704 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10705 // SendToProgram("go\n", onmove);
10706 onmove->maybeThinking = TRUE;
10707 SetMachineThinkingEnables();
10711 if(bookHit) { // [HGM] book: simulate book reply
10712 static char bookMove[MSG_SIZ]; // a bit generous?
10714 programStats.nodes = programStats.depth = programStats.time =
10715 programStats.score = programStats.got_only_move = 0;
10716 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10718 strcpy(bookMove, "move ");
10719 strcat(bookMove, bookHit);
10720 HandleMachineMove(bookMove, &first);
10727 if (gameMode == Training) {
10728 SetTrainingModeOff();
10729 gameMode = PlayFromGameFile;
10730 DisplayMessage("", _("Training mode off"));
10732 gameMode = Training;
10733 animateTraining = appData.animate;
10735 /* make sure we are not already at the end of the game */
10736 if (currentMove < forwardMostMove) {
10737 SetTrainingModeOn();
10738 DisplayMessage("", _("Training mode on"));
10740 gameMode = PlayFromGameFile;
10741 DisplayError(_("Already at end of game"), 0);
10750 if (!appData.icsActive) return;
10751 switch (gameMode) {
10752 case IcsPlayingWhite:
10753 case IcsPlayingBlack:
10756 case BeginningOfGame:
10764 EditPositionDone();
10777 gameMode = IcsIdle;
10788 switch (gameMode) {
10790 SetTrainingModeOff();
10792 case MachinePlaysWhite:
10793 case MachinePlaysBlack:
10794 case BeginningOfGame:
10795 SendToProgram("force\n", &first);
10796 SetUserThinkingEnables();
10798 case PlayFromGameFile:
10799 (void) StopLoadGameTimer();
10800 if (gameFileFP != NULL) {
10805 EditPositionDone();
10810 SendToProgram("force\n", &first);
10812 case TwoMachinesPlay:
10813 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10814 ResurrectChessProgram();
10815 SetUserThinkingEnables();
10818 ResurrectChessProgram();
10820 case IcsPlayingBlack:
10821 case IcsPlayingWhite:
10822 DisplayError(_("Warning: You are still playing a game"), 0);
10825 DisplayError(_("Warning: You are still observing a game"), 0);
10828 DisplayError(_("Warning: You are still examining a game"), 0);
10839 first.offeredDraw = second.offeredDraw = 0;
10841 if (gameMode == PlayFromGameFile) {
10842 whiteTimeRemaining = timeRemaining[0][currentMove];
10843 blackTimeRemaining = timeRemaining[1][currentMove];
10847 if (gameMode == MachinePlaysWhite ||
10848 gameMode == MachinePlaysBlack ||
10849 gameMode == TwoMachinesPlay ||
10850 gameMode == EndOfGame) {
10851 i = forwardMostMove;
10852 while (i > currentMove) {
10853 SendToProgram("undo\n", &first);
10856 whiteTimeRemaining = timeRemaining[0][currentMove];
10857 blackTimeRemaining = timeRemaining[1][currentMove];
10858 DisplayBothClocks();
10859 if (whiteFlag || blackFlag) {
10860 whiteFlag = blackFlag = 0;
10865 gameMode = EditGame;
10872 EditPositionEvent()
10874 if (gameMode == EditPosition) {
10880 if (gameMode != EditGame) return;
10882 gameMode = EditPosition;
10885 if (currentMove > 0)
10886 CopyBoard(boards[0], boards[currentMove]);
10888 blackPlaysFirst = !WhiteOnMove(currentMove);
10890 currentMove = forwardMostMove = backwardMostMove = 0;
10891 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10898 /* [DM] icsEngineAnalyze - possible call from other functions */
10899 if (appData.icsEngineAnalyze) {
10900 appData.icsEngineAnalyze = FALSE;
10902 DisplayMessage("",_("Close ICS engine analyze..."));
10904 if (first.analysisSupport && first.analyzing) {
10905 SendToProgram("exit\n", &first);
10906 first.analyzing = FALSE;
10909 thinkOutput[0] = NULLCHAR;
10915 startedFromSetupPosition = TRUE;
10916 InitChessProgram(&first, FALSE);
10917 SendToProgram("force\n", &first);
10918 if (blackPlaysFirst) {
10919 strcpy(moveList[0], "");
10920 strcpy(parseList[0], "");
10921 currentMove = forwardMostMove = backwardMostMove = 1;
10922 CopyBoard(boards[1], boards[0]);
10923 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10925 epStatus[1] = epStatus[0];
10926 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10929 currentMove = forwardMostMove = backwardMostMove = 0;
10931 SendBoard(&first, forwardMostMove);
10932 if (appData.debugMode) {
10933 fprintf(debugFP, "EditPosDone\n");
10936 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10937 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10938 gameMode = EditGame;
10940 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10941 ClearHighlights(); /* [AS] */
10944 /* Pause for `ms' milliseconds */
10945 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10955 } while (SubtractTimeMarks(&m2, &m1) < ms);
10958 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10960 SendMultiLineToICS(buf)
10963 char temp[MSG_SIZ+1], *p;
10970 strncpy(temp, buf, len);
10975 if (*p == '\n' || *p == '\r')
10980 strcat(temp, "\n");
10982 SendToPlayer(temp, strlen(temp));
10986 SetWhiteToPlayEvent()
10988 if (gameMode == EditPosition) {
10989 blackPlaysFirst = FALSE;
10990 DisplayBothClocks(); /* works because currentMove is 0 */
10991 } else if (gameMode == IcsExamining) {
10992 SendToICS(ics_prefix);
10993 SendToICS("tomove white\n");
10998 SetBlackToPlayEvent()
11000 if (gameMode == EditPosition) {
11001 blackPlaysFirst = TRUE;
11002 currentMove = 1; /* kludge */
11003 DisplayBothClocks();
11005 } else if (gameMode == IcsExamining) {
11006 SendToICS(ics_prefix);
11007 SendToICS("tomove black\n");
11012 EditPositionMenuEvent(selection, x, y)
11013 ChessSquare selection;
11017 ChessSquare piece = boards[0][y][x];
11019 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11021 switch (selection) {
11023 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11024 SendToICS(ics_prefix);
11025 SendToICS("bsetup clear\n");
11026 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11027 SendToICS(ics_prefix);
11028 SendToICS("clearboard\n");
11030 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11031 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11032 for (y = 0; y < BOARD_HEIGHT; y++) {
11033 if (gameMode == IcsExamining) {
11034 if (boards[currentMove][y][x] != EmptySquare) {
11035 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11040 boards[0][y][x] = p;
11045 if (gameMode == EditPosition) {
11046 DrawPosition(FALSE, boards[0]);
11051 SetWhiteToPlayEvent();
11055 SetBlackToPlayEvent();
11059 if (gameMode == IcsExamining) {
11060 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11063 boards[0][y][x] = EmptySquare;
11064 DrawPosition(FALSE, boards[0]);
11069 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11070 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11071 selection = (ChessSquare) (PROMOTED piece);
11072 } else if(piece == EmptySquare) selection = WhiteSilver;
11073 else selection = (ChessSquare)((int)piece - 1);
11077 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11078 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11079 selection = (ChessSquare) (DEMOTED piece);
11080 } else if(piece == EmptySquare) selection = BlackSilver;
11081 else selection = (ChessSquare)((int)piece + 1);
11086 if(gameInfo.variant == VariantShatranj ||
11087 gameInfo.variant == VariantXiangqi ||
11088 gameInfo.variant == VariantCourier )
11089 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11094 if(gameInfo.variant == VariantXiangqi)
11095 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11096 if(gameInfo.variant == VariantKnightmate)
11097 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11100 if (gameMode == IcsExamining) {
11101 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11102 PieceToChar(selection), AAA + x, ONE + y);
11105 boards[0][y][x] = selection;
11106 DrawPosition(FALSE, boards[0]);
11114 DropMenuEvent(selection, x, y)
11115 ChessSquare selection;
11118 ChessMove moveType;
11120 switch (gameMode) {
11121 case IcsPlayingWhite:
11122 case MachinePlaysBlack:
11123 if (!WhiteOnMove(currentMove)) {
11124 DisplayMoveError(_("It is Black's turn"));
11127 moveType = WhiteDrop;
11129 case IcsPlayingBlack:
11130 case MachinePlaysWhite:
11131 if (WhiteOnMove(currentMove)) {
11132 DisplayMoveError(_("It is White's turn"));
11135 moveType = BlackDrop;
11138 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11144 if (moveType == BlackDrop && selection < BlackPawn) {
11145 selection = (ChessSquare) ((int) selection
11146 + (int) BlackPawn - (int) WhitePawn);
11148 if (boards[currentMove][y][x] != EmptySquare) {
11149 DisplayMoveError(_("That square is occupied"));
11153 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11159 /* Accept a pending offer of any kind from opponent */
11161 if (appData.icsActive) {
11162 SendToICS(ics_prefix);
11163 SendToICS("accept\n");
11164 } else if (cmailMsgLoaded) {
11165 if (currentMove == cmailOldMove &&
11166 commentList[cmailOldMove] != NULL &&
11167 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11168 "Black offers a draw" : "White offers a draw")) {
11170 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11171 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11173 DisplayError(_("There is no pending offer on this move"), 0);
11174 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11177 /* Not used for offers from chess program */
11184 /* Decline a pending offer of any kind from opponent */
11186 if (appData.icsActive) {
11187 SendToICS(ics_prefix);
11188 SendToICS("decline\n");
11189 } else if (cmailMsgLoaded) {
11190 if (currentMove == cmailOldMove &&
11191 commentList[cmailOldMove] != NULL &&
11192 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11193 "Black offers a draw" : "White offers a draw")) {
11195 AppendComment(cmailOldMove, "Draw declined");
11196 DisplayComment(cmailOldMove - 1, "Draw declined");
11199 DisplayError(_("There is no pending offer on this move"), 0);
11202 /* Not used for offers from chess program */
11209 /* Issue ICS rematch command */
11210 if (appData.icsActive) {
11211 SendToICS(ics_prefix);
11212 SendToICS("rematch\n");
11219 /* Call your opponent's flag (claim a win on time) */
11220 if (appData.icsActive) {
11221 SendToICS(ics_prefix);
11222 SendToICS("flag\n");
11224 switch (gameMode) {
11227 case MachinePlaysWhite:
11230 GameEnds(GameIsDrawn, "Both players ran out of time",
11233 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11235 DisplayError(_("Your opponent is not out of time"), 0);
11238 case MachinePlaysBlack:
11241 GameEnds(GameIsDrawn, "Both players ran out of time",
11244 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11246 DisplayError(_("Your opponent is not out of time"), 0);
11256 /* Offer draw or accept pending draw offer from opponent */
11258 if (appData.icsActive) {
11259 /* Note: tournament rules require draw offers to be
11260 made after you make your move but before you punch
11261 your clock. Currently ICS doesn't let you do that;
11262 instead, you immediately punch your clock after making
11263 a move, but you can offer a draw at any time. */
11265 SendToICS(ics_prefix);
11266 SendToICS("draw\n");
11267 } else if (cmailMsgLoaded) {
11268 if (currentMove == cmailOldMove &&
11269 commentList[cmailOldMove] != NULL &&
11270 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11271 "Black offers a draw" : "White offers a draw")) {
11272 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11273 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11274 } else if (currentMove == cmailOldMove + 1) {
11275 char *offer = WhiteOnMove(cmailOldMove) ?
11276 "White offers a draw" : "Black offers a draw";
11277 AppendComment(currentMove, offer);
11278 DisplayComment(currentMove - 1, offer);
11279 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11281 DisplayError(_("You must make your move before offering a draw"), 0);
11282 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11284 } else if (first.offeredDraw) {
11285 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11287 if (first.sendDrawOffers) {
11288 SendToProgram("draw\n", &first);
11289 userOfferedDraw = TRUE;
11297 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11299 if (appData.icsActive) {
11300 SendToICS(ics_prefix);
11301 SendToICS("adjourn\n");
11303 /* Currently GNU Chess doesn't offer or accept Adjourns */
11311 /* Offer Abort or accept pending Abort offer from opponent */
11313 if (appData.icsActive) {
11314 SendToICS(ics_prefix);
11315 SendToICS("abort\n");
11317 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11324 /* Resign. You can do this even if it's not your turn. */
11326 if (appData.icsActive) {
11327 SendToICS(ics_prefix);
11328 SendToICS("resign\n");
11330 switch (gameMode) {
11331 case MachinePlaysWhite:
11332 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11334 case MachinePlaysBlack:
11335 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11338 if (cmailMsgLoaded) {
11340 if (WhiteOnMove(cmailOldMove)) {
11341 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11343 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11345 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11356 StopObservingEvent()
11358 /* Stop observing current games */
11359 SendToICS(ics_prefix);
11360 SendToICS("unobserve\n");
11364 StopExaminingEvent()
11366 /* Stop observing current game */
11367 SendToICS(ics_prefix);
11368 SendToICS("unexamine\n");
11372 ForwardInner(target)
11377 if (appData.debugMode)
11378 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11379 target, currentMove, forwardMostMove);
11381 if (gameMode == EditPosition)
11384 if (gameMode == PlayFromGameFile && !pausing)
11387 if (gameMode == IcsExamining && pausing)
11388 limit = pauseExamForwardMostMove;
11390 limit = forwardMostMove;
11392 if (target > limit) target = limit;
11394 if (target > 0 && moveList[target - 1][0]) {
11395 int fromX, fromY, toX, toY;
11396 toX = moveList[target - 1][2] - AAA;
11397 toY = moveList[target - 1][3] - ONE;
11398 if (moveList[target - 1][1] == '@') {
11399 if (appData.highlightLastMove) {
11400 SetHighlights(-1, -1, toX, toY);
11403 fromX = moveList[target - 1][0] - AAA;
11404 fromY = moveList[target - 1][1] - ONE;
11405 if (target == currentMove + 1) {
11406 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11408 if (appData.highlightLastMove) {
11409 SetHighlights(fromX, fromY, toX, toY);
11413 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11414 gameMode == Training || gameMode == PlayFromGameFile ||
11415 gameMode == AnalyzeFile) {
11416 while (currentMove < target) {
11417 SendMoveToProgram(currentMove++, &first);
11420 currentMove = target;
11423 if (gameMode == EditGame || gameMode == EndOfGame) {
11424 whiteTimeRemaining = timeRemaining[0][currentMove];
11425 blackTimeRemaining = timeRemaining[1][currentMove];
11427 DisplayBothClocks();
11428 DisplayMove(currentMove - 1);
11429 DrawPosition(FALSE, boards[currentMove]);
11430 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11431 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11432 DisplayComment(currentMove - 1, commentList[currentMove]);
11440 if (gameMode == IcsExamining && !pausing) {
11441 SendToICS(ics_prefix);
11442 SendToICS("forward\n");
11444 ForwardInner(currentMove + 1);
11451 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11452 /* to optimze, we temporarily turn off analysis mode while we feed
11453 * the remaining moves to the engine. Otherwise we get analysis output
11456 if (first.analysisSupport) {
11457 SendToProgram("exit\nforce\n", &first);
11458 first.analyzing = FALSE;
11462 if (gameMode == IcsExamining && !pausing) {
11463 SendToICS(ics_prefix);
11464 SendToICS("forward 999999\n");
11466 ForwardInner(forwardMostMove);
11469 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11470 /* we have fed all the moves, so reactivate analysis mode */
11471 SendToProgram("analyze\n", &first);
11472 first.analyzing = TRUE;
11473 /*first.maybeThinking = TRUE;*/
11474 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11479 BackwardInner(target)
11482 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11484 if (appData.debugMode)
11485 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11486 target, currentMove, forwardMostMove);
11488 if (gameMode == EditPosition) return;
11489 if (currentMove <= backwardMostMove) {
11491 DrawPosition(full_redraw, boards[currentMove]);
11494 if (gameMode == PlayFromGameFile && !pausing)
11497 if (moveList[target][0]) {
11498 int fromX, fromY, toX, toY;
11499 toX = moveList[target][2] - AAA;
11500 toY = moveList[target][3] - ONE;
11501 if (moveList[target][1] == '@') {
11502 if (appData.highlightLastMove) {
11503 SetHighlights(-1, -1, toX, toY);
11506 fromX = moveList[target][0] - AAA;
11507 fromY = moveList[target][1] - ONE;
11508 if (target == currentMove - 1) {
11509 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11511 if (appData.highlightLastMove) {
11512 SetHighlights(fromX, fromY, toX, toY);
11516 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11517 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11518 while (currentMove > target) {
11519 SendToProgram("undo\n", &first);
11523 currentMove = target;
11526 if (gameMode == EditGame || gameMode == EndOfGame) {
11527 whiteTimeRemaining = timeRemaining[0][currentMove];
11528 blackTimeRemaining = timeRemaining[1][currentMove];
11530 DisplayBothClocks();
11531 DisplayMove(currentMove - 1);
11532 DrawPosition(full_redraw, boards[currentMove]);
11533 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11534 // [HGM] PV info: routine tests if comment empty
11535 DisplayComment(currentMove - 1, commentList[currentMove]);
11541 if (gameMode == IcsExamining && !pausing) {
11542 SendToICS(ics_prefix);
11543 SendToICS("backward\n");
11545 BackwardInner(currentMove - 1);
11552 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11553 /* to optimze, we temporarily turn off analysis mode while we undo
11554 * all the moves. Otherwise we get analysis output after each undo.
11556 if (first.analysisSupport) {
11557 SendToProgram("exit\nforce\n", &first);
11558 first.analyzing = FALSE;
11562 if (gameMode == IcsExamining && !pausing) {
11563 SendToICS(ics_prefix);
11564 SendToICS("backward 999999\n");
11566 BackwardInner(backwardMostMove);
11569 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11570 /* we have fed all the moves, so reactivate analysis mode */
11571 SendToProgram("analyze\n", &first);
11572 first.analyzing = TRUE;
11573 /*first.maybeThinking = TRUE;*/
11574 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11581 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11582 if (to >= forwardMostMove) to = forwardMostMove;
11583 if (to <= backwardMostMove) to = backwardMostMove;
11584 if (to < currentMove) {
11594 if (gameMode != IcsExamining) {
11595 DisplayError(_("You are not examining a game"), 0);
11599 DisplayError(_("You can't revert while pausing"), 0);
11602 SendToICS(ics_prefix);
11603 SendToICS("revert\n");
11609 switch (gameMode) {
11610 case MachinePlaysWhite:
11611 case MachinePlaysBlack:
11612 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11613 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11616 if (forwardMostMove < 2) return;
11617 currentMove = forwardMostMove = forwardMostMove - 2;
11618 whiteTimeRemaining = timeRemaining[0][currentMove];
11619 blackTimeRemaining = timeRemaining[1][currentMove];
11620 DisplayBothClocks();
11621 DisplayMove(currentMove - 1);
11622 ClearHighlights();/*!! could figure this out*/
11623 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11624 SendToProgram("remove\n", &first);
11625 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11628 case BeginningOfGame:
11632 case IcsPlayingWhite:
11633 case IcsPlayingBlack:
11634 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11635 SendToICS(ics_prefix);
11636 SendToICS("takeback 2\n");
11638 SendToICS(ics_prefix);
11639 SendToICS("takeback 1\n");
11648 ChessProgramState *cps;
11650 switch (gameMode) {
11651 case MachinePlaysWhite:
11652 if (!WhiteOnMove(forwardMostMove)) {
11653 DisplayError(_("It is your turn"), 0);
11658 case MachinePlaysBlack:
11659 if (WhiteOnMove(forwardMostMove)) {
11660 DisplayError(_("It is your turn"), 0);
11665 case TwoMachinesPlay:
11666 if (WhiteOnMove(forwardMostMove) ==
11667 (first.twoMachinesColor[0] == 'w')) {
11673 case BeginningOfGame:
11677 SendToProgram("?\n", cps);
11681 TruncateGameEvent()
11684 if (gameMode != EditGame) return;
11691 if (forwardMostMove > currentMove) {
11692 if (gameInfo.resultDetails != NULL) {
11693 free(gameInfo.resultDetails);
11694 gameInfo.resultDetails = NULL;
11695 gameInfo.result = GameUnfinished;
11697 forwardMostMove = currentMove;
11698 HistorySet(parseList, backwardMostMove, forwardMostMove,
11706 if (appData.noChessProgram) return;
11707 switch (gameMode) {
11708 case MachinePlaysWhite:
11709 if (WhiteOnMove(forwardMostMove)) {
11710 DisplayError(_("Wait until your turn"), 0);
11714 case BeginningOfGame:
11715 case MachinePlaysBlack:
11716 if (!WhiteOnMove(forwardMostMove)) {
11717 DisplayError(_("Wait until your turn"), 0);
11722 DisplayError(_("No hint available"), 0);
11725 SendToProgram("hint\n", &first);
11726 hintRequested = TRUE;
11732 if (appData.noChessProgram) return;
11733 switch (gameMode) {
11734 case MachinePlaysWhite:
11735 if (WhiteOnMove(forwardMostMove)) {
11736 DisplayError(_("Wait until your turn"), 0);
11740 case BeginningOfGame:
11741 case MachinePlaysBlack:
11742 if (!WhiteOnMove(forwardMostMove)) {
11743 DisplayError(_("Wait until your turn"), 0);
11748 EditPositionDone();
11750 case TwoMachinesPlay:
11755 SendToProgram("bk\n", &first);
11756 bookOutput[0] = NULLCHAR;
11757 bookRequested = TRUE;
11763 char *tags = PGNTags(&gameInfo);
11764 TagsPopUp(tags, CmailMsg());
11768 /* end button procedures */
11771 PrintPosition(fp, move)
11777 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11778 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11779 char c = PieceToChar(boards[move][i][j]);
11780 fputc(c == 'x' ? '.' : c, fp);
11781 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11784 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11785 fprintf(fp, "white to play\n");
11787 fprintf(fp, "black to play\n");
11794 if (gameInfo.white != NULL) {
11795 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11801 /* Find last component of program's own name, using some heuristics */
11803 TidyProgramName(prog, host, buf)
11804 char *prog, *host, buf[MSG_SIZ];
11807 int local = (strcmp(host, "localhost") == 0);
11808 while (!local && (p = strchr(prog, ';')) != NULL) {
11810 while (*p == ' ') p++;
11813 if (*prog == '"' || *prog == '\'') {
11814 q = strchr(prog + 1, *prog);
11816 q = strchr(prog, ' ');
11818 if (q == NULL) q = prog + strlen(prog);
11820 while (p >= prog && *p != '/' && *p != '\\') p--;
11822 if(p == prog && *p == '"') p++;
11823 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11824 memcpy(buf, p, q - p);
11825 buf[q - p] = NULLCHAR;
11833 TimeControlTagValue()
11836 if (!appData.clockMode) {
11838 } else if (movesPerSession > 0) {
11839 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11840 } else if (timeIncrement == 0) {
11841 sprintf(buf, "%ld", timeControl/1000);
11843 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11845 return StrSave(buf);
11851 /* This routine is used only for certain modes */
11852 VariantClass v = gameInfo.variant;
11853 ClearGameInfo(&gameInfo);
11854 gameInfo.variant = v;
11856 switch (gameMode) {
11857 case MachinePlaysWhite:
11858 gameInfo.event = StrSave( appData.pgnEventHeader );
11859 gameInfo.site = StrSave(HostName());
11860 gameInfo.date = PGNDate();
11861 gameInfo.round = StrSave("-");
11862 gameInfo.white = StrSave(first.tidy);
11863 gameInfo.black = StrSave(UserName());
11864 gameInfo.timeControl = TimeControlTagValue();
11867 case MachinePlaysBlack:
11868 gameInfo.event = StrSave( appData.pgnEventHeader );
11869 gameInfo.site = StrSave(HostName());
11870 gameInfo.date = PGNDate();
11871 gameInfo.round = StrSave("-");
11872 gameInfo.white = StrSave(UserName());
11873 gameInfo.black = StrSave(first.tidy);
11874 gameInfo.timeControl = TimeControlTagValue();
11877 case TwoMachinesPlay:
11878 gameInfo.event = StrSave( appData.pgnEventHeader );
11879 gameInfo.site = StrSave(HostName());
11880 gameInfo.date = PGNDate();
11881 if (matchGame > 0) {
11883 sprintf(buf, "%d", matchGame);
11884 gameInfo.round = StrSave(buf);
11886 gameInfo.round = StrSave("-");
11888 if (first.twoMachinesColor[0] == 'w') {
11889 gameInfo.white = StrSave(first.tidy);
11890 gameInfo.black = StrSave(second.tidy);
11892 gameInfo.white = StrSave(second.tidy);
11893 gameInfo.black = StrSave(first.tidy);
11895 gameInfo.timeControl = TimeControlTagValue();
11899 gameInfo.event = StrSave("Edited game");
11900 gameInfo.site = StrSave(HostName());
11901 gameInfo.date = PGNDate();
11902 gameInfo.round = StrSave("-");
11903 gameInfo.white = StrSave("-");
11904 gameInfo.black = StrSave("-");
11908 gameInfo.event = StrSave("Edited position");
11909 gameInfo.site = StrSave(HostName());
11910 gameInfo.date = PGNDate();
11911 gameInfo.round = StrSave("-");
11912 gameInfo.white = StrSave("-");
11913 gameInfo.black = StrSave("-");
11916 case IcsPlayingWhite:
11917 case IcsPlayingBlack:
11922 case PlayFromGameFile:
11923 gameInfo.event = StrSave("Game from non-PGN file");
11924 gameInfo.site = StrSave(HostName());
11925 gameInfo.date = PGNDate();
11926 gameInfo.round = StrSave("-");
11927 gameInfo.white = StrSave("?");
11928 gameInfo.black = StrSave("?");
11937 ReplaceComment(index, text)
11943 while (*text == '\n') text++;
11944 len = strlen(text);
11945 while (len > 0 && text[len - 1] == '\n') len--;
11947 if (commentList[index] != NULL)
11948 free(commentList[index]);
11951 commentList[index] = NULL;
11954 commentList[index] = (char *) malloc(len + 2);
11955 strncpy(commentList[index], text, len);
11956 commentList[index][len] = '\n';
11957 commentList[index][len + 1] = NULLCHAR;
11970 if (ch == '\r') continue;
11972 } while (ch != '\0');
11976 AppendComment(index, text)
11983 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11986 while (*text == '\n') text++;
11987 len = strlen(text);
11988 while (len > 0 && text[len - 1] == '\n') len--;
11990 if (len == 0) return;
11992 if (commentList[index] != NULL) {
11993 old = commentList[index];
11994 oldlen = strlen(old);
11995 commentList[index] = (char *) malloc(oldlen + len + 2);
11996 strcpy(commentList[index], old);
11998 strncpy(&commentList[index][oldlen], text, len);
11999 commentList[index][oldlen + len] = '\n';
12000 commentList[index][oldlen + len + 1] = NULLCHAR;
12002 commentList[index] = (char *) malloc(len + 2);
12003 strncpy(commentList[index], text, len);
12004 commentList[index][len] = '\n';
12005 commentList[index][len + 1] = NULLCHAR;
12009 static char * FindStr( char * text, char * sub_text )
12011 char * result = strstr( text, sub_text );
12013 if( result != NULL ) {
12014 result += strlen( sub_text );
12020 /* [AS] Try to extract PV info from PGN comment */
12021 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12022 char *GetInfoFromComment( int index, char * text )
12026 if( text != NULL && index > 0 ) {
12029 int time = -1, sec = 0, deci;
12030 char * s_eval = FindStr( text, "[%eval " );
12031 char * s_emt = FindStr( text, "[%emt " );
12033 if( s_eval != NULL || s_emt != NULL ) {
12037 if( s_eval != NULL ) {
12038 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12042 if( delim != ']' ) {
12047 if( s_emt != NULL ) {
12051 /* We expect something like: [+|-]nnn.nn/dd */
12054 sep = strchr( text, '/' );
12055 if( sep == NULL || sep < (text+4) ) {
12059 time = -1; sec = -1; deci = -1;
12060 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12061 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12062 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12063 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12067 if( score_lo < 0 || score_lo >= 100 ) {
12071 if(sec >= 0) time = 600*time + 10*sec; else
12072 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12074 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12076 /* [HGM] PV time: now locate end of PV info */
12077 while( *++sep >= '0' && *sep <= '9'); // strip depth
12079 while( *++sep >= '0' && *sep <= '9'); // strip time
12081 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12083 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12084 while(*sep == ' ') sep++;
12095 pvInfoList[index-1].depth = depth;
12096 pvInfoList[index-1].score = score;
12097 pvInfoList[index-1].time = 10*time; // centi-sec
12103 SendToProgram(message, cps)
12105 ChessProgramState *cps;
12107 int count, outCount, error;
12110 if (cps->pr == NULL) return;
12113 if (appData.debugMode) {
12116 fprintf(debugFP, "%ld >%-6s: %s",
12117 SubtractTimeMarks(&now, &programStartTime),
12118 cps->which, message);
12121 count = strlen(message);
12122 outCount = OutputToProcess(cps->pr, message, count, &error);
12123 if (outCount < count && !exiting
12124 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12125 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12126 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12127 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12128 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12129 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12131 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12133 gameInfo.resultDetails = buf;
12135 DisplayFatalError(buf, error, 1);
12140 ReceiveFromProgram(isr, closure, message, count, error)
12141 InputSourceRef isr;
12149 ChessProgramState *cps = (ChessProgramState *)closure;
12151 if (isr != cps->isr) return; /* Killed intentionally */
12155 _("Error: %s chess program (%s) exited unexpectedly"),
12156 cps->which, cps->program);
12157 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12158 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12159 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12160 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12162 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12164 gameInfo.resultDetails = buf;
12166 RemoveInputSource(cps->isr);
12167 DisplayFatalError(buf, 0, 1);
12170 _("Error reading from %s chess program (%s)"),
12171 cps->which, cps->program);
12172 RemoveInputSource(cps->isr);
12174 /* [AS] Program is misbehaving badly... kill it */
12175 if( count == -2 ) {
12176 DestroyChildProcess( cps->pr, 9 );
12180 DisplayFatalError(buf, error, 1);
12185 if ((end_str = strchr(message, '\r')) != NULL)
12186 *end_str = NULLCHAR;
12187 if ((end_str = strchr(message, '\n')) != NULL)
12188 *end_str = NULLCHAR;
12190 if (appData.debugMode) {
12191 TimeMark now; int print = 1;
12192 char *quote = ""; char c; int i;
12194 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12195 char start = message[0];
12196 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12197 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12198 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12199 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12200 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12201 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12202 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12203 { quote = "# "; print = (appData.engineComments == 2); }
12204 message[0] = start; // restore original message
12208 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12209 SubtractTimeMarks(&now, &programStartTime), cps->which,
12215 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12216 if (appData.icsEngineAnalyze) {
12217 if (strstr(message, "whisper") != NULL ||
12218 strstr(message, "kibitz") != NULL ||
12219 strstr(message, "tellics") != NULL) return;
12222 HandleMachineMove(message, cps);
12227 SendTimeControl(cps, mps, tc, inc, sd, st)
12228 ChessProgramState *cps;
12229 int mps, inc, sd, st;
12235 if( timeControl_2 > 0 ) {
12236 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12237 tc = timeControl_2;
12240 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12241 inc /= cps->timeOdds;
12242 st /= cps->timeOdds;
12244 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12247 /* Set exact time per move, normally using st command */
12248 if (cps->stKludge) {
12249 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12251 if (seconds == 0) {
12252 sprintf(buf, "level 1 %d\n", st/60);
12254 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12257 sprintf(buf, "st %d\n", st);
12260 /* Set conventional or incremental time control, using level command */
12261 if (seconds == 0) {
12262 /* Note old gnuchess bug -- minutes:seconds used to not work.
12263 Fixed in later versions, but still avoid :seconds
12264 when seconds is 0. */
12265 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12267 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12268 seconds, inc/1000);
12271 SendToProgram(buf, cps);
12273 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12274 /* Orthogonally, limit search to given depth */
12276 if (cps->sdKludge) {
12277 sprintf(buf, "depth\n%d\n", sd);
12279 sprintf(buf, "sd %d\n", sd);
12281 SendToProgram(buf, cps);
12284 if(cps->nps > 0) { /* [HGM] nps */
12285 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12287 sprintf(buf, "nps %d\n", cps->nps);
12288 SendToProgram(buf, cps);
12293 ChessProgramState *WhitePlayer()
12294 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12296 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12297 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12303 SendTimeRemaining(cps, machineWhite)
12304 ChessProgramState *cps;
12305 int /*boolean*/ machineWhite;
12307 char message[MSG_SIZ];
12310 /* Note: this routine must be called when the clocks are stopped
12311 or when they have *just* been set or switched; otherwise
12312 it will be off by the time since the current tick started.
12314 if (machineWhite) {
12315 time = whiteTimeRemaining / 10;
12316 otime = blackTimeRemaining / 10;
12318 time = blackTimeRemaining / 10;
12319 otime = whiteTimeRemaining / 10;
12321 /* [HGM] translate opponent's time by time-odds factor */
12322 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12323 if (appData.debugMode) {
12324 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12327 if (time <= 0) time = 1;
12328 if (otime <= 0) otime = 1;
12330 sprintf(message, "time %ld\n", time);
12331 SendToProgram(message, cps);
12333 sprintf(message, "otim %ld\n", otime);
12334 SendToProgram(message, cps);
12338 BoolFeature(p, name, loc, cps)
12342 ChessProgramState *cps;
12345 int len = strlen(name);
12347 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12349 sscanf(*p, "%d", &val);
12351 while (**p && **p != ' ') (*p)++;
12352 sprintf(buf, "accepted %s\n", name);
12353 SendToProgram(buf, cps);
12360 IntFeature(p, name, loc, cps)
12364 ChessProgramState *cps;
12367 int len = strlen(name);
12368 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12370 sscanf(*p, "%d", loc);
12371 while (**p && **p != ' ') (*p)++;
12372 sprintf(buf, "accepted %s\n", name);
12373 SendToProgram(buf, cps);
12380 StringFeature(p, name, loc, cps)
12384 ChessProgramState *cps;
12387 int len = strlen(name);
12388 if (strncmp((*p), name, len) == 0
12389 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12391 sscanf(*p, "%[^\"]", loc);
12392 while (**p && **p != '\"') (*p)++;
12393 if (**p == '\"') (*p)++;
12394 sprintf(buf, "accepted %s\n", name);
12395 SendToProgram(buf, cps);
12402 ParseOption(Option *opt, ChessProgramState *cps)
12403 // [HGM] options: process the string that defines an engine option, and determine
12404 // name, type, default value, and allowed value range
12406 char *p, *q, buf[MSG_SIZ];
12407 int n, min = (-1)<<31, max = 1<<31, def;
12409 if(p = strstr(opt->name, " -spin ")) {
12410 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12411 if(max < min) max = min; // enforce consistency
12412 if(def < min) def = min;
12413 if(def > max) def = max;
12418 } else if((p = strstr(opt->name, " -slider "))) {
12419 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12420 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12421 if(max < min) max = min; // enforce consistency
12422 if(def < min) def = min;
12423 if(def > max) def = max;
12427 opt->type = Spin; // Slider;
12428 } else if((p = strstr(opt->name, " -string "))) {
12429 opt->textValue = p+9;
12430 opt->type = TextBox;
12431 } else if((p = strstr(opt->name, " -file "))) {
12432 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12433 opt->textValue = p+7;
12434 opt->type = TextBox; // FileName;
12435 } else if((p = strstr(opt->name, " -path "))) {
12436 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12437 opt->textValue = p+7;
12438 opt->type = TextBox; // PathName;
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 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12447 opt->value = n = 0;
12448 while(q = StrStr(q, " /// ")) {
12449 n++; *q = 0; // count choices, and null-terminate each of them
12451 if(*q == '*') { // remember default, which is marked with * prefix
12455 cps->comboList[cps->comboCnt++] = q;
12457 cps->comboList[cps->comboCnt++] = NULL;
12459 opt->type = ComboBox;
12460 } else if(p = strstr(opt->name, " -button")) {
12461 opt->type = Button;
12462 } else if(p = strstr(opt->name, " -save")) {
12463 opt->type = SaveButton;
12464 } else return FALSE;
12465 *p = 0; // terminate option name
12466 // now look if the command-line options define a setting for this engine option.
12467 if(cps->optionSettings && cps->optionSettings[0])
12468 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12469 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12470 sprintf(buf, "option %s", p);
12471 if(p = strstr(buf, ",")) *p = 0;
12473 SendToProgram(buf, cps);
12479 FeatureDone(cps, val)
12480 ChessProgramState* cps;
12483 DelayedEventCallback cb = GetDelayedEvent();
12484 if ((cb == InitBackEnd3 && cps == &first) ||
12485 (cb == TwoMachinesEventIfReady && cps == &second)) {
12486 CancelDelayedEvent();
12487 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12489 cps->initDone = val;
12492 /* Parse feature command from engine */
12494 ParseFeatures(args, cps)
12496 ChessProgramState *cps;
12504 while (*p == ' ') p++;
12505 if (*p == NULLCHAR) return;
12507 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12508 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12509 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12510 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12511 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12512 if (BoolFeature(&p, "reuse", &val, cps)) {
12513 /* Engine can disable reuse, but can't enable it if user said no */
12514 if (!val) cps->reuse = FALSE;
12517 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12518 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12519 if (gameMode == TwoMachinesPlay) {
12520 DisplayTwoMachinesTitle();
12526 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12527 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12528 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12529 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12530 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12531 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12532 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12533 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12534 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12535 if (IntFeature(&p, "done", &val, cps)) {
12536 FeatureDone(cps, val);
12539 /* Added by Tord: */
12540 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12541 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12542 /* End of additions by Tord */
12544 /* [HGM] added features: */
12545 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12546 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12547 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12548 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12549 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12550 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12551 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12552 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
12553 if(cps->nrOptions >= MAX_OPTIONS) {
12555 sprintf(buf, "%s engine has too many options\n", cps->which);
12556 DisplayError(buf, 0);
12560 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12561 /* End of additions by HGM */
12563 /* unknown feature: complain and skip */
12565 while (*q && *q != '=') q++;
12566 sprintf(buf, "rejected %.*s\n", q-p, p);
12567 SendToProgram(buf, cps);
12573 while (*p && *p != '\"') p++;
12574 if (*p == '\"') p++;
12576 while (*p && *p != ' ') p++;
12584 PeriodicUpdatesEvent(newState)
12587 if (newState == appData.periodicUpdates)
12590 appData.periodicUpdates=newState;
12592 /* Display type changes, so update it now */
12595 /* Get the ball rolling again... */
12597 AnalysisPeriodicEvent(1);
12598 StartAnalysisClock();
12603 PonderNextMoveEvent(newState)
12606 if (newState == appData.ponderNextMove) return;
12607 if (gameMode == EditPosition) EditPositionDone();
12609 SendToProgram("hard\n", &first);
12610 if (gameMode == TwoMachinesPlay) {
12611 SendToProgram("hard\n", &second);
12614 SendToProgram("easy\n", &first);
12615 thinkOutput[0] = NULLCHAR;
12616 if (gameMode == TwoMachinesPlay) {
12617 SendToProgram("easy\n", &second);
12620 appData.ponderNextMove = newState;
12624 NewSettingEvent(option, command, value)
12630 if (gameMode == EditPosition) EditPositionDone();
12631 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12632 SendToProgram(buf, &first);
12633 if (gameMode == TwoMachinesPlay) {
12634 SendToProgram(buf, &second);
12639 ShowThinkingEvent()
12640 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12642 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12643 int newState = appData.showThinking
12644 // [HGM] thinking: other features now need thinking output as well
12645 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12647 if (oldState == newState) return;
12648 oldState = newState;
12649 if (gameMode == EditPosition) EditPositionDone();
12651 SendToProgram("post\n", &first);
12652 if (gameMode == TwoMachinesPlay) {
12653 SendToProgram("post\n", &second);
12656 SendToProgram("nopost\n", &first);
12657 thinkOutput[0] = NULLCHAR;
12658 if (gameMode == TwoMachinesPlay) {
12659 SendToProgram("nopost\n", &second);
12662 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12666 AskQuestionEvent(title, question, replyPrefix, which)
12667 char *title; char *question; char *replyPrefix; char *which;
12669 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12670 if (pr == NoProc) return;
12671 AskQuestion(title, question, replyPrefix, pr);
12675 DisplayMove(moveNumber)
12678 char message[MSG_SIZ];
12680 char cpThinkOutput[MSG_SIZ];
12682 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12684 if (moveNumber == forwardMostMove - 1 ||
12685 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12687 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12689 if (strchr(cpThinkOutput, '\n')) {
12690 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12693 *cpThinkOutput = NULLCHAR;
12696 /* [AS] Hide thinking from human user */
12697 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12698 *cpThinkOutput = NULLCHAR;
12699 if( thinkOutput[0] != NULLCHAR ) {
12702 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12703 cpThinkOutput[i] = '.';
12705 cpThinkOutput[i] = NULLCHAR;
12706 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12710 if (moveNumber == forwardMostMove - 1 &&
12711 gameInfo.resultDetails != NULL) {
12712 if (gameInfo.resultDetails[0] == NULLCHAR) {
12713 sprintf(res, " %s", PGNResult(gameInfo.result));
12715 sprintf(res, " {%s} %s",
12716 gameInfo.resultDetails, PGNResult(gameInfo.result));
12722 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12723 DisplayMessage(res, cpThinkOutput);
12725 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12726 WhiteOnMove(moveNumber) ? " " : ".. ",
12727 parseList[moveNumber], res);
12728 DisplayMessage(message, cpThinkOutput);
12733 DisplayAnalysisText(text)
12738 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12739 || appData.icsEngineAnalyze) {
12740 sprintf(buf, "Analysis (%s)", first.tidy);
12741 AnalysisPopUp(buf, text);
12749 while (*str && isspace(*str)) ++str;
12750 while (*str && !isspace(*str)) ++str;
12751 if (!*str) return 1;
12752 while (*str && isspace(*str)) ++str;
12753 if (!*str) return 1;
12761 char lst[MSG_SIZ / 2];
12763 static char *xtra[] = { "", " (--)", " (++)" };
12766 if (programStats.time == 0) {
12767 programStats.time = 1;
12770 if (programStats.got_only_move) {
12771 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12773 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12775 nps = (u64ToDouble(programStats.nodes) /
12776 ((double)programStats.time /100.0));
12778 cs = programStats.time % 100;
12779 s = programStats.time / 100;
12785 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12786 if (programStats.move_name[0] != NULLCHAR) {
12787 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12788 programStats.depth,
12789 programStats.nr_moves-programStats.moves_left,
12790 programStats.nr_moves, programStats.move_name,
12791 ((float)programStats.score)/100.0, lst,
12792 only_one_move(lst)?
12793 xtra[programStats.got_fail] : "",
12794 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12796 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12797 programStats.depth,
12798 programStats.nr_moves-programStats.moves_left,
12799 programStats.nr_moves, ((float)programStats.score)/100.0,
12801 only_one_move(lst)?
12802 xtra[programStats.got_fail] : "",
12803 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12806 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12807 programStats.depth,
12808 ((float)programStats.score)/100.0,
12810 only_one_move(lst)?
12811 xtra[programStats.got_fail] : "",
12812 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12815 DisplayAnalysisText(buf);
12819 DisplayComment(moveNumber, text)
12823 char title[MSG_SIZ];
12824 char buf[8000]; // comment can be long!
12827 if( appData.autoDisplayComment ) {
12828 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12829 strcpy(title, "Comment");
12831 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12832 WhiteOnMove(moveNumber) ? " " : ".. ",
12833 parseList[moveNumber]);
12835 // [HGM] PV info: display PV info together with (or as) comment
12836 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12837 if(text == NULL) text = "";
12838 score = pvInfoList[moveNumber].score;
12839 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12840 depth, (pvInfoList[moveNumber].time+50)/100, text);
12843 } else title[0] = 0;
12846 CommentPopUp(title, text);
12849 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12850 * might be busy thinking or pondering. It can be omitted if your
12851 * gnuchess is configured to stop thinking immediately on any user
12852 * input. However, that gnuchess feature depends on the FIONREAD
12853 * ioctl, which does not work properly on some flavors of Unix.
12857 ChessProgramState *cps;
12860 if (!cps->useSigint) return;
12861 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12862 switch (gameMode) {
12863 case MachinePlaysWhite:
12864 case MachinePlaysBlack:
12865 case TwoMachinesPlay:
12866 case IcsPlayingWhite:
12867 case IcsPlayingBlack:
12870 /* Skip if we know it isn't thinking */
12871 if (!cps->maybeThinking) return;
12872 if (appData.debugMode)
12873 fprintf(debugFP, "Interrupting %s\n", cps->which);
12874 InterruptChildProcess(cps->pr);
12875 cps->maybeThinking = FALSE;
12880 #endif /*ATTENTION*/
12886 if (whiteTimeRemaining <= 0) {
12889 if (appData.icsActive) {
12890 if (appData.autoCallFlag &&
12891 gameMode == IcsPlayingBlack && !blackFlag) {
12892 SendToICS(ics_prefix);
12893 SendToICS("flag\n");
12897 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12899 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12900 if (appData.autoCallFlag) {
12901 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12908 if (blackTimeRemaining <= 0) {
12911 if (appData.icsActive) {
12912 if (appData.autoCallFlag &&
12913 gameMode == IcsPlayingWhite && !whiteFlag) {
12914 SendToICS(ics_prefix);
12915 SendToICS("flag\n");
12919 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12921 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12922 if (appData.autoCallFlag) {
12923 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12936 if (!appData.clockMode || appData.icsActive ||
12937 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12940 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12942 if ( !WhiteOnMove(forwardMostMove) )
12943 /* White made time control */
12944 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12945 /* [HGM] time odds: correct new time quota for time odds! */
12946 / WhitePlayer()->timeOdds;
12948 /* Black made time control */
12949 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12950 / WhitePlayer()->other->timeOdds;
12954 DisplayBothClocks()
12956 int wom = gameMode == EditPosition ?
12957 !blackPlaysFirst : WhiteOnMove(currentMove);
12958 DisplayWhiteClock(whiteTimeRemaining, wom);
12959 DisplayBlackClock(blackTimeRemaining, !wom);
12963 /* Timekeeping seems to be a portability nightmare. I think everyone
12964 has ftime(), but I'm really not sure, so I'm including some ifdefs
12965 to use other calls if you don't. Clocks will be less accurate if
12966 you have neither ftime nor gettimeofday.
12969 /* VS 2008 requires the #include outside of the function */
12970 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12971 #include <sys/timeb.h>
12974 /* Get the current time as a TimeMark */
12979 #if HAVE_GETTIMEOFDAY
12981 struct timeval timeVal;
12982 struct timezone timeZone;
12984 gettimeofday(&timeVal, &timeZone);
12985 tm->sec = (long) timeVal.tv_sec;
12986 tm->ms = (int) (timeVal.tv_usec / 1000L);
12988 #else /*!HAVE_GETTIMEOFDAY*/
12991 // include <sys/timeb.h> / moved to just above start of function
12992 struct timeb timeB;
12995 tm->sec = (long) timeB.time;
12996 tm->ms = (int) timeB.millitm;
12998 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12999 tm->sec = (long) time(NULL);
13005 /* Return the difference in milliseconds between two
13006 time marks. We assume the difference will fit in a long!
13009 SubtractTimeMarks(tm2, tm1)
13010 TimeMark *tm2, *tm1;
13012 return 1000L*(tm2->sec - tm1->sec) +
13013 (long) (tm2->ms - tm1->ms);
13018 * Code to manage the game clocks.
13020 * In tournament play, black starts the clock and then white makes a move.
13021 * We give the human user a slight advantage if he is playing white---the
13022 * clocks don't run until he makes his first move, so it takes zero time.
13023 * Also, we don't account for network lag, so we could get out of sync
13024 * with GNU Chess's clock -- but then, referees are always right.
13027 static TimeMark tickStartTM;
13028 static long intendedTickLength;
13031 NextTickLength(timeRemaining)
13032 long timeRemaining;
13034 long nominalTickLength, nextTickLength;
13036 if (timeRemaining > 0L && timeRemaining <= 10000L)
13037 nominalTickLength = 100L;
13039 nominalTickLength = 1000L;
13040 nextTickLength = timeRemaining % nominalTickLength;
13041 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13043 return nextTickLength;
13046 /* Adjust clock one minute up or down */
13048 AdjustClock(Boolean which, int dir)
13050 if(which) blackTimeRemaining += 60000*dir;
13051 else whiteTimeRemaining += 60000*dir;
13052 DisplayBothClocks();
13055 /* Stop clocks and reset to a fresh time control */
13059 (void) StopClockTimer();
13060 if (appData.icsActive) {
13061 whiteTimeRemaining = blackTimeRemaining = 0;
13062 } else { /* [HGM] correct new time quote for time odds */
13063 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13064 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13066 if (whiteFlag || blackFlag) {
13068 whiteFlag = blackFlag = FALSE;
13070 DisplayBothClocks();
13073 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13075 /* Decrement running clock by amount of time that has passed */
13079 long timeRemaining;
13080 long lastTickLength, fudge;
13083 if (!appData.clockMode) return;
13084 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13088 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13090 /* Fudge if we woke up a little too soon */
13091 fudge = intendedTickLength - lastTickLength;
13092 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13094 if (WhiteOnMove(forwardMostMove)) {
13095 if(whiteNPS >= 0) lastTickLength = 0;
13096 timeRemaining = whiteTimeRemaining -= lastTickLength;
13097 DisplayWhiteClock(whiteTimeRemaining - fudge,
13098 WhiteOnMove(currentMove));
13100 if(blackNPS >= 0) lastTickLength = 0;
13101 timeRemaining = blackTimeRemaining -= lastTickLength;
13102 DisplayBlackClock(blackTimeRemaining - fudge,
13103 !WhiteOnMove(currentMove));
13106 if (CheckFlags()) return;
13109 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13110 StartClockTimer(intendedTickLength);
13112 /* if the time remaining has fallen below the alarm threshold, sound the
13113 * alarm. if the alarm has sounded and (due to a takeback or time control
13114 * with increment) the time remaining has increased to a level above the
13115 * threshold, reset the alarm so it can sound again.
13118 if (appData.icsActive && appData.icsAlarm) {
13120 /* make sure we are dealing with the user's clock */
13121 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13122 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13125 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13126 alarmSounded = FALSE;
13127 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13129 alarmSounded = TRUE;
13135 /* A player has just moved, so stop the previously running
13136 clock and (if in clock mode) start the other one.
13137 We redisplay both clocks in case we're in ICS mode, because
13138 ICS gives us an update to both clocks after every move.
13139 Note that this routine is called *after* forwardMostMove
13140 is updated, so the last fractional tick must be subtracted
13141 from the color that is *not* on move now.
13146 long lastTickLength;
13148 int flagged = FALSE;
13152 if (StopClockTimer() && appData.clockMode) {
13153 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13154 if (WhiteOnMove(forwardMostMove)) {
13155 if(blackNPS >= 0) lastTickLength = 0;
13156 blackTimeRemaining -= lastTickLength;
13157 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13158 // if(pvInfoList[forwardMostMove-1].time == -1)
13159 pvInfoList[forwardMostMove-1].time = // use GUI time
13160 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13162 if(whiteNPS >= 0) lastTickLength = 0;
13163 whiteTimeRemaining -= lastTickLength;
13164 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13165 // if(pvInfoList[forwardMostMove-1].time == -1)
13166 pvInfoList[forwardMostMove-1].time =
13167 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13169 flagged = CheckFlags();
13171 CheckTimeControl();
13173 if (flagged || !appData.clockMode) return;
13175 switch (gameMode) {
13176 case MachinePlaysBlack:
13177 case MachinePlaysWhite:
13178 case BeginningOfGame:
13179 if (pausing) return;
13183 case PlayFromGameFile:
13192 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13193 whiteTimeRemaining : blackTimeRemaining);
13194 StartClockTimer(intendedTickLength);
13198 /* Stop both clocks */
13202 long lastTickLength;
13205 if (!StopClockTimer()) return;
13206 if (!appData.clockMode) return;
13210 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13211 if (WhiteOnMove(forwardMostMove)) {
13212 if(whiteNPS >= 0) lastTickLength = 0;
13213 whiteTimeRemaining -= lastTickLength;
13214 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13216 if(blackNPS >= 0) lastTickLength = 0;
13217 blackTimeRemaining -= lastTickLength;
13218 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13223 /* Start clock of player on move. Time may have been reset, so
13224 if clock is already running, stop and restart it. */
13228 (void) StopClockTimer(); /* in case it was running already */
13229 DisplayBothClocks();
13230 if (CheckFlags()) return;
13232 if (!appData.clockMode) return;
13233 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13235 GetTimeMark(&tickStartTM);
13236 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13237 whiteTimeRemaining : blackTimeRemaining);
13239 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13240 whiteNPS = blackNPS = -1;
13241 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13242 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13243 whiteNPS = first.nps;
13244 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13245 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13246 blackNPS = first.nps;
13247 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13248 whiteNPS = second.nps;
13249 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13250 blackNPS = second.nps;
13251 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13253 StartClockTimer(intendedTickLength);
13260 long second, minute, hour, day;
13262 static char buf[32];
13264 if (ms > 0 && ms <= 9900) {
13265 /* convert milliseconds to tenths, rounding up */
13266 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13268 sprintf(buf, " %03.1f ", tenths/10.0);
13272 /* convert milliseconds to seconds, rounding up */
13273 /* use floating point to avoid strangeness of integer division
13274 with negative dividends on many machines */
13275 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13282 day = second / (60 * 60 * 24);
13283 second = second % (60 * 60 * 24);
13284 hour = second / (60 * 60);
13285 second = second % (60 * 60);
13286 minute = second / 60;
13287 second = second % 60;
13290 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13291 sign, day, hour, minute, second);
13293 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13295 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13302 * This is necessary because some C libraries aren't ANSI C compliant yet.
13305 StrStr(string, match)
13306 char *string, *match;
13310 length = strlen(match);
13312 for (i = strlen(string) - length; i >= 0; i--, string++)
13313 if (!strncmp(match, string, length))
13320 StrCaseStr(string, match)
13321 char *string, *match;
13325 length = strlen(match);
13327 for (i = strlen(string) - length; i >= 0; i--, string++) {
13328 for (j = 0; j < length; j++) {
13329 if (ToLower(match[j]) != ToLower(string[j]))
13332 if (j == length) return string;
13346 c1 = ToLower(*s1++);
13347 c2 = ToLower(*s2++);
13348 if (c1 > c2) return 1;
13349 if (c1 < c2) return -1;
13350 if (c1 == NULLCHAR) return 0;
13359 return isupper(c) ? tolower(c) : c;
13367 return islower(c) ? toupper(c) : c;
13369 #endif /* !_amigados */
13377 if ((ret = (char *) malloc(strlen(s) + 1))) {
13384 StrSavePtr(s, savePtr)
13385 char *s, **savePtr;
13390 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13391 strcpy(*savePtr, s);
13403 clock = time((time_t *)NULL);
13404 tm = localtime(&clock);
13405 sprintf(buf, "%04d.%02d.%02d",
13406 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13407 return StrSave(buf);
13412 PositionToFEN(move, overrideCastling)
13414 char *overrideCastling;
13416 int i, j, fromX, fromY, toX, toY;
13423 whiteToPlay = (gameMode == EditPosition) ?
13424 !blackPlaysFirst : (move % 2 == 0);
13427 /* Piece placement data */
13428 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13430 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13431 if (boards[move][i][j] == EmptySquare) {
13433 } else { ChessSquare piece = boards[move][i][j];
13434 if (emptycount > 0) {
13435 if(emptycount<10) /* [HGM] can be >= 10 */
13436 *p++ = '0' + emptycount;
13437 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13440 if(PieceToChar(piece) == '+') {
13441 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13443 piece = (ChessSquare)(DEMOTED piece);
13445 *p++ = PieceToChar(piece);
13447 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13448 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13453 if (emptycount > 0) {
13454 if(emptycount<10) /* [HGM] can be >= 10 */
13455 *p++ = '0' + emptycount;
13456 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13463 /* [HGM] print Crazyhouse or Shogi holdings */
13464 if( gameInfo.holdingsWidth ) {
13465 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13467 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13468 piece = boards[move][i][BOARD_WIDTH-1];
13469 if( piece != EmptySquare )
13470 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13471 *p++ = PieceToChar(piece);
13473 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13474 piece = boards[move][BOARD_HEIGHT-i-1][0];
13475 if( piece != EmptySquare )
13476 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13477 *p++ = PieceToChar(piece);
13480 if( q == p ) *p++ = '-';
13486 *p++ = whiteToPlay ? 'w' : 'b';
13489 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13490 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13492 if(nrCastlingRights) {
13494 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13495 /* [HGM] write directly from rights */
13496 if(castlingRights[move][2] >= 0 &&
13497 castlingRights[move][0] >= 0 )
13498 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13499 if(castlingRights[move][2] >= 0 &&
13500 castlingRights[move][1] >= 0 )
13501 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13502 if(castlingRights[move][5] >= 0 &&
13503 castlingRights[move][3] >= 0 )
13504 *p++ = castlingRights[move][3] + AAA;
13505 if(castlingRights[move][5] >= 0 &&
13506 castlingRights[move][4] >= 0 )
13507 *p++ = castlingRights[move][4] + AAA;
13510 /* [HGM] write true castling rights */
13511 if( nrCastlingRights == 6 ) {
13512 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13513 castlingRights[move][2] >= 0 ) *p++ = 'K';
13514 if(castlingRights[move][1] == BOARD_LEFT &&
13515 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13516 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13517 castlingRights[move][5] >= 0 ) *p++ = 'k';
13518 if(castlingRights[move][4] == BOARD_LEFT &&
13519 castlingRights[move][5] >= 0 ) *p++ = 'q';
13522 if (q == p) *p++ = '-'; /* No castling rights */
13526 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13527 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13528 /* En passant target square */
13529 if (move > backwardMostMove) {
13530 fromX = moveList[move - 1][0] - AAA;
13531 fromY = moveList[move - 1][1] - ONE;
13532 toX = moveList[move - 1][2] - AAA;
13533 toY = moveList[move - 1][3] - ONE;
13534 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13535 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13536 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13538 /* 2-square pawn move just happened */
13540 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13551 /* [HGM] find reversible plies */
13552 { int i = 0, j=move;
13554 if (appData.debugMode) { int k;
13555 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13556 for(k=backwardMostMove; k<=forwardMostMove; k++)
13557 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13561 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13562 if( j == backwardMostMove ) i += initialRulePlies;
13563 sprintf(p, "%d ", i);
13564 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13566 /* Fullmove number */
13567 sprintf(p, "%d", (move / 2) + 1);
13569 return StrSave(buf);
13573 ParseFEN(board, blackPlaysFirst, fen)
13575 int *blackPlaysFirst;
13585 /* [HGM] by default clear Crazyhouse holdings, if present */
13586 if(gameInfo.holdingsWidth) {
13587 for(i=0; i<BOARD_HEIGHT; i++) {
13588 board[i][0] = EmptySquare; /* black holdings */
13589 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13590 board[i][1] = (ChessSquare) 0; /* black counts */
13591 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13595 /* Piece placement data */
13596 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13599 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13600 if (*p == '/') p++;
13601 emptycount = gameInfo.boardWidth - j;
13602 while (emptycount--)
13603 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13605 #if(BOARD_SIZE >= 10)
13606 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13607 p++; emptycount=10;
13608 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13609 while (emptycount--)
13610 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13612 } else if (isdigit(*p)) {
13613 emptycount = *p++ - '0';
13614 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13615 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13616 while (emptycount--)
13617 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13618 } else if (*p == '+' || isalpha(*p)) {
13619 if (j >= gameInfo.boardWidth) return FALSE;
13621 piece = CharToPiece(*++p);
13622 if(piece == EmptySquare) return FALSE; /* unknown piece */
13623 piece = (ChessSquare) (PROMOTED piece ); p++;
13624 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13625 } else piece = CharToPiece(*p++);
13627 if(piece==EmptySquare) return FALSE; /* unknown piece */
13628 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13629 piece = (ChessSquare) (PROMOTED piece);
13630 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13633 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13639 while (*p == '/' || *p == ' ') p++;
13641 /* [HGM] look for Crazyhouse holdings here */
13642 while(*p==' ') p++;
13643 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13645 if(*p == '-' ) *p++; /* empty holdings */ else {
13646 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13647 /* if we would allow FEN reading to set board size, we would */
13648 /* have to add holdings and shift the board read so far here */
13649 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13651 if((int) piece >= (int) BlackPawn ) {
13652 i = (int)piece - (int)BlackPawn;
13653 i = PieceToNumber((ChessSquare)i);
13654 if( i >= gameInfo.holdingsSize ) return FALSE;
13655 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13656 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13658 i = (int)piece - (int)WhitePawn;
13659 i = PieceToNumber((ChessSquare)i);
13660 if( i >= gameInfo.holdingsSize ) return FALSE;
13661 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13662 board[i][BOARD_WIDTH-2]++; /* black holdings */
13666 if(*p == ']') *p++;
13669 while(*p == ' ') p++;
13674 *blackPlaysFirst = FALSE;
13677 *blackPlaysFirst = TRUE;
13683 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13684 /* return the extra info in global variiables */
13686 /* set defaults in case FEN is incomplete */
13687 FENepStatus = EP_UNKNOWN;
13688 for(i=0; i<nrCastlingRights; i++ ) {
13689 FENcastlingRights[i] =
13690 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13691 } /* assume possible unless obviously impossible */
13692 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13693 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13694 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13695 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13696 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13697 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13700 while(*p==' ') p++;
13701 if(nrCastlingRights) {
13702 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13703 /* castling indicator present, so default becomes no castlings */
13704 for(i=0; i<nrCastlingRights; i++ ) {
13705 FENcastlingRights[i] = -1;
13708 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13709 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13710 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13711 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13712 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13714 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13715 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13716 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13720 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13721 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13722 FENcastlingRights[2] = whiteKingFile;
13725 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13726 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13727 FENcastlingRights[2] = whiteKingFile;
13730 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13731 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13732 FENcastlingRights[5] = blackKingFile;
13735 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13736 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13737 FENcastlingRights[5] = blackKingFile;
13740 default: /* FRC castlings */
13741 if(c >= 'a') { /* black rights */
13742 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13743 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13744 if(i == BOARD_RGHT) break;
13745 FENcastlingRights[5] = i;
13747 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13748 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13750 FENcastlingRights[3] = c;
13752 FENcastlingRights[4] = c;
13753 } else { /* white rights */
13754 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13755 if(board[0][i] == WhiteKing) break;
13756 if(i == BOARD_RGHT) break;
13757 FENcastlingRights[2] = i;
13758 c -= AAA - 'a' + 'A';
13759 if(board[0][c] >= WhiteKing) break;
13761 FENcastlingRights[0] = c;
13763 FENcastlingRights[1] = c;
13767 if (appData.debugMode) {
13768 fprintf(debugFP, "FEN castling rights:");
13769 for(i=0; i<nrCastlingRights; i++)
13770 fprintf(debugFP, " %d", FENcastlingRights[i]);
13771 fprintf(debugFP, "\n");
13774 while(*p==' ') p++;
13777 /* read e.p. field in games that know e.p. capture */
13778 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13779 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13781 p++; FENepStatus = EP_NONE;
13783 char c = *p++ - AAA;
13785 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13786 if(*p >= '0' && *p <='9') *p++;
13792 if(sscanf(p, "%d", &i) == 1) {
13793 FENrulePlies = i; /* 50-move ply counter */
13794 /* (The move number is still ignored) */
13801 EditPositionPasteFEN(char *fen)
13804 Board initial_position;
13806 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13807 DisplayError(_("Bad FEN position in clipboard"), 0);
13810 int savedBlackPlaysFirst = blackPlaysFirst;
13811 EditPositionEvent();
13812 blackPlaysFirst = savedBlackPlaysFirst;
13813 CopyBoard(boards[0], initial_position);
13814 /* [HGM] copy FEN attributes as well */
13816 initialRulePlies = FENrulePlies;
13817 epStatus[0] = FENepStatus;
13818 for( i=0; i<nrCastlingRights; i++ )
13819 castlingRights[0][i] = FENcastlingRights[i];
13821 EditPositionDone();
13822 DisplayBothClocks();
13823 DrawPosition(FALSE, boards[currentMove]);