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) {
3889 if (appData.premove)
3891 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893 ClearPremoveHighlights();
3895 DrawPosition(FALSE, boards[currentMove]);
3896 DisplayMove(moveNum - 1);
3897 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3900 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3904 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3906 if(bookHit) { // [HGM] book: simulate book reply
3907 static char bookMove[MSG_SIZ]; // a bit generous?
3909 programStats.nodes = programStats.depth = programStats.time =
3910 programStats.score = programStats.got_only_move = 0;
3911 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3913 strcpy(bookMove, "move ");
3914 strcat(bookMove, bookHit);
3915 HandleMachineMove(bookMove, &first);
3924 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925 ics_getting_history = H_REQUESTED;
3926 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3932 AnalysisPeriodicEvent(force)
3935 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936 && !force) || !appData.periodicUpdates)
3939 /* Send . command to Crafty to collect stats */
3940 SendToProgram(".\n", &first);
3942 /* Don't send another until we get a response (this makes
3943 us stop sending to old Crafty's which don't understand
3944 the "." command (sending illegal cmds resets node count & time,
3945 which looks bad)) */
3946 programStats.ok_to_send = 0;
3950 SendMoveToProgram(moveNum, cps)
3952 ChessProgramState *cps;
3956 if (cps->useUsermove) {
3957 SendToProgram("usermove ", cps);
3961 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3962 int len = space - parseList[moveNum];
3963 memcpy(buf, parseList[moveNum], len);
3965 buf[len] = NULLCHAR;
3967 sprintf(buf, "%s\n", parseList[moveNum]);
3969 SendToProgram(buf, cps);
3971 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3972 AlphaRank(moveList[moveNum], 4);
3973 SendToProgram(moveList[moveNum], cps);
3974 AlphaRank(moveList[moveNum], 4); // and back
3976 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3977 * the engine. It would be nice to have a better way to identify castle
3979 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3980 && cps->useOOCastle) {
3981 int fromX = moveList[moveNum][0] - AAA;
3982 int fromY = moveList[moveNum][1] - ONE;
3983 int toX = moveList[moveNum][2] - AAA;
3984 int toY = moveList[moveNum][3] - ONE;
3985 if((boards[moveNum][fromY][fromX] == WhiteKing
3986 && boards[moveNum][toY][toX] == WhiteRook)
3987 || (boards[moveNum][fromY][fromX] == BlackKing
3988 && boards[moveNum][toY][toX] == BlackRook)) {
3989 if(toX > fromX) SendToProgram("O-O\n", cps);
3990 else SendToProgram("O-O-O\n", cps);
3992 else SendToProgram(moveList[moveNum], cps);
3994 else SendToProgram(moveList[moveNum], cps);
3995 /* End of additions by Tord */
3998 /* [HGM] setting up the opening has brought engine in force mode! */
3999 /* Send 'go' if we are in a mode where machine should play. */
4000 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4001 (gameMode == TwoMachinesPlay ||
4003 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4005 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4006 SendToProgram("go\n", cps);
4007 if (appData.debugMode) {
4008 fprintf(debugFP, "(extra)\n");
4011 setboardSpoiledMachineBlack = 0;
4015 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4017 int fromX, fromY, toX, toY;
4019 char user_move[MSG_SIZ];
4023 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4024 (int)moveType, fromX, fromY, toX, toY);
4025 DisplayError(user_move + strlen("say "), 0);
4027 case WhiteKingSideCastle:
4028 case BlackKingSideCastle:
4029 case WhiteQueenSideCastleWild:
4030 case BlackQueenSideCastleWild:
4032 case WhiteHSideCastleFR:
4033 case BlackHSideCastleFR:
4035 sprintf(user_move, "o-o\n");
4037 case WhiteQueenSideCastle:
4038 case BlackQueenSideCastle:
4039 case WhiteKingSideCastleWild:
4040 case BlackKingSideCastleWild:
4042 case WhiteASideCastleFR:
4043 case BlackASideCastleFR:
4045 sprintf(user_move, "o-o-o\n");
4047 case WhitePromotionQueen:
4048 case BlackPromotionQueen:
4049 case WhitePromotionRook:
4050 case BlackPromotionRook:
4051 case WhitePromotionBishop:
4052 case BlackPromotionBishop:
4053 case WhitePromotionKnight:
4054 case BlackPromotionKnight:
4055 case WhitePromotionKing:
4056 case BlackPromotionKing:
4057 case WhitePromotionChancellor:
4058 case BlackPromotionChancellor:
4059 case WhitePromotionArchbishop:
4060 case BlackPromotionArchbishop:
4061 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4062 sprintf(user_move, "%c%c%c%c=%c\n",
4063 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4064 PieceToChar(WhiteFerz));
4065 else if(gameInfo.variant == VariantGreat)
4066 sprintf(user_move, "%c%c%c%c=%c\n",
4067 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068 PieceToChar(WhiteMan));
4070 sprintf(user_move, "%c%c%c%c=%c\n",
4071 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072 PieceToChar(PromoPiece(moveType)));
4076 sprintf(user_move, "%c@%c%c\n",
4077 ToUpper(PieceToChar((ChessSquare) fromX)),
4078 AAA + toX, ONE + toY);
4081 case WhiteCapturesEnPassant:
4082 case BlackCapturesEnPassant:
4083 case IllegalMove: /* could be a variant we don't quite understand */
4084 sprintf(user_move, "%c%c%c%c\n",
4085 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4088 SendToICS(user_move);
4092 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4097 if (rf == DROP_RANK) {
4098 sprintf(move, "%c@%c%c\n",
4099 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4101 if (promoChar == 'x' || promoChar == NULLCHAR) {
4102 sprintf(move, "%c%c%c%c\n",
4103 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4105 sprintf(move, "%c%c%c%c%c\n",
4106 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4112 ProcessICSInitScript(f)
4117 while (fgets(buf, MSG_SIZ, f)) {
4118 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4125 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4127 AlphaRank(char *move, int n)
4129 // char *p = move, c; int x, y;
4131 if (appData.debugMode) {
4132 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4136 move[2]>='0' && move[2]<='9' &&
4137 move[3]>='a' && move[3]<='x' ) {
4139 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4140 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4142 if(move[0]>='0' && move[0]<='9' &&
4143 move[1]>='a' && move[1]<='x' &&
4144 move[2]>='0' && move[2]<='9' &&
4145 move[3]>='a' && move[3]<='x' ) {
4146 /* input move, Shogi -> normal */
4147 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4148 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4149 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4150 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4153 move[3]>='0' && move[3]<='9' &&
4154 move[2]>='a' && move[2]<='x' ) {
4156 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4157 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4160 move[0]>='a' && move[0]<='x' &&
4161 move[3]>='0' && move[3]<='9' &&
4162 move[2]>='a' && move[2]<='x' ) {
4163 /* output move, normal -> Shogi */
4164 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4165 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4166 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4167 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4168 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4170 if (appData.debugMode) {
4171 fprintf(debugFP, " out = '%s'\n", move);
4175 /* Parser for moves from gnuchess, ICS, or user typein box */
4177 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4180 ChessMove *moveType;
4181 int *fromX, *fromY, *toX, *toY;
4184 if (appData.debugMode) {
4185 fprintf(debugFP, "move to parse: %s\n", move);
4187 *moveType = yylexstr(moveNum, move);
4189 switch (*moveType) {
4190 case WhitePromotionChancellor:
4191 case BlackPromotionChancellor:
4192 case WhitePromotionArchbishop:
4193 case BlackPromotionArchbishop:
4194 case WhitePromotionQueen:
4195 case BlackPromotionQueen:
4196 case WhitePromotionRook:
4197 case BlackPromotionRook:
4198 case WhitePromotionBishop:
4199 case BlackPromotionBishop:
4200 case WhitePromotionKnight:
4201 case BlackPromotionKnight:
4202 case WhitePromotionKing:
4203 case BlackPromotionKing:
4205 case WhiteCapturesEnPassant:
4206 case BlackCapturesEnPassant:
4207 case WhiteKingSideCastle:
4208 case WhiteQueenSideCastle:
4209 case BlackKingSideCastle:
4210 case BlackQueenSideCastle:
4211 case WhiteKingSideCastleWild:
4212 case WhiteQueenSideCastleWild:
4213 case BlackKingSideCastleWild:
4214 case BlackQueenSideCastleWild:
4215 /* Code added by Tord: */
4216 case WhiteHSideCastleFR:
4217 case WhiteASideCastleFR:
4218 case BlackHSideCastleFR:
4219 case BlackASideCastleFR:
4220 /* End of code added by Tord */
4221 case IllegalMove: /* bug or odd chess variant */
4222 *fromX = currentMoveString[0] - AAA;
4223 *fromY = currentMoveString[1] - ONE;
4224 *toX = currentMoveString[2] - AAA;
4225 *toY = currentMoveString[3] - ONE;
4226 *promoChar = currentMoveString[4];
4227 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4228 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4229 if (appData.debugMode) {
4230 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4232 *fromX = *fromY = *toX = *toY = 0;
4235 if (appData.testLegality) {
4236 return (*moveType != IllegalMove);
4238 return !(fromX == fromY && toX == toY);
4243 *fromX = *moveType == WhiteDrop ?
4244 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4245 (int) CharToPiece(ToLower(currentMoveString[0]));
4247 *toX = currentMoveString[2] - AAA;
4248 *toY = currentMoveString[3] - ONE;
4249 *promoChar = NULLCHAR;
4253 case ImpossibleMove:
4254 case (ChessMove) 0: /* end of file */
4263 if (appData.debugMode) {
4264 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4267 *fromX = *fromY = *toX = *toY = 0;
4268 *promoChar = NULLCHAR;
4273 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4274 // All positions will have equal probability, but the current method will not provide a unique
4275 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4281 int piecesLeft[(int)BlackPawn];
4282 int seed, nrOfShuffles;
4284 void GetPositionNumber()
4285 { // sets global variable seed
4288 seed = appData.defaultFrcPosition;
4289 if(seed < 0) { // randomize based on time for negative FRC position numbers
4290 for(i=0; i<50; i++) seed += random();
4291 seed = random() ^ random() >> 8 ^ random() << 8;
4292 if(seed<0) seed = -seed;
4296 int put(Board board, int pieceType, int rank, int n, int shade)
4297 // put the piece on the (n-1)-th empty squares of the given shade
4301 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4302 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4303 board[rank][i] = (ChessSquare) pieceType;
4304 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4306 piecesLeft[pieceType]--;
4314 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4315 // calculate where the next piece goes, (any empty square), and put it there
4319 i = seed % squaresLeft[shade];
4320 nrOfShuffles *= squaresLeft[shade];
4321 seed /= squaresLeft[shade];
4322 put(board, pieceType, rank, i, shade);
4325 void AddTwoPieces(Board board, int pieceType, int rank)
4326 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4328 int i, n=squaresLeft[ANY], j=n-1, k;
4330 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4331 i = seed % k; // pick one
4334 while(i >= j) i -= j--;
4335 j = n - 1 - j; i += j;
4336 put(board, pieceType, rank, j, ANY);
4337 put(board, pieceType, rank, i, ANY);
4340 void SetUpShuffle(Board board, int number)
4344 GetPositionNumber(); nrOfShuffles = 1;
4346 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4347 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4348 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4350 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4352 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4353 p = (int) board[0][i];
4354 if(p < (int) BlackPawn) piecesLeft[p] ++;
4355 board[0][i] = EmptySquare;
4358 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4359 // shuffles restricted to allow normal castling put KRR first
4360 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4361 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4362 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4363 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4364 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4365 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4366 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4367 put(board, WhiteRook, 0, 0, ANY);
4368 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4371 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4372 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4373 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4374 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4375 while(piecesLeft[p] >= 2) {
4376 AddOnePiece(board, p, 0, LITE);
4377 AddOnePiece(board, p, 0, DARK);
4379 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4382 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4383 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4384 // but we leave King and Rooks for last, to possibly obey FRC restriction
4385 if(p == (int)WhiteRook) continue;
4386 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4387 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4390 // now everything is placed, except perhaps King (Unicorn) and Rooks
4392 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4393 // Last King gets castling rights
4394 while(piecesLeft[(int)WhiteUnicorn]) {
4395 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4396 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4399 while(piecesLeft[(int)WhiteKing]) {
4400 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4406 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4407 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4410 // Only Rooks can be left; simply place them all
4411 while(piecesLeft[(int)WhiteRook]) {
4412 i = put(board, WhiteRook, 0, 0, ANY);
4413 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4416 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4418 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4421 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4422 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4425 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4428 int SetCharTable( char *table, const char * map )
4429 /* [HGM] moved here from winboard.c because of its general usefulness */
4430 /* Basically a safe strcpy that uses the last character as King */
4432 int result = FALSE; int NrPieces;
4434 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4435 && NrPieces >= 12 && !(NrPieces&1)) {
4436 int i; /* [HGM] Accept even length from 12 to 34 */
4438 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4439 for( i=0; i<NrPieces/2-1; i++ ) {
4441 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4443 table[(int) WhiteKing] = map[NrPieces/2-1];
4444 table[(int) BlackKing] = map[NrPieces-1];
4452 void Prelude(Board board)
4453 { // [HGM] superchess: random selection of exo-pieces
4454 int i, j, k; ChessSquare p;
4455 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4457 GetPositionNumber(); // use FRC position number
4459 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4460 SetCharTable(pieceToChar, appData.pieceToCharTable);
4461 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4462 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4465 j = seed%4; seed /= 4;
4466 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4467 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4468 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4469 j = seed%3 + (seed%3 >= j); seed /= 3;
4470 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4471 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4472 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4473 j = seed%3; seed /= 3;
4474 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4475 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4476 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4477 j = seed%2 + (seed%2 >= j); seed /= 2;
4478 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4479 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4480 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4481 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4482 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4483 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4484 put(board, exoPieces[0], 0, 0, ANY);
4485 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4489 InitPosition(redraw)
4492 ChessSquare (* pieces)[BOARD_SIZE];
4493 int i, j, pawnRow, overrule,
4494 oldx = gameInfo.boardWidth,
4495 oldy = gameInfo.boardHeight,
4496 oldh = gameInfo.holdingsWidth,
4497 oldv = gameInfo.variant;
4499 currentMove = forwardMostMove = backwardMostMove = 0;
4500 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4502 /* [AS] Initialize pv info list [HGM] and game status */
4504 for( i=0; i<MAX_MOVES; i++ ) {
4505 pvInfoList[i].depth = 0;
4506 epStatus[i]=EP_NONE;
4507 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4510 initialRulePlies = 0; /* 50-move counter start */
4512 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4513 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4517 /* [HGM] logic here is completely changed. In stead of full positions */
4518 /* the initialized data only consist of the two backranks. The switch */
4519 /* selects which one we will use, which is than copied to the Board */
4520 /* initialPosition, which for the rest is initialized by Pawns and */
4521 /* empty squares. This initial position is then copied to boards[0], */
4522 /* possibly after shuffling, so that it remains available. */
4524 gameInfo.holdingsWidth = 0; /* default board sizes */
4525 gameInfo.boardWidth = 8;
4526 gameInfo.boardHeight = 8;
4527 gameInfo.holdingsSize = 0;
4528 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4529 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4530 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4532 switch (gameInfo.variant) {
4533 case VariantFischeRandom:
4534 shuffleOpenings = TRUE;
4538 case VariantShatranj:
4539 pieces = ShatranjArray;
4540 nrCastlingRights = 0;
4541 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4543 case VariantTwoKings:
4544 pieces = twoKingsArray;
4546 case VariantCapaRandom:
4547 shuffleOpenings = TRUE;
4548 case VariantCapablanca:
4549 pieces = CapablancaArray;
4550 gameInfo.boardWidth = 10;
4551 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4554 pieces = GothicArray;
4555 gameInfo.boardWidth = 10;
4556 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4559 pieces = JanusArray;
4560 gameInfo.boardWidth = 10;
4561 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4562 nrCastlingRights = 6;
4563 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4564 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4565 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4566 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4567 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4568 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4571 pieces = FalconArray;
4572 gameInfo.boardWidth = 10;
4573 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4575 case VariantXiangqi:
4576 pieces = XiangqiArray;
4577 gameInfo.boardWidth = 9;
4578 gameInfo.boardHeight = 10;
4579 nrCastlingRights = 0;
4580 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4583 pieces = ShogiArray;
4584 gameInfo.boardWidth = 9;
4585 gameInfo.boardHeight = 9;
4586 gameInfo.holdingsSize = 7;
4587 nrCastlingRights = 0;
4588 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4590 case VariantCourier:
4591 pieces = CourierArray;
4592 gameInfo.boardWidth = 12;
4593 nrCastlingRights = 0;
4594 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4595 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4597 case VariantKnightmate:
4598 pieces = KnightmateArray;
4599 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4602 pieces = fairyArray;
4603 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4606 pieces = GreatArray;
4607 gameInfo.boardWidth = 10;
4608 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4609 gameInfo.holdingsSize = 8;
4613 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4614 gameInfo.holdingsSize = 8;
4615 startedFromSetupPosition = TRUE;
4617 case VariantCrazyhouse:
4618 case VariantBughouse:
4620 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4621 gameInfo.holdingsSize = 5;
4623 case VariantWildCastle:
4625 /* !!?shuffle with kings guaranteed to be on d or e file */
4626 shuffleOpenings = 1;
4628 case VariantNoCastle:
4630 nrCastlingRights = 0;
4631 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4632 /* !!?unconstrained back-rank shuffle */
4633 shuffleOpenings = 1;
4638 if(appData.NrFiles >= 0) {
4639 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4640 gameInfo.boardWidth = appData.NrFiles;
4642 if(appData.NrRanks >= 0) {
4643 gameInfo.boardHeight = appData.NrRanks;
4645 if(appData.holdingsSize >= 0) {
4646 i = appData.holdingsSize;
4647 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4648 gameInfo.holdingsSize = i;
4650 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4651 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4652 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4654 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4655 if(pawnRow < 1) pawnRow = 1;
4657 /* User pieceToChar list overrules defaults */
4658 if(appData.pieceToCharTable != NULL)
4659 SetCharTable(pieceToChar, appData.pieceToCharTable);
4661 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4663 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4664 s = (ChessSquare) 0; /* account holding counts in guard band */
4665 for( i=0; i<BOARD_HEIGHT; i++ )
4666 initialPosition[i][j] = s;
4668 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4669 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4670 initialPosition[pawnRow][j] = WhitePawn;
4671 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4672 if(gameInfo.variant == VariantXiangqi) {
4674 initialPosition[pawnRow][j] =
4675 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4676 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4677 initialPosition[2][j] = WhiteCannon;
4678 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4682 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4684 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4687 initialPosition[1][j] = WhiteBishop;
4688 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4690 initialPosition[1][j] = WhiteRook;
4691 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4694 if( nrCastlingRights == -1) {
4695 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4696 /* This sets default castling rights from none to normal corners */
4697 /* Variants with other castling rights must set them themselves above */
4698 nrCastlingRights = 6;
4700 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4701 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4702 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4703 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4704 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4705 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4708 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4709 if(gameInfo.variant == VariantGreat) { // promotion commoners
4710 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4711 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4712 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4713 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4716 if(gameInfo.variant == VariantFischeRandom) {
4717 if( appData.defaultFrcPosition < 0 ) {
4718 ShuffleFRC( initialPosition );
4721 SetupFRC( initialPosition, appData.defaultFrcPosition );
4723 startedFromSetupPosition = TRUE;
4726 if (appData.debugMode) {
4727 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4729 if(shuffleOpenings) {
4730 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731 startedFromSetupPosition = TRUE;
4734 if(startedFromPositionFile) {
4735 /* [HGM] loadPos: use PositionFile for every new game */
4736 CopyBoard(initialPosition, filePosition);
4737 for(i=0; i<nrCastlingRights; i++)
4738 castlingRights[0][i] = initialRights[i] = fileRights[i];
4739 startedFromSetupPosition = TRUE;
4742 CopyBoard(boards[0], initialPosition);
4743 if(oldx != gameInfo.boardWidth ||
4744 oldy != gameInfo.boardHeight ||
4745 oldh != gameInfo.holdingsWidth
4747 || oldv == VariantGothic || // For licensing popups
4748 gameInfo.variant == VariantGothic
4751 || oldv == VariantFalcon ||
4752 gameInfo.variant == VariantFalcon
4756 InitDrawingSizes(-2 ,0);
4760 DrawPosition(TRUE, boards[currentMove]);
4765 SendBoard(cps, moveNum)
4766 ChessProgramState *cps;
4769 char message[MSG_SIZ];
4771 if (cps->useSetboard) {
4772 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4773 sprintf(message, "setboard %s\n", fen);
4774 SendToProgram(message, cps);
4780 /* Kludge to set black to move, avoiding the troublesome and now
4781 * deprecated "black" command.
4783 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4785 SendToProgram("edit\n", cps);
4786 SendToProgram("#\n", cps);
4787 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4788 bp = &boards[moveNum][i][BOARD_LEFT];
4789 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4790 if ((int) *bp < (int) BlackPawn) {
4791 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4793 if(message[0] == '+' || message[0] == '~') {
4794 sprintf(message, "%c%c%c+\n",
4795 PieceToChar((ChessSquare)(DEMOTED *bp)),
4798 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4799 message[1] = BOARD_RGHT - 1 - j + '1';
4800 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4802 SendToProgram(message, cps);
4807 SendToProgram("c\n", cps);
4808 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4809 bp = &boards[moveNum][i][BOARD_LEFT];
4810 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4811 if (((int) *bp != (int) EmptySquare)
4812 && ((int) *bp >= (int) BlackPawn)) {
4813 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4815 if(message[0] == '+' || message[0] == '~') {
4816 sprintf(message, "%c%c%c+\n",
4817 PieceToChar((ChessSquare)(DEMOTED *bp)),
4820 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4821 message[1] = BOARD_RGHT - 1 - j + '1';
4822 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4824 SendToProgram(message, cps);
4829 SendToProgram(".\n", cps);
4831 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4835 IsPromotion(fromX, fromY, toX, toY)
4836 int fromX, fromY, toX, toY;
4838 /* [HGM] add Shogi promotions */
4839 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4842 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4843 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4844 /* [HGM] Note to self: line above also weeds out drops */
4845 piece = boards[currentMove][fromY][fromX];
4846 if(gameInfo.variant == VariantShogi) {
4847 promotionZoneSize = 3;
4848 highestPromotingPiece = (int)WhiteKing;
4849 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4850 and if in normal chess we then allow promotion to King, why not
4851 allow promotion of other piece in Shogi? */
4853 if((int)piece >= BlackPawn) {
4854 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4856 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4858 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4859 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4861 return ( (int)piece <= highestPromotingPiece );
4865 InPalace(row, column)
4867 { /* [HGM] for Xiangqi */
4868 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4869 column < (BOARD_WIDTH + 4)/2 &&
4870 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4875 PieceForSquare (x, y)
4879 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4882 return boards[currentMove][y][x];
4886 OKToStartUserMove(x, y)
4889 ChessSquare from_piece;
4892 if (matchMode) return FALSE;
4893 if (gameMode == EditPosition) return TRUE;
4895 if (x >= 0 && y >= 0)
4896 from_piece = boards[currentMove][y][x];
4898 from_piece = EmptySquare;
4900 if (from_piece == EmptySquare) return FALSE;
4902 white_piece = (int)from_piece >= (int)WhitePawn &&
4903 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4906 case PlayFromGameFile:
4908 case TwoMachinesPlay:
4916 case MachinePlaysWhite:
4917 case IcsPlayingBlack:
4918 if (appData.zippyPlay) return FALSE;
4920 DisplayMoveError(_("You are playing Black"));
4925 case MachinePlaysBlack:
4926 case IcsPlayingWhite:
4927 if (appData.zippyPlay) return FALSE;
4929 DisplayMoveError(_("You are playing White"));
4935 if (!white_piece && WhiteOnMove(currentMove)) {
4936 DisplayMoveError(_("It is White's turn"));
4939 if (white_piece && !WhiteOnMove(currentMove)) {
4940 DisplayMoveError(_("It is Black's turn"));
4943 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4944 /* Editing correspondence game history */
4945 /* Could disallow this or prompt for confirmation */
4948 if (currentMove < forwardMostMove) {
4949 /* Discarding moves */
4950 /* Could prompt for confirmation here,
4951 but I don't think that's such a good idea */
4952 forwardMostMove = currentMove;
4956 case BeginningOfGame:
4957 if (appData.icsActive) return FALSE;
4958 if (!appData.noChessProgram) {
4960 DisplayMoveError(_("You are playing White"));
4967 if (!white_piece && WhiteOnMove(currentMove)) {
4968 DisplayMoveError(_("It is White's turn"));
4971 if (white_piece && !WhiteOnMove(currentMove)) {
4972 DisplayMoveError(_("It is Black's turn"));
4981 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4982 && gameMode != AnalyzeFile && gameMode != Training) {
4983 DisplayMoveError(_("Displayed position is not current"));
4989 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4990 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4991 int lastLoadGameUseList = FALSE;
4992 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4993 ChessMove lastLoadGameStart = (ChessMove) 0;
4997 UserMoveTest(fromX, fromY, toX, toY, promoChar)
4998 int fromX, fromY, toX, toY;
5002 ChessSquare pdown, pup;
5004 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5005 if ((fromX == toX) && (fromY == toY)) {
5006 return ImpossibleMove;
5009 /* [HGM] suppress all moves into holdings area and guard band */
5010 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5011 return ImpossibleMove;
5013 /* [HGM] <sameColor> moved to here from winboard.c */
5014 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5015 pdown = boards[currentMove][fromY][fromX];
5016 pup = boards[currentMove][toY][toX];
5017 if ( gameMode != EditPosition &&
5018 (WhitePawn <= pdown && pdown < BlackPawn &&
5019 WhitePawn <= pup && pup < BlackPawn ||
5020 BlackPawn <= pdown && pdown < EmptySquare &&
5021 BlackPawn <= pup && pup < EmptySquare
5022 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5023 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5024 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5026 return ImpossibleMove;
5028 /* Check if the user is playing in turn. This is complicated because we
5029 let the user "pick up" a piece before it is his turn. So the piece he
5030 tried to pick up may have been captured by the time he puts it down!
5031 Therefore we use the color the user is supposed to be playing in this
5032 test, not the color of the piece that is currently on the starting
5033 square---except in EditGame mode, where the user is playing both
5034 sides; fortunately there the capture race can't happen. (It can
5035 now happen in IcsExamining mode, but that's just too bad. The user
5036 will get a somewhat confusing message in that case.)
5040 case PlayFromGameFile:
5042 case TwoMachinesPlay:
5046 /* We switched into a game mode where moves are not accepted,
5047 perhaps while the mouse button was down. */
5048 return ImpossibleMove;
5050 case MachinePlaysWhite:
5051 /* User is moving for Black */
5052 if (WhiteOnMove(currentMove)) {
5053 DisplayMoveError(_("It is White's turn"));
5054 return ImpossibleMove;
5058 case MachinePlaysBlack:
5059 /* User is moving for White */
5060 if (!WhiteOnMove(currentMove)) {
5061 DisplayMoveError(_("It is Black's turn"));
5062 return ImpossibleMove;
5068 case BeginningOfGame:
5071 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5072 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5073 /* User is moving for Black */
5074 if (WhiteOnMove(currentMove)) {
5075 DisplayMoveError(_("It is White's turn"));
5076 return ImpossibleMove;
5079 /* User is moving for White */
5080 if (!WhiteOnMove(currentMove)) {
5081 DisplayMoveError(_("It is Black's turn"));
5082 return ImpossibleMove;
5087 case IcsPlayingBlack:
5088 /* User is moving for Black */
5089 if (WhiteOnMove(currentMove)) {
5090 if (!appData.premove) {
5091 DisplayMoveError(_("It is White's turn"));
5092 } else if (toX >= 0 && toY >= 0) {
5095 premoveFromX = fromX;
5096 premoveFromY = fromY;
5097 premovePromoChar = promoChar;
5099 if (appData.debugMode)
5100 fprintf(debugFP, "Got premove: fromX %d,"
5101 "fromY %d, toX %d, toY %d\n",
5102 fromX, fromY, toX, toY);
5104 return ImpossibleMove;
5108 case IcsPlayingWhite:
5109 /* User is moving for White */
5110 if (!WhiteOnMove(currentMove)) {
5111 if (!appData.premove) {
5112 DisplayMoveError(_("It is Black's turn"));
5113 } else if (toX >= 0 && toY >= 0) {
5116 premoveFromX = fromX;
5117 premoveFromY = fromY;
5118 premovePromoChar = promoChar;
5120 if (appData.debugMode)
5121 fprintf(debugFP, "Got premove: fromX %d,"
5122 "fromY %d, toX %d, toY %d\n",
5123 fromX, fromY, toX, toY);
5125 return ImpossibleMove;
5133 /* EditPosition, empty square, or different color piece;
5134 click-click move is possible */
5135 if (toX == -2 || toY == -2) {
5136 boards[0][fromY][fromX] = EmptySquare;
5137 return AmbiguousMove;
5138 } else if (toX >= 0 && toY >= 0) {
5139 boards[0][toY][toX] = boards[0][fromY][fromX];
5140 boards[0][fromY][fromX] = EmptySquare;
5141 return AmbiguousMove;
5143 return ImpossibleMove;
5146 /* [HGM] If move started in holdings, it means a drop */
5147 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5148 if( pup != EmptySquare ) return ImpossibleMove;
5149 if(appData.testLegality) {
5150 /* it would be more logical if LegalityTest() also figured out
5151 * which drops are legal. For now we forbid pawns on back rank.
5152 * Shogi is on its own here...
5154 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5155 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5156 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5158 return WhiteDrop; /* Not needed to specify white or black yet */
5161 userOfferedDraw = FALSE;
5163 /* [HGM] always test for legality, to get promotion info */
5164 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5165 epStatus[currentMove], castlingRights[currentMove],
5166 fromY, fromX, toY, toX, promoChar);
5168 /* [HGM] but possibly ignore an IllegalMove result */
5169 if (appData.testLegality) {
5170 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5171 DisplayMoveError(_("Illegal move"));
5172 return ImpossibleMove;
5175 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5177 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5178 function is made into one that returns an OK move type if FinishMove
5179 should be called. This to give the calling driver routine the
5180 opportunity to finish the userMove input with a promotion popup,
5181 without bothering the user with this for invalid or illegal moves */
5183 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5186 /* Common tail of UserMoveEvent and DropMenuEvent */
5188 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5190 int fromX, fromY, toX, toY;
5191 /*char*/int promoChar;
5195 if(appData.debugMode)
5196 fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5198 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5200 // [HGM] superchess: suppress promotions to non-available piece
5201 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5202 if(WhiteOnMove(currentMove))
5204 if(!boards[currentMove][k][BOARD_WIDTH-2])
5209 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5214 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5215 move type in caller when we know the move is a legal promotion */
5216 if(moveType == NormalMove && promoChar)
5217 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5219 if(appData.debugMode)
5220 fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5222 /* [HGM] convert drag-and-drop piece drops to standard form */
5223 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5225 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5226 if(appData.debugMode)
5227 fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5228 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5229 // fromX = boards[currentMove][fromY][fromX];
5230 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5232 fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5234 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5236 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare)
5242 /* [HGM] <popupFix> The following if has been moved here from
5243 UserMoveEvent(). Because it seemed to belon here (why not allow
5244 piece drops in training games?), and because it can only be
5245 performed after it is known to what we promote. */
5246 if (gameMode == Training)
5248 /* compare the move played on the board to the next move in the
5249 * game. If they match, display the move and the opponent's response.
5250 * If they don't match, display an error message.
5253 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5254 CopyBoard(testBoard, boards[currentMove]);
5255 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5257 if (CompareBoards(testBoard, boards[currentMove+1]))
5259 ForwardInner(currentMove+1);
5261 /* Autoplay the opponent's response.
5262 * if appData.animate was TRUE when Training mode was entered,
5263 * the response will be animated.
5265 saveAnimate = appData.animate;
5266 appData.animate = animateTraining;
5267 ForwardInner(currentMove+1);
5268 appData.animate = saveAnimate;
5270 /* check for the end of the game */
5271 if (currentMove >= forwardMostMove)
5273 gameMode = PlayFromGameFile;
5275 SetTrainingModeOff();
5276 DisplayInformation(_("End of game"));
5281 DisplayError(_("Incorrect move"), 0);
5286 /* Ok, now we know that the move is good, so we can kill
5287 the previous line in Analysis Mode */
5288 if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5290 forwardMostMove = currentMove;
5293 /* If we need the chess program but it's dead, restart it */
5294 ResurrectChessProgram();
5296 /* A user move restarts a paused game*/
5300 thinkOutput[0] = NULLCHAR;
5302 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5304 if (gameMode == BeginningOfGame)
5306 if (appData.noChessProgram)
5308 gameMode = EditGame;
5314 gameMode = MachinePlaysBlack;
5317 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5321 sprintf(buf, "name %s\n", gameInfo.white);
5322 SendToProgram(buf, &first);
5328 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5330 /* Relay move to ICS or chess engine */
5331 if (appData.icsActive)
5333 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5334 gameMode == IcsExamining)
5336 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5342 if (first.sendTime && (gameMode == BeginningOfGame ||
5343 gameMode == MachinePlaysWhite ||
5344 gameMode == MachinePlaysBlack))
5346 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5348 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5350 // [HGM] book: if program might be playing, let it use book
5351 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5352 first.maybeThinking = TRUE;
5355 SendMoveToProgram(forwardMostMove-1, &first);
5356 if (currentMove == cmailOldMove + 1)
5358 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5362 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5367 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5368 EP_UNKNOWN, castlingRights[currentMove]) )
5375 if (WhiteOnMove(currentMove))
5377 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5381 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5385 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5390 case MachinePlaysBlack:
5391 case MachinePlaysWhite:
5392 /* disable certain menu options while machine is thinking */
5393 SetMachineThinkingEnables();
5401 { // [HGM] book: simulate book reply
5402 static char bookMove[MSG_SIZ]; // a bit generous?
5404 programStats.nodes = programStats.depth = programStats.time =
5405 programStats.score = programStats.got_only_move = 0;
5406 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5408 strcpy(bookMove, "move ");
5409 strcat(bookMove, bookHit);
5410 HandleMachineMove(bookMove, &first);
5417 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5418 int fromX, fromY, toX, toY;
5421 /* [HGM] This routine was added to allow calling of its two logical
5422 parts from other modules in the old way. Before, UserMoveEvent()
5423 automatically called FinishMove() if the move was OK, and returned
5424 otherwise. I separated the two, in order to make it possible to
5425 slip a promotion popup in between. But that it always needs two
5426 calls, to the first part, (now called UserMoveTest() ), and to
5427 FinishMove if the first part succeeded. Calls that do not need
5428 to do anything in between, can call this routine the old way.
5430 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5431 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5432 if(moveType != ImpossibleMove)
5433 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5436 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5438 // char * hint = lastHint;
5439 FrontEndProgramStats stats;
5441 stats.which = cps == &first ? 0 : 1;
5442 stats.depth = cpstats->depth;
5443 stats.nodes = cpstats->nodes;
5444 stats.score = cpstats->score;
5445 stats.time = cpstats->time;
5446 stats.pv = cpstats->movelist;
5447 stats.hint = lastHint;
5448 stats.an_move_index = 0;
5449 stats.an_move_count = 0;
5451 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5452 stats.hint = cpstats->move_name;
5453 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5454 stats.an_move_count = cpstats->nr_moves;
5457 SetProgramStats( &stats );
5460 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5461 { // [HGM] book: this routine intercepts moves to simulate book replies
5462 char *bookHit = NULL;
5464 //first determine if the incoming move brings opponent into his book
5465 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5466 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5467 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5468 if(bookHit != NULL && !cps->bookSuspend) {
5469 // make sure opponent is not going to reply after receiving move to book position
5470 SendToProgram("force\n", cps);
5471 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5473 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5474 // now arrange restart after book miss
5476 // after a book hit we never send 'go', and the code after the call to this routine
5477 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5479 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5480 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5481 SendToProgram(buf, cps);
5482 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5483 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5484 SendToProgram("go\n", cps);
5485 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5486 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5487 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5488 SendToProgram("go\n", cps);
5489 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5491 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5495 ChessProgramState *savedState;
5496 void DeferredBookMove(void)
5498 if(savedState->lastPing != savedState->lastPong)
5499 ScheduleDelayedEvent(DeferredBookMove, 10);
5501 HandleMachineMove(savedMessage, savedState);
5505 HandleMachineMove(message, cps)
5507 ChessProgramState *cps;
5509 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5510 char realname[MSG_SIZ];
5511 int fromX, fromY, toX, toY;
5518 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5520 * Kludge to ignore BEL characters
5522 while (*message == '\007') message++;
5525 * [HGM] engine debug message: ignore lines starting with '#' character
5527 if(cps->debug && *message == '#') return;
5530 * Look for book output
5532 if (cps == &first && bookRequested) {
5533 if (message[0] == '\t' || message[0] == ' ') {
5534 /* Part of the book output is here; append it */
5535 strcat(bookOutput, message);
5536 strcat(bookOutput, " \n");
5538 } else if (bookOutput[0] != NULLCHAR) {
5539 /* All of book output has arrived; display it */
5540 char *p = bookOutput;
5541 while (*p != NULLCHAR) {
5542 if (*p == '\t') *p = ' ';
5545 DisplayInformation(bookOutput);
5546 bookRequested = FALSE;
5547 /* Fall through to parse the current output */
5552 * Look for machine move.
5554 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5555 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5557 /* This method is only useful on engines that support ping */
5558 if (cps->lastPing != cps->lastPong) {
5559 if (gameMode == BeginningOfGame) {
5560 /* Extra move from before last new; ignore */
5561 if (appData.debugMode) {
5562 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5565 if (appData.debugMode) {
5566 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5567 cps->which, gameMode);
5570 SendToProgram("undo\n", cps);
5576 case BeginningOfGame:
5577 /* Extra move from before last reset; ignore */
5578 if (appData.debugMode) {
5579 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5586 /* Extra move after we tried to stop. The mode test is
5587 not a reliable way of detecting this problem, but it's
5588 the best we can do on engines that don't support ping.
5590 if (appData.debugMode) {
5591 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5592 cps->which, gameMode);
5594 SendToProgram("undo\n", cps);
5597 case MachinePlaysWhite:
5598 case IcsPlayingWhite:
5599 machineWhite = TRUE;
5602 case MachinePlaysBlack:
5603 case IcsPlayingBlack:
5604 machineWhite = FALSE;
5607 case TwoMachinesPlay:
5608 machineWhite = (cps->twoMachinesColor[0] == 'w');
5611 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5612 if (appData.debugMode) {
5614 "Ignoring move out of turn by %s, gameMode %d"
5615 ", forwardMost %d\n",
5616 cps->which, gameMode, forwardMostMove);
5621 if (appData.debugMode) { int f = forwardMostMove;
5622 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5623 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5625 if(cps->alphaRank) AlphaRank(machineMove, 4);
5626 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5627 &fromX, &fromY, &toX, &toY, &promoChar)) {
5628 /* Machine move could not be parsed; ignore it. */
5629 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5630 machineMove, cps->which);
5631 DisplayError(buf1, 0);
5632 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5633 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5634 if (gameMode == TwoMachinesPlay) {
5635 GameEnds(machineWhite ? BlackWins : WhiteWins,
5641 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5642 /* So we have to redo legality test with true e.p. status here, */
5643 /* to make sure an illegal e.p. capture does not slip through, */
5644 /* to cause a forfeit on a justified illegal-move complaint */
5645 /* of the opponent. */
5646 if( gameMode==TwoMachinesPlay && appData.testLegality
5647 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5650 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5651 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5652 fromY, fromX, toY, toX, promoChar);
5653 if (appData.debugMode) {
5655 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5656 castlingRights[forwardMostMove][i], castlingRank[i]);
5657 fprintf(debugFP, "castling rights\n");
5659 if(moveType == IllegalMove) {
5660 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5661 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5662 GameEnds(machineWhite ? BlackWins : WhiteWins,
5665 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5666 /* [HGM] Kludge to handle engines that send FRC-style castling
5667 when they shouldn't (like TSCP-Gothic) */
5669 case WhiteASideCastleFR:
5670 case BlackASideCastleFR:
5672 currentMoveString[2]++;
5674 case WhiteHSideCastleFR:
5675 case BlackHSideCastleFR:
5677 currentMoveString[2]--;
5679 default: ; // nothing to do, but suppresses warning of pedantic compilers
5682 hintRequested = FALSE;
5683 lastHint[0] = NULLCHAR;
5684 bookRequested = FALSE;
5685 /* Program may be pondering now */
5686 cps->maybeThinking = TRUE;
5687 if (cps->sendTime == 2) cps->sendTime = 1;
5688 if (cps->offeredDraw) cps->offeredDraw--;
5691 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5693 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5695 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5696 char buf[3*MSG_SIZ];
5698 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %1.0f knps) PV=%s\n",
5699 programStats.score / 100.,
5701 programStats.time / 100.,
5702 (unsigned int)programStats.nodes,
5703 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5704 programStats.movelist);
5709 /* currentMoveString is set as a side-effect of ParseOneMove */
5710 strcpy(machineMove, currentMoveString);
5711 strcat(machineMove, "\n");
5712 strcpy(moveList[forwardMostMove], machineMove);
5714 /* [AS] Save move info and clear stats for next move */
5715 pvInfoList[ forwardMostMove ].score = programStats.score;
5716 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5717 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5718 ClearProgramStats();
5719 thinkOutput[0] = NULLCHAR;
5720 hiddenThinkOutputState = 0;
5722 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5724 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5725 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5728 while( count < adjudicateLossPlies ) {
5729 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5732 score = -score; /* Flip score for winning side */
5735 if( score > adjudicateLossThreshold ) {
5742 if( count >= adjudicateLossPlies ) {
5743 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5745 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5746 "Xboard adjudication",
5753 if( gameMode == TwoMachinesPlay ) {
5754 // [HGM] some adjudications useful with buggy engines
5755 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5756 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5759 if( appData.testLegality )
5760 { /* [HGM] Some more adjudications for obstinate engines */
5761 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5762 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5763 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5764 static int moveCount = 6;
5766 char *reason = NULL;
5768 /* Count what is on board. */
5769 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5770 { ChessSquare p = boards[forwardMostMove][i][j];
5774 { /* count B,N,R and other of each side */
5777 NrK++; break; // [HGM] atomic: count Kings
5781 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5782 bishopsColor |= 1 << ((i^j)&1);
5787 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5788 bishopsColor |= 1 << ((i^j)&1);
5803 PawnAdvance += m; NrPawns++;
5805 NrPieces += (p != EmptySquare);
5806 NrW += ((int)p < (int)BlackPawn);
5807 if(gameInfo.variant == VariantXiangqi &&
5808 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5809 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5810 NrW -= ((int)p < (int)BlackPawn);
5814 /* Some material-based adjudications that have to be made before stalemate test */
5815 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5816 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5817 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5818 if(appData.checkMates) {
5819 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5820 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5821 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5822 "Xboard adjudication: King destroyed", GE_XBOARD );
5827 /* Bare King in Shatranj (loses) or Losers (wins) */
5828 if( NrW == 1 || NrPieces - NrW == 1) {
5829 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5830 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5831 if(appData.checkMates) {
5832 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5833 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5834 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5835 "Xboard adjudication: Bare king", GE_XBOARD );
5839 if( gameInfo.variant == VariantShatranj && --bare < 0)
5841 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5842 if(appData.checkMates) {
5843 /* but only adjudicate if adjudication enabled */
5844 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5845 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5847 "Xboard adjudication: Bare king", GE_XBOARD );
5854 // don't wait for engine to announce game end if we can judge ourselves
5855 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5856 castlingRights[forwardMostMove]) ) {
5858 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5859 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5860 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5861 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5864 reason = "Xboard adjudication: 3rd check";
5865 epStatus[forwardMostMove] = EP_CHECKMATE;
5875 reason = "Xboard adjudication: Stalemate";
5876 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5877 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5878 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5879 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5880 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5881 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5882 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5883 EP_CHECKMATE : EP_WINS);
5884 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5885 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5889 reason = "Xboard adjudication: Checkmate";
5890 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5894 switch(i = epStatus[forwardMostMove]) {
5896 result = GameIsDrawn; break;
5898 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5900 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5902 result = (ChessMove) 0;
5904 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5905 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5906 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5907 GameEnds( result, reason, GE_XBOARD );
5911 /* Next absolutely insufficient mating material. */
5912 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5913 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5914 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5915 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5916 { /* KBK, KNK, KK of KBKB with like Bishops */
5918 /* always flag draws, for judging claims */
5919 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5921 if(appData.materialDraws) {
5922 /* but only adjudicate them if adjudication enabled */
5923 SendToProgram("force\n", cps->other); // suppress reply
5924 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5925 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5926 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5931 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5933 ( NrWR == 1 && NrBR == 1 /* KRKR */
5934 || NrWQ==1 && NrBQ==1 /* KQKQ */
5935 || NrWN==2 || NrBN==2 /* KNNK */
5936 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5938 if(--moveCount < 0 && appData.trivialDraws)
5939 { /* if the first 3 moves do not show a tactical win, declare draw */
5940 SendToProgram("force\n", cps->other); // suppress reply
5941 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5942 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5943 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5946 } else moveCount = 6;
5950 if (appData.debugMode) { int i;
5951 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5952 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5953 appData.drawRepeats);
5954 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5955 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5959 /* Check for rep-draws */
5961 for(k = forwardMostMove-2;
5962 k>=backwardMostMove && k>=forwardMostMove-100 &&
5963 epStatus[k] < EP_UNKNOWN &&
5964 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5968 if (appData.debugMode) {
5969 fprintf(debugFP, " loop\n");
5972 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5974 if (appData.debugMode) {
5975 fprintf(debugFP, "match\n");
5978 /* compare castling rights */
5979 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5980 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5981 rights++; /* King lost rights, while rook still had them */
5982 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5983 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5984 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5985 rights++; /* but at least one rook lost them */
5987 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5988 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5990 if( castlingRights[forwardMostMove][5] >= 0 ) {
5991 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5992 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5996 if (appData.debugMode) {
5997 for(i=0; i<nrCastlingRights; i++)
5998 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6001 if (appData.debugMode) {
6002 fprintf(debugFP, " %d %d\n", rights, k);
6005 if( rights == 0 && ++count > appData.drawRepeats-2
6006 && appData.drawRepeats > 1) {
6007 /* adjudicate after user-specified nr of repeats */
6008 SendToProgram("force\n", cps->other); // suppress reply
6009 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6010 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6011 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6012 // [HGM] xiangqi: check for forbidden perpetuals
6013 int m, ourPerpetual = 1, hisPerpetual = 1;
6014 for(m=forwardMostMove; m>k; m-=2) {
6015 if(MateTest(boards[m], PosFlags(m),
6016 EP_NONE, castlingRights[m]) != MT_CHECK)
6017 ourPerpetual = 0; // the current mover did not always check
6018 if(MateTest(boards[m-1], PosFlags(m-1),
6019 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6020 hisPerpetual = 0; // the opponent did not always check
6022 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6023 ourPerpetual, hisPerpetual);
6024 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6025 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6026 "Xboard adjudication: perpetual checking", GE_XBOARD );
6029 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6030 break; // (or we would have caught him before). Abort repetition-checking loop.
6031 // Now check for perpetual chases
6032 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6033 hisPerpetual = PerpetualChase(k, forwardMostMove);
6034 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6035 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6036 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6037 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6040 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6041 break; // Abort repetition-checking loop.
6043 // if neither of us is checking or chasing all the time, or both are, it is draw
6045 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6048 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6049 epStatus[forwardMostMove] = EP_REP_DRAW;
6053 /* Now we test for 50-move draws. Determine ply count */
6054 count = forwardMostMove;
6055 /* look for last irreversble move */
6056 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6058 /* if we hit starting position, add initial plies */
6059 if( count == backwardMostMove )
6060 count -= initialRulePlies;
6061 count = forwardMostMove - count;
6063 epStatus[forwardMostMove] = EP_RULE_DRAW;
6064 /* this is used to judge if draw claims are legal */
6065 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6066 SendToProgram("force\n", cps->other); // suppress reply
6067 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6068 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6073 /* if draw offer is pending, treat it as a draw claim
6074 * when draw condition present, to allow engines a way to
6075 * claim draws before making their move to avoid a race
6076 * condition occurring after their move
6078 if( cps->other->offeredDraw || cps->offeredDraw ) {
6080 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6081 p = "Draw claim: 50-move rule";
6082 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6083 p = "Draw claim: 3-fold repetition";
6084 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6085 p = "Draw claim: insufficient mating material";
6087 SendToProgram("force\n", cps->other); // suppress reply
6088 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6089 GameEnds( GameIsDrawn, p, GE_XBOARD );
6090 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6096 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6097 SendToProgram("force\n", cps->other); // suppress reply
6098 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6099 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6101 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6108 if (gameMode == TwoMachinesPlay) {
6109 /* [HGM] relaying draw offers moved to after reception of move */
6110 /* and interpreting offer as claim if it brings draw condition */
6111 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6112 SendToProgram("draw\n", cps->other);
6114 if (cps->other->sendTime) {
6115 SendTimeRemaining(cps->other,
6116 cps->other->twoMachinesColor[0] == 'w');
6118 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6119 if (firstMove && !bookHit) {
6121 if (cps->other->useColors) {
6122 SendToProgram(cps->other->twoMachinesColor, cps->other);
6124 SendToProgram("go\n", cps->other);
6126 cps->other->maybeThinking = TRUE;
6129 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6131 if (!pausing && appData.ringBellAfterMoves) {
6136 * Reenable menu items that were disabled while
6137 * machine was thinking
6139 if (gameMode != TwoMachinesPlay)
6140 SetUserThinkingEnables();
6142 // [HGM] book: after book hit opponent has received move and is now in force mode
6143 // force the book reply into it, and then fake that it outputted this move by jumping
6144 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6146 static char bookMove[MSG_SIZ]; // a bit generous?
6148 strcpy(bookMove, "move ");
6149 strcat(bookMove, bookHit);
6152 programStats.nodes = programStats.depth = programStats.time =
6153 programStats.score = programStats.got_only_move = 0;
6154 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6156 if(cps->lastPing != cps->lastPong) {
6157 savedMessage = message; // args for deferred call
6159 ScheduleDelayedEvent(DeferredBookMove, 10);
6168 /* Set special modes for chess engines. Later something general
6169 * could be added here; for now there is just one kludge feature,
6170 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6171 * when "xboard" is given as an interactive command.
6173 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6174 cps->useSigint = FALSE;
6175 cps->useSigterm = FALSE;
6177 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6178 ParseFeatures(message+8, cps);
6179 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6182 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6183 * want this, I was asked to put it in, and obliged.
6185 if (!strncmp(message, "setboard ", 9)) {
6186 Board initial_position; int i;
6188 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6190 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6191 DisplayError(_("Bad FEN received from engine"), 0);
6194 Reset(FALSE, FALSE);
6195 CopyBoard(boards[0], initial_position);
6196 initialRulePlies = FENrulePlies;
6197 epStatus[0] = FENepStatus;
6198 for( i=0; i<nrCastlingRights; i++ )
6199 castlingRights[0][i] = FENcastlingRights[i];
6200 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6201 else gameMode = MachinePlaysBlack;
6202 DrawPosition(FALSE, boards[currentMove]);
6208 * Look for communication commands
6210 if (!strncmp(message, "telluser ", 9)) {
6211 DisplayNote(message + 9);
6214 if (!strncmp(message, "tellusererror ", 14)) {
6215 DisplayError(message + 14, 0);
6218 if (!strncmp(message, "tellopponent ", 13)) {
6219 if (appData.icsActive) {
6221 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6225 DisplayNote(message + 13);
6229 if (!strncmp(message, "tellothers ", 11)) {
6230 if (appData.icsActive) {
6232 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6238 if (!strncmp(message, "tellall ", 8)) {
6239 if (appData.icsActive) {
6241 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6245 DisplayNote(message + 8);
6249 if (strncmp(message, "warning", 7) == 0) {
6250 /* Undocumented feature, use tellusererror in new code */
6251 DisplayError(message, 0);
6254 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6255 strcpy(realname, cps->tidy);
6256 strcat(realname, " query");
6257 AskQuestion(realname, buf2, buf1, cps->pr);
6260 /* Commands from the engine directly to ICS. We don't allow these to be
6261 * sent until we are logged on. Crafty kibitzes have been known to
6262 * interfere with the login process.
6265 if (!strncmp(message, "tellics ", 8)) {
6266 SendToICS(message + 8);
6270 if (!strncmp(message, "tellicsnoalias ", 15)) {
6271 SendToICS(ics_prefix);
6272 SendToICS(message + 15);
6276 /* The following are for backward compatibility only */
6277 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6278 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6279 SendToICS(ics_prefix);
6285 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6289 * If the move is illegal, cancel it and redraw the board.
6290 * Also deal with other error cases. Matching is rather loose
6291 * here to accommodate engines written before the spec.
6293 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6294 strncmp(message, "Error", 5) == 0) {
6295 if (StrStr(message, "name") ||
6296 StrStr(message, "rating") || StrStr(message, "?") ||
6297 StrStr(message, "result") || StrStr(message, "board") ||
6298 StrStr(message, "bk") || StrStr(message, "computer") ||
6299 StrStr(message, "variant") || StrStr(message, "hint") ||
6300 StrStr(message, "random") || StrStr(message, "depth") ||
6301 StrStr(message, "accepted")) {
6304 if (StrStr(message, "protover")) {
6305 /* Program is responding to input, so it's apparently done
6306 initializing, and this error message indicates it is
6307 protocol version 1. So we don't need to wait any longer
6308 for it to initialize and send feature commands. */
6309 FeatureDone(cps, 1);
6310 cps->protocolVersion = 1;
6313 cps->maybeThinking = FALSE;
6315 if (StrStr(message, "draw")) {
6316 /* Program doesn't have "draw" command */
6317 cps->sendDrawOffers = 0;
6320 if (cps->sendTime != 1 &&
6321 (StrStr(message, "time") || StrStr(message, "otim"))) {
6322 /* Program apparently doesn't have "time" or "otim" command */
6326 if (StrStr(message, "analyze")) {
6327 cps->analysisSupport = FALSE;
6328 cps->analyzing = FALSE;
6330 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6331 DisplayError(buf2, 0);
6334 if (StrStr(message, "(no matching move)st")) {
6335 /* Special kludge for GNU Chess 4 only */
6336 cps->stKludge = TRUE;
6337 SendTimeControl(cps, movesPerSession, timeControl,
6338 timeIncrement, appData.searchDepth,
6342 if (StrStr(message, "(no matching move)sd")) {
6343 /* Special kludge for GNU Chess 4 only */
6344 cps->sdKludge = TRUE;
6345 SendTimeControl(cps, movesPerSession, timeControl,
6346 timeIncrement, appData.searchDepth,
6350 if (!StrStr(message, "llegal")) {
6353 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6354 gameMode == IcsIdle) return;
6355 if (forwardMostMove <= backwardMostMove) return;
6357 /* Following removed: it caused a bug where a real illegal move
6358 message in analyze mored would be ignored. */
6359 if (cps == &first && programStats.ok_to_send == 0) {
6360 /* Bogus message from Crafty responding to "." This filtering
6361 can miss some of the bad messages, but fortunately the bug
6362 is fixed in current Crafty versions, so it doesn't matter. */
6366 if (pausing) PauseEvent();
6367 if (gameMode == PlayFromGameFile) {
6368 /* Stop reading this game file */
6369 gameMode = EditGame;
6372 currentMove = --forwardMostMove;
6373 DisplayMove(currentMove-1); /* before DisplayMoveError */
6375 DisplayBothClocks();
6376 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6377 parseList[currentMove], cps->which);
6378 DisplayMoveError(buf1);
6379 DrawPosition(FALSE, boards[currentMove]);
6381 /* [HGM] illegal-move claim should forfeit game when Xboard */
6382 /* only passes fully legal moves */
6383 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6384 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6385 "False illegal-move claim", GE_XBOARD );
6389 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6390 /* Program has a broken "time" command that
6391 outputs a string not ending in newline.
6397 * If chess program startup fails, exit with an error message.
6398 * Attempts to recover here are futile.
6400 if ((StrStr(message, "unknown host") != NULL)
6401 || (StrStr(message, "No remote directory") != NULL)
6402 || (StrStr(message, "not found") != NULL)
6403 || (StrStr(message, "No such file") != NULL)
6404 || (StrStr(message, "can't alloc") != NULL)
6405 || (StrStr(message, "Permission denied") != NULL)) {
6407 cps->maybeThinking = FALSE;
6408 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6409 cps->which, cps->program, cps->host, message);
6410 RemoveInputSource(cps->isr);
6411 DisplayFatalError(buf1, 0, 1);
6416 * Look for hint output
6418 if (sscanf(message, "Hint: %s", buf1) == 1) {
6419 if (cps == &first && hintRequested) {
6420 hintRequested = FALSE;
6421 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6422 &fromX, &fromY, &toX, &toY, &promoChar)) {
6423 (void) CoordsToAlgebraic(boards[forwardMostMove],
6424 PosFlags(forwardMostMove), EP_UNKNOWN,
6425 fromY, fromX, toY, toX, promoChar, buf1);
6426 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6427 DisplayInformation(buf2);
6429 /* Hint move could not be parsed!? */
6430 snprintf(buf2, sizeof(buf2),
6431 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6433 DisplayError(buf2, 0);
6436 strcpy(lastHint, buf1);
6442 * Ignore other messages if game is not in progress
6444 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6445 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6448 * look for win, lose, draw, or draw offer
6450 if (strncmp(message, "1-0", 3) == 0) {
6451 char *p, *q, *r = "";
6452 p = strchr(message, '{');
6460 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6462 } else if (strncmp(message, "0-1", 3) == 0) {
6463 char *p, *q, *r = "";
6464 p = strchr(message, '{');
6472 /* Kludge for Arasan 4.1 bug */
6473 if (strcmp(r, "Black resigns") == 0) {
6474 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6477 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6479 } else if (strncmp(message, "1/2", 3) == 0) {
6480 char *p, *q, *r = "";
6481 p = strchr(message, '{');
6490 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6493 } else if (strncmp(message, "White resign", 12) == 0) {
6494 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6496 } else if (strncmp(message, "Black resign", 12) == 0) {
6497 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6499 } else if (strncmp(message, "White matches", 13) == 0 ||
6500 strncmp(message, "Black matches", 13) == 0 ) {
6501 /* [HGM] ignore GNUShogi noises */
6503 } else if (strncmp(message, "White", 5) == 0 &&
6504 message[5] != '(' &&
6505 StrStr(message, "Black") == NULL) {
6506 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6508 } else if (strncmp(message, "Black", 5) == 0 &&
6509 message[5] != '(') {
6510 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6512 } else if (strcmp(message, "resign") == 0 ||
6513 strcmp(message, "computer resigns") == 0) {
6515 case MachinePlaysBlack:
6516 case IcsPlayingBlack:
6517 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6519 case MachinePlaysWhite:
6520 case IcsPlayingWhite:
6521 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6523 case TwoMachinesPlay:
6524 if (cps->twoMachinesColor[0] == 'w')
6525 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6527 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6534 } else if (strncmp(message, "opponent mates", 14) == 0) {
6536 case MachinePlaysBlack:
6537 case IcsPlayingBlack:
6538 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6540 case MachinePlaysWhite:
6541 case IcsPlayingWhite:
6542 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6544 case TwoMachinesPlay:
6545 if (cps->twoMachinesColor[0] == 'w')
6546 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6548 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6555 } else if (strncmp(message, "computer mates", 14) == 0) {
6557 case MachinePlaysBlack:
6558 case IcsPlayingBlack:
6559 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6561 case MachinePlaysWhite:
6562 case IcsPlayingWhite:
6563 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6565 case TwoMachinesPlay:
6566 if (cps->twoMachinesColor[0] == 'w')
6567 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6569 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6576 } else if (strncmp(message, "checkmate", 9) == 0) {
6577 if (WhiteOnMove(forwardMostMove)) {
6578 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6580 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6583 } else if (strstr(message, "Draw") != NULL ||
6584 strstr(message, "game is a draw") != NULL) {
6585 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6587 } else if (strstr(message, "offer") != NULL &&
6588 strstr(message, "draw") != NULL) {
6590 if (appData.zippyPlay && first.initDone) {
6591 /* Relay offer to ICS */
6592 SendToICS(ics_prefix);
6593 SendToICS("draw\n");
6596 cps->offeredDraw = 2; /* valid until this engine moves twice */
6597 if (gameMode == TwoMachinesPlay) {
6598 if (cps->other->offeredDraw) {
6599 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6600 /* [HGM] in two-machine mode we delay relaying draw offer */
6601 /* until after we also have move, to see if it is really claim */
6605 if (cps->other->sendDrawOffers) {
6606 SendToProgram("draw\n", cps->other);
6610 } else if (gameMode == MachinePlaysWhite ||
6611 gameMode == MachinePlaysBlack) {
6612 if (userOfferedDraw) {
6613 DisplayInformation(_("Machine accepts your draw offer"));
6614 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6616 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6623 * Look for thinking output
6625 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6626 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6628 int plylev, mvleft, mvtot, curscore, time;
6629 char mvname[MOVE_LEN];
6633 int prefixHint = FALSE;
6634 mvname[0] = NULLCHAR;
6637 case MachinePlaysBlack:
6638 case IcsPlayingBlack:
6639 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6641 case MachinePlaysWhite:
6642 case IcsPlayingWhite:
6643 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6648 case IcsObserving: /* [DM] icsEngineAnalyze */
6649 if (!appData.icsEngineAnalyze) ignore = TRUE;
6651 case TwoMachinesPlay:
6652 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6663 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6664 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6666 if (plyext != ' ' && plyext != '\t') {
6670 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6671 if( cps->scoreIsAbsolute &&
6672 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6674 curscore = -curscore;
6678 programStats.depth = plylev;
6679 programStats.nodes = nodes;
6680 programStats.time = time;
6681 programStats.score = curscore;
6682 programStats.got_only_move = 0;
6684 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6687 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6688 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6689 if(WhiteOnMove(forwardMostMove))
6690 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6691 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6694 /* Buffer overflow protection */
6695 if (buf1[0] != NULLCHAR) {
6696 if (strlen(buf1) >= sizeof(programStats.movelist)
6697 && appData.debugMode) {
6699 "PV is too long; using the first %d bytes.\n",
6700 sizeof(programStats.movelist) - 1);
6703 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6705 sprintf(programStats.movelist, " no PV\n");
6708 if (programStats.seen_stat) {
6709 programStats.ok_to_send = 1;
6712 if (strchr(programStats.movelist, '(') != NULL) {
6713 programStats.line_is_book = 1;
6714 programStats.nr_moves = 0;
6715 programStats.moves_left = 0;
6717 programStats.line_is_book = 0;
6720 SendProgramStatsToFrontend( cps, &programStats );
6723 [AS] Protect the thinkOutput buffer from overflow... this
6724 is only useful if buf1 hasn't overflowed first!
6726 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6728 (gameMode == TwoMachinesPlay ?
6729 ToUpper(cps->twoMachinesColor[0]) : ' '),
6730 ((double) curscore) / 100.0,
6731 prefixHint ? lastHint : "",
6732 prefixHint ? " " : "" );
6734 if( buf1[0] != NULLCHAR ) {
6735 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6737 if( strlen(buf1) > max_len ) {
6738 if( appData.debugMode) {
6739 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6741 buf1[max_len+1] = '\0';
6744 strcat( thinkOutput, buf1 );
6747 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6748 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6749 DisplayMove(currentMove - 1);
6754 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6755 /* crafty (9.25+) says "(only move) <move>"
6756 * if there is only 1 legal move
6758 sscanf(p, "(only move) %s", buf1);
6759 sprintf(thinkOutput, "%s (only move)", buf1);
6760 sprintf(programStats.movelist, "%s (only move)", buf1);
6761 programStats.depth = 1;
6762 programStats.nr_moves = 1;
6763 programStats.moves_left = 1;
6764 programStats.nodes = 1;
6765 programStats.time = 1;
6766 programStats.got_only_move = 1;
6768 /* Not really, but we also use this member to
6769 mean "line isn't going to change" (Crafty
6770 isn't searching, so stats won't change) */
6771 programStats.line_is_book = 1;
6773 SendProgramStatsToFrontend( cps, &programStats );
6775 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6776 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6777 DisplayMove(currentMove - 1);
6781 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6782 &time, &nodes, &plylev, &mvleft,
6783 &mvtot, mvname) >= 5) {
6784 /* The stat01: line is from Crafty (9.29+) in response
6785 to the "." command */
6786 programStats.seen_stat = 1;
6787 cps->maybeThinking = TRUE;
6789 if (programStats.got_only_move || !appData.periodicUpdates)
6792 programStats.depth = plylev;
6793 programStats.time = time;
6794 programStats.nodes = nodes;
6795 programStats.moves_left = mvleft;
6796 programStats.nr_moves = mvtot;
6797 strcpy(programStats.move_name, mvname);
6798 programStats.ok_to_send = 1;
6799 programStats.movelist[0] = '\0';
6801 SendProgramStatsToFrontend( cps, &programStats );
6806 } else if (strncmp(message,"++",2) == 0) {
6807 /* Crafty 9.29+ outputs this */
6808 programStats.got_fail = 2;
6811 } else if (strncmp(message,"--",2) == 0) {
6812 /* Crafty 9.29+ outputs this */
6813 programStats.got_fail = 1;
6816 } else if (thinkOutput[0] != NULLCHAR &&
6817 strncmp(message, " ", 4) == 0) {
6818 unsigned message_len;
6821 while (*p && *p == ' ') p++;
6823 message_len = strlen( p );
6825 /* [AS] Avoid buffer overflow */
6826 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6827 strcat(thinkOutput, " ");
6828 strcat(thinkOutput, p);
6831 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6832 strcat(programStats.movelist, " ");
6833 strcat(programStats.movelist, p);
6836 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6837 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6838 DisplayMove(currentMove - 1);
6847 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6848 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6850 ChessProgramStats cpstats;
6852 if (plyext != ' ' && plyext != '\t') {
6856 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6857 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6858 curscore = -curscore;
6861 cpstats.depth = plylev;
6862 cpstats.nodes = nodes;
6863 cpstats.time = time;
6864 cpstats.score = curscore;
6865 cpstats.got_only_move = 0;
6866 cpstats.movelist[0] = '\0';
6868 if (buf1[0] != NULLCHAR) {
6869 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6872 cpstats.ok_to_send = 0;
6873 cpstats.line_is_book = 0;
6874 cpstats.nr_moves = 0;
6875 cpstats.moves_left = 0;
6877 SendProgramStatsToFrontend( cps, &cpstats );
6884 /* Parse a game score from the character string "game", and
6885 record it as the history of the current game. The game
6886 score is NOT assumed to start from the standard position.
6887 The display is not updated in any way.
6890 ParseGameHistory(game)
6894 int fromX, fromY, toX, toY, boardIndex;
6899 if (appData.debugMode)
6900 fprintf(debugFP, "Parsing game history: %s\n", game);
6902 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6903 gameInfo.site = StrSave(appData.icsHost);
6904 gameInfo.date = PGNDate();
6905 gameInfo.round = StrSave("-");
6907 /* Parse out names of players */
6908 while (*game == ' ') game++;
6910 while (*game != ' ') *p++ = *game++;
6912 gameInfo.white = StrSave(buf);
6913 while (*game == ' ') game++;
6915 while (*game != ' ' && *game != '\n') *p++ = *game++;
6917 gameInfo.black = StrSave(buf);
6920 boardIndex = blackPlaysFirst ? 1 : 0;
6923 yyboardindex = boardIndex;
6924 moveType = (ChessMove) yylex();
6926 case IllegalMove: /* maybe suicide chess, etc. */
6927 if (appData.debugMode) {
6928 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6929 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6930 setbuf(debugFP, NULL);
6932 case WhitePromotionChancellor:
6933 case BlackPromotionChancellor:
6934 case WhitePromotionArchbishop:
6935 case BlackPromotionArchbishop:
6936 case WhitePromotionQueen:
6937 case BlackPromotionQueen:
6938 case WhitePromotionRook:
6939 case BlackPromotionRook:
6940 case WhitePromotionBishop:
6941 case BlackPromotionBishop:
6942 case WhitePromotionKnight:
6943 case BlackPromotionKnight:
6944 case WhitePromotionKing:
6945 case BlackPromotionKing:
6947 case WhiteCapturesEnPassant:
6948 case BlackCapturesEnPassant:
6949 case WhiteKingSideCastle:
6950 case WhiteQueenSideCastle:
6951 case BlackKingSideCastle:
6952 case BlackQueenSideCastle:
6953 case WhiteKingSideCastleWild:
6954 case WhiteQueenSideCastleWild:
6955 case BlackKingSideCastleWild:
6956 case BlackQueenSideCastleWild:
6958 case WhiteHSideCastleFR:
6959 case WhiteASideCastleFR:
6960 case BlackHSideCastleFR:
6961 case BlackASideCastleFR:
6963 fromX = currentMoveString[0] - AAA;
6964 fromY = currentMoveString[1] - ONE;
6965 toX = currentMoveString[2] - AAA;
6966 toY = currentMoveString[3] - ONE;
6967 promoChar = currentMoveString[4];
6971 fromX = moveType == WhiteDrop ?
6972 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6973 (int) CharToPiece(ToLower(currentMoveString[0]));
6975 toX = currentMoveString[2] - AAA;
6976 toY = currentMoveString[3] - ONE;
6977 promoChar = NULLCHAR;
6981 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6982 if (appData.debugMode) {
6983 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6984 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6985 setbuf(debugFP, NULL);
6987 DisplayError(buf, 0);
6989 case ImpossibleMove:
6991 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6992 if (appData.debugMode) {
6993 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6994 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6995 setbuf(debugFP, NULL);
6997 DisplayError(buf, 0);
6999 case (ChessMove) 0: /* end of file */
7000 if (boardIndex < backwardMostMove) {
7001 /* Oops, gap. How did that happen? */
7002 DisplayError(_("Gap in move list"), 0);
7005 backwardMostMove = blackPlaysFirst ? 1 : 0;
7006 if (boardIndex > forwardMostMove) {
7007 forwardMostMove = boardIndex;
7011 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7012 strcat(parseList[boardIndex-1], " ");
7013 strcat(parseList[boardIndex-1], yy_text);
7025 case GameUnfinished:
7026 if (gameMode == IcsExamining) {
7027 if (boardIndex < backwardMostMove) {
7028 /* Oops, gap. How did that happen? */
7031 backwardMostMove = blackPlaysFirst ? 1 : 0;
7034 gameInfo.result = moveType;
7035 p = strchr(yy_text, '{');
7036 if (p == NULL) p = strchr(yy_text, '(');
7039 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7041 q = strchr(p, *p == '{' ? '}' : ')');
7042 if (q != NULL) *q = NULLCHAR;
7045 gameInfo.resultDetails = StrSave(p);
7048 if (boardIndex >= forwardMostMove &&
7049 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7050 backwardMostMove = blackPlaysFirst ? 1 : 0;
7053 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7054 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7055 parseList[boardIndex]);
7056 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7057 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7058 /* currentMoveString is set as a side-effect of yylex */
7059 strcpy(moveList[boardIndex], currentMoveString);
7060 strcat(moveList[boardIndex], "\n");
7062 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7063 castlingRights[boardIndex], &epStatus[boardIndex]);
7064 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7065 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7071 if(gameInfo.variant != VariantShogi)
7072 strcat(parseList[boardIndex - 1], "+");
7076 strcat(parseList[boardIndex - 1], "#");
7083 /* Apply a move to the given board */
7085 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7086 int fromX, fromY, toX, toY;
7092 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7094 /* [HGM] compute & store e.p. status and castling rights for new position */
7095 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7098 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7102 if( board[toY][toX] != EmptySquare )
7105 if( board[fromY][fromX] == WhitePawn ) {
7106 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7109 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7110 gameInfo.variant != VariantBerolina || toX < fromX)
7111 *ep = toX | berolina;
7112 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7113 gameInfo.variant != VariantBerolina || toX > fromX)
7117 if( board[fromY][fromX] == BlackPawn ) {
7118 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7120 if( toY-fromY== -2) {
7121 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7122 gameInfo.variant != VariantBerolina || toX < fromX)
7123 *ep = toX | berolina;
7124 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7125 gameInfo.variant != VariantBerolina || toX > fromX)
7130 for(i=0; i<nrCastlingRights; i++) {
7131 if(castling[i] == fromX && castlingRank[i] == fromY ||
7132 castling[i] == toX && castlingRank[i] == toY
7133 ) castling[i] = -1; // revoke for moved or captured piece
7138 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7139 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7140 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7142 if (fromX == toX && fromY == toY) return;
7144 if (fromY == DROP_RANK) {
7146 piece = board[toY][toX] = (ChessSquare) fromX;
7148 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7149 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7150 if(gameInfo.variant == VariantKnightmate)
7151 king += (int) WhiteUnicorn - (int) WhiteKing;
7153 /* Code added by Tord: */
7154 /* FRC castling assumed when king captures friendly rook. */
7155 if (board[fromY][fromX] == WhiteKing &&
7156 board[toY][toX] == WhiteRook) {
7157 board[fromY][fromX] = EmptySquare;
7158 board[toY][toX] = EmptySquare;
7160 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7162 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7164 } else if (board[fromY][fromX] == BlackKing &&
7165 board[toY][toX] == BlackRook) {
7166 board[fromY][fromX] = EmptySquare;
7167 board[toY][toX] = EmptySquare;
7169 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7171 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7173 /* End of code added by Tord */
7175 } else if (board[fromY][fromX] == king
7176 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7177 && toY == fromY && toX > fromX+1) {
7178 board[fromY][fromX] = EmptySquare;
7179 board[toY][toX] = king;
7180 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7181 board[fromY][BOARD_RGHT-1] = EmptySquare;
7182 } else if (board[fromY][fromX] == king
7183 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7184 && toY == fromY && toX < fromX-1) {
7185 board[fromY][fromX] = EmptySquare;
7186 board[toY][toX] = king;
7187 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7188 board[fromY][BOARD_LEFT] = EmptySquare;
7189 } else if (board[fromY][fromX] == WhitePawn
7190 && toY == BOARD_HEIGHT-1
7191 && gameInfo.variant != VariantXiangqi
7193 /* white pawn promotion */
7194 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7195 if (board[toY][toX] == EmptySquare) {
7196 board[toY][toX] = WhiteQueen;
7198 if(gameInfo.variant==VariantBughouse ||
7199 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7200 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7201 board[fromY][fromX] = EmptySquare;
7202 } else if ((fromY == BOARD_HEIGHT-4)
7204 && gameInfo.variant != VariantXiangqi
7205 && gameInfo.variant != VariantBerolina
7206 && (board[fromY][fromX] == WhitePawn)
7207 && (board[toY][toX] == EmptySquare)) {
7208 board[fromY][fromX] = EmptySquare;
7209 board[toY][toX] = WhitePawn;
7210 captured = board[toY - 1][toX];
7211 board[toY - 1][toX] = EmptySquare;
7212 } else if ((fromY == BOARD_HEIGHT-4)
7214 && gameInfo.variant == VariantBerolina
7215 && (board[fromY][fromX] == WhitePawn)
7216 && (board[toY][toX] == EmptySquare)) {
7217 board[fromY][fromX] = EmptySquare;
7218 board[toY][toX] = WhitePawn;
7219 if(oldEP & EP_BEROLIN_A) {
7220 captured = board[fromY][fromX-1];
7221 board[fromY][fromX-1] = EmptySquare;
7222 }else{ captured = board[fromY][fromX+1];
7223 board[fromY][fromX+1] = EmptySquare;
7225 } else if (board[fromY][fromX] == king
7226 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7227 && toY == fromY && toX > fromX+1) {
7228 board[fromY][fromX] = EmptySquare;
7229 board[toY][toX] = king;
7230 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7231 board[fromY][BOARD_RGHT-1] = EmptySquare;
7232 } else if (board[fromY][fromX] == king
7233 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7234 && toY == fromY && toX < fromX-1) {
7235 board[fromY][fromX] = EmptySquare;
7236 board[toY][toX] = king;
7237 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7238 board[fromY][BOARD_LEFT] = EmptySquare;
7239 } else if (fromY == 7 && fromX == 3
7240 && board[fromY][fromX] == BlackKing
7241 && toY == 7 && toX == 5) {
7242 board[fromY][fromX] = EmptySquare;
7243 board[toY][toX] = BlackKing;
7244 board[fromY][7] = EmptySquare;
7245 board[toY][4] = BlackRook;
7246 } else if (fromY == 7 && fromX == 3
7247 && board[fromY][fromX] == BlackKing
7248 && toY == 7 && toX == 1) {
7249 board[fromY][fromX] = EmptySquare;
7250 board[toY][toX] = BlackKing;
7251 board[fromY][0] = EmptySquare;
7252 board[toY][2] = BlackRook;
7253 } else if (board[fromY][fromX] == BlackPawn
7255 && gameInfo.variant != VariantXiangqi
7257 /* black pawn promotion */
7258 board[0][toX] = CharToPiece(ToLower(promoChar));
7259 if (board[0][toX] == EmptySquare) {
7260 board[0][toX] = BlackQueen;
7262 if(gameInfo.variant==VariantBughouse ||
7263 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7264 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7265 board[fromY][fromX] = EmptySquare;
7266 } else if ((fromY == 3)
7268 && gameInfo.variant != VariantXiangqi
7269 && gameInfo.variant != VariantBerolina
7270 && (board[fromY][fromX] == BlackPawn)
7271 && (board[toY][toX] == EmptySquare)) {
7272 board[fromY][fromX] = EmptySquare;
7273 board[toY][toX] = BlackPawn;
7274 captured = board[toY + 1][toX];
7275 board[toY + 1][toX] = EmptySquare;
7276 } else if ((fromY == 3)
7278 && gameInfo.variant == VariantBerolina
7279 && (board[fromY][fromX] == BlackPawn)
7280 && (board[toY][toX] == EmptySquare)) {
7281 board[fromY][fromX] = EmptySquare;
7282 board[toY][toX] = BlackPawn;
7283 if(oldEP & EP_BEROLIN_A) {
7284 captured = board[fromY][fromX-1];
7285 board[fromY][fromX-1] = EmptySquare;
7286 }else{ captured = board[fromY][fromX+1];
7287 board[fromY][fromX+1] = EmptySquare;
7290 board[toY][toX] = board[fromY][fromX];
7291 board[fromY][fromX] = EmptySquare;
7294 /* [HGM] now we promote for Shogi, if needed */
7295 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7296 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7299 if (gameInfo.holdingsWidth != 0) {
7301 /* !!A lot more code needs to be written to support holdings */
7302 /* [HGM] OK, so I have written it. Holdings are stored in the */
7303 /* penultimate board files, so they are automaticlly stored */
7304 /* in the game history. */
7305 if (fromY == DROP_RANK) {
7306 /* Delete from holdings, by decreasing count */
7307 /* and erasing image if necessary */
7309 if(p < (int) BlackPawn) { /* white drop */
7310 p -= (int)WhitePawn;
7311 if(p >= gameInfo.holdingsSize) p = 0;
7312 if(--board[p][BOARD_WIDTH-2] == 0)
7313 board[p][BOARD_WIDTH-1] = EmptySquare;
7314 } else { /* black drop */
7315 p -= (int)BlackPawn;
7316 if(p >= gameInfo.holdingsSize) p = 0;
7317 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7318 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7321 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7322 && gameInfo.variant != VariantBughouse ) {
7323 /* [HGM] holdings: Add to holdings, if holdings exist */
7324 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7325 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7326 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7329 if (p >= (int) BlackPawn) {
7330 p -= (int)BlackPawn;
7331 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7332 /* in Shogi restore piece to its original first */
7333 captured = (ChessSquare) (DEMOTED captured);
7336 p = PieceToNumber((ChessSquare)p);
7337 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7338 board[p][BOARD_WIDTH-2]++;
7339 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7341 p -= (int)WhitePawn;
7342 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7343 captured = (ChessSquare) (DEMOTED captured);
7346 p = PieceToNumber((ChessSquare)p);
7347 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7348 board[BOARD_HEIGHT-1-p][1]++;
7349 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7353 } else if (gameInfo.variant == VariantAtomic) {
7354 if (captured != EmptySquare) {
7356 for (y = toY-1; y <= toY+1; y++) {
7357 for (x = toX-1; x <= toX+1; x++) {
7358 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7359 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7360 board[y][x] = EmptySquare;
7364 board[toY][toX] = EmptySquare;
7367 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7368 /* [HGM] Shogi promotions */
7369 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7372 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7373 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7374 // [HGM] superchess: take promotion piece out of holdings
7375 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7376 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7377 if(!--board[k][BOARD_WIDTH-2])
7378 board[k][BOARD_WIDTH-1] = EmptySquare;
7380 if(!--board[BOARD_HEIGHT-1-k][1])
7381 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7387 /* Updates forwardMostMove */
7389 MakeMove(fromX, fromY, toX, toY, promoChar)
7390 int fromX, fromY, toX, toY;
7393 // forwardMostMove++; // [HGM] bare: moved downstream
7395 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7396 int timeLeft; static int lastLoadFlag=0; int king, piece;
7397 piece = boards[forwardMostMove][fromY][fromX];
7398 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7399 if(gameInfo.variant == VariantKnightmate)
7400 king += (int) WhiteUnicorn - (int) WhiteKing;
7401 if(forwardMostMove == 0) {
7403 fprintf(serverMoves, "%s;", second.tidy);
7404 fprintf(serverMoves, "%s;", first.tidy);
7405 if(!blackPlaysFirst)
7406 fprintf(serverMoves, "%s;", second.tidy);
7407 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7408 lastLoadFlag = loadFlag;
7410 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7411 // print castling suffix
7412 if( toY == fromY && piece == king ) {
7414 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7416 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7419 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7420 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7421 boards[forwardMostMove][toY][toX] == EmptySquare
7423 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7425 if(promoChar != NULLCHAR)
7426 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7428 fprintf(serverMoves, "/%d/%d",
7429 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7430 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7431 else timeLeft = blackTimeRemaining/1000;
7432 fprintf(serverMoves, "/%d", timeLeft);
7434 fflush(serverMoves);
7437 if (forwardMostMove+1 >= MAX_MOVES) {
7438 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7443 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7444 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7445 if (commentList[forwardMostMove+1] != NULL) {
7446 free(commentList[forwardMostMove+1]);
7447 commentList[forwardMostMove+1] = NULL;
7449 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7450 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7451 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7452 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7453 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7454 gameInfo.result = GameUnfinished;
7455 if (gameInfo.resultDetails != NULL) {
7456 free(gameInfo.resultDetails);
7457 gameInfo.resultDetails = NULL;
7459 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7460 moveList[forwardMostMove - 1]);
7461 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7462 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7463 fromY, fromX, toY, toX, promoChar,
7464 parseList[forwardMostMove - 1]);
7465 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7466 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7467 castlingRights[forwardMostMove]) ) {
7473 if(gameInfo.variant != VariantShogi)
7474 strcat(parseList[forwardMostMove - 1], "+");
7478 strcat(parseList[forwardMostMove - 1], "#");
7481 if (appData.debugMode) {
7482 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7487 /* Updates currentMove if not pausing */
7489 ShowMove(fromX, fromY, toX, toY)
7491 int instant = (gameMode == PlayFromGameFile) ?
7492 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7494 if(appData.noGUI) return;
7496 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7500 if (forwardMostMove == currentMove + 1)
7503 // AnimateMove(boards[forwardMostMove - 1],
7504 // fromX, fromY, toX, toY);
7506 if (appData.highlightLastMove)
7508 SetHighlights(fromX, fromY, toX, toY);
7511 currentMove = forwardMostMove;
7514 if (instant) return;
7516 DisplayMove(currentMove - 1);
7517 DrawPosition(FALSE, boards[currentMove]);
7518 DisplayBothClocks();
7519 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7524 void SendEgtPath(ChessProgramState *cps)
7525 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7526 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7528 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7531 char c, *q = name+1, *r, *s;
7533 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7534 while(*p && *p != ',') *q++ = *p++;
7536 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7537 strcmp(name, ",nalimov:") == 0 ) {
7538 // take nalimov path from the menu-changeable option first, if it is defined
7539 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7540 SendToProgram(buf,cps); // send egtbpath command for nalimov
7542 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7543 (s = StrStr(appData.egtFormats, name)) != NULL) {
7544 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7545 s = r = StrStr(s, ":") + 1; // beginning of path info
7546 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7547 c = *r; *r = 0; // temporarily null-terminate path info
7548 *--q = 0; // strip of trailig ':' from name
7549 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7551 SendToProgram(buf,cps); // send egtbpath command for this format
7553 if(*p == ',') p++; // read away comma to position for next format name
7558 InitChessProgram(cps, setup)
7559 ChessProgramState *cps;
7560 int setup; /* [HGM] needed to setup FRC opening position */
7562 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7563 if (appData.noChessProgram) return;
7564 hintRequested = FALSE;
7565 bookRequested = FALSE;
7567 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7568 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7569 if(cps->memSize) { /* [HGM] memory */
7570 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7571 SendToProgram(buf, cps);
7573 SendEgtPath(cps); /* [HGM] EGT */
7574 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7575 sprintf(buf, "cores %d\n", appData.smpCores);
7576 SendToProgram(buf, cps);
7579 SendToProgram(cps->initString, cps);
7580 if (gameInfo.variant != VariantNormal &&
7581 gameInfo.variant != VariantLoadable
7582 /* [HGM] also send variant if board size non-standard */
7583 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7585 char *v = VariantName(gameInfo.variant);
7586 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7587 /* [HGM] in protocol 1 we have to assume all variants valid */
7588 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7589 DisplayFatalError(buf, 0, 1);
7593 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7594 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7595 if( gameInfo.variant == VariantXiangqi )
7596 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7597 if( gameInfo.variant == VariantShogi )
7598 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7599 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7600 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7601 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7602 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7603 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7604 if( gameInfo.variant == VariantCourier )
7605 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7606 if( gameInfo.variant == VariantSuper )
7607 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7608 if( gameInfo.variant == VariantGreat )
7609 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7612 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7613 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7614 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7615 if(StrStr(cps->variants, b) == NULL) {
7616 // specific sized variant not known, check if general sizing allowed
7617 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7618 if(StrStr(cps->variants, "boardsize") == NULL) {
7619 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7620 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7621 DisplayFatalError(buf, 0, 1);
7624 /* [HGM] here we really should compare with the maximum supported board size */
7627 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7628 sprintf(buf, "variant %s\n", b);
7629 SendToProgram(buf, cps);
7631 currentlyInitializedVariant = gameInfo.variant;
7633 /* [HGM] send opening position in FRC to first engine */
7635 SendToProgram("force\n", cps);
7637 /* engine is now in force mode! Set flag to wake it up after first move. */
7638 setboardSpoiledMachineBlack = 1;
7642 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7643 SendToProgram(buf, cps);
7645 cps->maybeThinking = FALSE;
7646 cps->offeredDraw = 0;
7647 if (!appData.icsActive) {
7648 SendTimeControl(cps, movesPerSession, timeControl,
7649 timeIncrement, appData.searchDepth,
7652 if (appData.showThinking
7653 // [HGM] thinking: four options require thinking output to be sent
7654 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7656 SendToProgram("post\n", cps);
7658 SendToProgram("hard\n", cps);
7659 if (!appData.ponderNextMove) {
7660 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7661 it without being sure what state we are in first. "hard"
7662 is not a toggle, so that one is OK.
7664 SendToProgram("easy\n", cps);
7667 sprintf(buf, "ping %d\n", ++cps->lastPing);
7668 SendToProgram(buf, cps);
7670 cps->initDone = TRUE;
7675 StartChessProgram(cps)
7676 ChessProgramState *cps;
7681 if (appData.noChessProgram) return;
7682 cps->initDone = FALSE;
7684 if (strcmp(cps->host, "localhost") == 0) {
7685 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7686 } else if (*appData.remoteShell == NULLCHAR) {
7687 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7689 if (*appData.remoteUser == NULLCHAR) {
7690 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7693 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7694 cps->host, appData.remoteUser, cps->program);
7696 err = StartChildProcess(buf, "", &cps->pr);
7700 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7701 DisplayFatalError(buf, err, 1);
7707 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7708 if (cps->protocolVersion > 1) {
7709 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7710 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7711 cps->comboCnt = 0; // and values of combo boxes
7712 SendToProgram(buf, cps);
7714 SendToProgram("xboard\n", cps);
7720 TwoMachinesEventIfReady P((void))
7722 if (first.lastPing != first.lastPong) {
7723 DisplayMessage("", _("Waiting for first chess program"));
7724 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7727 if (second.lastPing != second.lastPong) {
7728 DisplayMessage("", _("Waiting for second chess program"));
7729 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7737 NextMatchGame P((void))
7739 int index; /* [HGM] autoinc: step lod index during match */
7741 if (*appData.loadGameFile != NULLCHAR) {
7742 index = appData.loadGameIndex;
7743 if(index < 0) { // [HGM] autoinc
7744 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7745 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7747 LoadGameFromFile(appData.loadGameFile,
7749 appData.loadGameFile, FALSE);
7750 } else if (*appData.loadPositionFile != NULLCHAR) {
7751 index = appData.loadPositionIndex;
7752 if(index < 0) { // [HGM] autoinc
7753 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7754 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7756 LoadPositionFromFile(appData.loadPositionFile,
7758 appData.loadPositionFile);
7760 TwoMachinesEventIfReady();
7763 void UserAdjudicationEvent( int result )
7765 ChessMove gameResult = GameIsDrawn;
7768 gameResult = WhiteWins;
7770 else if( result < 0 ) {
7771 gameResult = BlackWins;
7774 if( gameMode == TwoMachinesPlay ) {
7775 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7780 // [HGM] save: calculate checksum of game to make games easily identifiable
7781 int StringCheckSum(char *s)
7784 if(s==NULL) return 0;
7785 while(*s) i = i*259 + *s++;
7792 for(i=backwardMostMove; i<forwardMostMove; i++) {
7793 sum += pvInfoList[i].depth;
7794 sum += StringCheckSum(parseList[i]);
7795 sum += StringCheckSum(commentList[i]);
7798 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7799 return sum + StringCheckSum(commentList[i]);
7800 } // end of save patch
7803 GameEnds(result, resultDetails, whosays)
7805 char *resultDetails;
7808 GameMode nextGameMode;
7812 if(endingGame) return; /* [HGM] crash: forbid recursion */
7815 if (appData.debugMode) {
7816 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7817 result, resultDetails ? resultDetails : "(null)", whosays);
7820 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7821 /* If we are playing on ICS, the server decides when the
7822 game is over, but the engine can offer to draw, claim
7826 if (appData.zippyPlay && first.initDone) {
7827 if (result == GameIsDrawn) {
7828 /* In case draw still needs to be claimed */
7829 SendToICS(ics_prefix);
7830 SendToICS("draw\n");
7831 } else if (StrCaseStr(resultDetails, "resign")) {
7832 SendToICS(ics_prefix);
7833 SendToICS("resign\n");
7837 endingGame = 0; /* [HGM] crash */
7841 /* If we're loading the game from a file, stop */
7842 if (whosays == GE_FILE) {
7843 (void) StopLoadGameTimer();
7847 /* Cancel draw offers */
7848 first.offeredDraw = second.offeredDraw = 0;
7850 /* If this is an ICS game, only ICS can really say it's done;
7851 if not, anyone can. */
7852 isIcsGame = (gameMode == IcsPlayingWhite ||
7853 gameMode == IcsPlayingBlack ||
7854 gameMode == IcsObserving ||
7855 gameMode == IcsExamining);
7857 if (!isIcsGame || whosays == GE_ICS) {
7858 /* OK -- not an ICS game, or ICS said it was done */
7860 if (!isIcsGame && !appData.noChessProgram)
7861 SetUserThinkingEnables();
7863 /* [HGM] if a machine claims the game end we verify this claim */
7864 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7865 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7867 ChessMove trueResult = (ChessMove) -1;
7869 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7870 first.twoMachinesColor[0] :
7871 second.twoMachinesColor[0] ;
7873 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7874 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7875 /* [HGM] verify: engine mate claims accepted if they were flagged */
7876 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7878 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7879 /* [HGM] verify: engine mate claims accepted if they were flagged */
7880 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7882 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7883 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7886 // now verify win claims, but not in drop games, as we don't understand those yet
7887 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7888 || gameInfo.variant == VariantGreat) &&
7889 (result == WhiteWins && claimer == 'w' ||
7890 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7891 if (appData.debugMode) {
7892 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7893 result, epStatus[forwardMostMove], forwardMostMove);
7895 if(result != trueResult) {
7896 sprintf(buf, "False win claim: '%s'", resultDetails);
7897 result = claimer == 'w' ? BlackWins : WhiteWins;
7898 resultDetails = buf;
7901 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7902 && (forwardMostMove <= backwardMostMove ||
7903 epStatus[forwardMostMove-1] > EP_DRAWS ||
7904 (claimer=='b')==(forwardMostMove&1))
7906 /* [HGM] verify: draws that were not flagged are false claims */
7907 sprintf(buf, "False draw claim: '%s'", resultDetails);
7908 result = claimer == 'w' ? BlackWins : WhiteWins;
7909 resultDetails = buf;
7911 /* (Claiming a loss is accepted no questions asked!) */
7914 /* [HGM] bare: don't allow bare King to win */
7915 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7916 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7917 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7918 && result != GameIsDrawn)
7919 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7920 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7921 int p = (int)boards[forwardMostMove][i][j] - color;
7922 if(p >= 0 && p <= (int)WhiteKing) k++;
7924 if (appData.debugMode) {
7925 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7926 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7929 result = GameIsDrawn;
7930 sprintf(buf, "%s but bare king", resultDetails);
7931 resultDetails = buf;
7936 if(serverMoves != NULL && !loadFlag) { char c = '=';
7937 if(result==WhiteWins) c = '+';
7938 if(result==BlackWins) c = '-';
7939 if(resultDetails != NULL)
7940 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7942 if (resultDetails != NULL) {
7943 gameInfo.result = result;
7944 gameInfo.resultDetails = StrSave(resultDetails);
7946 /* display last move only if game was not loaded from file */
7947 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7948 DisplayMove(currentMove - 1);
7950 if (forwardMostMove != 0) {
7951 if (gameMode != PlayFromGameFile && gameMode != EditGame
7952 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7954 if (*appData.saveGameFile != NULLCHAR) {
7955 SaveGameToFile(appData.saveGameFile, TRUE);
7956 } else if (appData.autoSaveGames) {
7959 if (*appData.savePositionFile != NULLCHAR) {
7960 SavePositionToFile(appData.savePositionFile);
7965 /* Tell program how game ended in case it is learning */
7966 /* [HGM] Moved this to after saving the PGN, just in case */
7967 /* engine died and we got here through time loss. In that */
7968 /* case we will get a fatal error writing the pipe, which */
7969 /* would otherwise lose us the PGN. */
7970 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7971 /* output during GameEnds should never be fatal anymore */
7972 if (gameMode == MachinePlaysWhite ||
7973 gameMode == MachinePlaysBlack ||
7974 gameMode == TwoMachinesPlay ||
7975 gameMode == IcsPlayingWhite ||
7976 gameMode == IcsPlayingBlack ||
7977 gameMode == BeginningOfGame) {
7979 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7981 if (first.pr != NoProc) {
7982 SendToProgram(buf, &first);
7984 if (second.pr != NoProc &&
7985 gameMode == TwoMachinesPlay) {
7986 SendToProgram(buf, &second);
7991 if (appData.icsActive) {
7992 if (appData.quietPlay &&
7993 (gameMode == IcsPlayingWhite ||
7994 gameMode == IcsPlayingBlack)) {
7995 SendToICS(ics_prefix);
7996 SendToICS("set shout 1\n");
7998 nextGameMode = IcsIdle;
7999 ics_user_moved = FALSE;
8000 /* clean up premove. It's ugly when the game has ended and the
8001 * premove highlights are still on the board.
8005 ClearPremoveHighlights();
8006 DrawPosition(FALSE, boards[currentMove]);
8008 if (whosays == GE_ICS) {
8011 if (gameMode == IcsPlayingWhite)
8013 else if(gameMode == IcsPlayingBlack)
8017 if (gameMode == IcsPlayingBlack)
8019 else if(gameMode == IcsPlayingWhite)
8026 PlayIcsUnfinishedSound();
8029 } else if (gameMode == EditGame ||
8030 gameMode == PlayFromGameFile ||
8031 gameMode == AnalyzeMode ||
8032 gameMode == AnalyzeFile) {
8033 nextGameMode = gameMode;
8035 nextGameMode = EndOfGame;
8040 nextGameMode = gameMode;
8043 if (appData.noChessProgram) {
8044 gameMode = nextGameMode;
8046 endingGame = 0; /* [HGM] crash */
8051 /* Put first chess program into idle state */
8052 if (first.pr != NoProc &&
8053 (gameMode == MachinePlaysWhite ||
8054 gameMode == MachinePlaysBlack ||
8055 gameMode == TwoMachinesPlay ||
8056 gameMode == IcsPlayingWhite ||
8057 gameMode == IcsPlayingBlack ||
8058 gameMode == BeginningOfGame)) {
8059 SendToProgram("force\n", &first);
8060 if (first.usePing) {
8062 sprintf(buf, "ping %d\n", ++first.lastPing);
8063 SendToProgram(buf, &first);
8066 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8067 /* Kill off first chess program */
8068 if (first.isr != NULL)
8069 RemoveInputSource(first.isr);
8072 if (first.pr != NoProc) {
8074 DoSleep( appData.delayBeforeQuit );
8075 SendToProgram("quit\n", &first);
8076 DoSleep( appData.delayAfterQuit );
8077 DestroyChildProcess(first.pr, first.useSigterm);
8082 /* Put second chess program into idle state */
8083 if (second.pr != NoProc &&
8084 gameMode == TwoMachinesPlay) {
8085 SendToProgram("force\n", &second);
8086 if (second.usePing) {
8088 sprintf(buf, "ping %d\n", ++second.lastPing);
8089 SendToProgram(buf, &second);
8092 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8093 /* Kill off second chess program */
8094 if (second.isr != NULL)
8095 RemoveInputSource(second.isr);
8098 if (second.pr != NoProc) {
8099 DoSleep( appData.delayBeforeQuit );
8100 SendToProgram("quit\n", &second);
8101 DoSleep( appData.delayAfterQuit );
8102 DestroyChildProcess(second.pr, second.useSigterm);
8107 if (matchMode && gameMode == TwoMachinesPlay) {
8110 if (first.twoMachinesColor[0] == 'w') {
8117 if (first.twoMachinesColor[0] == 'b') {
8126 if (matchGame < appData.matchGames) {
8128 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8129 tmp = first.twoMachinesColor;
8130 first.twoMachinesColor = second.twoMachinesColor;
8131 second.twoMachinesColor = tmp;
8133 gameMode = nextGameMode;
8135 if(appData.matchPause>10000 || appData.matchPause<10)
8136 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8137 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8138 endingGame = 0; /* [HGM] crash */
8142 gameMode = nextGameMode;
8143 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8144 first.tidy, second.tidy,
8145 first.matchWins, second.matchWins,
8146 appData.matchGames - (first.matchWins + second.matchWins));
8147 DisplayFatalError(buf, 0, 0);
8150 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8151 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8153 gameMode = nextGameMode;
8155 endingGame = 0; /* [HGM] crash */
8158 /* Assumes program was just initialized (initString sent).
8159 Leaves program in force mode. */
8161 FeedMovesToProgram(cps, upto)
8162 ChessProgramState *cps;
8167 if (appData.debugMode)
8168 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8169 startedFromSetupPosition ? "position and " : "",
8170 backwardMostMove, upto, cps->which);
8171 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8172 // [HGM] variantswitch: make engine aware of new variant
8173 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8174 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8175 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8176 SendToProgram(buf, cps);
8177 currentlyInitializedVariant = gameInfo.variant;
8179 SendToProgram("force\n", cps);
8180 if (startedFromSetupPosition) {
8181 SendBoard(cps, backwardMostMove);
8182 if (appData.debugMode) {
8183 fprintf(debugFP, "feedMoves\n");
8186 for (i = backwardMostMove; i < upto; i++) {
8187 SendMoveToProgram(i, cps);
8193 ResurrectChessProgram()
8195 /* The chess program may have exited.
8196 If so, restart it and feed it all the moves made so far. */
8198 if (appData.noChessProgram || first.pr != NoProc) return;
8200 StartChessProgram(&first);
8201 InitChessProgram(&first, FALSE);
8202 FeedMovesToProgram(&first, currentMove);
8204 if (!first.sendTime) {
8205 /* can't tell gnuchess what its clock should read,
8206 so we bow to its notion. */
8208 timeRemaining[0][currentMove] = whiteTimeRemaining;
8209 timeRemaining[1][currentMove] = blackTimeRemaining;
8212 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8213 appData.icsEngineAnalyze) && first.analysisSupport) {
8214 SendToProgram("analyze\n", &first);
8215 first.analyzing = TRUE;
8228 if (appData.debugMode) {
8229 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8230 redraw, init, gameMode);
8232 pausing = pauseExamInvalid = FALSE;
8233 startedFromSetupPosition = blackPlaysFirst = FALSE;
8235 whiteFlag = blackFlag = FALSE;
8236 userOfferedDraw = FALSE;
8237 hintRequested = bookRequested = FALSE;
8238 first.maybeThinking = FALSE;
8239 second.maybeThinking = FALSE;
8240 first.bookSuspend = FALSE; // [HGM] book
8241 second.bookSuspend = FALSE;
8242 thinkOutput[0] = NULLCHAR;
8243 lastHint[0] = NULLCHAR;
8244 ClearGameInfo(&gameInfo);
8245 gameInfo.variant = StringToVariant(appData.variant);
8246 ics_user_moved = ics_clock_paused = FALSE;
8247 ics_getting_history = H_FALSE;
8249 white_holding[0] = black_holding[0] = NULLCHAR;
8250 ClearProgramStats();
8251 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8255 flipView = appData.flipView;
8256 ClearPremoveHighlights();
8258 alarmSounded = FALSE;
8260 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8261 if(appData.serverMovesName != NULL) {
8262 /* [HGM] prepare to make moves file for broadcasting */
8263 clock_t t = clock();
8264 if(serverMoves != NULL) fclose(serverMoves);
8265 serverMoves = fopen(appData.serverMovesName, "r");
8266 if(serverMoves != NULL) {
8267 fclose(serverMoves);
8268 /* delay 15 sec before overwriting, so all clients can see end */
8269 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8271 serverMoves = fopen(appData.serverMovesName, "w");
8275 gameMode = BeginningOfGame;
8278 if(appData.icsActive) gameInfo.variant = VariantNormal;
8279 InitPosition(redraw);
8280 for (i = 0; i < MAX_MOVES; i++) {
8281 if (commentList[i] != NULL) {
8282 free(commentList[i]);
8283 commentList[i] = NULL;
8288 timeRemaining[0][0] = whiteTimeRemaining;
8289 timeRemaining[1][0] = blackTimeRemaining;
8290 if (first.pr == NULL) {
8291 StartChessProgram(&first);
8294 InitChessProgram(&first, startedFromSetupPosition);
8298 DisplayMessage("", "");
8299 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8300 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8308 if (!AutoPlayOneMove())
8310 if (matchMode || appData.timeDelay == 0)
8312 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8314 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8323 int fromX, fromY, toX, toY;
8325 if (appData.debugMode) {
8326 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8329 if (gameMode != PlayFromGameFile)
8332 if (currentMove >= forwardMostMove) {
8333 gameMode = EditGame;
8336 /* [AS] Clear current move marker at the end of a game */
8337 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8342 toX = moveList[currentMove][2] - AAA;
8343 toY = moveList[currentMove][3] - ONE;
8345 if (moveList[currentMove][1] == '@') {
8346 if (appData.highlightLastMove) {
8347 SetHighlights(-1, -1, toX, toY);
8350 fromX = moveList[currentMove][0] - AAA;
8351 fromY = moveList[currentMove][1] - ONE;
8353 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8355 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8357 if (appData.highlightLastMove) {
8358 SetHighlights(fromX, fromY, toX, toY);
8361 DisplayMove(currentMove);
8362 SendMoveToProgram(currentMove++, &first);
8363 DisplayBothClocks();
8364 DrawPosition(FALSE, boards[currentMove]);
8365 // [HGM] PV info: always display, routine tests if empty
8366 DisplayComment(currentMove - 1, commentList[currentMove]);
8372 LoadGameOneMove(readAhead)
8373 ChessMove readAhead;
8375 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8376 char promoChar = NULLCHAR;
8381 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8382 gameMode != AnalyzeMode && gameMode != Training) {
8387 yyboardindex = forwardMostMove;
8388 if (readAhead != (ChessMove)0) {
8389 moveType = readAhead;
8391 if (gameFileFP == NULL)
8393 moveType = (ChessMove) yylex();
8399 if (appData.debugMode)
8400 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8402 if (*p == '{' || *p == '[' || *p == '(') {
8403 p[strlen(p) - 1] = NULLCHAR;
8407 /* append the comment but don't display it */
8408 while (*p == '\n') p++;
8409 AppendComment(currentMove, p);
8412 case WhiteCapturesEnPassant:
8413 case BlackCapturesEnPassant:
8414 case WhitePromotionChancellor:
8415 case BlackPromotionChancellor:
8416 case WhitePromotionArchbishop:
8417 case BlackPromotionArchbishop:
8418 case WhitePromotionCentaur:
8419 case BlackPromotionCentaur:
8420 case WhitePromotionQueen:
8421 case BlackPromotionQueen:
8422 case WhitePromotionRook:
8423 case BlackPromotionRook:
8424 case WhitePromotionBishop:
8425 case BlackPromotionBishop:
8426 case WhitePromotionKnight:
8427 case BlackPromotionKnight:
8428 case WhitePromotionKing:
8429 case BlackPromotionKing:
8431 case WhiteKingSideCastle:
8432 case WhiteQueenSideCastle:
8433 case BlackKingSideCastle:
8434 case BlackQueenSideCastle:
8435 case WhiteKingSideCastleWild:
8436 case WhiteQueenSideCastleWild:
8437 case BlackKingSideCastleWild:
8438 case BlackQueenSideCastleWild:
8440 case WhiteHSideCastleFR:
8441 case WhiteASideCastleFR:
8442 case BlackHSideCastleFR:
8443 case BlackASideCastleFR:
8445 if (appData.debugMode)
8446 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8447 fromX = currentMoveString[0] - AAA;
8448 fromY = currentMoveString[1] - ONE;
8449 toX = currentMoveString[2] - AAA;
8450 toY = currentMoveString[3] - ONE;
8451 promoChar = currentMoveString[4];
8456 if (appData.debugMode)
8457 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8458 fromX = moveType == WhiteDrop ?
8459 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8460 (int) CharToPiece(ToLower(currentMoveString[0]));
8462 toX = currentMoveString[2] - AAA;
8463 toY = currentMoveString[3] - ONE;
8469 case GameUnfinished:
8470 if (appData.debugMode)
8471 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8472 p = strchr(yy_text, '{');
8473 if (p == NULL) p = strchr(yy_text, '(');
8476 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8478 q = strchr(p, *p == '{' ? '}' : ')');
8479 if (q != NULL) *q = NULLCHAR;
8482 GameEnds(moveType, p, GE_FILE);
8484 if (cmailMsgLoaded) {
8486 flipView = WhiteOnMove(currentMove);
8487 if (moveType == GameUnfinished) flipView = !flipView;
8488 if (appData.debugMode)
8489 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8493 case (ChessMove) 0: /* end of file */
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parser hit end of file\n");
8496 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8497 EP_UNKNOWN, castlingRights[currentMove]) ) {
8503 if (WhiteOnMove(currentMove)) {
8504 GameEnds(BlackWins, "Black mates", GE_FILE);
8506 GameEnds(WhiteWins, "White mates", GE_FILE);
8510 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8517 if (lastLoadGameStart == GNUChessGame) {
8518 /* GNUChessGames have numbers, but they aren't move numbers */
8519 if (appData.debugMode)
8520 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8521 yy_text, (int) moveType);
8522 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8524 /* else fall thru */
8529 /* Reached start of next game in file */
8530 if (appData.debugMode)
8531 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8532 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8533 EP_UNKNOWN, castlingRights[currentMove]) ) {
8539 if (WhiteOnMove(currentMove)) {
8540 GameEnds(BlackWins, "Black mates", GE_FILE);
8542 GameEnds(WhiteWins, "White mates", GE_FILE);
8546 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8552 case PositionDiagram: /* should not happen; ignore */
8553 case ElapsedTime: /* ignore */
8554 case NAG: /* ignore */
8555 if (appData.debugMode)
8556 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8557 yy_text, (int) moveType);
8558 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8561 if (appData.testLegality) {
8562 if (appData.debugMode)
8563 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8564 sprintf(move, _("Illegal move: %d.%s%s"),
8565 (forwardMostMove / 2) + 1,
8566 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8567 DisplayError(move, 0);
8570 if (appData.debugMode)
8571 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8572 yy_text, currentMoveString);
8573 fromX = currentMoveString[0] - AAA;
8574 fromY = currentMoveString[1] - ONE;
8575 toX = currentMoveString[2] - AAA;
8576 toY = currentMoveString[3] - ONE;
8577 promoChar = currentMoveString[4];
8582 if (appData.debugMode)
8583 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8584 sprintf(move, _("Ambiguous move: %d.%s%s"),
8585 (forwardMostMove / 2) + 1,
8586 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8587 DisplayError(move, 0);
8592 case ImpossibleMove:
8593 if (appData.debugMode)
8594 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8595 sprintf(move, _("Illegal move: %d.%s%s"),
8596 (forwardMostMove / 2) + 1,
8597 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8598 DisplayError(move, 0);
8604 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8605 DrawPosition(FALSE, boards[currentMove]);
8606 DisplayBothClocks();
8607 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8608 DisplayComment(currentMove - 1, commentList[currentMove]);
8610 (void) StopLoadGameTimer();
8612 cmailOldMove = forwardMostMove;
8615 /* currentMoveString is set as a side-effect of yylex */
8616 strcat(currentMoveString, "\n");
8617 strcpy(moveList[forwardMostMove], currentMoveString);
8619 thinkOutput[0] = NULLCHAR;
8620 MakeMove(fromX, fromY, toX, toY, promoChar);
8621 currentMove = forwardMostMove;
8626 /* Load the nth game from the given file */
8628 LoadGameFromFile(filename, n, title, useList)
8632 /*Boolean*/ int useList;
8637 if (strcmp(filename, "-") == 0) {
8641 f = fopen(filename, "rb");
8643 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8644 DisplayError(buf, errno);
8648 if (fseek(f, 0, 0) == -1) {
8649 /* f is not seekable; probably a pipe */
8652 if (useList && n == 0) {
8653 int error = GameListBuild(f);
8655 DisplayError(_("Cannot build game list"), error);
8656 } else if (!ListEmpty(&gameList) &&
8657 ((ListGame *) gameList.tailPred)->number > 1) {
8658 GameListPopUp(f, title);
8665 return LoadGame(f, n, title, FALSE);
8670 MakeRegisteredMove()
8672 int fromX, fromY, toX, toY;
8674 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8675 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8678 if (appData.debugMode)
8679 fprintf(debugFP, "Restoring %s for game %d\n",
8680 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8682 thinkOutput[0] = NULLCHAR;
8683 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8684 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8685 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8686 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8687 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8688 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8689 MakeMove(fromX, fromY, toX, toY, promoChar);
8690 ShowMove(fromX, fromY, toX, toY);
8692 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8693 EP_UNKNOWN, castlingRights[currentMove]) ) {
8700 if (WhiteOnMove(currentMove)) {
8701 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8703 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8708 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8715 if (WhiteOnMove(currentMove)) {
8716 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8718 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8723 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8734 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8736 CmailLoadGame(f, gameNumber, title, useList)
8744 if (gameNumber > nCmailGames) {
8745 DisplayError(_("No more games in this message"), 0);
8748 if (f == lastLoadGameFP) {
8749 int offset = gameNumber - lastLoadGameNumber;
8751 cmailMsg[0] = NULLCHAR;
8752 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8753 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8754 nCmailMovesRegistered--;
8756 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8757 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8758 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8761 if (! RegisterMove()) return FALSE;
8765 retVal = LoadGame(f, gameNumber, title, useList);
8767 /* Make move registered during previous look at this game, if any */
8768 MakeRegisteredMove();
8770 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8771 commentList[currentMove]
8772 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8773 DisplayComment(currentMove - 1, commentList[currentMove]);
8779 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8784 int gameNumber = lastLoadGameNumber + offset;
8785 if (lastLoadGameFP == NULL) {
8786 DisplayError(_("No game has been loaded yet"), 0);
8789 if (gameNumber <= 0) {
8790 DisplayError(_("Can't back up any further"), 0);
8793 if (cmailMsgLoaded) {
8794 return CmailLoadGame(lastLoadGameFP, gameNumber,
8795 lastLoadGameTitle, lastLoadGameUseList);
8797 return LoadGame(lastLoadGameFP, gameNumber,
8798 lastLoadGameTitle, lastLoadGameUseList);
8804 /* Load the nth game from open file f */
8806 LoadGame(f, gameNumber, title, useList)
8814 int gn = gameNumber;
8815 ListGame *lg = NULL;
8818 GameMode oldGameMode;
8819 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8821 if (appData.debugMode)
8822 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8824 if (gameMode == Training )
8825 SetTrainingModeOff();
8827 oldGameMode = gameMode;
8828 if (gameMode != BeginningOfGame) {
8833 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8834 fclose(lastLoadGameFP);
8838 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8841 fseek(f, lg->offset, 0);
8842 GameListHighlight(gameNumber);
8846 DisplayError(_("Game number out of range"), 0);
8851 if (fseek(f, 0, 0) == -1) {
8852 if (f == lastLoadGameFP ?
8853 gameNumber == lastLoadGameNumber + 1 :
8857 DisplayError(_("Can't seek on game file"), 0);
8863 lastLoadGameNumber = gameNumber;
8864 strcpy(lastLoadGameTitle, title);
8865 lastLoadGameUseList = useList;
8869 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8870 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8871 lg->gameInfo.black);
8873 } else if (*title != NULLCHAR) {
8874 if (gameNumber > 1) {
8875 sprintf(buf, "%s %d", title, gameNumber);
8878 DisplayTitle(title);
8882 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8883 gameMode = PlayFromGameFile;
8887 currentMove = forwardMostMove = backwardMostMove = 0;
8888 CopyBoard(boards[0], initialPosition);
8892 * Skip the first gn-1 games in the file.
8893 * Also skip over anything that precedes an identifiable
8894 * start of game marker, to avoid being confused by
8895 * garbage at the start of the file. Currently
8896 * recognized start of game markers are the move number "1",
8897 * the pattern "gnuchess .* game", the pattern
8898 * "^[#;%] [^ ]* game file", and a PGN tag block.
8899 * A game that starts with one of the latter two patterns
8900 * will also have a move number 1, possibly
8901 * following a position diagram.
8902 * 5-4-02: Let's try being more lenient and allowing a game to
8903 * start with an unnumbered move. Does that break anything?
8905 cm = lastLoadGameStart = (ChessMove) 0;
8907 yyboardindex = forwardMostMove;
8908 cm = (ChessMove) yylex();
8911 if (cmailMsgLoaded) {
8912 nCmailGames = CMAIL_MAX_GAMES - gn;
8915 DisplayError(_("Game not found in file"), 0);
8922 lastLoadGameStart = cm;
8926 switch (lastLoadGameStart) {
8933 gn--; /* count this game */
8934 lastLoadGameStart = cm;
8943 switch (lastLoadGameStart) {
8948 gn--; /* count this game */
8949 lastLoadGameStart = cm;
8952 lastLoadGameStart = cm; /* game counted already */
8960 yyboardindex = forwardMostMove;
8961 cm = (ChessMove) yylex();
8962 } while (cm == PGNTag || cm == Comment);
8969 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8970 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8971 != CMAIL_OLD_RESULT) {
8973 cmailResult[ CMAIL_MAX_GAMES
8974 - gn - 1] = CMAIL_OLD_RESULT;
8980 /* Only a NormalMove can be at the start of a game
8981 * without a position diagram. */
8982 if (lastLoadGameStart == (ChessMove) 0) {
8984 lastLoadGameStart = MoveNumberOne;
8993 if (appData.debugMode)
8994 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8996 if (cm == XBoardGame) {
8997 /* Skip any header junk before position diagram and/or move 1 */
8999 yyboardindex = forwardMostMove;
9000 cm = (ChessMove) yylex();
9002 if (cm == (ChessMove) 0 ||
9003 cm == GNUChessGame || cm == XBoardGame) {
9004 /* Empty game; pretend end-of-file and handle later */
9009 if (cm == MoveNumberOne || cm == PositionDiagram ||
9010 cm == PGNTag || cm == Comment)
9013 } else if (cm == GNUChessGame) {
9014 if (gameInfo.event != NULL) {
9015 free(gameInfo.event);
9017 gameInfo.event = StrSave(yy_text);
9020 startedFromSetupPosition = FALSE;
9021 while (cm == PGNTag) {
9022 if (appData.debugMode)
9023 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9024 err = ParsePGNTag(yy_text, &gameInfo);
9025 if (!err) numPGNTags++;
9027 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9028 if(gameInfo.variant != oldVariant) {
9029 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9031 oldVariant = gameInfo.variant;
9032 if (appData.debugMode)
9033 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9037 if (gameInfo.fen != NULL) {
9038 Board initial_position;
9039 startedFromSetupPosition = TRUE;
9040 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9042 DisplayError(_("Bad FEN position in file"), 0);
9045 CopyBoard(boards[0], initial_position);
9046 if (blackPlaysFirst) {
9047 currentMove = forwardMostMove = backwardMostMove = 1;
9048 CopyBoard(boards[1], initial_position);
9049 strcpy(moveList[0], "");
9050 strcpy(parseList[0], "");
9051 timeRemaining[0][1] = whiteTimeRemaining;
9052 timeRemaining[1][1] = blackTimeRemaining;
9053 if (commentList[0] != NULL) {
9054 commentList[1] = commentList[0];
9055 commentList[0] = NULL;
9058 currentMove = forwardMostMove = backwardMostMove = 0;
9060 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9062 initialRulePlies = FENrulePlies;
9063 epStatus[forwardMostMove] = FENepStatus;
9064 for( i=0; i< nrCastlingRights; i++ )
9065 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9067 yyboardindex = forwardMostMove;
9069 gameInfo.fen = NULL;
9072 yyboardindex = forwardMostMove;
9073 cm = (ChessMove) yylex();
9075 /* Handle comments interspersed among the tags */
9076 while (cm == Comment) {
9078 if (appData.debugMode)
9079 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9081 if (*p == '{' || *p == '[' || *p == '(') {
9082 p[strlen(p) - 1] = NULLCHAR;
9085 while (*p == '\n') p++;
9086 AppendComment(currentMove, p);
9087 yyboardindex = forwardMostMove;
9088 cm = (ChessMove) yylex();
9092 /* don't rely on existence of Event tag since if game was
9093 * pasted from clipboard the Event tag may not exist
9095 if (numPGNTags > 0){
9097 if (gameInfo.variant == VariantNormal) {
9098 gameInfo.variant = StringToVariant(gameInfo.event);
9101 if( appData.autoDisplayTags ) {
9102 tags = PGNTags(&gameInfo);
9103 TagsPopUp(tags, CmailMsg());
9108 /* Make something up, but don't display it now */
9113 if (cm == PositionDiagram) {
9116 Board initial_position;
9118 if (appData.debugMode)
9119 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9121 if (!startedFromSetupPosition) {
9123 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9124 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9134 initial_position[i][j++] = CharToPiece(*p);
9137 while (*p == ' ' || *p == '\t' ||
9138 *p == '\n' || *p == '\r') p++;
9140 if (strncmp(p, "black", strlen("black"))==0)
9141 blackPlaysFirst = TRUE;
9143 blackPlaysFirst = FALSE;
9144 startedFromSetupPosition = TRUE;
9146 CopyBoard(boards[0], initial_position);
9147 if (blackPlaysFirst) {
9148 currentMove = forwardMostMove = backwardMostMove = 1;
9149 CopyBoard(boards[1], initial_position);
9150 strcpy(moveList[0], "");
9151 strcpy(parseList[0], "");
9152 timeRemaining[0][1] = whiteTimeRemaining;
9153 timeRemaining[1][1] = blackTimeRemaining;
9154 if (commentList[0] != NULL) {
9155 commentList[1] = commentList[0];
9156 commentList[0] = NULL;
9159 currentMove = forwardMostMove = backwardMostMove = 0;
9162 yyboardindex = forwardMostMove;
9163 cm = (ChessMove) yylex();
9166 if (first.pr == NoProc) {
9167 StartChessProgram(&first);
9169 InitChessProgram(&first, FALSE);
9170 SendToProgram("force\n", &first);
9171 if (startedFromSetupPosition) {
9172 SendBoard(&first, forwardMostMove);
9173 if (appData.debugMode) {
9174 fprintf(debugFP, "Load Game\n");
9176 DisplayBothClocks();
9179 /* [HGM] server: flag to write setup moves in broadcast file as one */
9180 loadFlag = appData.suppressLoadMoves;
9182 while (cm == Comment) {
9184 if (appData.debugMode)
9185 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9187 if (*p == '{' || *p == '[' || *p == '(') {
9188 p[strlen(p) - 1] = NULLCHAR;
9191 while (*p == '\n') p++;
9192 AppendComment(currentMove, p);
9193 yyboardindex = forwardMostMove;
9194 cm = (ChessMove) yylex();
9197 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9198 cm == WhiteWins || cm == BlackWins ||
9199 cm == GameIsDrawn || cm == GameUnfinished) {
9200 DisplayMessage("", _("No moves in game"));
9201 if (cmailMsgLoaded) {
9202 if (appData.debugMode)
9203 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9207 DrawPosition(FALSE, boards[currentMove]);
9208 DisplayBothClocks();
9209 gameMode = EditGame;
9216 // [HGM] PV info: routine tests if comment empty
9217 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9218 DisplayComment(currentMove - 1, commentList[currentMove]);
9220 if (!matchMode && appData.timeDelay != 0)
9221 DrawPosition(FALSE, boards[currentMove]);
9223 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9224 programStats.ok_to_send = 1;
9227 /* if the first token after the PGN tags is a move
9228 * and not move number 1, retrieve it from the parser
9230 if (cm != MoveNumberOne)
9231 LoadGameOneMove(cm);
9233 /* load the remaining moves from the file */
9234 while (LoadGameOneMove((ChessMove)0)) {
9235 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9236 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9239 /* rewind to the start of the game */
9240 currentMove = backwardMostMove;
9242 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9244 if (oldGameMode == AnalyzeFile ||
9245 oldGameMode == AnalyzeMode) {
9249 if (matchMode || appData.timeDelay == 0) {
9251 gameMode = EditGame;
9253 } else if (appData.timeDelay > 0) {
9257 if (appData.debugMode)
9258 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9260 loadFlag = 0; /* [HGM] true game starts */
9264 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9266 ReloadPosition(offset)
9269 int positionNumber = lastLoadPositionNumber + offset;
9270 if (lastLoadPositionFP == NULL) {
9271 DisplayError(_("No position has been loaded yet"), 0);
9274 if (positionNumber <= 0) {
9275 DisplayError(_("Can't back up any further"), 0);
9278 return LoadPosition(lastLoadPositionFP, positionNumber,
9279 lastLoadPositionTitle);
9282 /* Load the nth position from the given file */
9284 LoadPositionFromFile(filename, n, title)
9292 if (strcmp(filename, "-") == 0) {
9293 return LoadPosition(stdin, n, "stdin");
9295 f = fopen(filename, "rb");
9297 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9298 DisplayError(buf, errno);
9301 return LoadPosition(f, n, title);
9306 /* Load the nth position from the given open file, and close it */
9308 LoadPosition(f, positionNumber, title)
9313 char *p, line[MSG_SIZ];
9314 Board initial_position;
9315 int i, j, fenMode, pn;
9317 if (gameMode == Training )
9318 SetTrainingModeOff();
9320 if (gameMode != BeginningOfGame) {
9323 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9324 fclose(lastLoadPositionFP);
9326 if (positionNumber == 0) positionNumber = 1;
9327 lastLoadPositionFP = f;
9328 lastLoadPositionNumber = positionNumber;
9329 strcpy(lastLoadPositionTitle, title);
9330 if (first.pr == NoProc) {
9331 StartChessProgram(&first);
9332 InitChessProgram(&first, FALSE);
9334 pn = positionNumber;
9335 if (positionNumber < 0) {
9336 /* Negative position number means to seek to that byte offset */
9337 if (fseek(f, -positionNumber, 0) == -1) {
9338 DisplayError(_("Can't seek on position file"), 0);
9343 if (fseek(f, 0, 0) == -1) {
9344 if (f == lastLoadPositionFP ?
9345 positionNumber == lastLoadPositionNumber + 1 :
9346 positionNumber == 1) {
9349 DisplayError(_("Can't seek on position file"), 0);
9354 /* See if this file is FEN or old-style xboard */
9355 if (fgets(line, MSG_SIZ, f) == NULL) {
9356 DisplayError(_("Position not found in file"), 0);
9365 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9366 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9367 case '1': case '2': case '3': case '4': case '5': case '6':
9368 case '7': case '8': case '9':
9369 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9370 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9371 case 'C': case 'W': case 'c': case 'w':
9376 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9377 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9381 if (fenMode || line[0] == '#') pn--;
9383 /* skip positions before number pn */
9384 if (fgets(line, MSG_SIZ, f) == NULL) {
9386 DisplayError(_("Position not found in file"), 0);
9389 if (fenMode || line[0] == '#') pn--;
9394 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9395 DisplayError(_("Bad FEN position in file"), 0);
9399 (void) fgets(line, MSG_SIZ, f);
9400 (void) fgets(line, MSG_SIZ, f);
9402 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9403 (void) fgets(line, MSG_SIZ, f);
9404 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9407 initial_position[i][j++] = CharToPiece(*p);
9411 blackPlaysFirst = FALSE;
9413 (void) fgets(line, MSG_SIZ, f);
9414 if (strncmp(line, "black", strlen("black"))==0)
9415 blackPlaysFirst = TRUE;
9418 startedFromSetupPosition = TRUE;
9420 SendToProgram("force\n", &first);
9421 CopyBoard(boards[0], initial_position);
9422 if (blackPlaysFirst) {
9423 currentMove = forwardMostMove = backwardMostMove = 1;
9424 strcpy(moveList[0], "");
9425 strcpy(parseList[0], "");
9426 CopyBoard(boards[1], initial_position);
9427 DisplayMessage("", _("Black to play"));
9429 currentMove = forwardMostMove = backwardMostMove = 0;
9430 DisplayMessage("", _("White to play"));
9432 /* [HGM] copy FEN attributes as well */
9434 initialRulePlies = FENrulePlies;
9435 epStatus[forwardMostMove] = FENepStatus;
9436 for( i=0; i< nrCastlingRights; i++ )
9437 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9439 SendBoard(&first, forwardMostMove);
9440 if (appData.debugMode) {
9442 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9443 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9444 fprintf(debugFP, "Load Position\n");
9447 if (positionNumber > 1) {
9448 sprintf(line, "%s %d", title, positionNumber);
9451 DisplayTitle(title);
9453 gameMode = EditGame;
9456 timeRemaining[0][1] = whiteTimeRemaining;
9457 timeRemaining[1][1] = blackTimeRemaining;
9458 DrawPosition(FALSE, boards[currentMove]);
9465 CopyPlayerNameIntoFileName(dest, src)
9468 while (*src != NULLCHAR && *src != ',') {
9473 *(*dest)++ = *src++;
9478 char *DefaultFileName(ext)
9481 static char def[MSG_SIZ];
9484 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9486 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9488 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9497 /* Save the current game to the given file */
9499 SaveGameToFile(filename, append)
9506 if (strcmp(filename, "-") == 0) {
9507 return SaveGame(stdout, 0, NULL);
9509 f = fopen(filename, append ? "a" : "w");
9511 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9512 DisplayError(buf, errno);
9515 return SaveGame(f, 0, NULL);
9524 static char buf[MSG_SIZ];
9527 p = strchr(str, ' ');
9528 if (p == NULL) return str;
9529 strncpy(buf, str, p - str);
9530 buf[p - str] = NULLCHAR;
9534 #define PGN_MAX_LINE 75
9536 #define PGN_SIDE_WHITE 0
9537 #define PGN_SIDE_BLACK 1
9540 static int FindFirstMoveOutOfBook( int side )
9544 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9545 int index = backwardMostMove;
9546 int has_book_hit = 0;
9548 if( (index % 2) != side ) {
9552 while( index < forwardMostMove ) {
9553 /* Check to see if engine is in book */
9554 int depth = pvInfoList[index].depth;
9555 int score = pvInfoList[index].score;
9561 else if( score == 0 && depth == 63 ) {
9562 in_book = 1; /* Zappa */
9564 else if( score == 2 && depth == 99 ) {
9565 in_book = 1; /* Abrok */
9568 has_book_hit += in_book;
9584 void GetOutOfBookInfo( char * buf )
9588 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9590 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9591 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9595 if( oob[0] >= 0 || oob[1] >= 0 ) {
9596 for( i=0; i<2; i++ ) {
9600 if( i > 0 && oob[0] >= 0 ) {
9604 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9605 sprintf( buf+strlen(buf), "%s%.2f",
9606 pvInfoList[idx].score >= 0 ? "+" : "",
9607 pvInfoList[idx].score / 100.0 );
9613 /* Save game in PGN style and close the file */
9618 int i, offset, linelen, newblock;
9622 int movelen, numlen, blank;
9623 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9625 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9627 tm = time((time_t *) NULL);
9629 PrintPGNTags(f, &gameInfo);
9631 if (backwardMostMove > 0 || startedFromSetupPosition) {
9632 char *fen = PositionToFEN(backwardMostMove, NULL);
9633 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9634 fprintf(f, "\n{--------------\n");
9635 PrintPosition(f, backwardMostMove);
9636 fprintf(f, "--------------}\n");
9640 /* [AS] Out of book annotation */
9641 if( appData.saveOutOfBookInfo ) {
9644 GetOutOfBookInfo( buf );
9646 if( buf[0] != '\0' ) {
9647 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9654 i = backwardMostMove;
9658 while (i < forwardMostMove) {
9659 /* Print comments preceding this move */
9660 if (commentList[i] != NULL) {
9661 if (linelen > 0) fprintf(f, "\n");
9662 fprintf(f, "{\n%s}\n", commentList[i]);
9667 /* Format move number */
9669 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9672 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9674 numtext[0] = NULLCHAR;
9677 numlen = strlen(numtext);
9680 /* Print move number */
9681 blank = linelen > 0 && numlen > 0;
9682 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9691 fprintf(f, numtext);
9695 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9696 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9698 // SavePart already does this!
9699 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9700 int p = movelen - 1;
9701 if(move_buffer[p] == ' ') p--;
9702 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9703 while(p && move_buffer[--p] != '(');
9704 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9709 blank = linelen > 0 && movelen > 0;
9710 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9719 fprintf(f, move_buffer);
9722 /* [AS] Add PV info if present */
9723 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9724 /* [HGM] add time */
9725 char buf[MSG_SIZ]; int seconds = 0;
9728 if(i >= backwardMostMove) {
9730 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9731 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9733 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9734 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9736 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9738 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9741 if( seconds <= 0) buf[0] = 0; else
9742 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9743 seconds = (seconds + 4)/10; // round to full seconds
9744 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9745 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9748 sprintf( move_buffer, "{%s%.2f/%d%s}",
9749 pvInfoList[i].score >= 0 ? "+" : "",
9750 pvInfoList[i].score / 100.0,
9751 pvInfoList[i].depth,
9754 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9756 /* Print score/depth */
9757 blank = linelen > 0 && movelen > 0;
9758 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9767 fprintf(f, move_buffer);
9774 /* Start a new line */
9775 if (linelen > 0) fprintf(f, "\n");
9777 /* Print comments after last move */
9778 if (commentList[i] != NULL) {
9779 fprintf(f, "{\n%s}\n", commentList[i]);
9783 if (gameInfo.resultDetails != NULL &&
9784 gameInfo.resultDetails[0] != NULLCHAR) {
9785 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9786 PGNResult(gameInfo.result));
9788 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9792 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9796 /* Save game in old style and close the file */
9804 tm = time((time_t *) NULL);
9806 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9809 if (backwardMostMove > 0 || startedFromSetupPosition) {
9810 fprintf(f, "\n[--------------\n");
9811 PrintPosition(f, backwardMostMove);
9812 fprintf(f, "--------------]\n");
9817 i = backwardMostMove;
9818 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9820 while (i < forwardMostMove) {
9821 if (commentList[i] != NULL) {
9822 fprintf(f, "[%s]\n", commentList[i]);
9826 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9829 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9831 if (commentList[i] != NULL) {
9835 if (i >= forwardMostMove) {
9839 fprintf(f, "%s\n", parseList[i]);
9844 if (commentList[i] != NULL) {
9845 fprintf(f, "[%s]\n", commentList[i]);
9848 /* This isn't really the old style, but it's close enough */
9849 if (gameInfo.resultDetails != NULL &&
9850 gameInfo.resultDetails[0] != NULLCHAR) {
9851 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9852 gameInfo.resultDetails);
9854 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9861 /* Save the current game to open file f and close the file */
9863 SaveGame(f, dummy, dummy2)
9868 if (gameMode == EditPosition) EditPositionDone();
9869 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9870 if (appData.oldSaveStyle)
9871 return SaveGameOldStyle(f);
9873 return SaveGamePGN(f);
9876 /* Save the current position to the given file */
9878 SavePositionToFile(filename)
9884 if (strcmp(filename, "-") == 0) {
9885 return SavePosition(stdout, 0, NULL);
9887 f = fopen(filename, "a");
9889 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9890 DisplayError(buf, errno);
9893 SavePosition(f, 0, NULL);
9899 /* Save the current position to the given open file and close the file */
9901 SavePosition(f, dummy, dummy2)
9909 if (appData.oldSaveStyle) {
9910 tm = time((time_t *) NULL);
9912 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9914 fprintf(f, "[--------------\n");
9915 PrintPosition(f, currentMove);
9916 fprintf(f, "--------------]\n");
9918 fen = PositionToFEN(currentMove, NULL);
9919 fprintf(f, "%s\n", fen);
9927 ReloadCmailMsgEvent(unregister)
9931 static char *inFilename = NULL;
9932 static char *outFilename;
9934 struct stat inbuf, outbuf;
9937 /* Any registered moves are unregistered if unregister is set, */
9938 /* i.e. invoked by the signal handler */
9940 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9941 cmailMoveRegistered[i] = FALSE;
9942 if (cmailCommentList[i] != NULL) {
9943 free(cmailCommentList[i]);
9944 cmailCommentList[i] = NULL;
9947 nCmailMovesRegistered = 0;
9950 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9951 cmailResult[i] = CMAIL_NOT_RESULT;
9955 if (inFilename == NULL) {
9956 /* Because the filenames are static they only get malloced once */
9957 /* and they never get freed */
9958 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9959 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9961 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9962 sprintf(outFilename, "%s.out", appData.cmailGameName);
9965 status = stat(outFilename, &outbuf);
9967 cmailMailedMove = FALSE;
9969 status = stat(inFilename, &inbuf);
9970 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9973 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9974 counts the games, notes how each one terminated, etc.
9976 It would be nice to remove this kludge and instead gather all
9977 the information while building the game list. (And to keep it
9978 in the game list nodes instead of having a bunch of fixed-size
9979 parallel arrays.) Note this will require getting each game's
9980 termination from the PGN tags, as the game list builder does
9981 not process the game moves. --mann
9983 cmailMsgLoaded = TRUE;
9984 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9986 /* Load first game in the file or popup game menu */
9987 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9997 char string[MSG_SIZ];
9999 if ( cmailMailedMove
10000 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10001 return TRUE; /* Allow free viewing */
10004 /* Unregister move to ensure that we don't leave RegisterMove */
10005 /* with the move registered when the conditions for registering no */
10007 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10008 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10009 nCmailMovesRegistered --;
10011 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10013 free(cmailCommentList[lastLoadGameNumber - 1]);
10014 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10018 if (cmailOldMove == -1) {
10019 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10023 if (currentMove > cmailOldMove + 1) {
10024 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10028 if (currentMove < cmailOldMove) {
10029 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10033 if (forwardMostMove > currentMove) {
10034 /* Silently truncate extra moves */
10038 if ( (currentMove == cmailOldMove + 1)
10039 || ( (currentMove == cmailOldMove)
10040 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10041 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10042 if (gameInfo.result != GameUnfinished) {
10043 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10046 if (commentList[currentMove] != NULL) {
10047 cmailCommentList[lastLoadGameNumber - 1]
10048 = StrSave(commentList[currentMove]);
10050 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10052 if (appData.debugMode)
10053 fprintf(debugFP, "Saving %s for game %d\n",
10054 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10057 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10059 f = fopen(string, "w");
10060 if (appData.oldSaveStyle) {
10061 SaveGameOldStyle(f); /* also closes the file */
10063 sprintf(string, "%s.pos.out", appData.cmailGameName);
10064 f = fopen(string, "w");
10065 SavePosition(f, 0, NULL); /* also closes the file */
10067 fprintf(f, "{--------------\n");
10068 PrintPosition(f, currentMove);
10069 fprintf(f, "--------------}\n\n");
10071 SaveGame(f, 0, NULL); /* also closes the file*/
10074 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10075 nCmailMovesRegistered ++;
10076 } else if (nCmailGames == 1) {
10077 DisplayError(_("You have not made a move yet"), 0);
10088 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10089 FILE *commandOutput;
10090 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10091 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10097 if (! cmailMsgLoaded) {
10098 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10102 if (nCmailGames == nCmailResults) {
10103 DisplayError(_("No unfinished games"), 0);
10107 #if CMAIL_PROHIBIT_REMAIL
10108 if (cmailMailedMove) {
10109 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);
10110 DisplayError(msg, 0);
10115 if (! (cmailMailedMove || RegisterMove())) return;
10117 if ( cmailMailedMove
10118 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10119 sprintf(string, partCommandString,
10120 appData.debugMode ? " -v" : "", appData.cmailGameName);
10121 commandOutput = popen(string, "r");
10123 if (commandOutput == NULL) {
10124 DisplayError(_("Failed to invoke cmail"), 0);
10126 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10127 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10129 if (nBuffers > 1) {
10130 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10131 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10132 nBytes = MSG_SIZ - 1;
10134 (void) memcpy(msg, buffer, nBytes);
10136 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10138 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10139 cmailMailedMove = TRUE; /* Prevent >1 moves */
10142 for (i = 0; i < nCmailGames; i ++) {
10143 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10148 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10150 sprintf(buffer, "%s/%s.%s.archive",
10152 appData.cmailGameName,
10154 LoadGameFromFile(buffer, 1, buffer, FALSE);
10155 cmailMsgLoaded = FALSE;
10159 DisplayInformation(msg);
10160 pclose(commandOutput);
10163 if ((*cmailMsg) != '\0') {
10164 DisplayInformation(cmailMsg);
10169 #endif /* !WIN32 */
10178 int prependComma = 0;
10180 char string[MSG_SIZ]; /* Space for game-list */
10183 if (!cmailMsgLoaded) return "";
10185 if (cmailMailedMove) {
10186 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10188 /* Create a list of games left */
10189 sprintf(string, "[");
10190 for (i = 0; i < nCmailGames; i ++) {
10191 if (! ( cmailMoveRegistered[i]
10192 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10193 if (prependComma) {
10194 sprintf(number, ",%d", i + 1);
10196 sprintf(number, "%d", i + 1);
10200 strcat(string, number);
10203 strcat(string, "]");
10205 if (nCmailMovesRegistered + nCmailResults == 0) {
10206 switch (nCmailGames) {
10209 _("Still need to make move for game\n"));
10214 _("Still need to make moves for both games\n"));
10219 _("Still need to make moves for all %d games\n"),
10224 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10227 _("Still need to make a move for game %s\n"),
10232 if (nCmailResults == nCmailGames) {
10233 sprintf(cmailMsg, _("No unfinished games\n"));
10235 sprintf(cmailMsg, _("Ready to send mail\n"));
10241 _("Still need to make moves for games %s\n"),
10253 if (gameMode == Training)
10254 SetTrainingModeOff();
10257 cmailMsgLoaded = FALSE;
10258 if (appData.icsActive) {
10259 SendToICS(ics_prefix);
10260 SendToICS("refresh\n");
10270 /* Give up on clean exit */
10274 /* Keep trying for clean exit */
10278 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10280 if (telnetISR != NULL) {
10281 RemoveInputSource(telnetISR);
10283 if (icsPR != NoProc) {
10284 DestroyChildProcess(icsPR, TRUE);
10287 /* Save game if resource set and not already saved by GameEnds() */
10288 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10289 && forwardMostMove > 0) {
10290 if (*appData.saveGameFile != NULLCHAR) {
10291 SaveGameToFile(appData.saveGameFile, TRUE);
10292 } else if (appData.autoSaveGames) {
10295 if (*appData.savePositionFile != NULLCHAR) {
10296 SavePositionToFile(appData.savePositionFile);
10299 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10301 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10302 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10304 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10305 /* make sure this other one finishes before killing it! */
10306 if(endingGame) { int count = 0;
10307 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10308 while(endingGame && count++ < 10) DoSleep(1);
10309 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10312 /* Kill off chess programs */
10313 if (first.pr != NoProc) {
10316 DoSleep( appData.delayBeforeQuit );
10317 SendToProgram("quit\n", &first);
10318 DoSleep( appData.delayAfterQuit );
10319 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10321 if (second.pr != NoProc) {
10322 DoSleep( appData.delayBeforeQuit );
10323 SendToProgram("quit\n", &second);
10324 DoSleep( appData.delayAfterQuit );
10325 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10327 if (first.isr != NULL) {
10328 RemoveInputSource(first.isr);
10330 if (second.isr != NULL) {
10331 RemoveInputSource(second.isr);
10334 ShutDownFrontEnd();
10341 if (appData.debugMode)
10342 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10346 if (gameMode == MachinePlaysWhite ||
10347 gameMode == MachinePlaysBlack) {
10350 DisplayBothClocks();
10352 if (gameMode == PlayFromGameFile) {
10353 if (appData.timeDelay >= 0)
10354 AutoPlayGameLoop();
10355 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10356 Reset(FALSE, TRUE);
10357 SendToICS(ics_prefix);
10358 SendToICS("refresh\n");
10359 } else if (currentMove < forwardMostMove) {
10360 ForwardInner(forwardMostMove);
10362 pauseExamInvalid = FALSE;
10364 switch (gameMode) {
10368 pauseExamForwardMostMove = forwardMostMove;
10369 pauseExamInvalid = FALSE;
10372 case IcsPlayingWhite:
10373 case IcsPlayingBlack:
10377 case PlayFromGameFile:
10378 (void) StopLoadGameTimer();
10382 case BeginningOfGame:
10383 if (appData.icsActive) return;
10384 /* else fall through */
10385 case MachinePlaysWhite:
10386 case MachinePlaysBlack:
10387 case TwoMachinesPlay:
10388 if (forwardMostMove == 0)
10389 return; /* don't pause if no one has moved */
10390 if ((gameMode == MachinePlaysWhite &&
10391 !WhiteOnMove(forwardMostMove)) ||
10392 (gameMode == MachinePlaysBlack &&
10393 WhiteOnMove(forwardMostMove))) {
10406 char title[MSG_SIZ];
10408 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10409 strcpy(title, _("Edit comment"));
10411 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10412 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10413 parseList[currentMove - 1]);
10416 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10423 char *tags = PGNTags(&gameInfo);
10424 EditTagsPopUp(tags);
10431 if (appData.noChessProgram || gameMode == AnalyzeMode)
10434 if (gameMode != AnalyzeFile) {
10435 if (!appData.icsEngineAnalyze) {
10437 if (gameMode != EditGame) return;
10439 ResurrectChessProgram();
10440 SendToProgram("analyze\n", &first);
10441 first.analyzing = TRUE;
10442 /*first.maybeThinking = TRUE;*/
10443 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10444 AnalysisPopUp(_("Analysis"),
10445 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10447 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10452 StartAnalysisClock();
10453 GetTimeMark(&lastNodeCountTime);
10460 if (appData.noChessProgram || gameMode == AnalyzeFile)
10463 if (gameMode != AnalyzeMode) {
10465 if (gameMode != EditGame) return;
10466 ResurrectChessProgram();
10467 SendToProgram("analyze\n", &first);
10468 first.analyzing = TRUE;
10469 /*first.maybeThinking = TRUE;*/
10470 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10471 AnalysisPopUp(_("Analysis"),
10472 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10474 gameMode = AnalyzeFile;
10479 StartAnalysisClock();
10480 GetTimeMark(&lastNodeCountTime);
10485 MachineWhiteEvent()
10488 char *bookHit = NULL;
10490 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10494 if (gameMode == PlayFromGameFile ||
10495 gameMode == TwoMachinesPlay ||
10496 gameMode == Training ||
10497 gameMode == AnalyzeMode ||
10498 gameMode == EndOfGame)
10501 if (gameMode == EditPosition)
10502 EditPositionDone();
10504 if (!WhiteOnMove(currentMove)) {
10505 DisplayError(_("It is not White's turn"), 0);
10509 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10512 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10513 gameMode == AnalyzeFile)
10516 ResurrectChessProgram(); /* in case it isn't running */
10517 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10518 gameMode = MachinePlaysWhite;
10521 gameMode = MachinePlaysWhite;
10525 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10527 if (first.sendName) {
10528 sprintf(buf, "name %s\n", gameInfo.black);
10529 SendToProgram(buf, &first);
10531 if (first.sendTime) {
10532 if (first.useColors) {
10533 SendToProgram("black\n", &first); /*gnu kludge*/
10535 SendTimeRemaining(&first, TRUE);
10537 if (first.useColors) {
10538 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10540 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10541 SetMachineThinkingEnables();
10542 first.maybeThinking = TRUE;
10545 if (appData.autoFlipView && !flipView) {
10546 flipView = !flipView;
10547 DrawPosition(FALSE, NULL);
10548 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10551 if(bookHit) { // [HGM] book: simulate book reply
10552 static char bookMove[MSG_SIZ]; // a bit generous?
10554 programStats.nodes = programStats.depth = programStats.time =
10555 programStats.score = programStats.got_only_move = 0;
10556 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10558 strcpy(bookMove, "move ");
10559 strcat(bookMove, bookHit);
10560 HandleMachineMove(bookMove, &first);
10565 MachineBlackEvent()
10568 char *bookHit = NULL;
10570 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10574 if (gameMode == PlayFromGameFile ||
10575 gameMode == TwoMachinesPlay ||
10576 gameMode == Training ||
10577 gameMode == AnalyzeMode ||
10578 gameMode == EndOfGame)
10581 if (gameMode == EditPosition)
10582 EditPositionDone();
10584 if (WhiteOnMove(currentMove)) {
10585 DisplayError(_("It is not Black's turn"), 0);
10589 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10592 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10593 gameMode == AnalyzeFile)
10596 ResurrectChessProgram(); /* in case it isn't running */
10597 gameMode = MachinePlaysBlack;
10601 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10603 if (first.sendName) {
10604 sprintf(buf, "name %s\n", gameInfo.white);
10605 SendToProgram(buf, &first);
10607 if (first.sendTime) {
10608 if (first.useColors) {
10609 SendToProgram("white\n", &first); /*gnu kludge*/
10611 SendTimeRemaining(&first, FALSE);
10613 if (first.useColors) {
10614 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10616 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10617 SetMachineThinkingEnables();
10618 first.maybeThinking = TRUE;
10621 if (appData.autoFlipView && flipView) {
10622 flipView = !flipView;
10623 DrawPosition(FALSE, NULL);
10624 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10626 if(bookHit) { // [HGM] book: simulate book reply
10627 static char bookMove[MSG_SIZ]; // a bit generous?
10629 programStats.nodes = programStats.depth = programStats.time =
10630 programStats.score = programStats.got_only_move = 0;
10631 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10633 strcpy(bookMove, "move ");
10634 strcat(bookMove, bookHit);
10635 HandleMachineMove(bookMove, &first);
10641 DisplayTwoMachinesTitle()
10644 if (appData.matchGames > 0) {
10645 if (first.twoMachinesColor[0] == 'w') {
10646 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10647 gameInfo.white, gameInfo.black,
10648 first.matchWins, second.matchWins,
10649 matchGame - 1 - (first.matchWins + second.matchWins));
10651 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10652 gameInfo.white, gameInfo.black,
10653 second.matchWins, first.matchWins,
10654 matchGame - 1 - (first.matchWins + second.matchWins));
10657 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10663 TwoMachinesEvent P((void))
10667 ChessProgramState *onmove;
10668 char *bookHit = NULL;
10670 if (appData.noChessProgram) return;
10672 switch (gameMode) {
10673 case TwoMachinesPlay:
10675 case MachinePlaysWhite:
10676 case MachinePlaysBlack:
10677 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10678 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10682 case BeginningOfGame:
10683 case PlayFromGameFile:
10686 if (gameMode != EditGame) return;
10689 EditPositionDone();
10700 forwardMostMove = currentMove;
10701 ResurrectChessProgram(); /* in case first program isn't running */
10703 if (second.pr == NULL) {
10704 StartChessProgram(&second);
10705 if (second.protocolVersion == 1) {
10706 TwoMachinesEventIfReady();
10708 /* kludge: allow timeout for initial "feature" command */
10710 DisplayMessage("", _("Starting second chess program"));
10711 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10715 DisplayMessage("", "");
10716 InitChessProgram(&second, FALSE);
10717 SendToProgram("force\n", &second);
10718 if (startedFromSetupPosition) {
10719 SendBoard(&second, backwardMostMove);
10720 if (appData.debugMode) {
10721 fprintf(debugFP, "Two Machines\n");
10724 for (i = backwardMostMove; i < forwardMostMove; i++) {
10725 SendMoveToProgram(i, &second);
10728 gameMode = TwoMachinesPlay;
10732 DisplayTwoMachinesTitle();
10734 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10740 SendToProgram(first.computerString, &first);
10741 if (first.sendName) {
10742 sprintf(buf, "name %s\n", second.tidy);
10743 SendToProgram(buf, &first);
10745 SendToProgram(second.computerString, &second);
10746 if (second.sendName) {
10747 sprintf(buf, "name %s\n", first.tidy);
10748 SendToProgram(buf, &second);
10752 if (!first.sendTime || !second.sendTime) {
10753 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10754 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10756 if (onmove->sendTime) {
10757 if (onmove->useColors) {
10758 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10760 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10762 if (onmove->useColors) {
10763 SendToProgram(onmove->twoMachinesColor, onmove);
10765 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10766 // SendToProgram("go\n", onmove);
10767 onmove->maybeThinking = TRUE;
10768 SetMachineThinkingEnables();
10772 if(bookHit) { // [HGM] book: simulate book reply
10773 static char bookMove[MSG_SIZ]; // a bit generous?
10775 programStats.nodes = programStats.depth = programStats.time =
10776 programStats.score = programStats.got_only_move = 0;
10777 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10779 strcpy(bookMove, "move ");
10780 strcat(bookMove, bookHit);
10781 HandleMachineMove(bookMove, &first);
10788 if (gameMode == Training) {
10789 SetTrainingModeOff();
10790 gameMode = PlayFromGameFile;
10791 DisplayMessage("", _("Training mode off"));
10793 gameMode = Training;
10794 animateTraining = appData.animate;
10796 /* make sure we are not already at the end of the game */
10797 if (currentMove < forwardMostMove) {
10798 SetTrainingModeOn();
10799 DisplayMessage("", _("Training mode on"));
10801 gameMode = PlayFromGameFile;
10802 DisplayError(_("Already at end of game"), 0);
10811 if (!appData.icsActive) return;
10812 switch (gameMode) {
10813 case IcsPlayingWhite:
10814 case IcsPlayingBlack:
10817 case BeginningOfGame:
10825 EditPositionDone();
10838 gameMode = IcsIdle;
10849 switch (gameMode) {
10851 SetTrainingModeOff();
10853 case MachinePlaysWhite:
10854 case MachinePlaysBlack:
10855 case BeginningOfGame:
10856 SendToProgram("force\n", &first);
10857 SetUserThinkingEnables();
10859 case PlayFromGameFile:
10860 (void) StopLoadGameTimer();
10861 if (gameFileFP != NULL) {
10866 EditPositionDone();
10871 SendToProgram("force\n", &first);
10873 case TwoMachinesPlay:
10874 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10875 ResurrectChessProgram();
10876 SetUserThinkingEnables();
10879 ResurrectChessProgram();
10881 case IcsPlayingBlack:
10882 case IcsPlayingWhite:
10883 DisplayError(_("Warning: You are still playing a game"), 0);
10886 DisplayError(_("Warning: You are still observing a game"), 0);
10889 DisplayError(_("Warning: You are still examining a game"), 0);
10900 first.offeredDraw = second.offeredDraw = 0;
10902 if (gameMode == PlayFromGameFile) {
10903 whiteTimeRemaining = timeRemaining[0][currentMove];
10904 blackTimeRemaining = timeRemaining[1][currentMove];
10908 if (gameMode == MachinePlaysWhite ||
10909 gameMode == MachinePlaysBlack ||
10910 gameMode == TwoMachinesPlay ||
10911 gameMode == EndOfGame) {
10912 i = forwardMostMove;
10913 while (i > currentMove) {
10914 SendToProgram("undo\n", &first);
10917 whiteTimeRemaining = timeRemaining[0][currentMove];
10918 blackTimeRemaining = timeRemaining[1][currentMove];
10919 DisplayBothClocks();
10920 if (whiteFlag || blackFlag) {
10921 whiteFlag = blackFlag = 0;
10926 gameMode = EditGame;
10933 EditPositionEvent()
10935 if (gameMode == EditPosition) {
10941 if (gameMode != EditGame) return;
10943 gameMode = EditPosition;
10946 if (currentMove > 0)
10947 CopyBoard(boards[0], boards[currentMove]);
10949 blackPlaysFirst = !WhiteOnMove(currentMove);
10951 currentMove = forwardMostMove = backwardMostMove = 0;
10952 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10959 /* [DM] icsEngineAnalyze - possible call from other functions */
10960 if (appData.icsEngineAnalyze) {
10961 appData.icsEngineAnalyze = FALSE;
10963 DisplayMessage("",_("Close ICS engine analyze..."));
10965 if (first.analysisSupport && first.analyzing) {
10966 SendToProgram("exit\n", &first);
10967 first.analyzing = FALSE;
10970 thinkOutput[0] = NULLCHAR;
10976 startedFromSetupPosition = TRUE;
10977 InitChessProgram(&first, FALSE);
10978 SendToProgram("force\n", &first);
10979 if (blackPlaysFirst) {
10980 strcpy(moveList[0], "");
10981 strcpy(parseList[0], "");
10982 currentMove = forwardMostMove = backwardMostMove = 1;
10983 CopyBoard(boards[1], boards[0]);
10984 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10986 epStatus[1] = epStatus[0];
10987 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10990 currentMove = forwardMostMove = backwardMostMove = 0;
10992 SendBoard(&first, forwardMostMove);
10993 if (appData.debugMode) {
10994 fprintf(debugFP, "EditPosDone\n");
10997 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10998 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10999 gameMode = EditGame;
11001 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11002 ClearHighlights(); /* [AS] */
11005 /* Pause for `ms' milliseconds */
11006 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11016 } while (SubtractTimeMarks(&m2, &m1) < ms);
11019 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11021 SendMultiLineToICS(buf)
11024 char temp[MSG_SIZ+1], *p;
11031 strncpy(temp, buf, len);
11036 if (*p == '\n' || *p == '\r')
11041 strcat(temp, "\n");
11043 SendToPlayer(temp, strlen(temp));
11047 SetWhiteToPlayEvent()
11049 if (gameMode == EditPosition) {
11050 blackPlaysFirst = FALSE;
11051 DisplayBothClocks(); /* works because currentMove is 0 */
11052 } else if (gameMode == IcsExamining) {
11053 SendToICS(ics_prefix);
11054 SendToICS("tomove white\n");
11059 SetBlackToPlayEvent()
11061 if (gameMode == EditPosition) {
11062 blackPlaysFirst = TRUE;
11063 currentMove = 1; /* kludge */
11064 DisplayBothClocks();
11066 } else if (gameMode == IcsExamining) {
11067 SendToICS(ics_prefix);
11068 SendToICS("tomove black\n");
11073 EditPositionMenuEvent(selection, x, y)
11074 ChessSquare selection;
11078 ChessSquare piece = boards[0][y][x];
11080 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11082 switch (selection) {
11084 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11085 SendToICS(ics_prefix);
11086 SendToICS("bsetup clear\n");
11087 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11088 SendToICS(ics_prefix);
11089 SendToICS("clearboard\n");
11091 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11092 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11093 for (y = 0; y < BOARD_HEIGHT; y++) {
11094 if (gameMode == IcsExamining) {
11095 if (boards[currentMove][y][x] != EmptySquare) {
11096 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11101 boards[0][y][x] = p;
11106 if (gameMode == EditPosition) {
11107 DrawPosition(FALSE, boards[0]);
11112 SetWhiteToPlayEvent();
11116 SetBlackToPlayEvent();
11120 if (gameMode == IcsExamining) {
11121 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11124 boards[0][y][x] = EmptySquare;
11125 DrawPosition(FALSE, boards[0]);
11130 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11131 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11132 selection = (ChessSquare) (PROMOTED piece);
11133 } else if(piece == EmptySquare) selection = WhiteSilver;
11134 else selection = (ChessSquare)((int)piece - 1);
11138 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11139 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11140 selection = (ChessSquare) (DEMOTED piece);
11141 } else if(piece == EmptySquare) selection = BlackSilver;
11142 else selection = (ChessSquare)((int)piece + 1);
11147 if(gameInfo.variant == VariantShatranj ||
11148 gameInfo.variant == VariantXiangqi ||
11149 gameInfo.variant == VariantCourier )
11150 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11155 if(gameInfo.variant == VariantXiangqi)
11156 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11157 if(gameInfo.variant == VariantKnightmate)
11158 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11161 if (gameMode == IcsExamining) {
11162 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11163 PieceToChar(selection), AAA + x, ONE + y);
11166 boards[0][y][x] = selection;
11167 DrawPosition(FALSE, boards[0]);
11175 DropMenuEvent(selection, x, y)
11176 ChessSquare selection;
11179 ChessMove moveType;
11181 switch (gameMode) {
11182 case IcsPlayingWhite:
11183 case MachinePlaysBlack:
11184 if (!WhiteOnMove(currentMove)) {
11185 DisplayMoveError(_("It is Black's turn"));
11188 moveType = WhiteDrop;
11190 case IcsPlayingBlack:
11191 case MachinePlaysWhite:
11192 if (WhiteOnMove(currentMove)) {
11193 DisplayMoveError(_("It is White's turn"));
11196 moveType = BlackDrop;
11199 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11205 if (moveType == BlackDrop && selection < BlackPawn) {
11206 selection = (ChessSquare) ((int) selection
11207 + (int) BlackPawn - (int) WhitePawn);
11209 if (boards[currentMove][y][x] != EmptySquare) {
11210 DisplayMoveError(_("That square is occupied"));
11214 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11220 /* Accept a pending offer of any kind from opponent */
11222 if (appData.icsActive) {
11223 SendToICS(ics_prefix);
11224 SendToICS("accept\n");
11225 } else if (cmailMsgLoaded) {
11226 if (currentMove == cmailOldMove &&
11227 commentList[cmailOldMove] != NULL &&
11228 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11229 "Black offers a draw" : "White offers a draw")) {
11231 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11232 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11234 DisplayError(_("There is no pending offer on this move"), 0);
11235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11238 /* Not used for offers from chess program */
11245 /* Decline a pending offer of any kind from opponent */
11247 if (appData.icsActive) {
11248 SendToICS(ics_prefix);
11249 SendToICS("decline\n");
11250 } else if (cmailMsgLoaded) {
11251 if (currentMove == cmailOldMove &&
11252 commentList[cmailOldMove] != NULL &&
11253 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11254 "Black offers a draw" : "White offers a draw")) {
11256 AppendComment(cmailOldMove, "Draw declined");
11257 DisplayComment(cmailOldMove - 1, "Draw declined");
11260 DisplayError(_("There is no pending offer on this move"), 0);
11263 /* Not used for offers from chess program */
11270 /* Issue ICS rematch command */
11271 if (appData.icsActive) {
11272 SendToICS(ics_prefix);
11273 SendToICS("rematch\n");
11280 /* Call your opponent's flag (claim a win on time) */
11281 if (appData.icsActive) {
11282 SendToICS(ics_prefix);
11283 SendToICS("flag\n");
11285 switch (gameMode) {
11288 case MachinePlaysWhite:
11291 GameEnds(GameIsDrawn, "Both players ran out of time",
11294 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11296 DisplayError(_("Your opponent is not out of time"), 0);
11299 case MachinePlaysBlack:
11302 GameEnds(GameIsDrawn, "Both players ran out of time",
11305 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11307 DisplayError(_("Your opponent is not out of time"), 0);
11317 /* Offer draw or accept pending draw offer from opponent */
11319 if (appData.icsActive) {
11320 /* Note: tournament rules require draw offers to be
11321 made after you make your move but before you punch
11322 your clock. Currently ICS doesn't let you do that;
11323 instead, you immediately punch your clock after making
11324 a move, but you can offer a draw at any time. */
11326 SendToICS(ics_prefix);
11327 SendToICS("draw\n");
11328 } else if (cmailMsgLoaded) {
11329 if (currentMove == cmailOldMove &&
11330 commentList[cmailOldMove] != NULL &&
11331 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11332 "Black offers a draw" : "White offers a draw")) {
11333 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11334 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11335 } else if (currentMove == cmailOldMove + 1) {
11336 char *offer = WhiteOnMove(cmailOldMove) ?
11337 "White offers a draw" : "Black offers a draw";
11338 AppendComment(currentMove, offer);
11339 DisplayComment(currentMove - 1, offer);
11340 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11342 DisplayError(_("You must make your move before offering a draw"), 0);
11343 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11345 } else if (first.offeredDraw) {
11346 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11348 if (first.sendDrawOffers) {
11349 SendToProgram("draw\n", &first);
11350 userOfferedDraw = TRUE;
11358 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11360 if (appData.icsActive) {
11361 SendToICS(ics_prefix);
11362 SendToICS("adjourn\n");
11364 /* Currently GNU Chess doesn't offer or accept Adjourns */
11372 /* Offer Abort or accept pending Abort offer from opponent */
11374 if (appData.icsActive) {
11375 SendToICS(ics_prefix);
11376 SendToICS("abort\n");
11378 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11385 /* Resign. You can do this even if it's not your turn. */
11387 if (appData.icsActive) {
11388 SendToICS(ics_prefix);
11389 SendToICS("resign\n");
11391 switch (gameMode) {
11392 case MachinePlaysWhite:
11393 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11395 case MachinePlaysBlack:
11396 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11399 if (cmailMsgLoaded) {
11401 if (WhiteOnMove(cmailOldMove)) {
11402 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11404 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11406 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11417 StopObservingEvent()
11419 /* Stop observing current games */
11420 SendToICS(ics_prefix);
11421 SendToICS("unobserve\n");
11425 StopExaminingEvent()
11427 /* Stop observing current game */
11428 SendToICS(ics_prefix);
11429 SendToICS("unexamine\n");
11433 ForwardInner(target)
11438 if (appData.debugMode)
11439 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11440 target, currentMove, forwardMostMove);
11442 if (gameMode == EditPosition)
11445 if (gameMode == PlayFromGameFile && !pausing)
11448 if (gameMode == IcsExamining && pausing)
11449 limit = pauseExamForwardMostMove;
11451 limit = forwardMostMove;
11453 if (target > limit) target = limit;
11455 if (target > 0 && moveList[target - 1][0]) {
11456 int fromX, fromY, toX, toY;
11457 toX = moveList[target - 1][2] - AAA;
11458 toY = moveList[target - 1][3] - ONE;
11459 if (moveList[target - 1][1] == '@') {
11460 if (appData.highlightLastMove) {
11461 SetHighlights(-1, -1, toX, toY);
11464 fromX = moveList[target - 1][0] - AAA;
11465 fromY = moveList[target - 1][1] - ONE;
11466 if (target == currentMove + 1) {
11467 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11469 if (appData.highlightLastMove) {
11470 SetHighlights(fromX, fromY, toX, toY);
11474 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11475 gameMode == Training || gameMode == PlayFromGameFile ||
11476 gameMode == AnalyzeFile) {
11477 while (currentMove < target) {
11478 SendMoveToProgram(currentMove++, &first);
11481 currentMove = target;
11484 if (gameMode == EditGame || gameMode == EndOfGame) {
11485 whiteTimeRemaining = timeRemaining[0][currentMove];
11486 blackTimeRemaining = timeRemaining[1][currentMove];
11488 DisplayBothClocks();
11489 DisplayMove(currentMove - 1);
11490 DrawPosition(FALSE, boards[currentMove]);
11491 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11492 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11493 DisplayComment(currentMove - 1, commentList[currentMove]);
11501 if (gameMode == IcsExamining && !pausing) {
11502 SendToICS(ics_prefix);
11503 SendToICS("forward\n");
11505 ForwardInner(currentMove + 1);
11512 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11513 /* to optimze, we temporarily turn off analysis mode while we feed
11514 * the remaining moves to the engine. Otherwise we get analysis output
11517 if (first.analysisSupport) {
11518 SendToProgram("exit\nforce\n", &first);
11519 first.analyzing = FALSE;
11523 if (gameMode == IcsExamining && !pausing) {
11524 SendToICS(ics_prefix);
11525 SendToICS("forward 999999\n");
11527 ForwardInner(forwardMostMove);
11530 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11531 /* we have fed all the moves, so reactivate analysis mode */
11532 SendToProgram("analyze\n", &first);
11533 first.analyzing = TRUE;
11534 /*first.maybeThinking = TRUE;*/
11535 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11540 BackwardInner(target)
11543 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11545 if (appData.debugMode)
11546 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11547 target, currentMove, forwardMostMove);
11549 if (gameMode == EditPosition) return;
11550 if (currentMove <= backwardMostMove) {
11552 DrawPosition(full_redraw, boards[currentMove]);
11555 if (gameMode == PlayFromGameFile && !pausing)
11558 if (moveList[target][0]) {
11559 int fromX, fromY, toX, toY;
11560 toX = moveList[target][2] - AAA;
11561 toY = moveList[target][3] - ONE;
11562 if (moveList[target][1] == '@') {
11563 if (appData.highlightLastMove) {
11564 SetHighlights(-1, -1, toX, toY);
11567 fromX = moveList[target][0] - AAA;
11568 fromY = moveList[target][1] - ONE;
11569 if (target == currentMove - 1) {
11570 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11572 if (appData.highlightLastMove) {
11573 SetHighlights(fromX, fromY, toX, toY);
11577 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11578 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11579 while (currentMove > target) {
11580 SendToProgram("undo\n", &first);
11584 currentMove = target;
11587 if (gameMode == EditGame || gameMode == EndOfGame) {
11588 whiteTimeRemaining = timeRemaining[0][currentMove];
11589 blackTimeRemaining = timeRemaining[1][currentMove];
11591 DisplayBothClocks();
11592 DisplayMove(currentMove - 1);
11593 DrawPosition(full_redraw, boards[currentMove]);
11594 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11595 // [HGM] PV info: routine tests if comment empty
11596 DisplayComment(currentMove - 1, commentList[currentMove]);
11602 if (gameMode == IcsExamining && !pausing) {
11603 SendToICS(ics_prefix);
11604 SendToICS("backward\n");
11606 BackwardInner(currentMove - 1);
11613 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11614 /* to optimze, we temporarily turn off analysis mode while we undo
11615 * all the moves. Otherwise we get analysis output after each undo.
11617 if (first.analysisSupport) {
11618 SendToProgram("exit\nforce\n", &first);
11619 first.analyzing = FALSE;
11623 if (gameMode == IcsExamining && !pausing) {
11624 SendToICS(ics_prefix);
11625 SendToICS("backward 999999\n");
11627 BackwardInner(backwardMostMove);
11630 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11631 /* we have fed all the moves, so reactivate analysis mode */
11632 SendToProgram("analyze\n", &first);
11633 first.analyzing = TRUE;
11634 /*first.maybeThinking = TRUE;*/
11635 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11642 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11643 if (to >= forwardMostMove) to = forwardMostMove;
11644 if (to <= backwardMostMove) to = backwardMostMove;
11645 if (to < currentMove) {
11655 if (gameMode != IcsExamining) {
11656 DisplayError(_("You are not examining a game"), 0);
11660 DisplayError(_("You can't revert while pausing"), 0);
11663 SendToICS(ics_prefix);
11664 SendToICS("revert\n");
11670 switch (gameMode) {
11671 case MachinePlaysWhite:
11672 case MachinePlaysBlack:
11673 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11674 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11677 if (forwardMostMove < 2) return;
11678 currentMove = forwardMostMove = forwardMostMove - 2;
11679 whiteTimeRemaining = timeRemaining[0][currentMove];
11680 blackTimeRemaining = timeRemaining[1][currentMove];
11681 DisplayBothClocks();
11682 DisplayMove(currentMove - 1);
11683 ClearHighlights();/*!! could figure this out*/
11684 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11685 SendToProgram("remove\n", &first);
11686 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11689 case BeginningOfGame:
11693 case IcsPlayingWhite:
11694 case IcsPlayingBlack:
11695 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11696 SendToICS(ics_prefix);
11697 SendToICS("takeback 2\n");
11699 SendToICS(ics_prefix);
11700 SendToICS("takeback 1\n");
11709 ChessProgramState *cps;
11711 switch (gameMode) {
11712 case MachinePlaysWhite:
11713 if (!WhiteOnMove(forwardMostMove)) {
11714 DisplayError(_("It is your turn"), 0);
11719 case MachinePlaysBlack:
11720 if (WhiteOnMove(forwardMostMove)) {
11721 DisplayError(_("It is your turn"), 0);
11726 case TwoMachinesPlay:
11727 if (WhiteOnMove(forwardMostMove) ==
11728 (first.twoMachinesColor[0] == 'w')) {
11734 case BeginningOfGame:
11738 SendToProgram("?\n", cps);
11742 TruncateGameEvent()
11745 if (gameMode != EditGame) return;
11752 if (forwardMostMove > currentMove) {
11753 if (gameInfo.resultDetails != NULL) {
11754 free(gameInfo.resultDetails);
11755 gameInfo.resultDetails = NULL;
11756 gameInfo.result = GameUnfinished;
11758 forwardMostMove = currentMove;
11759 HistorySet(parseList, backwardMostMove, forwardMostMove,
11767 if (appData.noChessProgram) return;
11768 switch (gameMode) {
11769 case MachinePlaysWhite:
11770 if (WhiteOnMove(forwardMostMove)) {
11771 DisplayError(_("Wait until your turn"), 0);
11775 case BeginningOfGame:
11776 case MachinePlaysBlack:
11777 if (!WhiteOnMove(forwardMostMove)) {
11778 DisplayError(_("Wait until your turn"), 0);
11783 DisplayError(_("No hint available"), 0);
11786 SendToProgram("hint\n", &first);
11787 hintRequested = TRUE;
11793 if (appData.noChessProgram) return;
11794 switch (gameMode) {
11795 case MachinePlaysWhite:
11796 if (WhiteOnMove(forwardMostMove)) {
11797 DisplayError(_("Wait until your turn"), 0);
11801 case BeginningOfGame:
11802 case MachinePlaysBlack:
11803 if (!WhiteOnMove(forwardMostMove)) {
11804 DisplayError(_("Wait until your turn"), 0);
11809 EditPositionDone();
11811 case TwoMachinesPlay:
11816 SendToProgram("bk\n", &first);
11817 bookOutput[0] = NULLCHAR;
11818 bookRequested = TRUE;
11824 char *tags = PGNTags(&gameInfo);
11825 TagsPopUp(tags, CmailMsg());
11829 /* end button procedures */
11832 PrintPosition(fp, move)
11838 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11839 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11840 char c = PieceToChar(boards[move][i][j]);
11841 fputc(c == 'x' ? '.' : c, fp);
11842 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11845 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11846 fprintf(fp, "white to play\n");
11848 fprintf(fp, "black to play\n");
11855 if (gameInfo.white != NULL) {
11856 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11862 /* Find last component of program's own name, using some heuristics */
11864 TidyProgramName(prog, host, buf)
11865 char *prog, *host, buf[MSG_SIZ];
11868 int local = (strcmp(host, "localhost") == 0);
11869 while (!local && (p = strchr(prog, ';')) != NULL) {
11871 while (*p == ' ') p++;
11874 if (*prog == '"' || *prog == '\'') {
11875 q = strchr(prog + 1, *prog);
11877 q = strchr(prog, ' ');
11879 if (q == NULL) q = prog + strlen(prog);
11881 while (p >= prog && *p != '/' && *p != '\\') p--;
11883 if(p == prog && *p == '"') p++;
11884 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11885 memcpy(buf, p, q - p);
11886 buf[q - p] = NULLCHAR;
11894 TimeControlTagValue()
11897 if (!appData.clockMode) {
11899 } else if (movesPerSession > 0) {
11900 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11901 } else if (timeIncrement == 0) {
11902 sprintf(buf, "%ld", timeControl/1000);
11904 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11906 return StrSave(buf);
11912 /* This routine is used only for certain modes */
11913 VariantClass v = gameInfo.variant;
11914 ClearGameInfo(&gameInfo);
11915 gameInfo.variant = v;
11917 switch (gameMode) {
11918 case MachinePlaysWhite:
11919 gameInfo.event = StrSave( appData.pgnEventHeader );
11920 gameInfo.site = StrSave(HostName());
11921 gameInfo.date = PGNDate();
11922 gameInfo.round = StrSave("-");
11923 gameInfo.white = StrSave(first.tidy);
11924 gameInfo.black = StrSave(UserName());
11925 gameInfo.timeControl = TimeControlTagValue();
11928 case MachinePlaysBlack:
11929 gameInfo.event = StrSave( appData.pgnEventHeader );
11930 gameInfo.site = StrSave(HostName());
11931 gameInfo.date = PGNDate();
11932 gameInfo.round = StrSave("-");
11933 gameInfo.white = StrSave(UserName());
11934 gameInfo.black = StrSave(first.tidy);
11935 gameInfo.timeControl = TimeControlTagValue();
11938 case TwoMachinesPlay:
11939 gameInfo.event = StrSave( appData.pgnEventHeader );
11940 gameInfo.site = StrSave(HostName());
11941 gameInfo.date = PGNDate();
11942 if (matchGame > 0) {
11944 sprintf(buf, "%d", matchGame);
11945 gameInfo.round = StrSave(buf);
11947 gameInfo.round = StrSave("-");
11949 if (first.twoMachinesColor[0] == 'w') {
11950 gameInfo.white = StrSave(first.tidy);
11951 gameInfo.black = StrSave(second.tidy);
11953 gameInfo.white = StrSave(second.tidy);
11954 gameInfo.black = StrSave(first.tidy);
11956 gameInfo.timeControl = TimeControlTagValue();
11960 gameInfo.event = StrSave("Edited game");
11961 gameInfo.site = StrSave(HostName());
11962 gameInfo.date = PGNDate();
11963 gameInfo.round = StrSave("-");
11964 gameInfo.white = StrSave("-");
11965 gameInfo.black = StrSave("-");
11969 gameInfo.event = StrSave("Edited position");
11970 gameInfo.site = StrSave(HostName());
11971 gameInfo.date = PGNDate();
11972 gameInfo.round = StrSave("-");
11973 gameInfo.white = StrSave("-");
11974 gameInfo.black = StrSave("-");
11977 case IcsPlayingWhite:
11978 case IcsPlayingBlack:
11983 case PlayFromGameFile:
11984 gameInfo.event = StrSave("Game from non-PGN file");
11985 gameInfo.site = StrSave(HostName());
11986 gameInfo.date = PGNDate();
11987 gameInfo.round = StrSave("-");
11988 gameInfo.white = StrSave("?");
11989 gameInfo.black = StrSave("?");
11998 ReplaceComment(index, text)
12004 while (*text == '\n') text++;
12005 len = strlen(text);
12006 while (len > 0 && text[len - 1] == '\n') len--;
12008 if (commentList[index] != NULL)
12009 free(commentList[index]);
12012 commentList[index] = NULL;
12015 commentList[index] = (char *) malloc(len + 2);
12016 strncpy(commentList[index], text, len);
12017 commentList[index][len] = '\n';
12018 commentList[index][len + 1] = NULLCHAR;
12031 if (ch == '\r') continue;
12033 } while (ch != '\0');
12037 AppendComment(index, text)
12044 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12047 while (*text == '\n') text++;
12048 len = strlen(text);
12049 while (len > 0 && text[len - 1] == '\n') len--;
12051 if (len == 0) return;
12053 if (commentList[index] != NULL) {
12054 old = commentList[index];
12055 oldlen = strlen(old);
12056 commentList[index] = (char *) malloc(oldlen + len + 2);
12057 strcpy(commentList[index], old);
12059 strncpy(&commentList[index][oldlen], text, len);
12060 commentList[index][oldlen + len] = '\n';
12061 commentList[index][oldlen + len + 1] = NULLCHAR;
12063 commentList[index] = (char *) malloc(len + 2);
12064 strncpy(commentList[index], text, len);
12065 commentList[index][len] = '\n';
12066 commentList[index][len + 1] = NULLCHAR;
12070 static char * FindStr( char * text, char * sub_text )
12072 char * result = strstr( text, sub_text );
12074 if( result != NULL ) {
12075 result += strlen( sub_text );
12081 /* [AS] Try to extract PV info from PGN comment */
12082 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12083 char *GetInfoFromComment( int index, char * text )
12087 if( text != NULL && index > 0 ) {
12090 int time = -1, sec = 0, deci;
12091 char * s_eval = FindStr( text, "[%eval " );
12092 char * s_emt = FindStr( text, "[%emt " );
12094 if( s_eval != NULL || s_emt != NULL ) {
12098 if( s_eval != NULL ) {
12099 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12103 if( delim != ']' ) {
12108 if( s_emt != NULL ) {
12112 /* We expect something like: [+|-]nnn.nn/dd */
12115 sep = strchr( text, '/' );
12116 if( sep == NULL || sep < (text+4) ) {
12120 time = -1; sec = -1; deci = -1;
12121 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12122 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12123 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12124 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12128 if( score_lo < 0 || score_lo >= 100 ) {
12132 if(sec >= 0) time = 600*time + 10*sec; else
12133 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12135 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12137 /* [HGM] PV time: now locate end of PV info */
12138 while( *++sep >= '0' && *sep <= '9'); // strip depth
12140 while( *++sep >= '0' && *sep <= '9'); // strip time
12142 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12144 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12145 while(*sep == ' ') sep++;
12156 pvInfoList[index-1].depth = depth;
12157 pvInfoList[index-1].score = score;
12158 pvInfoList[index-1].time = 10*time; // centi-sec
12164 SendToProgram(message, cps)
12166 ChessProgramState *cps;
12168 int count, outCount, error;
12171 if (cps->pr == NULL) return;
12174 if (appData.debugMode) {
12177 fprintf(debugFP, "%ld >%-6s: %s",
12178 SubtractTimeMarks(&now, &programStartTime),
12179 cps->which, message);
12182 count = strlen(message);
12183 outCount = OutputToProcess(cps->pr, message, count, &error);
12184 if (outCount < count && !exiting
12185 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12186 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12187 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12188 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12189 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12190 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12192 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12194 gameInfo.resultDetails = buf;
12196 DisplayFatalError(buf, error, 1);
12201 ReceiveFromProgram(isr, closure, message, count, error)
12202 InputSourceRef isr;
12210 ChessProgramState *cps = (ChessProgramState *)closure;
12212 if (isr != cps->isr) return; /* Killed intentionally */
12216 _("Error: %s chess program (%s) exited unexpectedly"),
12217 cps->which, cps->program);
12218 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12219 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12220 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12221 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12223 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12225 gameInfo.resultDetails = buf;
12227 RemoveInputSource(cps->isr);
12228 DisplayFatalError(buf, 0, 1);
12231 _("Error reading from %s chess program (%s)"),
12232 cps->which, cps->program);
12233 RemoveInputSource(cps->isr);
12235 /* [AS] Program is misbehaving badly... kill it */
12236 if( count == -2 ) {
12237 DestroyChildProcess( cps->pr, 9 );
12241 DisplayFatalError(buf, error, 1);
12246 if ((end_str = strchr(message, '\r')) != NULL)
12247 *end_str = NULLCHAR;
12248 if ((end_str = strchr(message, '\n')) != NULL)
12249 *end_str = NULLCHAR;
12251 if (appData.debugMode) {
12252 TimeMark now; int print = 1;
12253 char *quote = ""; char c; int i;
12255 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12256 char start = message[0];
12257 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12258 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12259 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12260 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12261 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12262 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12263 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12264 { quote = "# "; print = (appData.engineComments == 2); }
12265 message[0] = start; // restore original message
12269 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12270 SubtractTimeMarks(&now, &programStartTime), cps->which,
12276 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12277 if (appData.icsEngineAnalyze) {
12278 if (strstr(message, "whisper") != NULL ||
12279 strstr(message, "kibitz") != NULL ||
12280 strstr(message, "tellics") != NULL) return;
12283 HandleMachineMove(message, cps);
12288 SendTimeControl(cps, mps, tc, inc, sd, st)
12289 ChessProgramState *cps;
12290 int mps, inc, sd, st;
12296 if( timeControl_2 > 0 ) {
12297 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12298 tc = timeControl_2;
12301 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12302 inc /= cps->timeOdds;
12303 st /= cps->timeOdds;
12305 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12308 /* Set exact time per move, normally using st command */
12309 if (cps->stKludge) {
12310 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12312 if (seconds == 0) {
12313 sprintf(buf, "level 1 %d\n", st/60);
12315 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12318 sprintf(buf, "st %d\n", st);
12321 /* Set conventional or incremental time control, using level command */
12322 if (seconds == 0) {
12323 /* Note old gnuchess bug -- minutes:seconds used to not work.
12324 Fixed in later versions, but still avoid :seconds
12325 when seconds is 0. */
12326 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12328 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12329 seconds, inc/1000);
12332 SendToProgram(buf, cps);
12334 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12335 /* Orthogonally, limit search to given depth */
12337 if (cps->sdKludge) {
12338 sprintf(buf, "depth\n%d\n", sd);
12340 sprintf(buf, "sd %d\n", sd);
12342 SendToProgram(buf, cps);
12345 if(cps->nps > 0) { /* [HGM] nps */
12346 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12348 sprintf(buf, "nps %d\n", cps->nps);
12349 SendToProgram(buf, cps);
12354 ChessProgramState *WhitePlayer()
12355 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12357 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12358 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12364 SendTimeRemaining(cps, machineWhite)
12365 ChessProgramState *cps;
12366 int /*boolean*/ machineWhite;
12368 char message[MSG_SIZ];
12371 /* Note: this routine must be called when the clocks are stopped
12372 or when they have *just* been set or switched; otherwise
12373 it will be off by the time since the current tick started.
12375 if (machineWhite) {
12376 time = whiteTimeRemaining / 10;
12377 otime = blackTimeRemaining / 10;
12379 time = blackTimeRemaining / 10;
12380 otime = whiteTimeRemaining / 10;
12382 /* [HGM] translate opponent's time by time-odds factor */
12383 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12384 if (appData.debugMode) {
12385 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12388 if (time <= 0) time = 1;
12389 if (otime <= 0) otime = 1;
12391 sprintf(message, "time %ld\n", time);
12392 SendToProgram(message, cps);
12394 sprintf(message, "otim %ld\n", otime);
12395 SendToProgram(message, cps);
12399 BoolFeature(p, name, loc, cps)
12403 ChessProgramState *cps;
12406 int len = strlen(name);
12408 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12410 sscanf(*p, "%d", &val);
12412 while (**p && **p != ' ') (*p)++;
12413 sprintf(buf, "accepted %s\n", name);
12414 SendToProgram(buf, cps);
12421 IntFeature(p, name, loc, cps)
12425 ChessProgramState *cps;
12428 int len = strlen(name);
12429 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12431 sscanf(*p, "%d", loc);
12432 while (**p && **p != ' ') (*p)++;
12433 sprintf(buf, "accepted %s\n", name);
12434 SendToProgram(buf, cps);
12441 StringFeature(p, name, loc, cps)
12445 ChessProgramState *cps;
12448 int len = strlen(name);
12449 if (strncmp((*p), name, len) == 0
12450 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12452 sscanf(*p, "%[^\"]", loc);
12453 while (**p && **p != '\"') (*p)++;
12454 if (**p == '\"') (*p)++;
12455 sprintf(buf, "accepted %s\n", name);
12456 SendToProgram(buf, cps);
12463 ParseOption(Option *opt, ChessProgramState *cps)
12464 // [HGM] options: process the string that defines an engine option, and determine
12465 // name, type, default value, and allowed value range
12467 char *p, *q, buf[MSG_SIZ];
12468 int n, min = (-1)<<31, max = 1<<31, def;
12470 if(p = strstr(opt->name, " -spin ")) {
12471 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12472 if(max < min) max = min; // enforce consistency
12473 if(def < min) def = min;
12474 if(def > max) def = max;
12479 } else if((p = strstr(opt->name, " -slider "))) {
12480 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12481 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12482 if(max < min) max = min; // enforce consistency
12483 if(def < min) def = min;
12484 if(def > max) def = max;
12488 opt->type = Spin; // Slider;
12489 } else if((p = strstr(opt->name, " -string "))) {
12490 opt->textValue = p+9;
12491 opt->type = TextBox;
12492 } else if((p = strstr(opt->name, " -file "))) {
12493 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12494 opt->textValue = p+7;
12495 opt->type = TextBox; // FileName;
12496 } else if((p = strstr(opt->name, " -path "))) {
12497 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12498 opt->textValue = p+7;
12499 opt->type = TextBox; // PathName;
12500 } else if(p = strstr(opt->name, " -check ")) {
12501 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12502 opt->value = (def != 0);
12503 opt->type = CheckBox;
12504 } else if(p = strstr(opt->name, " -combo ")) {
12505 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12506 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12507 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12508 opt->value = n = 0;
12509 while(q = StrStr(q, " /// ")) {
12510 n++; *q = 0; // count choices, and null-terminate each of them
12512 if(*q == '*') { // remember default, which is marked with * prefix
12516 cps->comboList[cps->comboCnt++] = q;
12518 cps->comboList[cps->comboCnt++] = NULL;
12520 opt->type = ComboBox;
12521 } else if(p = strstr(opt->name, " -button")) {
12522 opt->type = Button;
12523 } else if(p = strstr(opt->name, " -save")) {
12524 opt->type = SaveButton;
12525 } else return FALSE;
12526 *p = 0; // terminate option name
12527 // now look if the command-line options define a setting for this engine option.
12528 if(cps->optionSettings && cps->optionSettings[0])
12529 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12530 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12531 sprintf(buf, "option %s", p);
12532 if(p = strstr(buf, ",")) *p = 0;
12534 SendToProgram(buf, cps);
12540 FeatureDone(cps, val)
12541 ChessProgramState* cps;
12544 DelayedEventCallback cb = GetDelayedEvent();
12545 if ((cb == InitBackEnd3 && cps == &first) ||
12546 (cb == TwoMachinesEventIfReady && cps == &second)) {
12547 CancelDelayedEvent();
12548 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12550 cps->initDone = val;
12553 /* Parse feature command from engine */
12555 ParseFeatures(args, cps)
12557 ChessProgramState *cps;
12565 while (*p == ' ') p++;
12566 if (*p == NULLCHAR) return;
12568 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12569 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12570 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12571 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12572 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12573 if (BoolFeature(&p, "reuse", &val, cps)) {
12574 /* Engine can disable reuse, but can't enable it if user said no */
12575 if (!val) cps->reuse = FALSE;
12578 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12579 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12580 if (gameMode == TwoMachinesPlay) {
12581 DisplayTwoMachinesTitle();
12587 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12588 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12589 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12590 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12591 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12592 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12593 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12594 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12595 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12596 if (IntFeature(&p, "done", &val, cps)) {
12597 FeatureDone(cps, val);
12600 /* Added by Tord: */
12601 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12602 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12603 /* End of additions by Tord */
12605 /* [HGM] added features: */
12606 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12607 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12608 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12609 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12610 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12611 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12612 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12613 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12614 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12615 SendToProgram(buf, cps);
12618 if(cps->nrOptions >= MAX_OPTIONS) {
12620 sprintf(buf, "%s engine has too many options\n", cps->which);
12621 DisplayError(buf, 0);
12625 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12626 /* End of additions by HGM */
12628 /* unknown feature: complain and skip */
12630 while (*q && *q != '=') q++;
12631 sprintf(buf, "rejected %.*s\n", q-p, p);
12632 SendToProgram(buf, cps);
12638 while (*p && *p != '\"') p++;
12639 if (*p == '\"') p++;
12641 while (*p && *p != ' ') p++;
12649 PeriodicUpdatesEvent(newState)
12652 if (newState == appData.periodicUpdates)
12655 appData.periodicUpdates=newState;
12657 /* Display type changes, so update it now */
12660 /* Get the ball rolling again... */
12662 AnalysisPeriodicEvent(1);
12663 StartAnalysisClock();
12668 PonderNextMoveEvent(newState)
12671 if (newState == appData.ponderNextMove) return;
12672 if (gameMode == EditPosition) EditPositionDone();
12674 SendToProgram("hard\n", &first);
12675 if (gameMode == TwoMachinesPlay) {
12676 SendToProgram("hard\n", &second);
12679 SendToProgram("easy\n", &first);
12680 thinkOutput[0] = NULLCHAR;
12681 if (gameMode == TwoMachinesPlay) {
12682 SendToProgram("easy\n", &second);
12685 appData.ponderNextMove = newState;
12689 NewSettingEvent(option, command, value)
12695 if (gameMode == EditPosition) EditPositionDone();
12696 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12697 SendToProgram(buf, &first);
12698 if (gameMode == TwoMachinesPlay) {
12699 SendToProgram(buf, &second);
12704 ShowThinkingEvent()
12705 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12707 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12708 int newState = appData.showThinking
12709 // [HGM] thinking: other features now need thinking output as well
12710 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12712 if (oldState == newState) return;
12713 oldState = newState;
12714 if (gameMode == EditPosition) EditPositionDone();
12716 SendToProgram("post\n", &first);
12717 if (gameMode == TwoMachinesPlay) {
12718 SendToProgram("post\n", &second);
12721 SendToProgram("nopost\n", &first);
12722 thinkOutput[0] = NULLCHAR;
12723 if (gameMode == TwoMachinesPlay) {
12724 SendToProgram("nopost\n", &second);
12727 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12731 AskQuestionEvent(title, question, replyPrefix, which)
12732 char *title; char *question; char *replyPrefix; char *which;
12734 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12735 if (pr == NoProc) return;
12736 AskQuestion(title, question, replyPrefix, pr);
12740 DisplayMove(moveNumber)
12743 char message[MSG_SIZ];
12745 char cpThinkOutput[MSG_SIZ];
12747 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12749 if (moveNumber == forwardMostMove - 1 ||
12750 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12752 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12754 if (strchr(cpThinkOutput, '\n')) {
12755 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12758 *cpThinkOutput = NULLCHAR;
12761 /* [AS] Hide thinking from human user */
12762 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12763 *cpThinkOutput = NULLCHAR;
12764 if( thinkOutput[0] != NULLCHAR ) {
12767 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12768 cpThinkOutput[i] = '.';
12770 cpThinkOutput[i] = NULLCHAR;
12771 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12775 if (moveNumber == forwardMostMove - 1 &&
12776 gameInfo.resultDetails != NULL) {
12777 if (gameInfo.resultDetails[0] == NULLCHAR) {
12778 sprintf(res, " %s", PGNResult(gameInfo.result));
12780 sprintf(res, " {%s} %s",
12781 gameInfo.resultDetails, PGNResult(gameInfo.result));
12787 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12788 DisplayMessage(res, cpThinkOutput);
12790 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12791 WhiteOnMove(moveNumber) ? " " : ".. ",
12792 parseList[moveNumber], res);
12793 DisplayMessage(message, cpThinkOutput);
12798 DisplayAnalysisText(text)
12803 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12804 || appData.icsEngineAnalyze) {
12805 sprintf(buf, "Analysis (%s)", first.tidy);
12806 AnalysisPopUp(buf, text);
12814 while (*str && isspace(*str)) ++str;
12815 while (*str && !isspace(*str)) ++str;
12816 if (!*str) return 1;
12817 while (*str && isspace(*str)) ++str;
12818 if (!*str) return 1;
12826 char lst[MSG_SIZ / 2];
12828 static char *xtra[] = { "", " (--)", " (++)" };
12831 if (programStats.time == 0) {
12832 programStats.time = 1;
12835 if (programStats.got_only_move) {
12836 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12838 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12840 nps = (u64ToDouble(programStats.nodes) /
12841 ((double)programStats.time /100.0));
12843 cs = programStats.time % 100;
12844 s = programStats.time / 100;
12850 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12851 if (programStats.move_name[0] != NULLCHAR) {
12852 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12853 programStats.depth,
12854 programStats.nr_moves-programStats.moves_left,
12855 programStats.nr_moves, programStats.move_name,
12856 ((float)programStats.score)/100.0, lst,
12857 only_one_move(lst)?
12858 xtra[programStats.got_fail] : "",
12859 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12861 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12862 programStats.depth,
12863 programStats.nr_moves-programStats.moves_left,
12864 programStats.nr_moves, ((float)programStats.score)/100.0,
12866 only_one_move(lst)?
12867 xtra[programStats.got_fail] : "",
12868 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12871 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12872 programStats.depth,
12873 ((float)programStats.score)/100.0,
12875 only_one_move(lst)?
12876 xtra[programStats.got_fail] : "",
12877 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12880 DisplayAnalysisText(buf);
12884 DisplayComment(moveNumber, text)
12888 char title[MSG_SIZ];
12889 char buf[8000]; // comment can be long!
12892 if( appData.autoDisplayComment ) {
12893 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12894 strcpy(title, "Comment");
12896 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12897 WhiteOnMove(moveNumber) ? " " : ".. ",
12898 parseList[moveNumber]);
12900 // [HGM] PV info: display PV info together with (or as) comment
12901 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12902 if(text == NULL) text = "";
12903 score = pvInfoList[moveNumber].score;
12904 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12905 depth, (pvInfoList[moveNumber].time+50)/100, text);
12908 } else title[0] = 0;
12911 CommentPopUp(title, text);
12914 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12915 * might be busy thinking or pondering. It can be omitted if your
12916 * gnuchess is configured to stop thinking immediately on any user
12917 * input. However, that gnuchess feature depends on the FIONREAD
12918 * ioctl, which does not work properly on some flavors of Unix.
12922 ChessProgramState *cps;
12925 if (!cps->useSigint) return;
12926 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12927 switch (gameMode) {
12928 case MachinePlaysWhite:
12929 case MachinePlaysBlack:
12930 case TwoMachinesPlay:
12931 case IcsPlayingWhite:
12932 case IcsPlayingBlack:
12935 /* Skip if we know it isn't thinking */
12936 if (!cps->maybeThinking) return;
12937 if (appData.debugMode)
12938 fprintf(debugFP, "Interrupting %s\n", cps->which);
12939 InterruptChildProcess(cps->pr);
12940 cps->maybeThinking = FALSE;
12945 #endif /*ATTENTION*/
12951 if (whiteTimeRemaining <= 0) {
12954 if (appData.icsActive) {
12955 if (appData.autoCallFlag &&
12956 gameMode == IcsPlayingBlack && !blackFlag) {
12957 SendToICS(ics_prefix);
12958 SendToICS("flag\n");
12962 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12964 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12965 if (appData.autoCallFlag) {
12966 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12973 if (blackTimeRemaining <= 0) {
12976 if (appData.icsActive) {
12977 if (appData.autoCallFlag &&
12978 gameMode == IcsPlayingWhite && !whiteFlag) {
12979 SendToICS(ics_prefix);
12980 SendToICS("flag\n");
12984 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12986 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12987 if (appData.autoCallFlag) {
12988 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13001 if (!appData.clockMode || appData.icsActive ||
13002 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13005 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13007 if ( !WhiteOnMove(forwardMostMove) )
13008 /* White made time control */
13009 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13010 /* [HGM] time odds: correct new time quota for time odds! */
13011 / WhitePlayer()->timeOdds;
13013 /* Black made time control */
13014 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13015 / WhitePlayer()->other->timeOdds;
13019 DisplayBothClocks()
13021 int wom = gameMode == EditPosition ?
13022 !blackPlaysFirst : WhiteOnMove(currentMove);
13023 DisplayWhiteClock(whiteTimeRemaining, wom);
13024 DisplayBlackClock(blackTimeRemaining, !wom);
13028 /* Timekeeping seems to be a portability nightmare. I think everyone
13029 has ftime(), but I'm really not sure, so I'm including some ifdefs
13030 to use other calls if you don't. Clocks will be less accurate if
13031 you have neither ftime nor gettimeofday.
13034 /* VS 2008 requires the #include outside of the function */
13035 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13036 #include <sys/timeb.h>
13039 /* Get the current time as a TimeMark */
13044 #if HAVE_GETTIMEOFDAY
13046 struct timeval timeVal;
13047 struct timezone timeZone;
13049 gettimeofday(&timeVal, &timeZone);
13050 tm->sec = (long) timeVal.tv_sec;
13051 tm->ms = (int) (timeVal.tv_usec / 1000L);
13053 #else /*!HAVE_GETTIMEOFDAY*/
13056 // include <sys/timeb.h> / moved to just above start of function
13057 struct timeb timeB;
13060 tm->sec = (long) timeB.time;
13061 tm->ms = (int) timeB.millitm;
13063 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13064 tm->sec = (long) time(NULL);
13070 /* Return the difference in milliseconds between two
13071 time marks. We assume the difference will fit in a long!
13074 SubtractTimeMarks(tm2, tm1)
13075 TimeMark *tm2, *tm1;
13077 return 1000L*(tm2->sec - tm1->sec) +
13078 (long) (tm2->ms - tm1->ms);
13083 * Code to manage the game clocks.
13085 * In tournament play, black starts the clock and then white makes a move.
13086 * We give the human user a slight advantage if he is playing white---the
13087 * clocks don't run until he makes his first move, so it takes zero time.
13088 * Also, we don't account for network lag, so we could get out of sync
13089 * with GNU Chess's clock -- but then, referees are always right.
13092 static TimeMark tickStartTM;
13093 static long intendedTickLength;
13096 NextTickLength(timeRemaining)
13097 long timeRemaining;
13099 long nominalTickLength, nextTickLength;
13101 if (timeRemaining > 0L && timeRemaining <= 10000L)
13102 nominalTickLength = 100L;
13104 nominalTickLength = 1000L;
13105 nextTickLength = timeRemaining % nominalTickLength;
13106 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13108 return nextTickLength;
13111 /* Adjust clock one minute up or down */
13113 AdjustClock(Boolean which, int dir)
13115 if(which) blackTimeRemaining += 60000*dir;
13116 else whiteTimeRemaining += 60000*dir;
13117 DisplayBothClocks();
13120 /* Stop clocks and reset to a fresh time control */
13124 (void) StopClockTimer();
13125 if (appData.icsActive) {
13126 whiteTimeRemaining = blackTimeRemaining = 0;
13127 } else { /* [HGM] correct new time quote for time odds */
13128 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13129 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13131 if (whiteFlag || blackFlag) {
13133 whiteFlag = blackFlag = FALSE;
13135 DisplayBothClocks();
13138 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13140 /* Decrement running clock by amount of time that has passed */
13144 long timeRemaining;
13145 long lastTickLength, fudge;
13148 if (!appData.clockMode) return;
13149 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13153 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13155 /* Fudge if we woke up a little too soon */
13156 fudge = intendedTickLength - lastTickLength;
13157 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13159 if (WhiteOnMove(forwardMostMove)) {
13160 if(whiteNPS >= 0) lastTickLength = 0;
13161 timeRemaining = whiteTimeRemaining -= lastTickLength;
13162 DisplayWhiteClock(whiteTimeRemaining - fudge,
13163 WhiteOnMove(currentMove));
13165 if(blackNPS >= 0) lastTickLength = 0;
13166 timeRemaining = blackTimeRemaining -= lastTickLength;
13167 DisplayBlackClock(blackTimeRemaining - fudge,
13168 !WhiteOnMove(currentMove));
13171 if (CheckFlags()) return;
13174 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13175 StartClockTimer(intendedTickLength);
13177 /* if the time remaining has fallen below the alarm threshold, sound the
13178 * alarm. if the alarm has sounded and (due to a takeback or time control
13179 * with increment) the time remaining has increased to a level above the
13180 * threshold, reset the alarm so it can sound again.
13183 if (appData.icsActive && appData.icsAlarm) {
13185 /* make sure we are dealing with the user's clock */
13186 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13187 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13190 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13191 alarmSounded = FALSE;
13192 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13194 alarmSounded = TRUE;
13200 /* A player has just moved, so stop the previously running
13201 clock and (if in clock mode) start the other one.
13202 We redisplay both clocks in case we're in ICS mode, because
13203 ICS gives us an update to both clocks after every move.
13204 Note that this routine is called *after* forwardMostMove
13205 is updated, so the last fractional tick must be subtracted
13206 from the color that is *not* on move now.
13211 long lastTickLength;
13213 int flagged = FALSE;
13217 if (StopClockTimer() && appData.clockMode) {
13218 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13219 if (WhiteOnMove(forwardMostMove)) {
13220 if(blackNPS >= 0) lastTickLength = 0;
13221 blackTimeRemaining -= lastTickLength;
13222 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13223 // if(pvInfoList[forwardMostMove-1].time == -1)
13224 pvInfoList[forwardMostMove-1].time = // use GUI time
13225 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13227 if(whiteNPS >= 0) lastTickLength = 0;
13228 whiteTimeRemaining -= lastTickLength;
13229 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13230 // if(pvInfoList[forwardMostMove-1].time == -1)
13231 pvInfoList[forwardMostMove-1].time =
13232 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13234 flagged = CheckFlags();
13236 CheckTimeControl();
13238 if (flagged || !appData.clockMode) return;
13240 switch (gameMode) {
13241 case MachinePlaysBlack:
13242 case MachinePlaysWhite:
13243 case BeginningOfGame:
13244 if (pausing) return;
13248 case PlayFromGameFile:
13257 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13258 whiteTimeRemaining : blackTimeRemaining);
13259 StartClockTimer(intendedTickLength);
13263 /* Stop both clocks */
13267 long lastTickLength;
13270 if (!StopClockTimer()) return;
13271 if (!appData.clockMode) return;
13275 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13276 if (WhiteOnMove(forwardMostMove)) {
13277 if(whiteNPS >= 0) lastTickLength = 0;
13278 whiteTimeRemaining -= lastTickLength;
13279 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13281 if(blackNPS >= 0) lastTickLength = 0;
13282 blackTimeRemaining -= lastTickLength;
13283 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13288 /* Start clock of player on move. Time may have been reset, so
13289 if clock is already running, stop and restart it. */
13293 (void) StopClockTimer(); /* in case it was running already */
13294 DisplayBothClocks();
13295 if (CheckFlags()) return;
13297 if (!appData.clockMode) return;
13298 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13300 GetTimeMark(&tickStartTM);
13301 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13302 whiteTimeRemaining : blackTimeRemaining);
13304 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13305 whiteNPS = blackNPS = -1;
13306 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13307 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13308 whiteNPS = first.nps;
13309 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13310 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13311 blackNPS = first.nps;
13312 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13313 whiteNPS = second.nps;
13314 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13315 blackNPS = second.nps;
13316 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13318 StartClockTimer(intendedTickLength);
13325 long second, minute, hour, day;
13327 static char buf[32];
13329 if (ms > 0 && ms <= 9900) {
13330 /* convert milliseconds to tenths, rounding up */
13331 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13333 sprintf(buf, " %03.1f ", tenths/10.0);
13337 /* convert milliseconds to seconds, rounding up */
13338 /* use floating point to avoid strangeness of integer division
13339 with negative dividends on many machines */
13340 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13347 day = second / (60 * 60 * 24);
13348 second = second % (60 * 60 * 24);
13349 hour = second / (60 * 60);
13350 second = second % (60 * 60);
13351 minute = second / 60;
13352 second = second % 60;
13355 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13356 sign, day, hour, minute, second);
13358 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13360 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13367 * This is necessary because some C libraries aren't ANSI C compliant yet.
13370 StrStr(string, match)
13371 char *string, *match;
13375 length = strlen(match);
13377 for (i = strlen(string) - length; i >= 0; i--, string++)
13378 if (!strncmp(match, string, length))
13385 StrCaseStr(string, match)
13386 char *string, *match;
13390 length = strlen(match);
13392 for (i = strlen(string) - length; i >= 0; i--, string++) {
13393 for (j = 0; j < length; j++) {
13394 if (ToLower(match[j]) != ToLower(string[j]))
13397 if (j == length) return string;
13411 c1 = ToLower(*s1++);
13412 c2 = ToLower(*s2++);
13413 if (c1 > c2) return 1;
13414 if (c1 < c2) return -1;
13415 if (c1 == NULLCHAR) return 0;
13424 return isupper(c) ? tolower(c) : c;
13432 return islower(c) ? toupper(c) : c;
13434 #endif /* !_amigados */
13442 if ((ret = (char *) malloc(strlen(s) + 1))) {
13449 StrSavePtr(s, savePtr)
13450 char *s, **savePtr;
13455 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13456 strcpy(*savePtr, s);
13468 clock = time((time_t *)NULL);
13469 tm = localtime(&clock);
13470 sprintf(buf, "%04d.%02d.%02d",
13471 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13472 return StrSave(buf);
13477 PositionToFEN(move, overrideCastling)
13479 char *overrideCastling;
13481 int i, j, fromX, fromY, toX, toY;
13488 whiteToPlay = (gameMode == EditPosition) ?
13489 !blackPlaysFirst : (move % 2 == 0);
13492 /* Piece placement data */
13493 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13495 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13496 if (boards[move][i][j] == EmptySquare) {
13498 } else { ChessSquare piece = boards[move][i][j];
13499 if (emptycount > 0) {
13500 if(emptycount<10) /* [HGM] can be >= 10 */
13501 *p++ = '0' + emptycount;
13502 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13505 if(PieceToChar(piece) == '+') {
13506 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13508 piece = (ChessSquare)(DEMOTED piece);
13510 *p++ = PieceToChar(piece);
13512 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13513 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13518 if (emptycount > 0) {
13519 if(emptycount<10) /* [HGM] can be >= 10 */
13520 *p++ = '0' + emptycount;
13521 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13528 /* [HGM] print Crazyhouse or Shogi holdings */
13529 if( gameInfo.holdingsWidth ) {
13530 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13532 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13533 piece = boards[move][i][BOARD_WIDTH-1];
13534 if( piece != EmptySquare )
13535 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13536 *p++ = PieceToChar(piece);
13538 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13539 piece = boards[move][BOARD_HEIGHT-i-1][0];
13540 if( piece != EmptySquare )
13541 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13542 *p++ = PieceToChar(piece);
13545 if( q == p ) *p++ = '-';
13551 *p++ = whiteToPlay ? 'w' : 'b';
13554 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13555 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13557 if(nrCastlingRights) {
13559 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13560 /* [HGM] write directly from rights */
13561 if(castlingRights[move][2] >= 0 &&
13562 castlingRights[move][0] >= 0 )
13563 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13564 if(castlingRights[move][2] >= 0 &&
13565 castlingRights[move][1] >= 0 )
13566 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13567 if(castlingRights[move][5] >= 0 &&
13568 castlingRights[move][3] >= 0 )
13569 *p++ = castlingRights[move][3] + AAA;
13570 if(castlingRights[move][5] >= 0 &&
13571 castlingRights[move][4] >= 0 )
13572 *p++ = castlingRights[move][4] + AAA;
13575 /* [HGM] write true castling rights */
13576 if( nrCastlingRights == 6 ) {
13577 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13578 castlingRights[move][2] >= 0 ) *p++ = 'K';
13579 if(castlingRights[move][1] == BOARD_LEFT &&
13580 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13581 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13582 castlingRights[move][5] >= 0 ) *p++ = 'k';
13583 if(castlingRights[move][4] == BOARD_LEFT &&
13584 castlingRights[move][5] >= 0 ) *p++ = 'q';
13587 if (q == p) *p++ = '-'; /* No castling rights */
13591 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13592 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13593 /* En passant target square */
13594 if (move > backwardMostMove) {
13595 fromX = moveList[move - 1][0] - AAA;
13596 fromY = moveList[move - 1][1] - ONE;
13597 toX = moveList[move - 1][2] - AAA;
13598 toY = moveList[move - 1][3] - ONE;
13599 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13600 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13601 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13603 /* 2-square pawn move just happened */
13605 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13616 /* [HGM] find reversible plies */
13617 { int i = 0, j=move;
13619 if (appData.debugMode) { int k;
13620 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13621 for(k=backwardMostMove; k<=forwardMostMove; k++)
13622 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13626 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13627 if( j == backwardMostMove ) i += initialRulePlies;
13628 sprintf(p, "%d ", i);
13629 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13631 /* Fullmove number */
13632 sprintf(p, "%d", (move / 2) + 1);
13634 return StrSave(buf);
13638 ParseFEN(board, blackPlaysFirst, fen)
13640 int *blackPlaysFirst;
13650 /* [HGM] by default clear Crazyhouse holdings, if present */
13651 if(gameInfo.holdingsWidth) {
13652 for(i=0; i<BOARD_HEIGHT; i++) {
13653 board[i][0] = EmptySquare; /* black holdings */
13654 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13655 board[i][1] = (ChessSquare) 0; /* black counts */
13656 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13660 /* Piece placement data */
13661 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13664 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13665 if (*p == '/') p++;
13666 emptycount = gameInfo.boardWidth - j;
13667 while (emptycount--)
13668 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13670 #if(BOARD_SIZE >= 10)
13671 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13672 p++; emptycount=10;
13673 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13674 while (emptycount--)
13675 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13677 } else if (isdigit(*p)) {
13678 emptycount = *p++ - '0';
13679 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13680 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13681 while (emptycount--)
13682 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13683 } else if (*p == '+' || isalpha(*p)) {
13684 if (j >= gameInfo.boardWidth) return FALSE;
13686 piece = CharToPiece(*++p);
13687 if(piece == EmptySquare) return FALSE; /* unknown piece */
13688 piece = (ChessSquare) (PROMOTED piece ); p++;
13689 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13690 } else piece = CharToPiece(*p++);
13692 if(piece==EmptySquare) return FALSE; /* unknown piece */
13693 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13694 piece = (ChessSquare) (PROMOTED piece);
13695 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13698 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13704 while (*p == '/' || *p == ' ') p++;
13706 /* [HGM] look for Crazyhouse holdings here */
13707 while(*p==' ') p++;
13708 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13710 if(*p == '-' ) *p++; /* empty holdings */ else {
13711 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13712 /* if we would allow FEN reading to set board size, we would */
13713 /* have to add holdings and shift the board read so far here */
13714 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13716 if((int) piece >= (int) BlackPawn ) {
13717 i = (int)piece - (int)BlackPawn;
13718 i = PieceToNumber((ChessSquare)i);
13719 if( i >= gameInfo.holdingsSize ) return FALSE;
13720 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13721 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13723 i = (int)piece - (int)WhitePawn;
13724 i = PieceToNumber((ChessSquare)i);
13725 if( i >= gameInfo.holdingsSize ) return FALSE;
13726 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13727 board[i][BOARD_WIDTH-2]++; /* black holdings */
13731 if(*p == ']') *p++;
13734 while(*p == ' ') p++;
13739 *blackPlaysFirst = FALSE;
13742 *blackPlaysFirst = TRUE;
13748 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13749 /* return the extra info in global variiables */
13751 /* set defaults in case FEN is incomplete */
13752 FENepStatus = EP_UNKNOWN;
13753 for(i=0; i<nrCastlingRights; i++ ) {
13754 FENcastlingRights[i] =
13755 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13756 } /* assume possible unless obviously impossible */
13757 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13758 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13759 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13760 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13761 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13762 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13765 while(*p==' ') p++;
13766 if(nrCastlingRights) {
13767 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13768 /* castling indicator present, so default becomes no castlings */
13769 for(i=0; i<nrCastlingRights; i++ ) {
13770 FENcastlingRights[i] = -1;
13773 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13774 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13775 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13776 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13777 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13779 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13780 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13781 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13785 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13786 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13787 FENcastlingRights[2] = whiteKingFile;
13790 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13791 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13792 FENcastlingRights[2] = whiteKingFile;
13795 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13796 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13797 FENcastlingRights[5] = blackKingFile;
13800 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13801 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13802 FENcastlingRights[5] = blackKingFile;
13805 default: /* FRC castlings */
13806 if(c >= 'a') { /* black rights */
13807 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13808 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13809 if(i == BOARD_RGHT) break;
13810 FENcastlingRights[5] = i;
13812 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13813 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13815 FENcastlingRights[3] = c;
13817 FENcastlingRights[4] = c;
13818 } else { /* white rights */
13819 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13820 if(board[0][i] == WhiteKing) break;
13821 if(i == BOARD_RGHT) break;
13822 FENcastlingRights[2] = i;
13823 c -= AAA - 'a' + 'A';
13824 if(board[0][c] >= WhiteKing) break;
13826 FENcastlingRights[0] = c;
13828 FENcastlingRights[1] = c;
13832 if (appData.debugMode) {
13833 fprintf(debugFP, "FEN castling rights:");
13834 for(i=0; i<nrCastlingRights; i++)
13835 fprintf(debugFP, " %d", FENcastlingRights[i]);
13836 fprintf(debugFP, "\n");
13839 while(*p==' ') p++;
13842 /* read e.p. field in games that know e.p. capture */
13843 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13844 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13846 p++; FENepStatus = EP_NONE;
13848 char c = *p++ - AAA;
13850 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13851 if(*p >= '0' && *p <='9') *p++;
13857 if(sscanf(p, "%d", &i) == 1) {
13858 FENrulePlies = i; /* 50-move ply counter */
13859 /* (The move number is still ignored) */
13866 EditPositionPasteFEN(char *fen)
13869 Board initial_position;
13871 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13872 DisplayError(_("Bad FEN position in clipboard"), 0);
13875 int savedBlackPlaysFirst = blackPlaysFirst;
13876 EditPositionEvent();
13877 blackPlaysFirst = savedBlackPlaysFirst;
13878 CopyBoard(boards[0], initial_position);
13879 /* [HGM] copy FEN attributes as well */
13881 initialRulePlies = FENrulePlies;
13882 epStatus[0] = FENepStatus;
13883 for( i=0; i<nrCastlingRights; i++ )
13884 castlingRights[0][i] = FENcastlingRights[i];
13886 EditPositionDone();
13887 DisplayBothClocks();
13888 DrawPosition(FALSE, boards[currentMove]);