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 HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
473 ChessSquare FIDEArray[2][BOARD_SIZE] = {
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477 BlackKing, BlackBishop, BlackKnight, BlackRook }
480 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackKing, BlackKnight, BlackRook }
487 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
488 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490 { BlackRook, BlackMan, BlackBishop, BlackQueen,
491 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
494 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
495 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
496 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
498 BlackKing, BlackBishop, BlackKnight, BlackRook }
501 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
510 ChessSquare ShogiArray[2][BOARD_SIZE] = {
511 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
512 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
513 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
514 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
517 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
518 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
519 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
521 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
524 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
525 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
526 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
527 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
528 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
531 ChessSquare GreatArray[2][BOARD_SIZE] = {
532 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
533 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
534 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
535 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
538 ChessSquare JanusArray[2][BOARD_SIZE] = {
539 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
540 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
541 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
542 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
546 ChessSquare GothicArray[2][BOARD_SIZE] = {
547 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
548 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
550 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
553 #define GothicArray CapablancaArray
557 ChessSquare FalconArray[2][BOARD_SIZE] = {
558 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
559 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
561 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
564 #define FalconArray CapablancaArray
567 #else // !(BOARD_SIZE>=10)
568 #define XiangqiPosition FIDEArray
569 #define CapablancaArray FIDEArray
570 #define GothicArray FIDEArray
571 #define GreatArray FIDEArray
572 #endif // !(BOARD_SIZE>=10)
575 ChessSquare CourierArray[2][BOARD_SIZE] = {
576 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
577 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
579 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 #else // !(BOARD_SIZE>=12)
582 #define CourierArray CapablancaArray
583 #endif // !(BOARD_SIZE>=12)
586 Board initialPosition;
589 /* Convert str to a rating. Checks for special cases of "----",
591 "++++", etc. Also strips ()'s */
593 string_to_rating(str)
596 while(*str && !isdigit(*str)) ++str;
598 return 0; /* One of the special "no rating" cases */
606 /* Init programStats */
607 programStats.movelist[0] = 0;
608 programStats.depth = 0;
609 programStats.nr_moves = 0;
610 programStats.moves_left = 0;
611 programStats.nodes = 0;
612 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
613 programStats.score = 0;
614 programStats.got_only_move = 0;
615 programStats.got_fail = 0;
616 programStats.line_is_book = 0;
622 int matched, min, sec;
624 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626 GetTimeMark(&programStartTime);
627 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
630 programStats.ok_to_send = 1;
631 programStats.seen_stat = 0;
634 * Initialize game list
640 * Internet chess server status
642 if (appData.icsActive) {
643 appData.matchMode = FALSE;
644 appData.matchGames = 0;
646 appData.noChessProgram = !appData.zippyPlay;
648 appData.zippyPlay = FALSE;
649 appData.zippyTalk = FALSE;
650 appData.noChessProgram = TRUE;
652 if (*appData.icsHelper != NULLCHAR) {
653 appData.useTelnet = TRUE;
654 appData.telnetProgram = appData.icsHelper;
657 appData.zippyTalk = appData.zippyPlay = FALSE;
660 /* [AS] Initialize pv info list [HGM] and game state */
664 for( i=0; i<MAX_MOVES; i++ ) {
665 pvInfoList[i].depth = -1;
667 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
672 * Parse timeControl resource
674 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
675 appData.movesPerSession)) {
677 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
678 DisplayFatalError(buf, 0, 2);
682 * Parse searchTime resource
684 if (*appData.searchTime != NULLCHAR) {
685 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687 searchTime = min * 60;
688 } else if (matched == 2) {
689 searchTime = min * 60 + sec;
692 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
693 DisplayFatalError(buf, 0, 2);
697 /* [AS] Adjudication threshold */
698 adjudicateLossThreshold = appData.adjudicateLossThreshold;
700 first.which = "first";
701 second.which = "second";
702 first.maybeThinking = second.maybeThinking = FALSE;
703 first.pr = second.pr = NoProc;
704 first.isr = second.isr = NULL;
705 first.sendTime = second.sendTime = 2;
706 first.sendDrawOffers = 1;
707 if (appData.firstPlaysBlack) {
708 first.twoMachinesColor = "black\n";
709 second.twoMachinesColor = "white\n";
711 first.twoMachinesColor = "white\n";
712 second.twoMachinesColor = "black\n";
714 first.program = appData.firstChessProgram;
715 second.program = appData.secondChessProgram;
716 first.host = appData.firstHost;
717 second.host = appData.secondHost;
718 first.dir = appData.firstDirectory;
719 second.dir = appData.secondDirectory;
720 first.other = &second;
721 second.other = &first;
722 first.initString = appData.initString;
723 second.initString = appData.secondInitString;
724 first.computerString = appData.firstComputerString;
725 second.computerString = appData.secondComputerString;
726 first.useSigint = second.useSigint = TRUE;
727 first.useSigterm = second.useSigterm = TRUE;
728 first.reuse = appData.reuseFirst;
729 second.reuse = appData.reuseSecond;
730 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
731 second.nps = appData.secondNPS;
732 first.useSetboard = second.useSetboard = FALSE;
733 first.useSAN = second.useSAN = FALSE;
734 first.usePing = second.usePing = FALSE;
735 first.lastPing = second.lastPing = 0;
736 first.lastPong = second.lastPong = 0;
737 first.usePlayother = second.usePlayother = FALSE;
738 first.useColors = second.useColors = TRUE;
739 first.useUsermove = second.useUsermove = FALSE;
740 first.sendICS = second.sendICS = FALSE;
741 first.sendName = second.sendName = appData.icsActive;
742 first.sdKludge = second.sdKludge = FALSE;
743 first.stKludge = second.stKludge = FALSE;
744 TidyProgramName(first.program, first.host, first.tidy);
745 TidyProgramName(second.program, second.host, second.tidy);
746 first.matchWins = second.matchWins = 0;
747 strcpy(first.variants, appData.variant);
748 strcpy(second.variants, appData.variant);
749 first.analysisSupport = second.analysisSupport = 2; /* detect */
750 first.analyzing = second.analyzing = FALSE;
751 first.initDone = second.initDone = FALSE;
753 /* New features added by Tord: */
754 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
755 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
756 /* End of new features added by Tord. */
757 first.fenOverride = appData.fenOverride1;
758 second.fenOverride = appData.fenOverride2;
760 /* [HGM] time odds: set factor for each machine */
761 first.timeOdds = appData.firstTimeOdds;
762 second.timeOdds = appData.secondTimeOdds;
764 if(appData.timeOddsMode) {
765 norm = first.timeOdds;
766 if(norm > second.timeOdds) norm = second.timeOdds;
768 first.timeOdds /= norm;
769 second.timeOdds /= norm;
772 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
773 first.accumulateTC = appData.firstAccumulateTC;
774 second.accumulateTC = appData.secondAccumulateTC;
775 first.maxNrOfSessions = second.maxNrOfSessions = 1;
778 first.debug = second.debug = FALSE;
779 first.supportsNPS = second.supportsNPS = UNKNOWN;
782 first.optionSettings = appData.firstOptions;
783 second.optionSettings = appData.secondOptions;
785 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
786 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
787 first.isUCI = appData.firstIsUCI; /* [AS] */
788 second.isUCI = appData.secondIsUCI; /* [AS] */
789 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
790 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792 if (appData.firstProtocolVersion > PROTOVER ||
793 appData.firstProtocolVersion < 1) {
795 sprintf(buf, _("protocol version %d not supported"),
796 appData.firstProtocolVersion);
797 DisplayFatalError(buf, 0, 2);
799 first.protocolVersion = appData.firstProtocolVersion;
802 if (appData.secondProtocolVersion > PROTOVER ||
803 appData.secondProtocolVersion < 1) {
805 sprintf(buf, _("protocol version %d not supported"),
806 appData.secondProtocolVersion);
807 DisplayFatalError(buf, 0, 2);
809 second.protocolVersion = appData.secondProtocolVersion;
812 if (appData.icsActive) {
813 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
814 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
815 appData.clockMode = FALSE;
816 first.sendTime = second.sendTime = 0;
820 /* Override some settings from environment variables, for backward
821 compatibility. Unfortunately it's not feasible to have the env
822 vars just set defaults, at least in xboard. Ugh.
824 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
829 if (appData.noChessProgram) {
830 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
831 sprintf(programVersion, "%s", PACKAGE_STRING);
836 while (*q != ' ' && *q != NULLCHAR) q++;
838 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
839 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
840 sprintf(programVersion, "%s + ", PACKAGE_STRING);
841 strncat(programVersion, p, q - p);
843 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
844 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
845 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
849 if (!appData.icsActive) {
851 /* Check for variants that are supported only in ICS mode,
852 or not at all. Some that are accepted here nevertheless
853 have bugs; see comments below.
855 VariantClass variant = StringToVariant(appData.variant);
857 case VariantBughouse: /* need four players and two boards */
858 case VariantKriegspiel: /* need to hide pieces and move details */
859 /* case VariantFischeRandom: (Fabien: moved below) */
860 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861 DisplayFatalError(buf, 0, 2);
865 case VariantLoadable:
875 sprintf(buf, _("Unknown variant name %s"), appData.variant);
876 DisplayFatalError(buf, 0, 2);
879 case VariantXiangqi: /* [HGM] repetition rules not implemented */
880 case VariantFairy: /* [HGM] TestLegality definitely off! */
881 case VariantGothic: /* [HGM] should work */
882 case VariantCapablanca: /* [HGM] should work */
883 case VariantCourier: /* [HGM] initial forced moves not implemented */
884 case VariantShogi: /* [HGM] drops not tested for legality */
885 case VariantKnightmate: /* [HGM] should work */
886 case VariantCylinder: /* [HGM] untested */
887 case VariantFalcon: /* [HGM] untested */
888 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889 offboard interposition not understood */
890 case VariantNormal: /* definitely works! */
891 case VariantWildCastle: /* pieces not automatically shuffled */
892 case VariantNoCastle: /* pieces not automatically shuffled */
893 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894 case VariantLosers: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantSuicide: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantGiveaway: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantTwoKings: /* should work */
901 case VariantAtomic: /* should work except for win condition */
902 case Variant3Check: /* should work except for win condition */
903 case VariantShatranj: /* should work except for all win conditions */
904 case VariantBerolina: /* might work if TestLegality is off */
905 case VariantCapaRandom: /* should work */
906 case VariantJanus: /* should work */
907 case VariantSuper: /* experimental */
908 case VariantGreat: /* experimental, requires legality testing to be off */
913 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
914 InitEngineUCI( installDir, &second );
917 int NextIntegerFromString( char ** str, long * value )
922 while( *s == ' ' || *s == '\t' ) {
928 if( *s >= '0' && *s <= '9' ) {
929 while( *s >= '0' && *s <= '9' ) {
930 *value = *value * 10 + (*s - '0');
942 int NextTimeControlFromString( char ** str, long * value )
945 int result = NextIntegerFromString( str, &temp );
948 *value = temp * 60; /* Minutes */
951 result = NextIntegerFromString( str, &temp );
952 *value += temp; /* Seconds */
959 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
960 { /* [HGM] routine added to read '+moves/time' for secondary time control */
961 int result = -1; long temp, temp2;
963 if(**str != '+') return -1; // old params remain in force!
965 if( NextTimeControlFromString( str, &temp ) ) return -1;
968 /* time only: incremental or sudden-death time control */
969 if(**str == '+') { /* increment follows; read it */
971 if(result = NextIntegerFromString( str, &temp2)) return -1;
974 *moves = 0; *tc = temp * 1000;
976 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
978 (*str)++; /* classical time control */
979 result = NextTimeControlFromString( str, &temp2);
988 int GetTimeQuota(int movenr)
989 { /* [HGM] get time to add from the multi-session time-control string */
990 int moves=1; /* kludge to force reading of first session */
991 long time, increment;
992 char *s = fullTimeControlString;
994 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
997 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
998 if(movenr == -1) return time; /* last move before new session */
999 if(!moves) return increment; /* current session is incremental */
1000 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1001 } while(movenr >= -1); /* try again for next session */
1003 return 0; // no new time quota on this move
1007 ParseTimeControl(tc, ti, mps)
1013 int matched, min, sec;
1015 matched = sscanf(tc, "%d:%d", &min, &sec);
1017 timeControl = min * 60 * 1000;
1018 } else if (matched == 2) {
1019 timeControl = (min * 60 + sec) * 1000;
1028 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1031 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1032 else sprintf(buf, "+%s+%d", tc, ti);
1035 sprintf(buf, "+%d/%s", mps, tc);
1036 else sprintf(buf, "+%s", tc);
1038 fullTimeControlString = StrSave(buf);
1040 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1045 /* Parse second time control */
1048 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1056 timeControl_2 = tc2 * 1000;
1066 timeControl = tc1 * 1000;
1070 timeIncrement = ti * 1000; /* convert to ms */
1071 movesPerSession = 0;
1074 movesPerSession = mps;
1082 if (appData.debugMode) {
1083 fprintf(debugFP, "%s\n", programVersion);
1086 if (appData.matchGames > 0) {
1087 appData.matchMode = TRUE;
1088 } else if (appData.matchMode) {
1089 appData.matchGames = 1;
1091 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1092 appData.matchGames = appData.sameColorGames;
1093 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1094 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1095 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1098 if (appData.noChessProgram || first.protocolVersion == 1) {
1101 /* kludge: allow timeout for initial "feature" commands */
1103 DisplayMessage("", _("Starting chess program"));
1104 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1109 InitBackEnd3 P((void))
1111 GameMode initialMode;
1115 InitChessProgram(&first, startedFromSetupPosition);
1118 if (appData.icsActive) {
1120 /* [DM] Make a console window if needed [HGM] merged ifs */
1125 if (*appData.icsCommPort != NULLCHAR) {
1126 sprintf(buf, _("Could not open comm port %s"),
1127 appData.icsCommPort);
1129 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1130 appData.icsHost, appData.icsPort);
1132 DisplayFatalError(buf, err, 1);
1137 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1140 } else if (appData.noChessProgram) {
1146 if (*appData.cmailGameName != NULLCHAR) {
1148 OpenLoopback(&cmailPR);
1150 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1154 DisplayMessage("", "");
1155 if (StrCaseCmp(appData.initialMode, "") == 0) {
1156 initialMode = BeginningOfGame;
1157 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158 initialMode = TwoMachinesPlay;
1159 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160 initialMode = AnalyzeFile;
1161 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162 initialMode = AnalyzeMode;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164 initialMode = MachinePlaysWhite;
1165 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166 initialMode = MachinePlaysBlack;
1167 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168 initialMode = EditGame;
1169 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170 initialMode = EditPosition;
1171 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172 initialMode = Training;
1174 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175 DisplayFatalError(buf, 0, 2);
1179 if (appData.matchMode) {
1180 /* Set up machine vs. machine match */
1181 if (appData.noChessProgram) {
1182 DisplayFatalError(_("Can't have a match with no chess programs"),
1188 if (*appData.loadGameFile != NULLCHAR) {
1189 int index = appData.loadGameIndex; // [HGM] autoinc
1190 if(index<0) lastIndex = index = 1;
1191 if (!LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameFile, FALSE)) {
1194 DisplayFatalError(_("Bad game file"), 0, 1);
1197 } else if (*appData.loadPositionFile != NULLCHAR) {
1198 int index = appData.loadPositionIndex; // [HGM] autoinc
1199 if(index<0) lastIndex = index = 1;
1200 if (!LoadPositionFromFile(appData.loadPositionFile,
1202 appData.loadPositionFile)) {
1203 DisplayFatalError(_("Bad position file"), 0, 1);
1208 } else if (*appData.cmailGameName != NULLCHAR) {
1209 /* Set up cmail mode */
1210 ReloadCmailMsgEvent(TRUE);
1212 /* Set up other modes */
1213 if (initialMode == AnalyzeFile) {
1214 if (*appData.loadGameFile == NULLCHAR) {
1215 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1219 if (*appData.loadGameFile != NULLCHAR) {
1220 (void) LoadGameFromFile(appData.loadGameFile,
1221 appData.loadGameIndex,
1222 appData.loadGameFile, TRUE);
1223 } else if (*appData.loadPositionFile != NULLCHAR) {
1224 (void) LoadPositionFromFile(appData.loadPositionFile,
1225 appData.loadPositionIndex,
1226 appData.loadPositionFile);
1227 /* [HGM] try to make self-starting even after FEN load */
1228 /* to allow automatic setup of fairy variants with wtm */
1229 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230 gameMode = BeginningOfGame;
1231 setboardSpoiledMachineBlack = 1;
1233 /* [HGM] loadPos: make that every new game uses the setup */
1234 /* from file as long as we do not switch variant */
1235 if(!blackPlaysFirst) { int i;
1236 startedFromPositionFile = TRUE;
1237 CopyBoard(filePosition, boards[0]);
1238 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1241 if (initialMode == AnalyzeMode) {
1242 if (appData.noChessProgram) {
1243 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1251 } else if (initialMode == AnalyzeFile) {
1252 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253 ShowThinkingEvent();
1255 AnalysisPeriodicEvent(1);
1256 } else if (initialMode == MachinePlaysWhite) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1267 MachineWhiteEvent();
1268 } else if (initialMode == MachinePlaysBlack) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1279 MachineBlackEvent();
1280 } else if (initialMode == TwoMachinesPlay) {
1281 if (appData.noChessProgram) {
1282 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1286 if (appData.icsActive) {
1287 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1292 } else if (initialMode == EditGame) {
1294 } else if (initialMode == EditPosition) {
1295 EditPositionEvent();
1296 } else if (initialMode == Training) {
1297 if (*appData.loadGameFile == NULLCHAR) {
1298 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1307 * Establish will establish a contact to a remote host.port.
1308 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309 * used to talk to the host.
1310 * Returns 0 if okay, error code if not.
1317 if (*appData.icsCommPort != NULLCHAR) {
1318 /* Talk to the host through a serial comm port */
1319 return OpenCommPort(appData.icsCommPort, &icsPR);
1321 } else if (*appData.gateway != NULLCHAR) {
1322 if (*appData.remoteShell == NULLCHAR) {
1323 /* Use the rcmd protocol to run telnet program on a gateway host */
1324 snprintf(buf, sizeof(buf), "%s %s %s",
1325 appData.telnetProgram, appData.icsHost, appData.icsPort);
1326 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1329 /* Use the rsh program to run telnet program on a gateway host */
1330 if (*appData.remoteUser == NULLCHAR) {
1331 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332 appData.gateway, appData.telnetProgram,
1333 appData.icsHost, appData.icsPort);
1335 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336 appData.remoteShell, appData.gateway,
1337 appData.remoteUser, appData.telnetProgram,
1338 appData.icsHost, appData.icsPort);
1340 return StartChildProcess(buf, "", &icsPR);
1343 } else if (appData.useTelnet) {
1344 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1347 /* TCP socket interface differs somewhat between
1348 Unix and NT; handle details in the front end.
1350 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1355 show_bytes(fp, buf, count)
1361 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362 fprintf(fp, "\\%03o", *buf & 0xff);
1371 /* Returns an errno value */
1373 OutputMaybeTelnet(pr, message, count, outError)
1379 char buf[8192], *p, *q, *buflim;
1380 int left, newcount, outcount;
1382 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383 *appData.gateway != NULLCHAR) {
1384 if (appData.debugMode) {
1385 fprintf(debugFP, ">ICS: ");
1386 show_bytes(debugFP, message, count);
1387 fprintf(debugFP, "\n");
1389 return OutputToProcess(pr, message, count, outError);
1392 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1399 if (appData.debugMode) {
1400 fprintf(debugFP, ">ICS: ");
1401 show_bytes(debugFP, buf, newcount);
1402 fprintf(debugFP, "\n");
1404 outcount = OutputToProcess(pr, buf, newcount, outError);
1405 if (outcount < newcount) return -1; /* to be sure */
1412 } else if (((unsigned char) *p) == TN_IAC) {
1413 *q++ = (char) TN_IAC;
1420 if (appData.debugMode) {
1421 fprintf(debugFP, ">ICS: ");
1422 show_bytes(debugFP, buf, newcount);
1423 fprintf(debugFP, "\n");
1425 outcount = OutputToProcess(pr, buf, newcount, outError);
1426 if (outcount < newcount) return -1; /* to be sure */
1431 read_from_player(isr, closure, message, count, error)
1438 int outError, outCount;
1439 static int gotEof = 0;
1441 /* Pass data read from player on to ICS */
1444 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445 if (outCount < count) {
1446 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448 } else if (count < 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451 } else if (gotEof++ > 0) {
1452 RemoveInputSource(isr);
1453 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460 SendToICS("date\n");
1461 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1468 int count, outCount, outError;
1470 if (icsPR == NULL) return;
1473 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1474 if (outCount < count) {
1475 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479 /* This is used for sending logon scripts to the ICS. Sending
1480 without a delay causes problems when using timestamp on ICC
1481 (at least on my machine). */
1483 SendToICSDelayed(s,msdelay)
1487 int count, outCount, outError;
1489 if (icsPR == NULL) return;
1492 if (appData.debugMode) {
1493 fprintf(debugFP, ">ICS: ");
1494 show_bytes(debugFP, s, count);
1495 fprintf(debugFP, "\n");
1497 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499 if (outCount < count) {
1500 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505 /* Remove all highlighting escape sequences in s
1506 Also deletes any suffix starting with '('
1509 StripHighlightAndTitle(s)
1512 static char retbuf[MSG_SIZ];
1515 while (*s != NULLCHAR) {
1516 while (*s == '\033') {
1517 while (*s != NULLCHAR && !isalpha(*s)) s++;
1518 if (*s != NULLCHAR) s++;
1520 while (*s != NULLCHAR && *s != '\033') {
1521 if (*s == '(' || *s == '[') {
1532 /* Remove all highlighting escape sequences in s */
1537 static char retbuf[MSG_SIZ];
1540 while (*s != NULLCHAR) {
1541 while (*s == '\033') {
1542 while (*s != NULLCHAR && !isalpha(*s)) s++;
1543 if (*s != NULLCHAR) s++;
1545 while (*s != NULLCHAR && *s != '\033') {
1553 char *variantNames[] = VARIANT_NAMES;
1558 return variantNames[v];
1562 /* Identify a variant from the strings the chess servers use or the
1563 PGN Variant tag names we use. */
1570 VariantClass v = VariantNormal;
1571 int i, found = FALSE;
1576 /* [HGM] skip over optional board-size prefixes */
1577 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1578 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1579 while( *e++ != '_');
1582 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583 if (StrCaseStr(e, variantNames[i])) {
1584 v = (VariantClass) i;
1591 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592 || StrCaseStr(e, "wild/fr")
1593 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594 v = VariantFischeRandom;
1595 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596 (i = 1, p = StrCaseStr(e, "w"))) {
1598 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1605 case 0: /* FICS only, actually */
1607 /* Castling legal even if K starts on d-file */
1608 v = VariantWildCastle;
1613 /* Castling illegal even if K & R happen to start in
1614 normal positions. */
1615 v = VariantNoCastle;
1628 /* Castling legal iff K & R start in normal positions */
1634 /* Special wilds for position setup; unclear what to do here */
1635 v = VariantLoadable;
1638 /* Bizarre ICC game */
1639 v = VariantTwoKings;
1642 v = VariantKriegspiel;
1648 v = VariantFischeRandom;
1651 v = VariantCrazyhouse;
1654 v = VariantBughouse;
1660 /* Not quite the same as FICS suicide! */
1661 v = VariantGiveaway;
1667 v = VariantShatranj;
1670 /* Temporary names for future ICC types. The name *will* change in
1671 the next xboard/WinBoard release after ICC defines it. */
1709 v = VariantCapablanca;
1712 v = VariantKnightmate;
1718 v = VariantCylinder;
1724 v = VariantCapaRandom;
1727 v = VariantBerolina;
1739 /* Found "wild" or "w" in the string but no number;
1740 must assume it's normal chess. */
1744 sprintf(buf, _("Unknown wild type %d"), wnum);
1745 DisplayError(buf, 0);
1751 if (appData.debugMode) {
1752 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753 e, wnum, VariantName(v));
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762 advance *index beyond it, and set leftover_start to the new value of
1763 *index; else return FALSE. If pattern contains the character '*', it
1764 matches any sequence of characters not containing '\r', '\n', or the
1765 character following the '*' (if any), and the matched sequence(s) are
1766 copied into star_match.
1769 looking_at(buf, index, pattern)
1774 char *bufp = &buf[*index], *patternp = pattern;
1776 char *matchp = star_match[0];
1779 if (*patternp == NULLCHAR) {
1780 *index = leftover_start = bufp - buf;
1784 if (*bufp == NULLCHAR) return FALSE;
1785 if (*patternp == '*') {
1786 if (*bufp == *(patternp + 1)) {
1788 matchp = star_match[++star_count];
1792 } else if (*bufp == '\n' || *bufp == '\r') {
1794 if (*patternp == NULLCHAR)
1799 *matchp++ = *bufp++;
1803 if (*patternp != *bufp) return FALSE;
1810 SendToPlayer(data, length)
1814 int error, outCount;
1815 outCount = OutputToProcess(NoProc, data, length, &error);
1816 if (outCount < length) {
1817 DisplayFatalError(_("Error writing to display"), error, 1);
1822 PackHolding(packed, holding)
1834 switch (runlength) {
1845 sprintf(q, "%d", runlength);
1857 /* Telnet protocol requests from the front end */
1859 TelnetRequest(ddww, option)
1860 unsigned char ddww, option;
1862 unsigned char msg[3];
1863 int outCount, outError;
1865 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867 if (appData.debugMode) {
1868 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1884 sprintf(buf1, "%d", ddww);
1893 sprintf(buf2, "%d", option);
1896 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1901 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910 if (!appData.icsActive) return;
1911 TelnetRequest(TN_DO, TN_ECHO);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DONT, TN_ECHO);
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 /* put the holdings sent to us by the server on the board holdings area */
1925 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1929 if(gameInfo.holdingsWidth < 2) return;
1931 if( (int)lowestPiece >= BlackPawn ) {
1934 holdingsStartRow = BOARD_HEIGHT-1;
1937 holdingsColumn = BOARD_WIDTH-1;
1938 countsColumn = BOARD_WIDTH-2;
1939 holdingsStartRow = 0;
1943 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1944 board[i][holdingsColumn] = EmptySquare;
1945 board[i][countsColumn] = (ChessSquare) 0;
1947 while( (p=*holdings++) != NULLCHAR ) {
1948 piece = CharToPiece( ToUpper(p) );
1949 if(piece == EmptySquare) continue;
1950 /*j = (int) piece - (int) WhitePawn;*/
1951 j = PieceToNumber(piece);
1952 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1953 if(j < 0) continue; /* should not happen */
1954 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1955 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1956 board[holdingsStartRow+j*direction][countsColumn]++;
1963 VariantSwitch(Board board, VariantClass newVariant)
1965 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1966 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1967 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969 startedFromPositionFile = FALSE;
1970 if(gameInfo.variant == newVariant) return;
1972 /* [HGM] This routine is called each time an assignment is made to
1973 * gameInfo.variant during a game, to make sure the board sizes
1974 * are set to match the new variant. If that means adding or deleting
1975 * holdings, we shift the playing board accordingly
1976 * This kludge is needed because in ICS observe mode, we get boards
1977 * of an ongoing game without knowing the variant, and learn about the
1978 * latter only later. This can be because of the move list we requested,
1979 * in which case the game history is refilled from the beginning anyway,
1980 * but also when receiving holdings of a crazyhouse game. In the latter
1981 * case we want to add those holdings to the already received position.
1985 if (appData.debugMode) {
1986 fprintf(debugFP, "Switch board from %s to %s\n",
1987 VariantName(gameInfo.variant), VariantName(newVariant));
1988 setbuf(debugFP, NULL);
1990 shuffleOpenings = 0; /* [HGM] shuffle */
1991 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1992 switch(newVariant) {
1994 newWidth = 9; newHeight = 9;
1995 gameInfo.holdingsSize = 7;
1996 case VariantBughouse:
1997 case VariantCrazyhouse:
1998 newHoldingsWidth = 2; break;
2000 newHoldingsWidth = gameInfo.holdingsSize = 0;
2003 if(newWidth != gameInfo.boardWidth ||
2004 newHeight != gameInfo.boardHeight ||
2005 newHoldingsWidth != gameInfo.holdingsWidth ) {
2007 /* shift position to new playing area, if needed */
2008 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013 for(i=0; i<newHeight; i++) {
2014 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2015 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2018 for(i=0; i<BOARD_HEIGHT; i++)
2019 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2020 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024 gameInfo.boardWidth = newWidth;
2025 gameInfo.boardHeight = newHeight;
2026 gameInfo.holdingsWidth = newHoldingsWidth;
2027 gameInfo.variant = newVariant;
2028 InitDrawingSizes(-2, 0);
2030 /* [HGM] The following should definitely be solved in a better way */
2032 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2033 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2034 saveEP = epStatus[0];
2036 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2038 epStatus[0] = saveEP;
2039 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2040 CopyBoard(tempBoard, board); /* restore position received from ICS */
2042 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044 forwardMostMove = oldForwardMostMove;
2045 backwardMostMove = oldBackwardMostMove;
2046 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2049 static int loggedOn = FALSE;
2051 /*-- Game start info cache: --*/
2053 char gs_kind[MSG_SIZ];
2054 static char player1Name[128] = "";
2055 static char player2Name[128] = "";
2056 static int player1Rating = -1;
2057 static int player2Rating = -1;
2058 /*----------------------------*/
2060 ColorClass curColor = ColorNormal;
2061 int suppressKibitz = 0;
2064 read_from_ics(isr, closure, data, count, error)
2071 #define BUF_SIZE 8192
2072 #define STARTED_NONE 0
2073 #define STARTED_MOVES 1
2074 #define STARTED_BOARD 2
2075 #define STARTED_OBSERVE 3
2076 #define STARTED_HOLDINGS 4
2077 #define STARTED_CHATTER 5
2078 #define STARTED_COMMENT 6
2079 #define STARTED_MOVES_NOHIDE 7
2081 static int started = STARTED_NONE;
2082 static char parse[20000];
2083 static int parse_pos = 0;
2084 static char buf[BUF_SIZE + 1];
2085 static int firstTime = TRUE, intfSet = FALSE;
2086 static ColorClass prevColor = ColorNormal;
2087 static int savingComment = FALSE;
2093 int backup; /* [DM] For zippy color lines */
2095 char talker[MSG_SIZ]; // [HGM] chat
2098 if (appData.debugMode) {
2100 fprintf(debugFP, "<ICS: ");
2101 show_bytes(debugFP, data, count);
2102 fprintf(debugFP, "\n");
2106 if (appData.debugMode) { int f = forwardMostMove;
2107 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2108 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2111 /* If last read ended with a partial line that we couldn't parse,
2112 prepend it to the new read and try again. */
2113 if (leftover_len > 0) {
2114 for (i=0; i<leftover_len; i++)
2115 buf[i] = buf[leftover_start + i];
2118 /* Copy in new characters, removing nulls and \r's */
2119 buf_len = leftover_len;
2120 for (i = 0; i < count; i++) {
2121 if (data[i] != NULLCHAR && data[i] != '\r')
2122 buf[buf_len++] = data[i];
2123 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2124 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2125 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2126 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2130 buf[buf_len] = NULLCHAR;
2131 next_out = leftover_len;
2135 while (i < buf_len) {
2136 /* Deal with part of the TELNET option negotiation
2137 protocol. We refuse to do anything beyond the
2138 defaults, except that we allow the WILL ECHO option,
2139 which ICS uses to turn off password echoing when we are
2140 directly connected to it. We reject this option
2141 if localLineEditing mode is on (always on in xboard)
2142 and we are talking to port 23, which might be a real
2143 telnet server that will try to keep WILL ECHO on permanently.
2145 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2146 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2147 unsigned char option;
2149 switch ((unsigned char) buf[++i]) {
2151 if (appData.debugMode)
2152 fprintf(debugFP, "\n<WILL ");
2153 switch (option = (unsigned char) buf[++i]) {
2155 if (appData.debugMode)
2156 fprintf(debugFP, "ECHO ");
2157 /* Reply only if this is a change, according
2158 to the protocol rules. */
2159 if (remoteEchoOption) break;
2160 if (appData.localLineEditing &&
2161 atoi(appData.icsPort) == TN_PORT) {
2162 TelnetRequest(TN_DONT, TN_ECHO);
2165 TelnetRequest(TN_DO, TN_ECHO);
2166 remoteEchoOption = TRUE;
2170 if (appData.debugMode)
2171 fprintf(debugFP, "%d ", option);
2172 /* Whatever this is, we don't want it. */
2173 TelnetRequest(TN_DONT, option);
2178 if (appData.debugMode)
2179 fprintf(debugFP, "\n<WONT ");
2180 switch (option = (unsigned char) buf[++i]) {
2182 if (appData.debugMode)
2183 fprintf(debugFP, "ECHO ");
2184 /* Reply only if this is a change, according
2185 to the protocol rules. */
2186 if (!remoteEchoOption) break;
2188 TelnetRequest(TN_DONT, TN_ECHO);
2189 remoteEchoOption = FALSE;
2192 if (appData.debugMode)
2193 fprintf(debugFP, "%d ", (unsigned char) option);
2194 /* Whatever this is, it must already be turned
2195 off, because we never agree to turn on
2196 anything non-default, so according to the
2197 protocol rules, we don't reply. */
2202 if (appData.debugMode)
2203 fprintf(debugFP, "\n<DO ");
2204 switch (option = (unsigned char) buf[++i]) {
2206 /* Whatever this is, we refuse to do it. */
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 TelnetRequest(TN_WONT, option);
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<DONT ");
2216 switch (option = (unsigned char) buf[++i]) {
2218 if (appData.debugMode)
2219 fprintf(debugFP, "%d ", option);
2220 /* Whatever this is, we are already not doing
2221 it, because we never agree to do anything
2222 non-default, so according to the protocol
2223 rules, we don't reply. */
2228 if (appData.debugMode)
2229 fprintf(debugFP, "\n<IAC ");
2230 /* Doubled IAC; pass it through */
2234 if (appData.debugMode)
2235 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2236 /* Drop all other telnet commands on the floor */
2239 if (oldi > next_out)
2240 SendToPlayer(&buf[next_out], oldi - next_out);
2246 /* OK, this at least will *usually* work */
2247 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2251 if (loggedOn && !intfSet) {
2252 if (ics_type == ICS_ICC) {
2254 "/set-quietly interface %s\n/set-quietly style 12\n",
2257 } else if (ics_type == ICS_CHESSNET) {
2258 sprintf(str, "/style 12\n");
2260 strcpy(str, "alias $ @\n$set interface ");
2261 strcat(str, programVersion);
2262 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2264 strcat(str, "$iset nohighlight 1\n");
2266 strcat(str, "$iset lock 1\n$style 12\n");
2272 if (started == STARTED_COMMENT) {
2273 /* Accumulate characters in comment */
2274 parse[parse_pos++] = buf[i];
2275 if (buf[i] == '\n') {
2276 parse[parse_pos] = NULLCHAR;
2277 if(chattingPartner>=0) {
2279 sprintf(mess, "%s%s", talker, parse);
2280 OutputChatMessage(chattingPartner, mess);
2281 chattingPartner = -1;
2283 if(!suppressKibitz) // [HGM] kibitz
2284 AppendComment(forwardMostMove, StripHighlight(parse));
2285 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2286 int nrDigit = 0, nrAlph = 0, i;
2287 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2288 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2289 parse[parse_pos] = NULLCHAR;
2290 // try to be smart: if it does not look like search info, it should go to
2291 // ICS interaction window after all, not to engine-output window.
2292 for(i=0; i<parse_pos; i++) { // count letters and digits
2293 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2294 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2295 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2297 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2298 int depth=0; float score;
2299 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2300 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2301 pvInfoList[forwardMostMove-1].depth = depth;
2302 pvInfoList[forwardMostMove-1].score = 100*score;
2304 OutputKibitz(suppressKibitz, parse);
2307 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2308 SendToPlayer(tmp, strlen(tmp));
2311 started = STARTED_NONE;
2313 /* Don't match patterns against characters in chatter */
2318 if (started == STARTED_CHATTER) {
2319 if (buf[i] != '\n') {
2320 /* Don't match patterns against characters in chatter */
2324 started = STARTED_NONE;
2327 /* Kludge to deal with rcmd protocol */
2328 if (firstTime && looking_at(buf, &i, "\001*")) {
2329 DisplayFatalError(&buf[1], 0, 1);
2335 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2338 if (appData.debugMode)
2339 fprintf(debugFP, "ics_type %d\n", ics_type);
2342 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2343 ics_type = ICS_FICS;
2345 if (appData.debugMode)
2346 fprintf(debugFP, "ics_type %d\n", ics_type);
2349 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2350 ics_type = ICS_CHESSNET;
2352 if (appData.debugMode)
2353 fprintf(debugFP, "ics_type %d\n", ics_type);
2358 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2359 looking_at(buf, &i, "Logging you in as \"*\"") ||
2360 looking_at(buf, &i, "will be \"*\""))) {
2361 strcpy(ics_handle, star_match[0]);
2365 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2367 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2368 DisplayIcsInteractionTitle(buf);
2369 have_set_title = TRUE;
2372 /* skip finger notes */
2373 if (started == STARTED_NONE &&
2374 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2375 (buf[i] == '1' && buf[i+1] == '0')) &&
2376 buf[i+2] == ':' && buf[i+3] == ' ') {
2377 started = STARTED_CHATTER;
2382 /* skip formula vars */
2383 if (started == STARTED_NONE &&
2384 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2385 started = STARTED_CHATTER;
2391 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2392 if (appData.autoKibitz && started == STARTED_NONE &&
2393 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2394 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2395 if(looking_at(buf, &i, "* kibitzes: ") &&
2396 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2397 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2398 suppressKibitz = TRUE;
2399 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2400 && (gameMode == IcsPlayingWhite)) ||
2401 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2402 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2403 started = STARTED_CHATTER; // own kibitz we simply discard
2405 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2406 parse_pos = 0; parse[0] = NULLCHAR;
2407 savingComment = TRUE;
2408 suppressKibitz = gameMode != IcsObserving ? 2 :
2409 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2413 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2414 started = STARTED_CHATTER;
2415 suppressKibitz = TRUE;
2417 } // [HGM] kibitz: end of patch
2419 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2421 // [HGM] chat: intercept tells by users for which we have an open chat window
2423 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2424 looking_at(buf, &i, "* whispers:") ||
2425 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2426 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2428 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2429 chattingPartner = -1;
2431 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2432 for(p=0; p<MAX_CHAT; p++) {
2433 if(channel == atoi(chatPartner[p])) {
2434 talker[0] = '['; strcat(talker, "]");
2435 chattingPartner = p; break;
2438 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2439 for(p=0; p<MAX_CHAT; p++) {
2440 if(!strcmp("WHISPER", chatPartner[p])) {
2441 talker[0] = '['; strcat(talker, "]");
2442 chattingPartner = p; break;
2445 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2446 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2448 chattingPartner = p; break;
2450 if(chattingPartner<0) i = oldi; else {
2451 started = STARTED_COMMENT;
2452 parse_pos = 0; parse[0] = NULLCHAR;
2453 savingComment = TRUE;
2454 suppressKibitz = TRUE;
2456 } // [HGM] chat: end of patch
2458 if (appData.zippyTalk || appData.zippyPlay) {
2459 /* [DM] Backup address for color zippy lines */
2463 if (loggedOn == TRUE)
2464 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2465 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2467 if (ZippyControl(buf, &i) ||
2468 ZippyConverse(buf, &i) ||
2469 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2471 if (!appData.colorize) continue;
2475 } // [DM] 'else { ' deleted
2477 /* Regular tells and says */
2478 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2479 looking_at(buf, &i, "* (your partner) tells you: ") ||
2480 looking_at(buf, &i, "* says: ") ||
2481 /* Don't color "message" or "messages" output */
2482 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2483 looking_at(buf, &i, "*. * at *:*: ") ||
2484 looking_at(buf, &i, "--* (*:*): ") ||
2485 /* Message notifications (same color as tells) */
2486 looking_at(buf, &i, "* has left a message ") ||
2487 looking_at(buf, &i, "* just sent you a message:\n") ||
2488 /* Whispers and kibitzes */
2489 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2490 looking_at(buf, &i, "* kibitzes: ") ||
2492 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2494 if (tkind == 1 && strchr(star_match[0], ':')) {
2495 /* Avoid "tells you:" spoofs in channels */
2498 if (star_match[0][0] == NULLCHAR ||
2499 strchr(star_match[0], ' ') ||
2500 (tkind == 3 && strchr(star_match[1], ' '))) {
2501 /* Reject bogus matches */
2504 if (appData.colorize) {
2505 if (oldi > next_out) {
2506 SendToPlayer(&buf[next_out], oldi - next_out);
2511 Colorize(ColorTell, FALSE);
2512 curColor = ColorTell;
2515 Colorize(ColorKibitz, FALSE);
2516 curColor = ColorKibitz;
2519 p = strrchr(star_match[1], '(');
2526 Colorize(ColorChannel1, FALSE);
2527 curColor = ColorChannel1;
2529 Colorize(ColorChannel, FALSE);
2530 curColor = ColorChannel;
2534 curColor = ColorNormal;
2538 if (started == STARTED_NONE && appData.autoComment &&
2539 (gameMode == IcsObserving ||
2540 gameMode == IcsPlayingWhite ||
2541 gameMode == IcsPlayingBlack)) {
2542 parse_pos = i - oldi;
2543 memcpy(parse, &buf[oldi], parse_pos);
2544 parse[parse_pos] = NULLCHAR;
2545 started = STARTED_COMMENT;
2546 savingComment = TRUE;
2548 started = STARTED_CHATTER;
2549 savingComment = FALSE;
2556 if (looking_at(buf, &i, "* s-shouts: ") ||
2557 looking_at(buf, &i, "* c-shouts: ")) {
2558 if (appData.colorize) {
2559 if (oldi > next_out) {
2560 SendToPlayer(&buf[next_out], oldi - next_out);
2563 Colorize(ColorSShout, FALSE);
2564 curColor = ColorSShout;
2567 started = STARTED_CHATTER;
2571 if (looking_at(buf, &i, "--->")) {
2576 if (looking_at(buf, &i, "* shouts: ") ||
2577 looking_at(buf, &i, "--> ")) {
2578 if (appData.colorize) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(ColorShout, FALSE);
2584 curColor = ColorShout;
2587 started = STARTED_CHATTER;
2591 if (looking_at( buf, &i, "Challenge:")) {
2592 if (appData.colorize) {
2593 if (oldi > next_out) {
2594 SendToPlayer(&buf[next_out], oldi - next_out);
2597 Colorize(ColorChallenge, FALSE);
2598 curColor = ColorChallenge;
2604 if (looking_at(buf, &i, "* offers you") ||
2605 looking_at(buf, &i, "* offers to be") ||
2606 looking_at(buf, &i, "* would like to") ||
2607 looking_at(buf, &i, "* requests to") ||
2608 looking_at(buf, &i, "Your opponent offers") ||
2609 looking_at(buf, &i, "Your opponent requests")) {
2611 if (appData.colorize) {
2612 if (oldi > next_out) {
2613 SendToPlayer(&buf[next_out], oldi - next_out);
2616 Colorize(ColorRequest, FALSE);
2617 curColor = ColorRequest;
2622 if (looking_at(buf, &i, "* (*) seeking")) {
2623 if (appData.colorize) {
2624 if (oldi > next_out) {
2625 SendToPlayer(&buf[next_out], oldi - next_out);
2628 Colorize(ColorSeek, FALSE);
2629 curColor = ColorSeek;
2634 if (looking_at(buf, &i, "\\ ")) {
2635 if (prevColor != ColorNormal) {
2636 if (oldi > next_out) {
2637 SendToPlayer(&buf[next_out], oldi - next_out);
2640 Colorize(prevColor, TRUE);
2641 curColor = prevColor;
2643 if (savingComment) {
2644 parse_pos = i - oldi;
2645 memcpy(parse, &buf[oldi], parse_pos);
2646 parse[parse_pos] = NULLCHAR;
2647 started = STARTED_COMMENT;
2649 started = STARTED_CHATTER;
2654 if (looking_at(buf, &i, "Black Strength :") ||
2655 looking_at(buf, &i, "<<< style 10 board >>>") ||
2656 looking_at(buf, &i, "<10>") ||
2657 looking_at(buf, &i, "#@#")) {
2658 /* Wrong board style */
2660 SendToICS(ics_prefix);
2661 SendToICS("set style 12\n");
2662 SendToICS(ics_prefix);
2663 SendToICS("refresh\n");
2667 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2669 have_sent_ICS_logon = 1;
2673 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2674 (looking_at(buf, &i, "\n<12> ") ||
2675 looking_at(buf, &i, "<12> "))) {
2677 if (oldi > next_out) {
2678 SendToPlayer(&buf[next_out], oldi - next_out);
2681 started = STARTED_BOARD;
2686 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2687 looking_at(buf, &i, "<b1> ")) {
2688 if (oldi > next_out) {
2689 SendToPlayer(&buf[next_out], oldi - next_out);
2692 started = STARTED_HOLDINGS;
2697 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2699 /* Header for a move list -- first line */
2701 switch (ics_getting_history) {
2705 case BeginningOfGame:
2706 /* User typed "moves" or "oldmoves" while we
2707 were idle. Pretend we asked for these
2708 moves and soak them up so user can step
2709 through them and/or save them.
2712 gameMode = IcsObserving;
2715 ics_getting_history = H_GOT_UNREQ_HEADER;
2717 case EditGame: /*?*/
2718 case EditPosition: /*?*/
2719 /* Should above feature work in these modes too? */
2720 /* For now it doesn't */
2721 ics_getting_history = H_GOT_UNWANTED_HEADER;
2724 ics_getting_history = H_GOT_UNWANTED_HEADER;
2729 /* Is this the right one? */
2730 if (gameInfo.white && gameInfo.black &&
2731 strcmp(gameInfo.white, star_match[0]) == 0 &&
2732 strcmp(gameInfo.black, star_match[2]) == 0) {
2734 ics_getting_history = H_GOT_REQ_HEADER;
2737 case H_GOT_REQ_HEADER:
2738 case H_GOT_UNREQ_HEADER:
2739 case H_GOT_UNWANTED_HEADER:
2740 case H_GETTING_MOVES:
2741 /* Should not happen */
2742 DisplayError(_("Error gathering move list: two headers"), 0);
2743 ics_getting_history = H_FALSE;
2747 /* Save player ratings into gameInfo if needed */
2748 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2749 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2750 (gameInfo.whiteRating == -1 ||
2751 gameInfo.blackRating == -1)) {
2753 gameInfo.whiteRating = string_to_rating(star_match[1]);
2754 gameInfo.blackRating = string_to_rating(star_match[3]);
2755 if (appData.debugMode)
2756 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2757 gameInfo.whiteRating, gameInfo.blackRating);
2762 if (looking_at(buf, &i,
2763 "* * match, initial time: * minute*, increment: * second")) {
2764 /* Header for a move list -- second line */
2765 /* Initial board will follow if this is a wild game */
2766 if (gameInfo.event != NULL) free(gameInfo.event);
2767 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2768 gameInfo.event = StrSave(str);
2769 /* [HGM] we switched variant. Translate boards if needed. */
2770 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2774 if (looking_at(buf, &i, "Move ")) {
2775 /* Beginning of a move list */
2776 switch (ics_getting_history) {
2778 /* Normally should not happen */
2779 /* Maybe user hit reset while we were parsing */
2782 /* Happens if we are ignoring a move list that is not
2783 * the one we just requested. Common if the user
2784 * tries to observe two games without turning off
2787 case H_GETTING_MOVES:
2788 /* Should not happen */
2789 DisplayError(_("Error gathering move list: nested"), 0);
2790 ics_getting_history = H_FALSE;
2792 case H_GOT_REQ_HEADER:
2793 ics_getting_history = H_GETTING_MOVES;
2794 started = STARTED_MOVES;
2796 if (oldi > next_out) {
2797 SendToPlayer(&buf[next_out], oldi - next_out);
2800 case H_GOT_UNREQ_HEADER:
2801 ics_getting_history = H_GETTING_MOVES;
2802 started = STARTED_MOVES_NOHIDE;
2805 case H_GOT_UNWANTED_HEADER:
2806 ics_getting_history = H_FALSE;
2812 if (looking_at(buf, &i, "% ") ||
2813 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2814 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2815 savingComment = FALSE;
2818 case STARTED_MOVES_NOHIDE:
2819 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2820 parse[parse_pos + i - oldi] = NULLCHAR;
2821 ParseGameHistory(parse);
2823 if (appData.zippyPlay && first.initDone) {
2824 FeedMovesToProgram(&first, forwardMostMove);
2825 if (gameMode == IcsPlayingWhite) {
2826 if (WhiteOnMove(forwardMostMove)) {
2827 if (first.sendTime) {
2828 if (first.useColors) {
2829 SendToProgram("black\n", &first);
2831 SendTimeRemaining(&first, TRUE);
2834 if (first.useColors) {
2835 SendToProgram("white\ngo\n", &first);
2837 SendToProgram("go\n", &first);
2840 if (first.useColors) {
2841 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845 first.maybeThinking = TRUE;
2847 if (first.usePlayother) {
2848 if (first.sendTime) {
2849 SendTimeRemaining(&first, TRUE);
2851 SendToProgram("playother\n", &first);
2857 } else if (gameMode == IcsPlayingBlack) {
2858 if (!WhiteOnMove(forwardMostMove)) {
2859 if (first.sendTime) {
2860 if (first.useColors) {
2861 SendToProgram("white\n", &first);
2863 SendTimeRemaining(&first, FALSE);
2866 if (first.useColors) {
2867 SendToProgram("black\ngo\n", &first);
2869 SendToProgram("go\n", &first);
2872 if (first.useColors) {
2873 SendToProgram("black\n", &first);
2875 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2877 first.maybeThinking = TRUE;
2879 if (first.usePlayother) {
2880 if (first.sendTime) {
2881 SendTimeRemaining(&first, FALSE);
2883 SendToProgram("playother\n", &first);
2892 if (gameMode == IcsObserving && ics_gamenum == -1) {
2893 /* Moves came from oldmoves or moves command
2894 while we weren't doing anything else.
2896 currentMove = forwardMostMove;
2897 ClearHighlights();/*!!could figure this out*/
2898 flipView = appData.flipView;
2899 DrawPosition(FALSE, boards[currentMove]);
2900 DisplayBothClocks();
2901 sprintf(str, "%s vs. %s",
2902 gameInfo.white, gameInfo.black);
2906 /* Moves were history of an active game */
2907 if (gameInfo.resultDetails != NULL) {
2908 free(gameInfo.resultDetails);
2909 gameInfo.resultDetails = NULL;
2912 HistorySet(parseList, backwardMostMove,
2913 forwardMostMove, currentMove-1);
2914 DisplayMove(currentMove - 1);
2915 if (started == STARTED_MOVES) next_out = i;
2916 started = STARTED_NONE;
2917 ics_getting_history = H_FALSE;
2920 case STARTED_OBSERVE:
2921 started = STARTED_NONE;
2922 SendToICS(ics_prefix);
2923 SendToICS("refresh\n");
2929 if(bookHit) { // [HGM] book: simulate book reply
2930 static char bookMove[MSG_SIZ]; // a bit generous?
2932 programStats.nodes = programStats.depth = programStats.time =
2933 programStats.score = programStats.got_only_move = 0;
2934 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2936 strcpy(bookMove, "move ");
2937 strcat(bookMove, bookHit);
2938 HandleMachineMove(bookMove, &first);
2943 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2944 started == STARTED_HOLDINGS ||
2945 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2946 /* Accumulate characters in move list or board */
2947 parse[parse_pos++] = buf[i];
2950 /* Start of game messages. Mostly we detect start of game
2951 when the first board image arrives. On some versions
2952 of the ICS, though, we need to do a "refresh" after starting
2953 to observe in order to get the current board right away. */
2954 if (looking_at(buf, &i, "Adding game * to observation list")) {
2955 started = STARTED_OBSERVE;
2959 /* Handle auto-observe */
2960 if (appData.autoObserve &&
2961 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2962 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2964 /* Choose the player that was highlighted, if any. */
2965 if (star_match[0][0] == '\033' ||
2966 star_match[1][0] != '\033') {
2967 player = star_match[0];
2969 player = star_match[2];
2971 sprintf(str, "%sobserve %s\n",
2972 ics_prefix, StripHighlightAndTitle(player));
2975 /* Save ratings from notify string */
2976 strcpy(player1Name, star_match[0]);
2977 player1Rating = string_to_rating(star_match[1]);
2978 strcpy(player2Name, star_match[2]);
2979 player2Rating = string_to_rating(star_match[3]);
2981 if (appData.debugMode)
2983 "Ratings from 'Game notification:' %s %d, %s %d\n",
2984 player1Name, player1Rating,
2985 player2Name, player2Rating);
2990 /* Deal with automatic examine mode after a game,
2991 and with IcsObserving -> IcsExamining transition */
2992 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2993 looking_at(buf, &i, "has made you an examiner of game *")) {
2995 int gamenum = atoi(star_match[0]);
2996 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2997 gamenum == ics_gamenum) {
2998 /* We were already playing or observing this game;
2999 no need to refetch history */
3000 gameMode = IcsExamining;
3002 pauseExamForwardMostMove = forwardMostMove;
3003 } else if (currentMove < forwardMostMove) {
3004 ForwardInner(forwardMostMove);
3007 /* I don't think this case really can happen */
3008 SendToICS(ics_prefix);
3009 SendToICS("refresh\n");
3014 /* Error messages */
3015 // if (ics_user_moved) {
3016 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3017 if (looking_at(buf, &i, "Illegal move") ||
3018 looking_at(buf, &i, "Not a legal move") ||
3019 looking_at(buf, &i, "Your king is in check") ||
3020 looking_at(buf, &i, "It isn't your turn") ||
3021 looking_at(buf, &i, "It is not your move")) {
3023 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3024 currentMove = --forwardMostMove;
3025 DisplayMove(currentMove - 1); /* before DMError */
3026 DrawPosition(FALSE, boards[currentMove]);
3028 DisplayBothClocks();
3030 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3036 if (looking_at(buf, &i, "still have time") ||
3037 looking_at(buf, &i, "not out of time") ||
3038 looking_at(buf, &i, "either player is out of time") ||
3039 looking_at(buf, &i, "has timeseal; checking")) {
3040 /* We must have called his flag a little too soon */
3041 whiteFlag = blackFlag = FALSE;
3045 if (looking_at(buf, &i, "added * seconds to") ||
3046 looking_at(buf, &i, "seconds were added to")) {
3047 /* Update the clocks */
3048 SendToICS(ics_prefix);
3049 SendToICS("refresh\n");
3053 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3054 ics_clock_paused = TRUE;
3059 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3060 ics_clock_paused = FALSE;
3065 /* Grab player ratings from the Creating: message.
3066 Note we have to check for the special case when
3067 the ICS inserts things like [white] or [black]. */
3068 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3069 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3071 0 player 1 name (not necessarily white)
3073 2 empty, white, or black (IGNORED)
3074 3 player 2 name (not necessarily black)
3077 The names/ratings are sorted out when the game
3078 actually starts (below).
3080 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3081 player1Rating = string_to_rating(star_match[1]);
3082 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3083 player2Rating = string_to_rating(star_match[4]);
3085 if (appData.debugMode)
3087 "Ratings from 'Creating:' %s %d, %s %d\n",
3088 player1Name, player1Rating,
3089 player2Name, player2Rating);
3094 /* Improved generic start/end-of-game messages */
3095 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3096 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3097 /* If tkind == 0: */
3098 /* star_match[0] is the game number */
3099 /* [1] is the white player's name */
3100 /* [2] is the black player's name */
3101 /* For end-of-game: */
3102 /* [3] is the reason for the game end */
3103 /* [4] is a PGN end game-token, preceded by " " */
3104 /* For start-of-game: */
3105 /* [3] begins with "Creating" or "Continuing" */
3106 /* [4] is " *" or empty (don't care). */
3107 int gamenum = atoi(star_match[0]);
3108 char *whitename, *blackname, *why, *endtoken;
3109 ChessMove endtype = (ChessMove) 0;
3112 whitename = star_match[1];
3113 blackname = star_match[2];
3114 why = star_match[3];
3115 endtoken = star_match[4];
3117 whitename = star_match[1];
3118 blackname = star_match[3];
3119 why = star_match[5];
3120 endtoken = star_match[6];
3123 /* Game start messages */
3124 if (strncmp(why, "Creating ", 9) == 0 ||
3125 strncmp(why, "Continuing ", 11) == 0) {
3126 gs_gamenum = gamenum;
3127 strcpy(gs_kind, strchr(why, ' ') + 1);
3129 if (appData.zippyPlay) {
3130 ZippyGameStart(whitename, blackname);
3136 /* Game end messages */
3137 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3138 ics_gamenum != gamenum) {
3141 while (endtoken[0] == ' ') endtoken++;
3142 switch (endtoken[0]) {
3145 endtype = GameUnfinished;
3148 endtype = BlackWins;
3151 if (endtoken[1] == '/')
3152 endtype = GameIsDrawn;
3154 endtype = WhiteWins;
3157 GameEnds(endtype, why, GE_ICS);
3159 if (appData.zippyPlay && first.initDone) {
3160 ZippyGameEnd(endtype, why);
3161 if (first.pr == NULL) {
3162 /* Start the next process early so that we'll
3163 be ready for the next challenge */
3164 StartChessProgram(&first);
3166 /* Send "new" early, in case this command takes
3167 a long time to finish, so that we'll be ready
3168 for the next challenge. */
3169 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3176 if (looking_at(buf, &i, "Removing game * from observation") ||
3177 looking_at(buf, &i, "no longer observing game *") ||
3178 looking_at(buf, &i, "Game * (*) has no examiners")) {
3179 if (gameMode == IcsObserving &&
3180 atoi(star_match[0]) == ics_gamenum)
3182 /* icsEngineAnalyze */
3183 if (appData.icsEngineAnalyze) {
3190 ics_user_moved = FALSE;
3195 if (looking_at(buf, &i, "no longer examining game *")) {
3196 if (gameMode == IcsExamining &&
3197 atoi(star_match[0]) == ics_gamenum)
3201 ics_user_moved = FALSE;
3206 /* Advance leftover_start past any newlines we find,
3207 so only partial lines can get reparsed */
3208 if (looking_at(buf, &i, "\n")) {
3209 prevColor = curColor;
3210 if (curColor != ColorNormal) {
3211 if (oldi > next_out) {
3212 SendToPlayer(&buf[next_out], oldi - next_out);
3215 Colorize(ColorNormal, FALSE);
3216 curColor = ColorNormal;
3218 if (started == STARTED_BOARD) {
3219 started = STARTED_NONE;
3220 parse[parse_pos] = NULLCHAR;
3221 ParseBoard12(parse);
3224 /* Send premove here */
3225 if (appData.premove) {
3227 if (currentMove == 0 &&
3228 gameMode == IcsPlayingWhite &&
3229 appData.premoveWhite) {
3230 sprintf(str, "%s%s\n", ics_prefix,
3231 appData.premoveWhiteText);
3232 if (appData.debugMode)
3233 fprintf(debugFP, "Sending premove:\n");
3235 } else if (currentMove == 1 &&
3236 gameMode == IcsPlayingBlack &&
3237 appData.premoveBlack) {
3238 sprintf(str, "%s%s\n", ics_prefix,
3239 appData.premoveBlackText);
3240 if (appData.debugMode)
3241 fprintf(debugFP, "Sending premove:\n");
3243 } else if (gotPremove) {
3245 ClearPremoveHighlights();
3246 if (appData.debugMode)
3247 fprintf(debugFP, "Sending premove:\n");
3248 UserMoveEvent(premoveFromX, premoveFromY,
3249 premoveToX, premoveToY,
3254 /* Usually suppress following prompt */
3255 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3256 if (looking_at(buf, &i, "*% ")) {
3257 savingComment = FALSE;
3261 } else if (started == STARTED_HOLDINGS) {
3263 char new_piece[MSG_SIZ];
3264 started = STARTED_NONE;
3265 parse[parse_pos] = NULLCHAR;
3266 if (appData.debugMode)
3267 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3268 parse, currentMove);
3269 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3270 gamenum == ics_gamenum) {
3271 if (gameInfo.variant == VariantNormal) {
3272 /* [HGM] We seem to switch variant during a game!
3273 * Presumably no holdings were displayed, so we have
3274 * to move the position two files to the right to
3275 * create room for them!
3277 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3278 /* Get a move list just to see the header, which
3279 will tell us whether this is really bug or zh */
3280 if (ics_getting_history == H_FALSE) {
3281 ics_getting_history = H_REQUESTED;
3282 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3286 new_piece[0] = NULLCHAR;
3287 sscanf(parse, "game %d white [%s black [%s <- %s",
3288 &gamenum, white_holding, black_holding,
3290 white_holding[strlen(white_holding)-1] = NULLCHAR;
3291 black_holding[strlen(black_holding)-1] = NULLCHAR;
3292 /* [HGM] copy holdings to board holdings area */
3293 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3294 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3296 if (appData.zippyPlay && first.initDone) {
3297 ZippyHoldings(white_holding, black_holding,
3301 if (tinyLayout || smallLayout) {
3302 char wh[16], bh[16];
3303 PackHolding(wh, white_holding);
3304 PackHolding(bh, black_holding);
3305 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3306 gameInfo.white, gameInfo.black);
3308 sprintf(str, "%s [%s] vs. %s [%s]",
3309 gameInfo.white, white_holding,
3310 gameInfo.black, black_holding);
3313 DrawPosition(FALSE, boards[currentMove]);
3316 /* Suppress following prompt */
3317 if (looking_at(buf, &i, "*% ")) {
3318 savingComment = FALSE;
3325 i++; /* skip unparsed character and loop back */
3328 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3329 started != STARTED_HOLDINGS && i > next_out) {
3330 SendToPlayer(&buf[next_out], i - next_out);
3333 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3335 leftover_len = buf_len - leftover_start;
3336 /* if buffer ends with something we couldn't parse,
3337 reparse it after appending the next read */
3339 } else if (count == 0) {
3340 RemoveInputSource(isr);
3341 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3343 DisplayFatalError(_("Error reading from ICS"), error, 1);
3348 /* Board style 12 looks like this:
3350 <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
3352 * The "<12> " is stripped before it gets to this routine. The two
3353 * trailing 0's (flip state and clock ticking) are later addition, and
3354 * some chess servers may not have them, or may have only the first.
3355 * Additional trailing fields may be added in the future.
3358 #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"
3360 #define RELATION_OBSERVING_PLAYED 0
3361 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3362 #define RELATION_PLAYING_MYMOVE 1
3363 #define RELATION_PLAYING_NOTMYMOVE -1
3364 #define RELATION_EXAMINING 2
3365 #define RELATION_ISOLATED_BOARD -3
3366 #define RELATION_STARTING_POSITION -4 /* FICS only */
3369 ParseBoard12(string)
3372 GameMode newGameMode;
3373 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3374 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3375 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3376 char to_play, board_chars[200];
3377 char move_str[500], str[500], elapsed_time[500];
3378 char black[32], white[32];
3380 int prevMove = currentMove;
3383 int fromX, fromY, toX, toY;
3385 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3386 char *bookHit = NULL; // [HGM] book
3388 fromX = fromY = toX = toY = -1;
3392 if (appData.debugMode)
3393 fprintf(debugFP, _("Parsing board: %s\n"), string);
3395 move_str[0] = NULLCHAR;
3396 elapsed_time[0] = NULLCHAR;
3397 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3399 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3400 if(string[i] == ' ') { ranks++; files = 0; }
3404 for(j = 0; j <i; j++) board_chars[j] = string[j];
3405 board_chars[i] = '\0';
3408 n = sscanf(string, PATTERN, &to_play, &double_push,
3409 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3410 &gamenum, white, black, &relation, &basetime, &increment,
3411 &white_stren, &black_stren, &white_time, &black_time,
3412 &moveNum, str, elapsed_time, move_str, &ics_flip,
3416 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3417 DisplayError(str, 0);
3421 /* Convert the move number to internal form */
3422 moveNum = (moveNum - 1) * 2;
3423 if (to_play == 'B') moveNum++;
3424 if (moveNum >= MAX_MOVES) {
3425 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3431 case RELATION_OBSERVING_PLAYED:
3432 case RELATION_OBSERVING_STATIC:
3433 if (gamenum == -1) {
3434 /* Old ICC buglet */
3435 relation = RELATION_OBSERVING_STATIC;
3437 newGameMode = IcsObserving;
3439 case RELATION_PLAYING_MYMOVE:
3440 case RELATION_PLAYING_NOTMYMOVE:
3442 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3443 IcsPlayingWhite : IcsPlayingBlack;
3445 case RELATION_EXAMINING:
3446 newGameMode = IcsExamining;
3448 case RELATION_ISOLATED_BOARD:
3450 /* Just display this board. If user was doing something else,
3451 we will forget about it until the next board comes. */
3452 newGameMode = IcsIdle;
3454 case RELATION_STARTING_POSITION:
3455 newGameMode = gameMode;
3459 /* Modify behavior for initial board display on move listing
3462 switch (ics_getting_history) {
3466 case H_GOT_REQ_HEADER:
3467 case H_GOT_UNREQ_HEADER:
3468 /* This is the initial position of the current game */
3469 gamenum = ics_gamenum;
3470 moveNum = 0; /* old ICS bug workaround */
3471 if (to_play == 'B') {
3472 startedFromSetupPosition = TRUE;
3473 blackPlaysFirst = TRUE;
3475 if (forwardMostMove == 0) forwardMostMove = 1;
3476 if (backwardMostMove == 0) backwardMostMove = 1;
3477 if (currentMove == 0) currentMove = 1;
3479 newGameMode = gameMode;
3480 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3482 case H_GOT_UNWANTED_HEADER:
3483 /* This is an initial board that we don't want */
3485 case H_GETTING_MOVES:
3486 /* Should not happen */
3487 DisplayError(_("Error gathering move list: extra board"), 0);
3488 ics_getting_history = H_FALSE;
3492 /* Take action if this is the first board of a new game, or of a
3493 different game than is currently being displayed. */
3494 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3495 relation == RELATION_ISOLATED_BOARD) {
3497 /* Forget the old game and get the history (if any) of the new one */
3498 if (gameMode != BeginningOfGame) {
3502 if (appData.autoRaiseBoard) BoardToTop();
3504 if (gamenum == -1) {
3505 newGameMode = IcsIdle;
3506 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3507 appData.getMoveList) {
3508 /* Need to get game history */
3509 ics_getting_history = H_REQUESTED;
3510 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3514 /* Initially flip the board to have black on the bottom if playing
3515 black or if the ICS flip flag is set, but let the user change
3516 it with the Flip View button. */
3517 flipView = appData.autoFlipView ?
3518 (newGameMode == IcsPlayingBlack) || ics_flip :
3521 /* Done with values from previous mode; copy in new ones */
3522 gameMode = newGameMode;
3524 ics_gamenum = gamenum;
3525 if (gamenum == gs_gamenum) {
3526 int klen = strlen(gs_kind);
3527 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3528 sprintf(str, "ICS %s", gs_kind);
3529 gameInfo.event = StrSave(str);
3531 gameInfo.event = StrSave("ICS game");
3533 gameInfo.site = StrSave(appData.icsHost);
3534 gameInfo.date = PGNDate();
3535 gameInfo.round = StrSave("-");
3536 gameInfo.white = StrSave(white);
3537 gameInfo.black = StrSave(black);
3538 timeControl = basetime * 60 * 1000;
3540 timeIncrement = increment * 1000;
3541 movesPerSession = 0;
3542 gameInfo.timeControl = TimeControlTagValue();
3543 VariantSwitch(board, StringToVariant(gameInfo.event) );
3544 if (appData.debugMode) {
3545 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3546 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3547 setbuf(debugFP, NULL);
3550 gameInfo.outOfBook = NULL;
3552 /* Do we have the ratings? */
3553 if (strcmp(player1Name, white) == 0 &&
3554 strcmp(player2Name, black) == 0) {
3555 if (appData.debugMode)
3556 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3557 player1Rating, player2Rating);
3558 gameInfo.whiteRating = player1Rating;
3559 gameInfo.blackRating = player2Rating;
3560 } else if (strcmp(player2Name, white) == 0 &&
3561 strcmp(player1Name, black) == 0) {
3562 if (appData.debugMode)
3563 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3564 player2Rating, player1Rating);
3565 gameInfo.whiteRating = player2Rating;
3566 gameInfo.blackRating = player1Rating;
3568 player1Name[0] = player2Name[0] = NULLCHAR;
3570 /* Silence shouts if requested */
3571 if (appData.quietPlay &&
3572 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3573 SendToICS(ics_prefix);
3574 SendToICS("set shout 0\n");
3578 /* Deal with midgame name changes */
3580 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3581 if (gameInfo.white) free(gameInfo.white);
3582 gameInfo.white = StrSave(white);
3584 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3585 if (gameInfo.black) free(gameInfo.black);
3586 gameInfo.black = StrSave(black);
3590 /* Throw away game result if anything actually changes in examine mode */
3591 if (gameMode == IcsExamining && !newGame) {
3592 gameInfo.result = GameUnfinished;
3593 if (gameInfo.resultDetails != NULL) {
3594 free(gameInfo.resultDetails);
3595 gameInfo.resultDetails = NULL;
3599 /* In pausing && IcsExamining mode, we ignore boards coming
3600 in if they are in a different variation than we are. */
3601 if (pauseExamInvalid) return;
3602 if (pausing && gameMode == IcsExamining) {
3603 if (moveNum <= pauseExamForwardMostMove) {
3604 pauseExamInvalid = TRUE;
3605 forwardMostMove = pauseExamForwardMostMove;
3610 if (appData.debugMode) {
3611 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3613 /* Parse the board */
3614 for (k = 0; k < ranks; k++) {
3615 for (j = 0; j < files; j++)
3616 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3617 if(gameInfo.holdingsWidth > 1) {
3618 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3619 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3622 CopyBoard(boards[moveNum], board);
3624 startedFromSetupPosition =
3625 !CompareBoards(board, initialPosition);
3626 if(startedFromSetupPosition)
3627 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3630 /* [HGM] Set castling rights. Take the outermost Rooks,
3631 to make it also work for FRC opening positions. Note that board12
3632 is really defective for later FRC positions, as it has no way to
3633 indicate which Rook can castle if they are on the same side of King.
3634 For the initial position we grant rights to the outermost Rooks,
3635 and remember thos rights, and we then copy them on positions
3636 later in an FRC game. This means WB might not recognize castlings with
3637 Rooks that have moved back to their original position as illegal,
3638 but in ICS mode that is not its job anyway.
3640 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3641 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3643 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3644 if(board[0][i] == WhiteRook) j = i;
3645 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3646 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3647 if(board[0][i] == WhiteRook) j = i;
3648 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3649 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3651 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3654 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3657 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3658 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3659 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3660 if(board[BOARD_HEIGHT-1][k] == bKing)
3661 initialRights[5] = castlingRights[moveNum][5] = k;
3663 r = castlingRights[moveNum][0] = initialRights[0];
3664 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3665 r = castlingRights[moveNum][1] = initialRights[1];
3666 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3667 r = castlingRights[moveNum][3] = initialRights[3];
3668 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3669 r = castlingRights[moveNum][4] = initialRights[4];
3670 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3671 /* wildcastle kludge: always assume King has rights */
3672 r = castlingRights[moveNum][2] = initialRights[2];
3673 r = castlingRights[moveNum][5] = initialRights[5];
3675 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3676 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3679 if (ics_getting_history == H_GOT_REQ_HEADER ||
3680 ics_getting_history == H_GOT_UNREQ_HEADER) {
3681 /* This was an initial position from a move list, not
3682 the current position */
3686 /* Update currentMove and known move number limits */
3687 newMove = newGame || moveNum > forwardMostMove;
3689 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3690 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3691 takeback = forwardMostMove - moveNum;
3692 for (i = 0; i < takeback; i++) {
3693 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3694 SendToProgram("undo\n", &first);
3699 forwardMostMove = backwardMostMove = currentMove = moveNum;
3700 if (gameMode == IcsExamining && moveNum == 0) {
3701 /* Workaround for ICS limitation: we are not told the wild
3702 type when starting to examine a game. But if we ask for
3703 the move list, the move list header will tell us */
3704 ics_getting_history = H_REQUESTED;
3705 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3708 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3709 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3710 forwardMostMove = moveNum;
3711 if (!pausing || currentMove > forwardMostMove)
3712 currentMove = forwardMostMove;
3714 /* New part of history that is not contiguous with old part */
3715 if (pausing && gameMode == IcsExamining) {
3716 pauseExamInvalid = TRUE;
3717 forwardMostMove = pauseExamForwardMostMove;
3720 forwardMostMove = backwardMostMove = currentMove = moveNum;
3721 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3722 ics_getting_history = H_REQUESTED;
3723 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3728 /* Update the clocks */
3729 if (strchr(elapsed_time, '.')) {
3731 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3732 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3734 /* Time is in seconds */
3735 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3736 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3741 if (appData.zippyPlay && newGame &&
3742 gameMode != IcsObserving && gameMode != IcsIdle &&
3743 gameMode != IcsExamining)
3744 ZippyFirstBoard(moveNum, basetime, increment);
3747 /* Put the move on the move list, first converting
3748 to canonical algebraic form. */
3750 if (appData.debugMode) {
3751 if (appData.debugMode) { int f = forwardMostMove;
3752 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3753 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3755 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3756 fprintf(debugFP, "moveNum = %d\n", moveNum);
3757 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3758 setbuf(debugFP, NULL);
3760 if (moveNum <= backwardMostMove) {
3761 /* We don't know what the board looked like before
3763 strcpy(parseList[moveNum - 1], move_str);
3764 strcat(parseList[moveNum - 1], " ");
3765 strcat(parseList[moveNum - 1], elapsed_time);
3766 moveList[moveNum - 1][0] = NULLCHAR;
3767 } else if (strcmp(move_str, "none") == 0) {
3768 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3769 /* Again, we don't know what the board looked like;
3770 this is really the start of the game. */
3771 parseList[moveNum - 1][0] = NULLCHAR;
3772 moveList[moveNum - 1][0] = NULLCHAR;
3773 backwardMostMove = moveNum;
3774 startedFromSetupPosition = TRUE;
3775 fromX = fromY = toX = toY = -1;
3777 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3778 // So we parse the long-algebraic move string in stead of the SAN move
3779 int valid; char buf[MSG_SIZ], *prom;
3781 // str looks something like "Q/a1-a2"; kill the slash
3783 sprintf(buf, "%c%s", str[0], str+2);
3784 else strcpy(buf, str); // might be castling
3785 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3786 strcat(buf, prom); // long move lacks promo specification!
3787 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3788 if(appData.debugMode)
3789 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3790 strcpy(move_str, buf);
3792 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3793 &fromX, &fromY, &toX, &toY, &promoChar)
3794 || ParseOneMove(buf, moveNum - 1, &moveType,
3795 &fromX, &fromY, &toX, &toY, &promoChar);
3796 // end of long SAN patch
3798 (void) CoordsToAlgebraic(boards[moveNum - 1],
3799 PosFlags(moveNum - 1), EP_UNKNOWN,
3800 fromY, fromX, toY, toX, promoChar,
3801 parseList[moveNum-1]);
3802 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3803 castlingRights[moveNum]) ) {
3809 if(gameInfo.variant != VariantShogi)
3810 strcat(parseList[moveNum - 1], "+");
3813 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3814 strcat(parseList[moveNum - 1], "#");
3817 strcat(parseList[moveNum - 1], " ");
3818 strcat(parseList[moveNum - 1], elapsed_time);
3819 /* currentMoveString is set as a side-effect of ParseOneMove */
3820 strcpy(moveList[moveNum - 1], currentMoveString);
3821 strcat(moveList[moveNum - 1], "\n");
3823 /* Move from ICS was illegal!? Punt. */
3824 if (appData.debugMode) {
3825 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3826 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3829 if (appData.testLegality && appData.debugMode) {
3830 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3831 DisplayError(str, 0);
3834 strcpy(parseList[moveNum - 1], move_str);
3835 strcat(parseList[moveNum - 1], " ");
3836 strcat(parseList[moveNum - 1], elapsed_time);
3837 moveList[moveNum - 1][0] = NULLCHAR;
3838 fromX = fromY = toX = toY = -1;
3841 if (appData.debugMode) {
3842 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3843 setbuf(debugFP, NULL);
3847 /* Send move to chess program (BEFORE animating it). */
3848 if (appData.zippyPlay && !newGame && newMove &&
3849 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3851 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3852 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3853 if (moveList[moveNum - 1][0] == NULLCHAR) {
3854 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3856 DisplayError(str, 0);
3858 if (first.sendTime) {
3859 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3861 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3862 if (firstMove && !bookHit) {
3864 if (first.useColors) {
3865 SendToProgram(gameMode == IcsPlayingWhite ?
3867 "black\ngo\n", &first);
3869 SendToProgram("go\n", &first);
3871 first.maybeThinking = TRUE;
3874 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3875 if (moveList[moveNum - 1][0] == NULLCHAR) {
3876 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3877 DisplayError(str, 0);
3879 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3880 SendMoveToProgram(moveNum - 1, &first);
3887 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3888 /* If move comes from a remote source, animate it. If it
3889 isn't remote, it will have already been animated. */
3890 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3891 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3893 if (!pausing && appData.highlightLastMove) {
3894 SetHighlights(fromX, fromY, toX, toY);
3898 /* Start the clocks */
3899 whiteFlag = blackFlag = FALSE;
3900 appData.clockMode = !(basetime == 0 && increment == 0);
3902 ics_clock_paused = TRUE;
3904 } else if (ticking == 1) {
3905 ics_clock_paused = FALSE;
3907 if (gameMode == IcsIdle ||
3908 relation == RELATION_OBSERVING_STATIC ||
3909 relation == RELATION_EXAMINING ||
3911 DisplayBothClocks();
3915 /* Display opponents and material strengths */
3916 if (gameInfo.variant != VariantBughouse &&
3917 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3918 if (tinyLayout || smallLayout) {
3919 if(gameInfo.variant == VariantNormal)
3920 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3921 gameInfo.white, white_stren, gameInfo.black, black_stren,
3922 basetime, increment);
3924 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3925 gameInfo.white, white_stren, gameInfo.black, black_stren,
3926 basetime, increment, (int) gameInfo.variant);
3928 if(gameInfo.variant == VariantNormal)
3929 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3930 gameInfo.white, white_stren, gameInfo.black, black_stren,
3931 basetime, increment);
3933 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3934 gameInfo.white, white_stren, gameInfo.black, black_stren,
3935 basetime, increment, VariantName(gameInfo.variant));
3938 if (appData.debugMode) {
3939 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3944 /* Display the board */
3945 if (!pausing && !appData.noGUI) {
3947 if (appData.premove)
3949 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3950 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3951 ClearPremoveHighlights();
3953 DrawPosition(FALSE, boards[currentMove]);
3954 DisplayMove(moveNum - 1);
3955 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3956 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3957 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3958 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3962 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3964 if(bookHit) { // [HGM] book: simulate book reply
3965 static char bookMove[MSG_SIZ]; // a bit generous?
3967 programStats.nodes = programStats.depth = programStats.time =
3968 programStats.score = programStats.got_only_move = 0;
3969 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3971 strcpy(bookMove, "move ");
3972 strcat(bookMove, bookHit);
3973 HandleMachineMove(bookMove, &first);
3982 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3983 ics_getting_history = H_REQUESTED;
3984 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3990 AnalysisPeriodicEvent(force)
3993 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3994 && !force) || !appData.periodicUpdates)
3997 /* Send . command to Crafty to collect stats */
3998 SendToProgram(".\n", &first);
4000 /* Don't send another until we get a response (this makes
4001 us stop sending to old Crafty's which don't understand
4002 the "." command (sending illegal cmds resets node count & time,
4003 which looks bad)) */
4004 programStats.ok_to_send = 0;
4008 SendMoveToProgram(moveNum, cps)
4010 ChessProgramState *cps;
4014 if (cps->useUsermove) {
4015 SendToProgram("usermove ", cps);
4019 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4020 int len = space - parseList[moveNum];
4021 memcpy(buf, parseList[moveNum], len);
4023 buf[len] = NULLCHAR;
4025 sprintf(buf, "%s\n", parseList[moveNum]);
4027 SendToProgram(buf, cps);
4029 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4030 AlphaRank(moveList[moveNum], 4);
4031 SendToProgram(moveList[moveNum], cps);
4032 AlphaRank(moveList[moveNum], 4); // and back
4034 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4035 * the engine. It would be nice to have a better way to identify castle
4037 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4038 && cps->useOOCastle) {
4039 int fromX = moveList[moveNum][0] - AAA;
4040 int fromY = moveList[moveNum][1] - ONE;
4041 int toX = moveList[moveNum][2] - AAA;
4042 int toY = moveList[moveNum][3] - ONE;
4043 if((boards[moveNum][fromY][fromX] == WhiteKing
4044 && boards[moveNum][toY][toX] == WhiteRook)
4045 || (boards[moveNum][fromY][fromX] == BlackKing
4046 && boards[moveNum][toY][toX] == BlackRook)) {
4047 if(toX > fromX) SendToProgram("O-O\n", cps);
4048 else SendToProgram("O-O-O\n", cps);
4050 else SendToProgram(moveList[moveNum], cps);
4052 else SendToProgram(moveList[moveNum], cps);
4053 /* End of additions by Tord */
4056 /* [HGM] setting up the opening has brought engine in force mode! */
4057 /* Send 'go' if we are in a mode where machine should play. */
4058 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4059 (gameMode == TwoMachinesPlay ||
4061 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4063 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4064 SendToProgram("go\n", cps);
4065 if (appData.debugMode) {
4066 fprintf(debugFP, "(extra)\n");
4069 setboardSpoiledMachineBlack = 0;
4073 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4075 int fromX, fromY, toX, toY;
4077 char user_move[MSG_SIZ];
4081 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4082 (int)moveType, fromX, fromY, toX, toY);
4083 DisplayError(user_move + strlen("say "), 0);
4085 case WhiteKingSideCastle:
4086 case BlackKingSideCastle:
4087 case WhiteQueenSideCastleWild:
4088 case BlackQueenSideCastleWild:
4090 case WhiteHSideCastleFR:
4091 case BlackHSideCastleFR:
4093 sprintf(user_move, "o-o\n");
4095 case WhiteQueenSideCastle:
4096 case BlackQueenSideCastle:
4097 case WhiteKingSideCastleWild:
4098 case BlackKingSideCastleWild:
4100 case WhiteASideCastleFR:
4101 case BlackASideCastleFR:
4103 sprintf(user_move, "o-o-o\n");
4105 case WhitePromotionQueen:
4106 case BlackPromotionQueen:
4107 case WhitePromotionRook:
4108 case BlackPromotionRook:
4109 case WhitePromotionBishop:
4110 case BlackPromotionBishop:
4111 case WhitePromotionKnight:
4112 case BlackPromotionKnight:
4113 case WhitePromotionKing:
4114 case BlackPromotionKing:
4115 case WhitePromotionChancellor:
4116 case BlackPromotionChancellor:
4117 case WhitePromotionArchbishop:
4118 case BlackPromotionArchbishop:
4119 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4120 sprintf(user_move, "%c%c%c%c=%c\n",
4121 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4122 PieceToChar(WhiteFerz));
4123 else if(gameInfo.variant == VariantGreat)
4124 sprintf(user_move, "%c%c%c%c=%c\n",
4125 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4126 PieceToChar(WhiteMan));
4128 sprintf(user_move, "%c%c%c%c=%c\n",
4129 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4130 PieceToChar(PromoPiece(moveType)));
4134 sprintf(user_move, "%c@%c%c\n",
4135 ToUpper(PieceToChar((ChessSquare) fromX)),
4136 AAA + toX, ONE + toY);
4139 case WhiteCapturesEnPassant:
4140 case BlackCapturesEnPassant:
4141 case IllegalMove: /* could be a variant we don't quite understand */
4142 sprintf(user_move, "%c%c%c%c\n",
4143 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4146 SendToICS(user_move);
4147 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4148 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4152 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4157 if (rf == DROP_RANK) {
4158 sprintf(move, "%c@%c%c\n",
4159 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4161 if (promoChar == 'x' || promoChar == NULLCHAR) {
4162 sprintf(move, "%c%c%c%c\n",
4163 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4165 sprintf(move, "%c%c%c%c%c\n",
4166 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4172 ProcessICSInitScript(f)
4177 while (fgets(buf, MSG_SIZ, f)) {
4178 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4185 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4187 AlphaRank(char *move, int n)
4189 // char *p = move, c; int x, y;
4191 if (appData.debugMode) {
4192 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4196 move[2]>='0' && move[2]<='9' &&
4197 move[3]>='a' && move[3]<='x' ) {
4199 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4200 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4202 if(move[0]>='0' && move[0]<='9' &&
4203 move[1]>='a' && move[1]<='x' &&
4204 move[2]>='0' && move[2]<='9' &&
4205 move[3]>='a' && move[3]<='x' ) {
4206 /* input move, Shogi -> normal */
4207 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4208 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4209 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4210 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4213 move[3]>='0' && move[3]<='9' &&
4214 move[2]>='a' && move[2]<='x' ) {
4216 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4217 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4220 move[0]>='a' && move[0]<='x' &&
4221 move[3]>='0' && move[3]<='9' &&
4222 move[2]>='a' && move[2]<='x' ) {
4223 /* output move, normal -> Shogi */
4224 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4225 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4226 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4227 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4228 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4230 if (appData.debugMode) {
4231 fprintf(debugFP, " out = '%s'\n", move);
4235 /* Parser for moves from gnuchess, ICS, or user typein box */
4237 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4240 ChessMove *moveType;
4241 int *fromX, *fromY, *toX, *toY;
4244 if (appData.debugMode) {
4245 fprintf(debugFP, "move to parse: %s\n", move);
4247 *moveType = yylexstr(moveNum, move);
4249 switch (*moveType) {
4250 case WhitePromotionChancellor:
4251 case BlackPromotionChancellor:
4252 case WhitePromotionArchbishop:
4253 case BlackPromotionArchbishop:
4254 case WhitePromotionQueen:
4255 case BlackPromotionQueen:
4256 case WhitePromotionRook:
4257 case BlackPromotionRook:
4258 case WhitePromotionBishop:
4259 case BlackPromotionBishop:
4260 case WhitePromotionKnight:
4261 case BlackPromotionKnight:
4262 case WhitePromotionKing:
4263 case BlackPromotionKing:
4265 case WhiteCapturesEnPassant:
4266 case BlackCapturesEnPassant:
4267 case WhiteKingSideCastle:
4268 case WhiteQueenSideCastle:
4269 case BlackKingSideCastle:
4270 case BlackQueenSideCastle:
4271 case WhiteKingSideCastleWild:
4272 case WhiteQueenSideCastleWild:
4273 case BlackKingSideCastleWild:
4274 case BlackQueenSideCastleWild:
4275 /* Code added by Tord: */
4276 case WhiteHSideCastleFR:
4277 case WhiteASideCastleFR:
4278 case BlackHSideCastleFR:
4279 case BlackASideCastleFR:
4280 /* End of code added by Tord */
4281 case IllegalMove: /* bug or odd chess variant */
4282 *fromX = currentMoveString[0] - AAA;
4283 *fromY = currentMoveString[1] - ONE;
4284 *toX = currentMoveString[2] - AAA;
4285 *toY = currentMoveString[3] - ONE;
4286 *promoChar = currentMoveString[4];
4287 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4288 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4289 if (appData.debugMode) {
4290 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4292 *fromX = *fromY = *toX = *toY = 0;
4295 if (appData.testLegality) {
4296 return (*moveType != IllegalMove);
4298 return !(fromX == fromY && toX == toY);
4303 *fromX = *moveType == WhiteDrop ?
4304 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4305 (int) CharToPiece(ToLower(currentMoveString[0]));
4307 *toX = currentMoveString[2] - AAA;
4308 *toY = currentMoveString[3] - ONE;
4309 *promoChar = NULLCHAR;
4313 case ImpossibleMove:
4314 case (ChessMove) 0: /* end of file */
4323 if (appData.debugMode) {
4324 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4327 *fromX = *fromY = *toX = *toY = 0;
4328 *promoChar = NULLCHAR;
4333 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4334 // All positions will have equal probability, but the current method will not provide a unique
4335 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4341 int piecesLeft[(int)BlackPawn];
4342 int seed, nrOfShuffles;
4344 void GetPositionNumber()
4345 { // sets global variable seed
4348 seed = appData.defaultFrcPosition;
4349 if(seed < 0) { // randomize based on time for negative FRC position numbers
4350 for(i=0; i<50; i++) seed += random();
4351 seed = random() ^ random() >> 8 ^ random() << 8;
4352 if(seed<0) seed = -seed;
4356 int put(Board board, int pieceType, int rank, int n, int shade)
4357 // put the piece on the (n-1)-th empty squares of the given shade
4361 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4362 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4363 board[rank][i] = (ChessSquare) pieceType;
4364 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4366 piecesLeft[pieceType]--;
4374 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4375 // calculate where the next piece goes, (any empty square), and put it there
4379 i = seed % squaresLeft[shade];
4380 nrOfShuffles *= squaresLeft[shade];
4381 seed /= squaresLeft[shade];
4382 put(board, pieceType, rank, i, shade);
4385 void AddTwoPieces(Board board, int pieceType, int rank)
4386 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4388 int i, n=squaresLeft[ANY], j=n-1, k;
4390 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4391 i = seed % k; // pick one
4394 while(i >= j) i -= j--;
4395 j = n - 1 - j; i += j;
4396 put(board, pieceType, rank, j, ANY);
4397 put(board, pieceType, rank, i, ANY);
4400 void SetUpShuffle(Board board, int number)
4404 GetPositionNumber(); nrOfShuffles = 1;
4406 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4407 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4408 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4410 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4412 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4413 p = (int) board[0][i];
4414 if(p < (int) BlackPawn) piecesLeft[p] ++;
4415 board[0][i] = EmptySquare;
4418 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4419 // shuffles restricted to allow normal castling put KRR first
4420 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4421 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4422 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4423 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4424 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4425 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4426 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4427 put(board, WhiteRook, 0, 0, ANY);
4428 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4431 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4432 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4433 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4434 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4435 while(piecesLeft[p] >= 2) {
4436 AddOnePiece(board, p, 0, LITE);
4437 AddOnePiece(board, p, 0, DARK);
4439 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4442 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4443 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4444 // but we leave King and Rooks for last, to possibly obey FRC restriction
4445 if(p == (int)WhiteRook) continue;
4446 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4447 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4450 // now everything is placed, except perhaps King (Unicorn) and Rooks
4452 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4453 // Last King gets castling rights
4454 while(piecesLeft[(int)WhiteUnicorn]) {
4455 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4456 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4459 while(piecesLeft[(int)WhiteKing]) {
4460 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4461 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4466 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4467 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4470 // Only Rooks can be left; simply place them all
4471 while(piecesLeft[(int)WhiteRook]) {
4472 i = put(board, WhiteRook, 0, 0, ANY);
4473 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4476 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4478 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4481 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4482 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4485 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4488 int SetCharTable( char *table, const char * map )
4489 /* [HGM] moved here from winboard.c because of its general usefulness */
4490 /* Basically a safe strcpy that uses the last character as King */
4492 int result = FALSE; int NrPieces;
4494 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4495 && NrPieces >= 12 && !(NrPieces&1)) {
4496 int i; /* [HGM] Accept even length from 12 to 34 */
4498 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4499 for( i=0; i<NrPieces/2-1; i++ ) {
4501 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4503 table[(int) WhiteKing] = map[NrPieces/2-1];
4504 table[(int) BlackKing] = map[NrPieces-1];
4512 void Prelude(Board board)
4513 { // [HGM] superchess: random selection of exo-pieces
4514 int i, j, k; ChessSquare p;
4515 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4517 GetPositionNumber(); // use FRC position number
4519 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4520 SetCharTable(pieceToChar, appData.pieceToCharTable);
4521 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4522 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4525 j = seed%4; seed /= 4;
4526 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4527 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4528 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4529 j = seed%3 + (seed%3 >= j); seed /= 3;
4530 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4531 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4532 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4533 j = seed%3; seed /= 3;
4534 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4535 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4536 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4537 j = seed%2 + (seed%2 >= j); seed /= 2;
4538 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4539 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4540 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4541 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4542 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4543 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4544 put(board, exoPieces[0], 0, 0, ANY);
4545 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4549 InitPosition(redraw)
4552 ChessSquare (* pieces)[BOARD_SIZE];
4553 int i, j, pawnRow, overrule,
4554 oldx = gameInfo.boardWidth,
4555 oldy = gameInfo.boardHeight,
4556 oldh = gameInfo.holdingsWidth,
4557 oldv = gameInfo.variant;
4559 currentMove = forwardMostMove = backwardMostMove = 0;
4560 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4562 /* [AS] Initialize pv info list [HGM] and game status */
4564 for( i=0; i<MAX_MOVES; i++ ) {
4565 pvInfoList[i].depth = 0;
4566 epStatus[i]=EP_NONE;
4567 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4570 initialRulePlies = 0; /* 50-move counter start */
4572 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4573 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4577 /* [HGM] logic here is completely changed. In stead of full positions */
4578 /* the initialized data only consist of the two backranks. The switch */
4579 /* selects which one we will use, which is than copied to the Board */
4580 /* initialPosition, which for the rest is initialized by Pawns and */
4581 /* empty squares. This initial position is then copied to boards[0], */
4582 /* possibly after shuffling, so that it remains available. */
4584 gameInfo.holdingsWidth = 0; /* default board sizes */
4585 gameInfo.boardWidth = 8;
4586 gameInfo.boardHeight = 8;
4587 gameInfo.holdingsSize = 0;
4588 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4589 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4590 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4592 switch (gameInfo.variant) {
4593 case VariantFischeRandom:
4594 shuffleOpenings = TRUE;
4598 case VariantShatranj:
4599 pieces = ShatranjArray;
4600 nrCastlingRights = 0;
4601 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4603 case VariantTwoKings:
4604 pieces = twoKingsArray;
4606 case VariantCapaRandom:
4607 shuffleOpenings = TRUE;
4608 case VariantCapablanca:
4609 pieces = CapablancaArray;
4610 gameInfo.boardWidth = 10;
4611 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4614 pieces = GothicArray;
4615 gameInfo.boardWidth = 10;
4616 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4619 pieces = JanusArray;
4620 gameInfo.boardWidth = 10;
4621 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4622 nrCastlingRights = 6;
4623 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4624 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4625 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4626 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4627 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4628 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4631 pieces = FalconArray;
4632 gameInfo.boardWidth = 10;
4633 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4635 case VariantXiangqi:
4636 pieces = XiangqiArray;
4637 gameInfo.boardWidth = 9;
4638 gameInfo.boardHeight = 10;
4639 nrCastlingRights = 0;
4640 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4643 pieces = ShogiArray;
4644 gameInfo.boardWidth = 9;
4645 gameInfo.boardHeight = 9;
4646 gameInfo.holdingsSize = 7;
4647 nrCastlingRights = 0;
4648 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4650 case VariantCourier:
4651 pieces = CourierArray;
4652 gameInfo.boardWidth = 12;
4653 nrCastlingRights = 0;
4654 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4655 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4657 case VariantKnightmate:
4658 pieces = KnightmateArray;
4659 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4662 pieces = fairyArray;
4663 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4666 pieces = GreatArray;
4667 gameInfo.boardWidth = 10;
4668 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4669 gameInfo.holdingsSize = 8;
4673 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4674 gameInfo.holdingsSize = 8;
4675 startedFromSetupPosition = TRUE;
4677 case VariantCrazyhouse:
4678 case VariantBughouse:
4680 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4681 gameInfo.holdingsSize = 5;
4683 case VariantWildCastle:
4685 /* !!?shuffle with kings guaranteed to be on d or e file */
4686 shuffleOpenings = 1;
4688 case VariantNoCastle:
4690 nrCastlingRights = 0;
4691 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692 /* !!?unconstrained back-rank shuffle */
4693 shuffleOpenings = 1;
4698 if(appData.NrFiles >= 0) {
4699 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4700 gameInfo.boardWidth = appData.NrFiles;
4702 if(appData.NrRanks >= 0) {
4703 gameInfo.boardHeight = appData.NrRanks;
4705 if(appData.holdingsSize >= 0) {
4706 i = appData.holdingsSize;
4707 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4708 gameInfo.holdingsSize = i;
4710 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4711 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4712 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4714 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4715 if(pawnRow < 1) pawnRow = 1;
4717 /* User pieceToChar list overrules defaults */
4718 if(appData.pieceToCharTable != NULL)
4719 SetCharTable(pieceToChar, appData.pieceToCharTable);
4721 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4723 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4724 s = (ChessSquare) 0; /* account holding counts in guard band */
4725 for( i=0; i<BOARD_HEIGHT; i++ )
4726 initialPosition[i][j] = s;
4728 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4729 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4730 initialPosition[pawnRow][j] = WhitePawn;
4731 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4732 if(gameInfo.variant == VariantXiangqi) {
4734 initialPosition[pawnRow][j] =
4735 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4736 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4737 initialPosition[2][j] = WhiteCannon;
4738 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4742 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4744 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4747 initialPosition[1][j] = WhiteBishop;
4748 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4750 initialPosition[1][j] = WhiteRook;
4751 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4754 if( nrCastlingRights == -1) {
4755 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4756 /* This sets default castling rights from none to normal corners */
4757 /* Variants with other castling rights must set them themselves above */
4758 nrCastlingRights = 6;
4760 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4761 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4762 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4763 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4764 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4765 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4768 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4769 if(gameInfo.variant == VariantGreat) { // promotion commoners
4770 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4771 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4772 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4773 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4776 if(gameInfo.variant == VariantFischeRandom) {
4777 if( appData.defaultFrcPosition < 0 ) {
4778 ShuffleFRC( initialPosition );
4781 SetupFRC( initialPosition, appData.defaultFrcPosition );
4783 startedFromSetupPosition = TRUE;
4786 if (appData.debugMode) {
4787 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4789 if(shuffleOpenings) {
4790 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4791 startedFromSetupPosition = TRUE;
4794 if(startedFromPositionFile) {
4795 /* [HGM] loadPos: use PositionFile for every new game */
4796 CopyBoard(initialPosition, filePosition);
4797 for(i=0; i<nrCastlingRights; i++)
4798 castlingRights[0][i] = initialRights[i] = fileRights[i];
4799 startedFromSetupPosition = TRUE;
4802 CopyBoard(boards[0], initialPosition);
4804 if(oldx != gameInfo.boardWidth ||
4805 oldy != gameInfo.boardHeight ||
4806 oldh != gameInfo.holdingsWidth
4808 || oldv == VariantGothic || // For licensing popups
4809 gameInfo.variant == VariantGothic
4812 || oldv == VariantFalcon ||
4813 gameInfo.variant == VariantFalcon
4816 InitDrawingSizes(-2 ,0);
4819 DrawPosition(TRUE, boards[currentMove]);
4823 SendBoard(cps, moveNum)
4824 ChessProgramState *cps;
4827 char message[MSG_SIZ];
4829 if (cps->useSetboard) {
4830 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4831 sprintf(message, "setboard %s\n", fen);
4832 SendToProgram(message, cps);
4838 /* Kludge to set black to move, avoiding the troublesome and now
4839 * deprecated "black" command.
4841 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4843 SendToProgram("edit\n", cps);
4844 SendToProgram("#\n", cps);
4845 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4846 bp = &boards[moveNum][i][BOARD_LEFT];
4847 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4848 if ((int) *bp < (int) BlackPawn) {
4849 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4851 if(message[0] == '+' || message[0] == '~') {
4852 sprintf(message, "%c%c%c+\n",
4853 PieceToChar((ChessSquare)(DEMOTED *bp)),
4856 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4857 message[1] = BOARD_RGHT - 1 - j + '1';
4858 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4860 SendToProgram(message, cps);
4865 SendToProgram("c\n", cps);
4866 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4867 bp = &boards[moveNum][i][BOARD_LEFT];
4868 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4869 if (((int) *bp != (int) EmptySquare)
4870 && ((int) *bp >= (int) BlackPawn)) {
4871 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4873 if(message[0] == '+' || message[0] == '~') {
4874 sprintf(message, "%c%c%c+\n",
4875 PieceToChar((ChessSquare)(DEMOTED *bp)),
4878 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4879 message[1] = BOARD_RGHT - 1 - j + '1';
4880 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4882 SendToProgram(message, cps);
4887 SendToProgram(".\n", cps);
4889 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4893 IsPromotion(fromX, fromY, toX, toY)
4894 int fromX, fromY, toX, toY;
4896 /* [HGM] add Shogi promotions */
4897 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4900 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4901 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4902 /* [HGM] Note to self: line above also weeds out drops */
4903 piece = boards[currentMove][fromY][fromX];
4904 if(gameInfo.variant == VariantShogi) {
4905 promotionZoneSize = 3;
4906 highestPromotingPiece = (int)WhiteKing;
4907 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4908 and if in normal chess we then allow promotion to King, why not
4909 allow promotion of other piece in Shogi? */
4911 if((int)piece >= BlackPawn) {
4912 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4914 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4916 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4917 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4919 return ( (int)piece <= highestPromotingPiece );
4923 InPalace(row, column)
4925 { /* [HGM] for Xiangqi */
4926 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4927 column < (BOARD_WIDTH + 4)/2 &&
4928 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4933 PieceForSquare (x, y)
4937 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4940 return boards[currentMove][y][x];
4944 OKToStartUserMove(x, y)
4947 ChessSquare from_piece;
4950 if (matchMode) return FALSE;
4951 if (gameMode == EditPosition) return TRUE;
4953 if (x >= 0 && y >= 0)
4954 from_piece = boards[currentMove][y][x];
4956 from_piece = EmptySquare;
4958 if (from_piece == EmptySquare) return FALSE;
4960 white_piece = (int)from_piece >= (int)WhitePawn &&
4961 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4964 case PlayFromGameFile:
4966 case TwoMachinesPlay:
4974 case MachinePlaysWhite:
4975 case IcsPlayingBlack:
4976 if (appData.zippyPlay) return FALSE;
4978 DisplayMoveError(_("You are playing Black"));
4983 case MachinePlaysBlack:
4984 case IcsPlayingWhite:
4985 if (appData.zippyPlay) return FALSE;
4987 DisplayMoveError(_("You are playing White"));
4993 if (!white_piece && WhiteOnMove(currentMove)) {
4994 DisplayMoveError(_("It is White's turn"));
4997 if (white_piece && !WhiteOnMove(currentMove)) {
4998 DisplayMoveError(_("It is Black's turn"));
5001 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5002 /* Editing correspondence game history */
5003 /* Could disallow this or prompt for confirmation */
5006 if (currentMove < forwardMostMove) {
5007 /* Discarding moves */
5008 /* Could prompt for confirmation here,
5009 but I don't think that's such a good idea */
5010 forwardMostMove = currentMove;
5014 case BeginningOfGame:
5015 if (appData.icsActive) return FALSE;
5016 if (!appData.noChessProgram) {
5018 DisplayMoveError(_("You are playing White"));
5025 if (!white_piece && WhiteOnMove(currentMove)) {
5026 DisplayMoveError(_("It is White's turn"));
5029 if (white_piece && !WhiteOnMove(currentMove)) {
5030 DisplayMoveError(_("It is Black's turn"));
5039 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5040 && gameMode != AnalyzeFile && gameMode != Training) {
5041 DisplayMoveError(_("Displayed position is not current"));
5047 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5048 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5049 int lastLoadGameUseList = FALSE;
5050 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5051 ChessMove lastLoadGameStart = (ChessMove) 0;
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5056 int fromX, fromY, toX, toY;
5060 ChessSquare pdown, pup;
5062 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5063 if ((fromX == toX) && (fromY == toY)) {
5064 return ImpossibleMove;
5067 /* [HGM] suppress all moves into holdings area and guard band */
5068 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5069 return ImpossibleMove;
5071 /* [HGM] <sameColor> moved to here from winboard.c */
5072 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5073 pdown = boards[currentMove][fromY][fromX];
5074 pup = boards[currentMove][toY][toX];
5075 if ( gameMode != EditPosition &&
5076 (WhitePawn <= pdown && pdown < BlackPawn &&
5077 WhitePawn <= pup && pup < BlackPawn ||
5078 BlackPawn <= pdown && pdown < EmptySquare &&
5079 BlackPawn <= pup && pup < EmptySquare
5080 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5081 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5082 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5084 return ImpossibleMove;
5086 /* Check if the user is playing in turn. This is complicated because we
5087 let the user "pick up" a piece before it is his turn. So the piece he
5088 tried to pick up may have been captured by the time he puts it down!
5089 Therefore we use the color the user is supposed to be playing in this
5090 test, not the color of the piece that is currently on the starting
5091 square---except in EditGame mode, where the user is playing both
5092 sides; fortunately there the capture race can't happen. (It can
5093 now happen in IcsExamining mode, but that's just too bad. The user
5094 will get a somewhat confusing message in that case.)
5098 case PlayFromGameFile:
5100 case TwoMachinesPlay:
5104 /* We switched into a game mode where moves are not accepted,
5105 perhaps while the mouse button was down. */
5106 return ImpossibleMove;
5108 case MachinePlaysWhite:
5109 /* User is moving for Black */
5110 if (WhiteOnMove(currentMove)) {
5111 DisplayMoveError(_("It is White's turn"));
5112 return ImpossibleMove;
5116 case MachinePlaysBlack:
5117 /* User is moving for White */
5118 if (!WhiteOnMove(currentMove)) {
5119 DisplayMoveError(_("It is Black's turn"));
5120 return ImpossibleMove;
5126 case BeginningOfGame:
5129 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131 /* User is moving for Black */
5132 if (WhiteOnMove(currentMove)) {
5133 DisplayMoveError(_("It is White's turn"));
5134 return ImpossibleMove;
5137 /* User is moving for White */
5138 if (!WhiteOnMove(currentMove)) {
5139 DisplayMoveError(_("It is Black's turn"));
5140 return ImpossibleMove;
5145 case IcsPlayingBlack:
5146 /* User is moving for Black */
5147 if (WhiteOnMove(currentMove)) {
5148 if (!appData.premove) {
5149 DisplayMoveError(_("It is White's turn"));
5150 } else if (toX >= 0 && toY >= 0) {
5153 premoveFromX = fromX;
5154 premoveFromY = fromY;
5155 premovePromoChar = promoChar;
5157 if (appData.debugMode)
5158 fprintf(debugFP, "Got premove: fromX %d,"
5159 "fromY %d, toX %d, toY %d\n",
5160 fromX, fromY, toX, toY);
5162 return ImpossibleMove;
5166 case IcsPlayingWhite:
5167 /* User is moving for White */
5168 if (!WhiteOnMove(currentMove)) {
5169 if (!appData.premove) {
5170 DisplayMoveError(_("It is Black's turn"));
5171 } else if (toX >= 0 && toY >= 0) {
5174 premoveFromX = fromX;
5175 premoveFromY = fromY;
5176 premovePromoChar = promoChar;
5178 if (appData.debugMode)
5179 fprintf(debugFP, "Got premove: fromX %d,"
5180 "fromY %d, toX %d, toY %d\n",
5181 fromX, fromY, toX, toY);
5183 return ImpossibleMove;
5191 /* EditPosition, empty square, or different color piece;
5192 click-click move is possible */
5193 if (toX == -2 || toY == -2) {
5194 boards[0][fromY][fromX] = EmptySquare;
5195 return AmbiguousMove;
5196 } else if (toX >= 0 && toY >= 0) {
5197 boards[0][toY][toX] = boards[0][fromY][fromX];
5198 boards[0][fromY][fromX] = EmptySquare;
5199 return AmbiguousMove;
5201 return ImpossibleMove;
5204 /* [HGM] If move started in holdings, it means a drop */
5205 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5206 if( pup != EmptySquare ) return ImpossibleMove;
5207 if(appData.testLegality) {
5208 /* it would be more logical if LegalityTest() also figured out
5209 * which drops are legal. For now we forbid pawns on back rank.
5210 * Shogi is on its own here...
5212 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5213 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5214 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5216 return WhiteDrop; /* Not needed to specify white or black yet */
5219 userOfferedDraw = FALSE;
5221 /* [HGM] always test for legality, to get promotion info */
5222 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5223 epStatus[currentMove], castlingRights[currentMove],
5224 fromY, fromX, toY, toX, promoChar);
5226 /* [HGM] but possibly ignore an IllegalMove result */
5227 if (appData.testLegality) {
5228 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5229 DisplayMoveError(_("Illegal move"));
5230 return ImpossibleMove;
5233 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5235 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5236 function is made into one that returns an OK move type if FinishMove
5237 should be called. This to give the calling driver routine the
5238 opportunity to finish the userMove input with a promotion popup,
5239 without bothering the user with this for invalid or illegal moves */
5241 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5244 /* Common tail of UserMoveEvent and DropMenuEvent */
5246 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5248 int fromX, fromY, toX, toY;
5249 /*char*/int promoChar;
5252 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5253 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5254 // [HGM] superchess: suppress promotions to non-available piece
5255 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5256 if(WhiteOnMove(currentMove)) {
5257 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5259 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5263 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5264 move type in caller when we know the move is a legal promotion */
5265 if(moveType == NormalMove && promoChar)
5266 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5267 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5268 /* [HGM] convert drag-and-drop piece drops to standard form */
5269 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5270 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5271 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5272 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5273 // fromX = boards[currentMove][fromY][fromX];
5274 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5275 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5276 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5277 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5281 /* [HGM] <popupFix> The following if has been moved here from
5282 UserMoveEvent(). Because it seemed to belon here (why not allow
5283 piece drops in training games?), and because it can only be
5284 performed after it is known to what we promote. */
5285 if (gameMode == Training) {
5286 /* compare the move played on the board to the next move in the
5287 * game. If they match, display the move and the opponent's response.
5288 * If they don't match, display an error message.
5291 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5292 CopyBoard(testBoard, boards[currentMove]);
5293 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5295 if (CompareBoards(testBoard, boards[currentMove+1])) {
5296 ForwardInner(currentMove+1);
5298 /* Autoplay the opponent's response.
5299 * if appData.animate was TRUE when Training mode was entered,
5300 * the response will be animated.
5302 saveAnimate = appData.animate;
5303 appData.animate = animateTraining;
5304 ForwardInner(currentMove+1);
5305 appData.animate = saveAnimate;
5307 /* check for the end of the game */
5308 if (currentMove >= forwardMostMove) {
5309 gameMode = PlayFromGameFile;
5311 SetTrainingModeOff();
5312 DisplayInformation(_("End of game"));
5315 DisplayError(_("Incorrect move"), 0);
5320 /* Ok, now we know that the move is good, so we can kill
5321 the previous line in Analysis Mode */
5322 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5323 forwardMostMove = currentMove;
5326 /* If we need the chess program but it's dead, restart it */
5327 ResurrectChessProgram();
5329 /* A user move restarts a paused game*/
5333 thinkOutput[0] = NULLCHAR;
5335 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5337 if (gameMode == BeginningOfGame) {
5338 if (appData.noChessProgram) {
5339 gameMode = EditGame;
5343 gameMode = MachinePlaysBlack;
5346 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5348 if (first.sendName) {
5349 sprintf(buf, "name %s\n", gameInfo.white);
5350 SendToProgram(buf, &first);
5356 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5357 /* Relay move to ICS or chess engine */
5358 if (appData.icsActive) {
5359 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5360 gameMode == IcsExamining) {
5361 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5365 if (first.sendTime && (gameMode == BeginningOfGame ||
5366 gameMode == MachinePlaysWhite ||
5367 gameMode == MachinePlaysBlack)) {
5368 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5370 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5371 // [HGM] book: if program might be playing, let it use book
5372 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5373 first.maybeThinking = TRUE;
5374 } else SendMoveToProgram(forwardMostMove-1, &first);
5375 if (currentMove == cmailOldMove + 1) {
5376 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5380 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5384 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5385 EP_UNKNOWN, castlingRights[currentMove]) ) {
5391 if (WhiteOnMove(currentMove)) {
5392 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5394 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5398 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5403 case MachinePlaysBlack:
5404 case MachinePlaysWhite:
5405 /* disable certain menu options while machine is thinking */
5406 SetMachineThinkingEnables();
5413 if(bookHit) { // [HGM] book: simulate book reply
5414 static char bookMove[MSG_SIZ]; // a bit generous?
5416 programStats.nodes = programStats.depth = programStats.time =
5417 programStats.score = programStats.got_only_move = 0;
5418 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5420 strcpy(bookMove, "move ");
5421 strcat(bookMove, bookHit);
5422 HandleMachineMove(bookMove, &first);
5428 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5429 int fromX, fromY, toX, toY;
5432 /* [HGM] This routine was added to allow calling of its two logical
5433 parts from other modules in the old way. Before, UserMoveEvent()
5434 automatically called FinishMove() if the move was OK, and returned
5435 otherwise. I separated the two, in order to make it possible to
5436 slip a promotion popup in between. But that it always needs two
5437 calls, to the first part, (now called UserMoveTest() ), and to
5438 FinishMove if the first part succeeded. Calls that do not need
5439 to do anything in between, can call this routine the old way.
5441 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5442 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5443 if(moveType != ImpossibleMove)
5444 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5447 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5449 // char * hint = lastHint;
5450 FrontEndProgramStats stats;
5452 stats.which = cps == &first ? 0 : 1;
5453 stats.depth = cpstats->depth;
5454 stats.nodes = cpstats->nodes;
5455 stats.score = cpstats->score;
5456 stats.time = cpstats->time;
5457 stats.pv = cpstats->movelist;
5458 stats.hint = lastHint;
5459 stats.an_move_index = 0;
5460 stats.an_move_count = 0;
5462 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5463 stats.hint = cpstats->move_name;
5464 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5465 stats.an_move_count = cpstats->nr_moves;
5468 SetProgramStats( &stats );
5471 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5472 { // [HGM] book: this routine intercepts moves to simulate book replies
5473 char *bookHit = NULL;
5475 //first determine if the incoming move brings opponent into his book
5476 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5477 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5478 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5479 if(bookHit != NULL && !cps->bookSuspend) {
5480 // make sure opponent is not going to reply after receiving move to book position
5481 SendToProgram("force\n", cps);
5482 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5484 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5485 // now arrange restart after book miss
5487 // after a book hit we never send 'go', and the code after the call to this routine
5488 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5490 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5491 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5492 SendToProgram(buf, cps);
5493 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5494 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5495 SendToProgram("go\n", cps);
5496 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5497 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5498 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5499 SendToProgram("go\n", cps);
5500 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5502 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5506 ChessProgramState *savedState;
5507 void DeferredBookMove(void)
5509 if(savedState->lastPing != savedState->lastPong)
5510 ScheduleDelayedEvent(DeferredBookMove, 10);
5512 HandleMachineMove(savedMessage, savedState);
5516 HandleMachineMove(message, cps)
5518 ChessProgramState *cps;
5520 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5521 char realname[MSG_SIZ];
5522 int fromX, fromY, toX, toY;
5529 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5531 * Kludge to ignore BEL characters
5533 while (*message == '\007') message++;
5536 * [HGM] engine debug message: ignore lines starting with '#' character
5538 if(cps->debug && *message == '#') return;
5541 * Look for book output
5543 if (cps == &first && bookRequested) {
5544 if (message[0] == '\t' || message[0] == ' ') {
5545 /* Part of the book output is here; append it */
5546 strcat(bookOutput, message);
5547 strcat(bookOutput, " \n");
5549 } else if (bookOutput[0] != NULLCHAR) {
5550 /* All of book output has arrived; display it */
5551 char *p = bookOutput;
5552 while (*p != NULLCHAR) {
5553 if (*p == '\t') *p = ' ';
5556 DisplayInformation(bookOutput);
5557 bookRequested = FALSE;
5558 /* Fall through to parse the current output */
5563 * Look for machine move.
5565 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5566 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5568 /* This method is only useful on engines that support ping */
5569 if (cps->lastPing != cps->lastPong) {
5570 if (gameMode == BeginningOfGame) {
5571 /* Extra move from before last new; ignore */
5572 if (appData.debugMode) {
5573 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5576 if (appData.debugMode) {
5577 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5578 cps->which, gameMode);
5581 SendToProgram("undo\n", cps);
5587 case BeginningOfGame:
5588 /* Extra move from before last reset; ignore */
5589 if (appData.debugMode) {
5590 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5597 /* Extra move after we tried to stop. The mode test is
5598 not a reliable way of detecting this problem, but it's
5599 the best we can do on engines that don't support ping.
5601 if (appData.debugMode) {
5602 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5603 cps->which, gameMode);
5605 SendToProgram("undo\n", cps);
5608 case MachinePlaysWhite:
5609 case IcsPlayingWhite:
5610 machineWhite = TRUE;
5613 case MachinePlaysBlack:
5614 case IcsPlayingBlack:
5615 machineWhite = FALSE;
5618 case TwoMachinesPlay:
5619 machineWhite = (cps->twoMachinesColor[0] == 'w');
5622 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5623 if (appData.debugMode) {
5625 "Ignoring move out of turn by %s, gameMode %d"
5626 ", forwardMost %d\n",
5627 cps->which, gameMode, forwardMostMove);
5632 if (appData.debugMode) { int f = forwardMostMove;
5633 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5634 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5636 if(cps->alphaRank) AlphaRank(machineMove, 4);
5637 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5638 &fromX, &fromY, &toX, &toY, &promoChar)) {
5639 /* Machine move could not be parsed; ignore it. */
5640 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5641 machineMove, cps->which);
5642 DisplayError(buf1, 0);
5643 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5644 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5645 if (gameMode == TwoMachinesPlay) {
5646 GameEnds(machineWhite ? BlackWins : WhiteWins,
5652 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5653 /* So we have to redo legality test with true e.p. status here, */
5654 /* to make sure an illegal e.p. capture does not slip through, */
5655 /* to cause a forfeit on a justified illegal-move complaint */
5656 /* of the opponent. */
5657 if( gameMode==TwoMachinesPlay && appData.testLegality
5658 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5661 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5662 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5663 fromY, fromX, toY, toX, promoChar);
5664 if (appData.debugMode) {
5666 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5667 castlingRights[forwardMostMove][i], castlingRank[i]);
5668 fprintf(debugFP, "castling rights\n");
5670 if(moveType == IllegalMove) {
5671 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5672 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5673 GameEnds(machineWhite ? BlackWins : WhiteWins,
5676 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5677 /* [HGM] Kludge to handle engines that send FRC-style castling
5678 when they shouldn't (like TSCP-Gothic) */
5680 case WhiteASideCastleFR:
5681 case BlackASideCastleFR:
5683 currentMoveString[2]++;
5685 case WhiteHSideCastleFR:
5686 case BlackHSideCastleFR:
5688 currentMoveString[2]--;
5690 default: ; // nothing to do, but suppresses warning of pedantic compilers
5693 hintRequested = FALSE;
5694 lastHint[0] = NULLCHAR;
5695 bookRequested = FALSE;
5696 /* Program may be pondering now */
5697 cps->maybeThinking = TRUE;
5698 if (cps->sendTime == 2) cps->sendTime = 1;
5699 if (cps->offeredDraw) cps->offeredDraw--;
5702 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5704 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5706 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5707 char buf[3*MSG_SIZ];
5709 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5710 programStats.score / 100.,
5712 programStats.time / 100.,
5713 (unsigned int)programStats.nodes,
5714 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5715 programStats.movelist);
5717 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5721 /* currentMoveString is set as a side-effect of ParseOneMove */
5722 strcpy(machineMove, currentMoveString);
5723 strcat(machineMove, "\n");
5724 strcpy(moveList[forwardMostMove], machineMove);
5726 /* [AS] Save move info and clear stats for next move */
5727 pvInfoList[ forwardMostMove ].score = programStats.score;
5728 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5729 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5730 ClearProgramStats();
5731 thinkOutput[0] = NULLCHAR;
5732 hiddenThinkOutputState = 0;
5734 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5736 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5737 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5740 while( count < adjudicateLossPlies ) {
5741 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5744 score = -score; /* Flip score for winning side */
5747 if( score > adjudicateLossThreshold ) {
5754 if( count >= adjudicateLossPlies ) {
5755 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5757 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5758 "Xboard adjudication",
5765 if( gameMode == TwoMachinesPlay ) {
5766 // [HGM] some adjudications useful with buggy engines
5767 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5768 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5771 if( appData.testLegality )
5772 { /* [HGM] Some more adjudications for obstinate engines */
5773 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5774 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5775 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5776 static int moveCount = 6;
5778 char *reason = NULL;
5780 /* Count what is on board. */
5781 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5782 { ChessSquare p = boards[forwardMostMove][i][j];
5786 { /* count B,N,R and other of each side */
5789 NrK++; break; // [HGM] atomic: count Kings
5793 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5794 bishopsColor |= 1 << ((i^j)&1);
5799 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5800 bishopsColor |= 1 << ((i^j)&1);
5815 PawnAdvance += m; NrPawns++;
5817 NrPieces += (p != EmptySquare);
5818 NrW += ((int)p < (int)BlackPawn);
5819 if(gameInfo.variant == VariantXiangqi &&
5820 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5821 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5822 NrW -= ((int)p < (int)BlackPawn);
5826 /* Some material-based adjudications that have to be made before stalemate test */
5827 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5828 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5829 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5830 if(appData.checkMates) {
5831 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5832 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5833 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5834 "Xboard adjudication: King destroyed", GE_XBOARD );
5839 /* Bare King in Shatranj (loses) or Losers (wins) */
5840 if( NrW == 1 || NrPieces - NrW == 1) {
5841 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5842 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5843 if(appData.checkMates) {
5844 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5845 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5846 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5847 "Xboard adjudication: Bare king", GE_XBOARD );
5851 if( gameInfo.variant == VariantShatranj && --bare < 0)
5853 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5854 if(appData.checkMates) {
5855 /* but only adjudicate if adjudication enabled */
5856 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5857 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5859 "Xboard adjudication: Bare king", GE_XBOARD );
5866 // don't wait for engine to announce game end if we can judge ourselves
5867 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5868 castlingRights[forwardMostMove]) ) {
5870 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5871 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5872 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5873 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5876 reason = "Xboard adjudication: 3rd check";
5877 epStatus[forwardMostMove] = EP_CHECKMATE;
5887 reason = "Xboard adjudication: Stalemate";
5888 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5889 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5890 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5891 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5892 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5893 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5894 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5895 EP_CHECKMATE : EP_WINS);
5896 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5897 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5901 reason = "Xboard adjudication: Checkmate";
5902 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5906 switch(i = epStatus[forwardMostMove]) {
5908 result = GameIsDrawn; break;
5910 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5912 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5914 result = (ChessMove) 0;
5916 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5917 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5918 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5919 GameEnds( result, reason, GE_XBOARD );
5923 /* Next absolutely insufficient mating material. */
5924 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5925 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5926 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5927 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5928 { /* KBK, KNK, KK of KBKB with like Bishops */
5930 /* always flag draws, for judging claims */
5931 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5933 if(appData.materialDraws) {
5934 /* but only adjudicate them if adjudication enabled */
5935 SendToProgram("force\n", cps->other); // suppress reply
5936 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5937 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5938 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5943 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5945 ( NrWR == 1 && NrBR == 1 /* KRKR */
5946 || NrWQ==1 && NrBQ==1 /* KQKQ */
5947 || NrWN==2 || NrBN==2 /* KNNK */
5948 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5950 if(--moveCount < 0 && appData.trivialDraws)
5951 { /* if the first 3 moves do not show a tactical win, declare draw */
5952 SendToProgram("force\n", cps->other); // suppress reply
5953 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5954 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5955 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5958 } else moveCount = 6;
5962 if (appData.debugMode) { int i;
5963 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5964 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5965 appData.drawRepeats);
5966 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5967 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5971 /* Check for rep-draws */
5973 for(k = forwardMostMove-2;
5974 k>=backwardMostMove && k>=forwardMostMove-100 &&
5975 epStatus[k] < EP_UNKNOWN &&
5976 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5980 if (appData.debugMode) {
5981 fprintf(debugFP, " loop\n");
5984 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5986 if (appData.debugMode) {
5987 fprintf(debugFP, "match\n");
5990 /* compare castling rights */
5991 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5992 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5993 rights++; /* King lost rights, while rook still had them */
5994 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5995 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5996 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5997 rights++; /* but at least one rook lost them */
5999 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6000 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6002 if( castlingRights[forwardMostMove][5] >= 0 ) {
6003 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6004 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6008 if (appData.debugMode) {
6009 for(i=0; i<nrCastlingRights; i++)
6010 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6013 if (appData.debugMode) {
6014 fprintf(debugFP, " %d %d\n", rights, k);
6017 if( rights == 0 && ++count > appData.drawRepeats-2
6018 && appData.drawRepeats > 1) {
6019 /* adjudicate after user-specified nr of repeats */
6020 SendToProgram("force\n", cps->other); // suppress reply
6021 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6022 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6024 // [HGM] xiangqi: check for forbidden perpetuals
6025 int m, ourPerpetual = 1, hisPerpetual = 1;
6026 for(m=forwardMostMove; m>k; m-=2) {
6027 if(MateTest(boards[m], PosFlags(m),
6028 EP_NONE, castlingRights[m]) != MT_CHECK)
6029 ourPerpetual = 0; // the current mover did not always check
6030 if(MateTest(boards[m-1], PosFlags(m-1),
6031 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6032 hisPerpetual = 0; // the opponent did not always check
6034 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6035 ourPerpetual, hisPerpetual);
6036 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6037 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6038 "Xboard adjudication: perpetual checking", GE_XBOARD );
6041 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6042 break; // (or we would have caught him before). Abort repetition-checking loop.
6043 // Now check for perpetual chases
6044 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6045 hisPerpetual = PerpetualChase(k, forwardMostMove);
6046 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6047 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6048 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6049 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6052 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6053 break; // Abort repetition-checking loop.
6055 // if neither of us is checking or chasing all the time, or both are, it is draw
6057 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6060 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6061 epStatus[forwardMostMove] = EP_REP_DRAW;
6065 /* Now we test for 50-move draws. Determine ply count */
6066 count = forwardMostMove;
6067 /* look for last irreversble move */
6068 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6070 /* if we hit starting position, add initial plies */
6071 if( count == backwardMostMove )
6072 count -= initialRulePlies;
6073 count = forwardMostMove - count;
6075 epStatus[forwardMostMove] = EP_RULE_DRAW;
6076 /* this is used to judge if draw claims are legal */
6077 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6078 SendToProgram("force\n", cps->other); // suppress reply
6079 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6080 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6081 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6085 /* if draw offer is pending, treat it as a draw claim
6086 * when draw condition present, to allow engines a way to
6087 * claim draws before making their move to avoid a race
6088 * condition occurring after their move
6090 if( cps->other->offeredDraw || cps->offeredDraw ) {
6092 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6093 p = "Draw claim: 50-move rule";
6094 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6095 p = "Draw claim: 3-fold repetition";
6096 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6097 p = "Draw claim: insufficient mating material";
6099 SendToProgram("force\n", cps->other); // suppress reply
6100 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6101 GameEnds( GameIsDrawn, p, GE_XBOARD );
6102 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6108 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6109 SendToProgram("force\n", cps->other); // suppress reply
6110 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6111 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6113 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6120 if (gameMode == TwoMachinesPlay) {
6121 /* [HGM] relaying draw offers moved to after reception of move */
6122 /* and interpreting offer as claim if it brings draw condition */
6123 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6124 SendToProgram("draw\n", cps->other);
6126 if (cps->other->sendTime) {
6127 SendTimeRemaining(cps->other,
6128 cps->other->twoMachinesColor[0] == 'w');
6130 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6131 if (firstMove && !bookHit) {
6133 if (cps->other->useColors) {
6134 SendToProgram(cps->other->twoMachinesColor, cps->other);
6136 SendToProgram("go\n", cps->other);
6138 cps->other->maybeThinking = TRUE;
6141 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6143 if (!pausing && appData.ringBellAfterMoves) {
6148 * Reenable menu items that were disabled while
6149 * machine was thinking
6151 if (gameMode != TwoMachinesPlay)
6152 SetUserThinkingEnables();
6154 // [HGM] book: after book hit opponent has received move and is now in force mode
6155 // force the book reply into it, and then fake that it outputted this move by jumping
6156 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6158 static char bookMove[MSG_SIZ]; // a bit generous?
6160 strcpy(bookMove, "move ");
6161 strcat(bookMove, bookHit);
6164 programStats.nodes = programStats.depth = programStats.time =
6165 programStats.score = programStats.got_only_move = 0;
6166 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6168 if(cps->lastPing != cps->lastPong) {
6169 savedMessage = message; // args for deferred call
6171 ScheduleDelayedEvent(DeferredBookMove, 10);
6180 /* Set special modes for chess engines. Later something general
6181 * could be added here; for now there is just one kludge feature,
6182 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6183 * when "xboard" is given as an interactive command.
6185 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6186 cps->useSigint = FALSE;
6187 cps->useSigterm = FALSE;
6189 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6190 ParseFeatures(message+8, cps);
6191 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6194 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6195 * want this, I was asked to put it in, and obliged.
6197 if (!strncmp(message, "setboard ", 9)) {
6198 Board initial_position; int i;
6200 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6202 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6203 DisplayError(_("Bad FEN received from engine"), 0);
6206 Reset(FALSE, FALSE);
6207 CopyBoard(boards[0], initial_position);
6208 initialRulePlies = FENrulePlies;
6209 epStatus[0] = FENepStatus;
6210 for( i=0; i<nrCastlingRights; i++ )
6211 castlingRights[0][i] = FENcastlingRights[i];
6212 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6213 else gameMode = MachinePlaysBlack;
6214 DrawPosition(FALSE, boards[currentMove]);
6220 * Look for communication commands
6222 if (!strncmp(message, "telluser ", 9)) {
6223 DisplayNote(message + 9);
6226 if (!strncmp(message, "tellusererror ", 14)) {
6227 DisplayError(message + 14, 0);
6230 if (!strncmp(message, "tellopponent ", 13)) {
6231 if (appData.icsActive) {
6233 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6237 DisplayNote(message + 13);
6241 if (!strncmp(message, "tellothers ", 11)) {
6242 if (appData.icsActive) {
6244 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6250 if (!strncmp(message, "tellall ", 8)) {
6251 if (appData.icsActive) {
6253 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6257 DisplayNote(message + 8);
6261 if (strncmp(message, "warning", 7) == 0) {
6262 /* Undocumented feature, use tellusererror in new code */
6263 DisplayError(message, 0);
6266 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6267 strcpy(realname, cps->tidy);
6268 strcat(realname, " query");
6269 AskQuestion(realname, buf2, buf1, cps->pr);
6272 /* Commands from the engine directly to ICS. We don't allow these to be
6273 * sent until we are logged on. Crafty kibitzes have been known to
6274 * interfere with the login process.
6277 if (!strncmp(message, "tellics ", 8)) {
6278 SendToICS(message + 8);
6282 if (!strncmp(message, "tellicsnoalias ", 15)) {
6283 SendToICS(ics_prefix);
6284 SendToICS(message + 15);
6288 /* The following are for backward compatibility only */
6289 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6290 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6291 SendToICS(ics_prefix);
6297 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6301 * If the move is illegal, cancel it and redraw the board.
6302 * Also deal with other error cases. Matching is rather loose
6303 * here to accommodate engines written before the spec.
6305 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6306 strncmp(message, "Error", 5) == 0) {
6307 if (StrStr(message, "name") ||
6308 StrStr(message, "rating") || StrStr(message, "?") ||
6309 StrStr(message, "result") || StrStr(message, "board") ||
6310 StrStr(message, "bk") || StrStr(message, "computer") ||
6311 StrStr(message, "variant") || StrStr(message, "hint") ||
6312 StrStr(message, "random") || StrStr(message, "depth") ||
6313 StrStr(message, "accepted")) {
6316 if (StrStr(message, "protover")) {
6317 /* Program is responding to input, so it's apparently done
6318 initializing, and this error message indicates it is
6319 protocol version 1. So we don't need to wait any longer
6320 for it to initialize and send feature commands. */
6321 FeatureDone(cps, 1);
6322 cps->protocolVersion = 1;
6325 cps->maybeThinking = FALSE;
6327 if (StrStr(message, "draw")) {
6328 /* Program doesn't have "draw" command */
6329 cps->sendDrawOffers = 0;
6332 if (cps->sendTime != 1 &&
6333 (StrStr(message, "time") || StrStr(message, "otim"))) {
6334 /* Program apparently doesn't have "time" or "otim" command */
6338 if (StrStr(message, "analyze")) {
6339 cps->analysisSupport = FALSE;
6340 cps->analyzing = FALSE;
6342 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6343 DisplayError(buf2, 0);
6346 if (StrStr(message, "(no matching move)st")) {
6347 /* Special kludge for GNU Chess 4 only */
6348 cps->stKludge = TRUE;
6349 SendTimeControl(cps, movesPerSession, timeControl,
6350 timeIncrement, appData.searchDepth,
6354 if (StrStr(message, "(no matching move)sd")) {
6355 /* Special kludge for GNU Chess 4 only */
6356 cps->sdKludge = TRUE;
6357 SendTimeControl(cps, movesPerSession, timeControl,
6358 timeIncrement, appData.searchDepth,
6362 if (!StrStr(message, "llegal")) {
6365 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6366 gameMode == IcsIdle) return;
6367 if (forwardMostMove <= backwardMostMove) return;
6369 /* Following removed: it caused a bug where a real illegal move
6370 message in analyze mored would be ignored. */
6371 if (cps == &first && programStats.ok_to_send == 0) {
6372 /* Bogus message from Crafty responding to "." This filtering
6373 can miss some of the bad messages, but fortunately the bug
6374 is fixed in current Crafty versions, so it doesn't matter. */
6378 if (pausing) PauseEvent();
6379 if (gameMode == PlayFromGameFile) {
6380 /* Stop reading this game file */
6381 gameMode = EditGame;
6384 currentMove = --forwardMostMove;
6385 DisplayMove(currentMove-1); /* before DisplayMoveError */
6387 DisplayBothClocks();
6388 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6389 parseList[currentMove], cps->which);
6390 DisplayMoveError(buf1);
6391 DrawPosition(FALSE, boards[currentMove]);
6393 /* [HGM] illegal-move claim should forfeit game when Xboard */
6394 /* only passes fully legal moves */
6395 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6396 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6397 "False illegal-move claim", GE_XBOARD );
6401 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6402 /* Program has a broken "time" command that
6403 outputs a string not ending in newline.
6409 * If chess program startup fails, exit with an error message.
6410 * Attempts to recover here are futile.
6412 if ((StrStr(message, "unknown host") != NULL)
6413 || (StrStr(message, "No remote directory") != NULL)
6414 || (StrStr(message, "not found") != NULL)
6415 || (StrStr(message, "No such file") != NULL)
6416 || (StrStr(message, "can't alloc") != NULL)
6417 || (StrStr(message, "Permission denied") != NULL)) {
6419 cps->maybeThinking = FALSE;
6420 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6421 cps->which, cps->program, cps->host, message);
6422 RemoveInputSource(cps->isr);
6423 DisplayFatalError(buf1, 0, 1);
6428 * Look for hint output
6430 if (sscanf(message, "Hint: %s", buf1) == 1) {
6431 if (cps == &first && hintRequested) {
6432 hintRequested = FALSE;
6433 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6434 &fromX, &fromY, &toX, &toY, &promoChar)) {
6435 (void) CoordsToAlgebraic(boards[forwardMostMove],
6436 PosFlags(forwardMostMove), EP_UNKNOWN,
6437 fromY, fromX, toY, toX, promoChar, buf1);
6438 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6439 DisplayInformation(buf2);
6441 /* Hint move could not be parsed!? */
6442 snprintf(buf2, sizeof(buf2),
6443 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6445 DisplayError(buf2, 0);
6448 strcpy(lastHint, buf1);
6454 * Ignore other messages if game is not in progress
6456 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6457 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6460 * look for win, lose, draw, or draw offer
6462 if (strncmp(message, "1-0", 3) == 0) {
6463 char *p, *q, *r = "";
6464 p = strchr(message, '{');
6472 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6474 } else if (strncmp(message, "0-1", 3) == 0) {
6475 char *p, *q, *r = "";
6476 p = strchr(message, '{');
6484 /* Kludge for Arasan 4.1 bug */
6485 if (strcmp(r, "Black resigns") == 0) {
6486 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6489 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6491 } else if (strncmp(message, "1/2", 3) == 0) {
6492 char *p, *q, *r = "";
6493 p = strchr(message, '{');
6502 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6505 } else if (strncmp(message, "White resign", 12) == 0) {
6506 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6508 } else if (strncmp(message, "Black resign", 12) == 0) {
6509 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6511 } else if (strncmp(message, "White matches", 13) == 0 ||
6512 strncmp(message, "Black matches", 13) == 0 ) {
6513 /* [HGM] ignore GNUShogi noises */
6515 } else if (strncmp(message, "White", 5) == 0 &&
6516 message[5] != '(' &&
6517 StrStr(message, "Black") == NULL) {
6518 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520 } else if (strncmp(message, "Black", 5) == 0 &&
6521 message[5] != '(') {
6522 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6524 } else if (strcmp(message, "resign") == 0 ||
6525 strcmp(message, "computer resigns") == 0) {
6527 case MachinePlaysBlack:
6528 case IcsPlayingBlack:
6529 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6531 case MachinePlaysWhite:
6532 case IcsPlayingWhite:
6533 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6535 case TwoMachinesPlay:
6536 if (cps->twoMachinesColor[0] == 'w')
6537 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6539 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6546 } else if (strncmp(message, "opponent mates", 14) == 0) {
6548 case MachinePlaysBlack:
6549 case IcsPlayingBlack:
6550 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6552 case MachinePlaysWhite:
6553 case IcsPlayingWhite:
6554 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6556 case TwoMachinesPlay:
6557 if (cps->twoMachinesColor[0] == 'w')
6558 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6560 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6567 } else if (strncmp(message, "computer mates", 14) == 0) {
6569 case MachinePlaysBlack:
6570 case IcsPlayingBlack:
6571 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6573 case MachinePlaysWhite:
6574 case IcsPlayingWhite:
6575 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6577 case TwoMachinesPlay:
6578 if (cps->twoMachinesColor[0] == 'w')
6579 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6581 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6588 } else if (strncmp(message, "checkmate", 9) == 0) {
6589 if (WhiteOnMove(forwardMostMove)) {
6590 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6592 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6595 } else if (strstr(message, "Draw") != NULL ||
6596 strstr(message, "game is a draw") != NULL) {
6597 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6599 } else if (strstr(message, "offer") != NULL &&
6600 strstr(message, "draw") != NULL) {
6602 if (appData.zippyPlay && first.initDone) {
6603 /* Relay offer to ICS */
6604 SendToICS(ics_prefix);
6605 SendToICS("draw\n");
6608 cps->offeredDraw = 2; /* valid until this engine moves twice */
6609 if (gameMode == TwoMachinesPlay) {
6610 if (cps->other->offeredDraw) {
6611 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6612 /* [HGM] in two-machine mode we delay relaying draw offer */
6613 /* until after we also have move, to see if it is really claim */
6617 if (cps->other->sendDrawOffers) {
6618 SendToProgram("draw\n", cps->other);
6622 } else if (gameMode == MachinePlaysWhite ||
6623 gameMode == MachinePlaysBlack) {
6624 if (userOfferedDraw) {
6625 DisplayInformation(_("Machine accepts your draw offer"));
6626 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6628 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6635 * Look for thinking output
6637 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6638 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6640 int plylev, mvleft, mvtot, curscore, time;
6641 char mvname[MOVE_LEN];
6645 int prefixHint = FALSE;
6646 mvname[0] = NULLCHAR;
6649 case MachinePlaysBlack:
6650 case IcsPlayingBlack:
6651 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6653 case MachinePlaysWhite:
6654 case IcsPlayingWhite:
6655 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6660 case IcsObserving: /* [DM] icsEngineAnalyze */
6661 if (!appData.icsEngineAnalyze) ignore = TRUE;
6663 case TwoMachinesPlay:
6664 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6675 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6676 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6678 if (plyext != ' ' && plyext != '\t') {
6682 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6683 if( cps->scoreIsAbsolute &&
6684 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6686 curscore = -curscore;
6690 programStats.depth = plylev;
6691 programStats.nodes = nodes;
6692 programStats.time = time;
6693 programStats.score = curscore;
6694 programStats.got_only_move = 0;
6696 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6699 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6700 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6701 if(WhiteOnMove(forwardMostMove))
6702 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6703 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6706 /* Buffer overflow protection */
6707 if (buf1[0] != NULLCHAR) {
6708 if (strlen(buf1) >= sizeof(programStats.movelist)
6709 && appData.debugMode) {
6711 "PV is too long; using the first %d bytes.\n",
6712 sizeof(programStats.movelist) - 1);
6715 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6717 sprintf(programStats.movelist, " no PV\n");
6720 if (programStats.seen_stat) {
6721 programStats.ok_to_send = 1;
6724 if (strchr(programStats.movelist, '(') != NULL) {
6725 programStats.line_is_book = 1;
6726 programStats.nr_moves = 0;
6727 programStats.moves_left = 0;
6729 programStats.line_is_book = 0;
6732 SendProgramStatsToFrontend( cps, &programStats );
6735 [AS] Protect the thinkOutput buffer from overflow... this
6736 is only useful if buf1 hasn't overflowed first!
6738 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6740 (gameMode == TwoMachinesPlay ?
6741 ToUpper(cps->twoMachinesColor[0]) : ' '),
6742 ((double) curscore) / 100.0,
6743 prefixHint ? lastHint : "",
6744 prefixHint ? " " : "" );
6746 if( buf1[0] != NULLCHAR ) {
6747 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6749 if( strlen(buf1) > max_len ) {
6750 if( appData.debugMode) {
6751 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6753 buf1[max_len+1] = '\0';
6756 strcat( thinkOutput, buf1 );
6759 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6760 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6761 DisplayMove(currentMove - 1);
6766 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6767 /* crafty (9.25+) says "(only move) <move>"
6768 * if there is only 1 legal move
6770 sscanf(p, "(only move) %s", buf1);
6771 sprintf(thinkOutput, "%s (only move)", buf1);
6772 sprintf(programStats.movelist, "%s (only move)", buf1);
6773 programStats.depth = 1;
6774 programStats.nr_moves = 1;
6775 programStats.moves_left = 1;
6776 programStats.nodes = 1;
6777 programStats.time = 1;
6778 programStats.got_only_move = 1;
6780 /* Not really, but we also use this member to
6781 mean "line isn't going to change" (Crafty
6782 isn't searching, so stats won't change) */
6783 programStats.line_is_book = 1;
6785 SendProgramStatsToFrontend( cps, &programStats );
6787 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6788 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6789 DisplayMove(currentMove - 1);
6793 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6794 &time, &nodes, &plylev, &mvleft,
6795 &mvtot, mvname) >= 5) {
6796 /* The stat01: line is from Crafty (9.29+) in response
6797 to the "." command */
6798 programStats.seen_stat = 1;
6799 cps->maybeThinking = TRUE;
6801 if (programStats.got_only_move || !appData.periodicUpdates)
6804 programStats.depth = plylev;
6805 programStats.time = time;
6806 programStats.nodes = nodes;
6807 programStats.moves_left = mvleft;
6808 programStats.nr_moves = mvtot;
6809 strcpy(programStats.move_name, mvname);
6810 programStats.ok_to_send = 1;
6811 programStats.movelist[0] = '\0';
6813 SendProgramStatsToFrontend( cps, &programStats );
6818 } else if (strncmp(message,"++",2) == 0) {
6819 /* Crafty 9.29+ outputs this */
6820 programStats.got_fail = 2;
6823 } else if (strncmp(message,"--",2) == 0) {
6824 /* Crafty 9.29+ outputs this */
6825 programStats.got_fail = 1;
6828 } else if (thinkOutput[0] != NULLCHAR &&
6829 strncmp(message, " ", 4) == 0) {
6830 unsigned message_len;
6833 while (*p && *p == ' ') p++;
6835 message_len = strlen( p );
6837 /* [AS] Avoid buffer overflow */
6838 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6839 strcat(thinkOutput, " ");
6840 strcat(thinkOutput, p);
6843 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6844 strcat(programStats.movelist, " ");
6845 strcat(programStats.movelist, p);
6848 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6849 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6850 DisplayMove(currentMove - 1);
6859 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6860 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6862 ChessProgramStats cpstats;
6864 if (plyext != ' ' && plyext != '\t') {
6868 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6869 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6870 curscore = -curscore;
6873 cpstats.depth = plylev;
6874 cpstats.nodes = nodes;
6875 cpstats.time = time;
6876 cpstats.score = curscore;
6877 cpstats.got_only_move = 0;
6878 cpstats.movelist[0] = '\0';
6880 if (buf1[0] != NULLCHAR) {
6881 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6884 cpstats.ok_to_send = 0;
6885 cpstats.line_is_book = 0;
6886 cpstats.nr_moves = 0;
6887 cpstats.moves_left = 0;
6889 SendProgramStatsToFrontend( cps, &cpstats );
6896 /* Parse a game score from the character string "game", and
6897 record it as the history of the current game. The game
6898 score is NOT assumed to start from the standard position.
6899 The display is not updated in any way.
6902 ParseGameHistory(game)
6906 int fromX, fromY, toX, toY, boardIndex;
6911 if (appData.debugMode)
6912 fprintf(debugFP, "Parsing game history: %s\n", game);
6914 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6915 gameInfo.site = StrSave(appData.icsHost);
6916 gameInfo.date = PGNDate();
6917 gameInfo.round = StrSave("-");
6919 /* Parse out names of players */
6920 while (*game == ' ') game++;
6922 while (*game != ' ') *p++ = *game++;
6924 gameInfo.white = StrSave(buf);
6925 while (*game == ' ') game++;
6927 while (*game != ' ' && *game != '\n') *p++ = *game++;
6929 gameInfo.black = StrSave(buf);
6932 boardIndex = blackPlaysFirst ? 1 : 0;
6935 yyboardindex = boardIndex;
6936 moveType = (ChessMove) yylex();
6938 case IllegalMove: /* maybe suicide chess, etc. */
6939 if (appData.debugMode) {
6940 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6941 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6942 setbuf(debugFP, NULL);
6944 case WhitePromotionChancellor:
6945 case BlackPromotionChancellor:
6946 case WhitePromotionArchbishop:
6947 case BlackPromotionArchbishop:
6948 case WhitePromotionQueen:
6949 case BlackPromotionQueen:
6950 case WhitePromotionRook:
6951 case BlackPromotionRook:
6952 case WhitePromotionBishop:
6953 case BlackPromotionBishop:
6954 case WhitePromotionKnight:
6955 case BlackPromotionKnight:
6956 case WhitePromotionKing:
6957 case BlackPromotionKing:
6959 case WhiteCapturesEnPassant:
6960 case BlackCapturesEnPassant:
6961 case WhiteKingSideCastle:
6962 case WhiteQueenSideCastle:
6963 case BlackKingSideCastle:
6964 case BlackQueenSideCastle:
6965 case WhiteKingSideCastleWild:
6966 case WhiteQueenSideCastleWild:
6967 case BlackKingSideCastleWild:
6968 case BlackQueenSideCastleWild:
6970 case WhiteHSideCastleFR:
6971 case WhiteASideCastleFR:
6972 case BlackHSideCastleFR:
6973 case BlackASideCastleFR:
6975 fromX = currentMoveString[0] - AAA;
6976 fromY = currentMoveString[1] - ONE;
6977 toX = currentMoveString[2] - AAA;
6978 toY = currentMoveString[3] - ONE;
6979 promoChar = currentMoveString[4];
6983 fromX = moveType == WhiteDrop ?
6984 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6985 (int) CharToPiece(ToLower(currentMoveString[0]));
6987 toX = currentMoveString[2] - AAA;
6988 toY = currentMoveString[3] - ONE;
6989 promoChar = NULLCHAR;
6993 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6994 if (appData.debugMode) {
6995 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6996 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6997 setbuf(debugFP, NULL);
6999 DisplayError(buf, 0);
7001 case ImpossibleMove:
7003 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7004 if (appData.debugMode) {
7005 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7006 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7007 setbuf(debugFP, NULL);
7009 DisplayError(buf, 0);
7011 case (ChessMove) 0: /* end of file */
7012 if (boardIndex < backwardMostMove) {
7013 /* Oops, gap. How did that happen? */
7014 DisplayError(_("Gap in move list"), 0);
7017 backwardMostMove = blackPlaysFirst ? 1 : 0;
7018 if (boardIndex > forwardMostMove) {
7019 forwardMostMove = boardIndex;
7023 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7024 strcat(parseList[boardIndex-1], " ");
7025 strcat(parseList[boardIndex-1], yy_text);
7037 case GameUnfinished:
7038 if (gameMode == IcsExamining) {
7039 if (boardIndex < backwardMostMove) {
7040 /* Oops, gap. How did that happen? */
7043 backwardMostMove = blackPlaysFirst ? 1 : 0;
7046 gameInfo.result = moveType;
7047 p = strchr(yy_text, '{');
7048 if (p == NULL) p = strchr(yy_text, '(');
7051 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7053 q = strchr(p, *p == '{' ? '}' : ')');
7054 if (q != NULL) *q = NULLCHAR;
7057 gameInfo.resultDetails = StrSave(p);
7060 if (boardIndex >= forwardMostMove &&
7061 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7062 backwardMostMove = blackPlaysFirst ? 1 : 0;
7065 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7066 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7067 parseList[boardIndex]);
7068 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7069 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7070 /* currentMoveString is set as a side-effect of yylex */
7071 strcpy(moveList[boardIndex], currentMoveString);
7072 strcat(moveList[boardIndex], "\n");
7074 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7075 castlingRights[boardIndex], &epStatus[boardIndex]);
7076 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7077 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7083 if(gameInfo.variant != VariantShogi)
7084 strcat(parseList[boardIndex - 1], "+");
7088 strcat(parseList[boardIndex - 1], "#");
7095 /* Apply a move to the given board */
7097 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7098 int fromX, fromY, toX, toY;
7104 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7106 /* [HGM] compute & store e.p. status and castling rights for new position */
7107 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7110 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7114 if( board[toY][toX] != EmptySquare )
7117 if( board[fromY][fromX] == WhitePawn ) {
7118 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7121 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7122 gameInfo.variant != VariantBerolina || toX < fromX)
7123 *ep = toX | berolina;
7124 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7125 gameInfo.variant != VariantBerolina || toX > fromX)
7129 if( board[fromY][fromX] == BlackPawn ) {
7130 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7132 if( toY-fromY== -2) {
7133 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7134 gameInfo.variant != VariantBerolina || toX < fromX)
7135 *ep = toX | berolina;
7136 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7137 gameInfo.variant != VariantBerolina || toX > fromX)
7142 for(i=0; i<nrCastlingRights; i++) {
7143 if(castling[i] == fromX && castlingRank[i] == fromY ||
7144 castling[i] == toX && castlingRank[i] == toY
7145 ) castling[i] = -1; // revoke for moved or captured piece
7150 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7151 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7152 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7154 if (fromX == toX && fromY == toY) return;
7156 if (fromY == DROP_RANK) {
7158 piece = board[toY][toX] = (ChessSquare) fromX;
7160 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7161 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7162 if(gameInfo.variant == VariantKnightmate)
7163 king += (int) WhiteUnicorn - (int) WhiteKing;
7165 /* Code added by Tord: */
7166 /* FRC castling assumed when king captures friendly rook. */
7167 if (board[fromY][fromX] == WhiteKing &&
7168 board[toY][toX] == WhiteRook) {
7169 board[fromY][fromX] = EmptySquare;
7170 board[toY][toX] = EmptySquare;
7172 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7174 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7176 } else if (board[fromY][fromX] == BlackKing &&
7177 board[toY][toX] == BlackRook) {
7178 board[fromY][fromX] = EmptySquare;
7179 board[toY][toX] = EmptySquare;
7181 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7183 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7185 /* End of code added by Tord */
7187 } else if (board[fromY][fromX] == king
7188 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7189 && toY == fromY && toX > fromX+1) {
7190 board[fromY][fromX] = EmptySquare;
7191 board[toY][toX] = king;
7192 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7193 board[fromY][BOARD_RGHT-1] = EmptySquare;
7194 } else if (board[fromY][fromX] == king
7195 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7196 && toY == fromY && toX < fromX-1) {
7197 board[fromY][fromX] = EmptySquare;
7198 board[toY][toX] = king;
7199 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7200 board[fromY][BOARD_LEFT] = EmptySquare;
7201 } else if (board[fromY][fromX] == WhitePawn
7202 && toY == BOARD_HEIGHT-1
7203 && gameInfo.variant != VariantXiangqi
7205 /* white pawn promotion */
7206 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7207 if (board[toY][toX] == EmptySquare) {
7208 board[toY][toX] = WhiteQueen;
7210 if(gameInfo.variant==VariantBughouse ||
7211 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7212 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7213 board[fromY][fromX] = EmptySquare;
7214 } else if ((fromY == BOARD_HEIGHT-4)
7216 && gameInfo.variant != VariantXiangqi
7217 && gameInfo.variant != VariantBerolina
7218 && (board[fromY][fromX] == WhitePawn)
7219 && (board[toY][toX] == EmptySquare)) {
7220 board[fromY][fromX] = EmptySquare;
7221 board[toY][toX] = WhitePawn;
7222 captured = board[toY - 1][toX];
7223 board[toY - 1][toX] = EmptySquare;
7224 } else if ((fromY == BOARD_HEIGHT-4)
7226 && gameInfo.variant == VariantBerolina
7227 && (board[fromY][fromX] == WhitePawn)
7228 && (board[toY][toX] == EmptySquare)) {
7229 board[fromY][fromX] = EmptySquare;
7230 board[toY][toX] = WhitePawn;
7231 if(oldEP & EP_BEROLIN_A) {
7232 captured = board[fromY][fromX-1];
7233 board[fromY][fromX-1] = EmptySquare;
7234 }else{ captured = board[fromY][fromX+1];
7235 board[fromY][fromX+1] = EmptySquare;
7237 } else if (board[fromY][fromX] == king
7238 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7239 && toY == fromY && toX > fromX+1) {
7240 board[fromY][fromX] = EmptySquare;
7241 board[toY][toX] = king;
7242 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7243 board[fromY][BOARD_RGHT-1] = EmptySquare;
7244 } else if (board[fromY][fromX] == king
7245 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7246 && toY == fromY && toX < fromX-1) {
7247 board[fromY][fromX] = EmptySquare;
7248 board[toY][toX] = king;
7249 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7250 board[fromY][BOARD_LEFT] = EmptySquare;
7251 } else if (fromY == 7 && fromX == 3
7252 && board[fromY][fromX] == BlackKing
7253 && toY == 7 && toX == 5) {
7254 board[fromY][fromX] = EmptySquare;
7255 board[toY][toX] = BlackKing;
7256 board[fromY][7] = EmptySquare;
7257 board[toY][4] = BlackRook;
7258 } else if (fromY == 7 && fromX == 3
7259 && board[fromY][fromX] == BlackKing
7260 && toY == 7 && toX == 1) {
7261 board[fromY][fromX] = EmptySquare;
7262 board[toY][toX] = BlackKing;
7263 board[fromY][0] = EmptySquare;
7264 board[toY][2] = BlackRook;
7265 } else if (board[fromY][fromX] == BlackPawn
7267 && gameInfo.variant != VariantXiangqi
7269 /* black pawn promotion */
7270 board[0][toX] = CharToPiece(ToLower(promoChar));
7271 if (board[0][toX] == EmptySquare) {
7272 board[0][toX] = BlackQueen;
7274 if(gameInfo.variant==VariantBughouse ||
7275 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7276 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7277 board[fromY][fromX] = EmptySquare;
7278 } else if ((fromY == 3)
7280 && gameInfo.variant != VariantXiangqi
7281 && gameInfo.variant != VariantBerolina
7282 && (board[fromY][fromX] == BlackPawn)
7283 && (board[toY][toX] == EmptySquare)) {
7284 board[fromY][fromX] = EmptySquare;
7285 board[toY][toX] = BlackPawn;
7286 captured = board[toY + 1][toX];
7287 board[toY + 1][toX] = EmptySquare;
7288 } else if ((fromY == 3)
7290 && gameInfo.variant == VariantBerolina
7291 && (board[fromY][fromX] == BlackPawn)
7292 && (board[toY][toX] == EmptySquare)) {
7293 board[fromY][fromX] = EmptySquare;
7294 board[toY][toX] = BlackPawn;
7295 if(oldEP & EP_BEROLIN_A) {
7296 captured = board[fromY][fromX-1];
7297 board[fromY][fromX-1] = EmptySquare;
7298 }else{ captured = board[fromY][fromX+1];
7299 board[fromY][fromX+1] = EmptySquare;
7302 board[toY][toX] = board[fromY][fromX];
7303 board[fromY][fromX] = EmptySquare;
7306 /* [HGM] now we promote for Shogi, if needed */
7307 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7308 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7311 if (gameInfo.holdingsWidth != 0) {
7313 /* !!A lot more code needs to be written to support holdings */
7314 /* [HGM] OK, so I have written it. Holdings are stored in the */
7315 /* penultimate board files, so they are automaticlly stored */
7316 /* in the game history. */
7317 if (fromY == DROP_RANK) {
7318 /* Delete from holdings, by decreasing count */
7319 /* and erasing image if necessary */
7321 if(p < (int) BlackPawn) { /* white drop */
7322 p -= (int)WhitePawn;
7323 if(p >= gameInfo.holdingsSize) p = 0;
7324 if(--board[p][BOARD_WIDTH-2] == 0)
7325 board[p][BOARD_WIDTH-1] = EmptySquare;
7326 } else { /* black drop */
7327 p -= (int)BlackPawn;
7328 if(p >= gameInfo.holdingsSize) p = 0;
7329 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7330 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7333 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7334 && gameInfo.variant != VariantBughouse ) {
7335 /* [HGM] holdings: Add to holdings, if holdings exist */
7336 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7337 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7338 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7341 if (p >= (int) BlackPawn) {
7342 p -= (int)BlackPawn;
7343 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7344 /* in Shogi restore piece to its original first */
7345 captured = (ChessSquare) (DEMOTED captured);
7348 p = PieceToNumber((ChessSquare)p);
7349 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7350 board[p][BOARD_WIDTH-2]++;
7351 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7353 p -= (int)WhitePawn;
7354 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7355 captured = (ChessSquare) (DEMOTED captured);
7358 p = PieceToNumber((ChessSquare)p);
7359 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7360 board[BOARD_HEIGHT-1-p][1]++;
7361 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7365 } else if (gameInfo.variant == VariantAtomic) {
7366 if (captured != EmptySquare) {
7368 for (y = toY-1; y <= toY+1; y++) {
7369 for (x = toX-1; x <= toX+1; x++) {
7370 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7371 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7372 board[y][x] = EmptySquare;
7376 board[toY][toX] = EmptySquare;
7379 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7380 /* [HGM] Shogi promotions */
7381 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7384 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7385 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7386 // [HGM] superchess: take promotion piece out of holdings
7387 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7388 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7389 if(!--board[k][BOARD_WIDTH-2])
7390 board[k][BOARD_WIDTH-1] = EmptySquare;
7392 if(!--board[BOARD_HEIGHT-1-k][1])
7393 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7399 /* Updates forwardMostMove */
7401 MakeMove(fromX, fromY, toX, toY, promoChar)
7402 int fromX, fromY, toX, toY;
7405 // forwardMostMove++; // [HGM] bare: moved downstream
7407 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7408 int timeLeft; static int lastLoadFlag=0; int king, piece;
7409 piece = boards[forwardMostMove][fromY][fromX];
7410 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7411 if(gameInfo.variant == VariantKnightmate)
7412 king += (int) WhiteUnicorn - (int) WhiteKing;
7413 if(forwardMostMove == 0) {
7415 fprintf(serverMoves, "%s;", second.tidy);
7416 fprintf(serverMoves, "%s;", first.tidy);
7417 if(!blackPlaysFirst)
7418 fprintf(serverMoves, "%s;", second.tidy);
7419 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7420 lastLoadFlag = loadFlag;
7422 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7423 // print castling suffix
7424 if( toY == fromY && piece == king ) {
7426 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7428 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7431 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7432 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7433 boards[forwardMostMove][toY][toX] == EmptySquare
7435 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7437 if(promoChar != NULLCHAR)
7438 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7440 fprintf(serverMoves, "/%d/%d",
7441 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7442 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7443 else timeLeft = blackTimeRemaining/1000;
7444 fprintf(serverMoves, "/%d", timeLeft);
7446 fflush(serverMoves);
7449 if (forwardMostMove+1 >= MAX_MOVES) {
7450 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7455 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7456 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7457 if (commentList[forwardMostMove+1] != NULL) {
7458 free(commentList[forwardMostMove+1]);
7459 commentList[forwardMostMove+1] = NULL;
7461 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7462 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7463 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7464 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7465 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7466 gameInfo.result = GameUnfinished;
7467 if (gameInfo.resultDetails != NULL) {
7468 free(gameInfo.resultDetails);
7469 gameInfo.resultDetails = NULL;
7471 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7472 moveList[forwardMostMove - 1]);
7473 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7474 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7475 fromY, fromX, toY, toX, promoChar,
7476 parseList[forwardMostMove - 1]);
7477 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7478 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7479 castlingRights[forwardMostMove]) ) {
7485 if(gameInfo.variant != VariantShogi)
7486 strcat(parseList[forwardMostMove - 1], "+");
7490 strcat(parseList[forwardMostMove - 1], "#");
7493 if (appData.debugMode) {
7494 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7499 /* Updates currentMove if not pausing */
7501 ShowMove(fromX, fromY, toX, toY)
7503 int instant = (gameMode == PlayFromGameFile) ?
7504 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7505 if(appData.noGUI) return;
7506 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7508 if (forwardMostMove == currentMove + 1) {
7509 AnimateMove(boards[forwardMostMove - 1],
7510 fromX, fromY, toX, toY);
7512 if (appData.highlightLastMove) {
7513 SetHighlights(fromX, fromY, toX, toY);
7516 currentMove = forwardMostMove;
7519 if (instant) return;
7521 DisplayMove(currentMove - 1);
7522 DrawPosition(FALSE, boards[currentMove]);
7523 DisplayBothClocks();
7524 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7527 void SendEgtPath(ChessProgramState *cps)
7528 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7529 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7531 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7534 char c, *q = name+1, *r, *s;
7536 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7537 while(*p && *p != ',') *q++ = *p++;
7539 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7540 strcmp(name, ",nalimov:") == 0 ) {
7541 // take nalimov path from the menu-changeable option first, if it is defined
7542 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7543 SendToProgram(buf,cps); // send egtbpath command for nalimov
7545 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7546 (s = StrStr(appData.egtFormats, name)) != NULL) {
7547 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7548 s = r = StrStr(s, ":") + 1; // beginning of path info
7549 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7550 c = *r; *r = 0; // temporarily null-terminate path info
7551 *--q = 0; // strip of trailig ':' from name
7552 sprintf(buf, "egtbpath %s %s\n", name+1, s);
7554 SendToProgram(buf,cps); // send egtbpath command for this format
7556 if(*p == ',') p++; // read away comma to position for next format name
7561 InitChessProgram(cps, setup)
7562 ChessProgramState *cps;
7563 int setup; /* [HGM] needed to setup FRC opening position */
7565 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7566 if (appData.noChessProgram) return;
7567 hintRequested = FALSE;
7568 bookRequested = FALSE;
7570 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7571 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7572 if(cps->memSize) { /* [HGM] memory */
7573 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7574 SendToProgram(buf, cps);
7576 SendEgtPath(cps); /* [HGM] EGT */
7577 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7578 sprintf(buf, "cores %d\n", appData.smpCores);
7579 SendToProgram(buf, cps);
7582 SendToProgram(cps->initString, cps);
7583 if (gameInfo.variant != VariantNormal &&
7584 gameInfo.variant != VariantLoadable
7585 /* [HGM] also send variant if board size non-standard */
7586 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7588 char *v = VariantName(gameInfo.variant);
7589 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7590 /* [HGM] in protocol 1 we have to assume all variants valid */
7591 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7592 DisplayFatalError(buf, 0, 1);
7596 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7597 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7598 if( gameInfo.variant == VariantXiangqi )
7599 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7600 if( gameInfo.variant == VariantShogi )
7601 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7602 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7603 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7604 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7605 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7606 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7607 if( gameInfo.variant == VariantCourier )
7608 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7609 if( gameInfo.variant == VariantSuper )
7610 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7611 if( gameInfo.variant == VariantGreat )
7612 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7615 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7616 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7617 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7618 if(StrStr(cps->variants, b) == NULL) {
7619 // specific sized variant not known, check if general sizing allowed
7620 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7621 if(StrStr(cps->variants, "boardsize") == NULL) {
7622 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7623 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7624 DisplayFatalError(buf, 0, 1);
7627 /* [HGM] here we really should compare with the maximum supported board size */
7630 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7631 sprintf(buf, "variant %s\n", b);
7632 SendToProgram(buf, cps);
7634 currentlyInitializedVariant = gameInfo.variant;
7636 /* [HGM] send opening position in FRC to first engine */
7638 SendToProgram("force\n", cps);
7640 /* engine is now in force mode! Set flag to wake it up after first move. */
7641 setboardSpoiledMachineBlack = 1;
7645 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7646 SendToProgram(buf, cps);
7648 cps->maybeThinking = FALSE;
7649 cps->offeredDraw = 0;
7650 if (!appData.icsActive) {
7651 SendTimeControl(cps, movesPerSession, timeControl,
7652 timeIncrement, appData.searchDepth,
7655 if (appData.showThinking
7656 // [HGM] thinking: four options require thinking output to be sent
7657 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7659 SendToProgram("post\n", cps);
7661 SendToProgram("hard\n", cps);
7662 if (!appData.ponderNextMove) {
7663 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7664 it without being sure what state we are in first. "hard"
7665 is not a toggle, so that one is OK.
7667 SendToProgram("easy\n", cps);
7670 sprintf(buf, "ping %d\n", ++cps->lastPing);
7671 SendToProgram(buf, cps);
7673 cps->initDone = TRUE;
7678 StartChessProgram(cps)
7679 ChessProgramState *cps;
7684 if (appData.noChessProgram) return;
7685 cps->initDone = FALSE;
7687 if (strcmp(cps->host, "localhost") == 0) {
7688 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7689 } else if (*appData.remoteShell == NULLCHAR) {
7690 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7692 if (*appData.remoteUser == NULLCHAR) {
7693 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7696 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7697 cps->host, appData.remoteUser, cps->program);
7699 err = StartChildProcess(buf, "", &cps->pr);
7703 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7704 DisplayFatalError(buf, err, 1);
7710 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7711 if (cps->protocolVersion > 1) {
7712 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7713 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7714 cps->comboCnt = 0; // and values of combo boxes
7715 SendToProgram(buf, cps);
7717 SendToProgram("xboard\n", cps);
7723 TwoMachinesEventIfReady P((void))
7725 if (first.lastPing != first.lastPong) {
7726 DisplayMessage("", _("Waiting for first chess program"));
7727 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7730 if (second.lastPing != second.lastPong) {
7731 DisplayMessage("", _("Waiting for second chess program"));
7732 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7740 NextMatchGame P((void))
7742 int index; /* [HGM] autoinc: step lod index during match */
7744 if (*appData.loadGameFile != NULLCHAR) {
7745 index = appData.loadGameIndex;
7746 if(index < 0) { // [HGM] autoinc
7747 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7748 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7750 LoadGameFromFile(appData.loadGameFile,
7752 appData.loadGameFile, FALSE);
7753 } else if (*appData.loadPositionFile != NULLCHAR) {
7754 index = appData.loadPositionIndex;
7755 if(index < 0) { // [HGM] autoinc
7756 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7757 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7759 LoadPositionFromFile(appData.loadPositionFile,
7761 appData.loadPositionFile);
7763 TwoMachinesEventIfReady();
7766 void UserAdjudicationEvent( int result )
7768 ChessMove gameResult = GameIsDrawn;
7771 gameResult = WhiteWins;
7773 else if( result < 0 ) {
7774 gameResult = BlackWins;
7777 if( gameMode == TwoMachinesPlay ) {
7778 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7783 // [HGM] save: calculate checksum of game to make games easily identifiable
7784 int StringCheckSum(char *s)
7787 if(s==NULL) return 0;
7788 while(*s) i = i*259 + *s++;
7795 for(i=backwardMostMove; i<forwardMostMove; i++) {
7796 sum += pvInfoList[i].depth;
7797 sum += StringCheckSum(parseList[i]);
7798 sum += StringCheckSum(commentList[i]);
7801 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7802 return sum + StringCheckSum(commentList[i]);
7803 } // end of save patch
7806 GameEnds(result, resultDetails, whosays)
7808 char *resultDetails;
7811 GameMode nextGameMode;
7815 if(endingGame) return; /* [HGM] crash: forbid recursion */
7818 if (appData.debugMode) {
7819 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7820 result, resultDetails ? resultDetails : "(null)", whosays);
7823 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7824 /* If we are playing on ICS, the server decides when the
7825 game is over, but the engine can offer to draw, claim
7829 if (appData.zippyPlay && first.initDone) {
7830 if (result == GameIsDrawn) {
7831 /* In case draw still needs to be claimed */
7832 SendToICS(ics_prefix);
7833 SendToICS("draw\n");
7834 } else if (StrCaseStr(resultDetails, "resign")) {
7835 SendToICS(ics_prefix);
7836 SendToICS("resign\n");
7840 endingGame = 0; /* [HGM] crash */
7844 /* If we're loading the game from a file, stop */
7845 if (whosays == GE_FILE) {
7846 (void) StopLoadGameTimer();
7850 /* Cancel draw offers */
7851 first.offeredDraw = second.offeredDraw = 0;
7853 /* If this is an ICS game, only ICS can really say it's done;
7854 if not, anyone can. */
7855 isIcsGame = (gameMode == IcsPlayingWhite ||
7856 gameMode == IcsPlayingBlack ||
7857 gameMode == IcsObserving ||
7858 gameMode == IcsExamining);
7860 if (!isIcsGame || whosays == GE_ICS) {
7861 /* OK -- not an ICS game, or ICS said it was done */
7863 if (!isIcsGame && !appData.noChessProgram)
7864 SetUserThinkingEnables();
7866 /* [HGM] if a machine claims the game end we verify this claim */
7867 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7868 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7870 ChessMove trueResult = (ChessMove) -1;
7872 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7873 first.twoMachinesColor[0] :
7874 second.twoMachinesColor[0] ;
7876 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7877 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7878 /* [HGM] verify: engine mate claims accepted if they were flagged */
7879 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7881 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7882 /* [HGM] verify: engine mate claims accepted if they were flagged */
7883 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7885 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7886 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7889 // now verify win claims, but not in drop games, as we don't understand those yet
7890 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7891 || gameInfo.variant == VariantGreat) &&
7892 (result == WhiteWins && claimer == 'w' ||
7893 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7894 if (appData.debugMode) {
7895 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7896 result, epStatus[forwardMostMove], forwardMostMove);
7898 if(result != trueResult) {
7899 sprintf(buf, "False win claim: '%s'", resultDetails);
7900 result = claimer == 'w' ? BlackWins : WhiteWins;
7901 resultDetails = buf;
7904 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7905 && (forwardMostMove <= backwardMostMove ||
7906 epStatus[forwardMostMove-1] > EP_DRAWS ||
7907 (claimer=='b')==(forwardMostMove&1))
7909 /* [HGM] verify: draws that were not flagged are false claims */
7910 sprintf(buf, "False draw claim: '%s'", resultDetails);
7911 result = claimer == 'w' ? BlackWins : WhiteWins;
7912 resultDetails = buf;
7914 /* (Claiming a loss is accepted no questions asked!) */
7916 /* [HGM] bare: don't allow bare King to win */
7917 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7918 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7919 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7920 && result != GameIsDrawn)
7921 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7922 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7923 int p = (int)boards[forwardMostMove][i][j] - color;
7924 if(p >= 0 && p <= (int)WhiteKing) k++;
7926 if (appData.debugMode) {
7927 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7928 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7931 result = GameIsDrawn;
7932 sprintf(buf, "%s but bare king", resultDetails);
7933 resultDetails = buf;
7939 if(serverMoves != NULL && !loadFlag) { char c = '=';
7940 if(result==WhiteWins) c = '+';
7941 if(result==BlackWins) c = '-';
7942 if(resultDetails != NULL)
7943 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7945 if (resultDetails != NULL) {
7946 gameInfo.result = result;
7947 gameInfo.resultDetails = StrSave(resultDetails);
7949 /* display last move only if game was not loaded from file */
7950 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7951 DisplayMove(currentMove - 1);
7953 if (forwardMostMove != 0) {
7954 if (gameMode != PlayFromGameFile && gameMode != EditGame
7955 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7957 if (*appData.saveGameFile != NULLCHAR) {
7958 SaveGameToFile(appData.saveGameFile, TRUE);
7959 } else if (appData.autoSaveGames) {
7962 if (*appData.savePositionFile != NULLCHAR) {
7963 SavePositionToFile(appData.savePositionFile);
7968 /* Tell program how game ended in case it is learning */
7969 /* [HGM] Moved this to after saving the PGN, just in case */
7970 /* engine died and we got here through time loss. In that */
7971 /* case we will get a fatal error writing the pipe, which */
7972 /* would otherwise lose us the PGN. */
7973 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7974 /* output during GameEnds should never be fatal anymore */
7975 if (gameMode == MachinePlaysWhite ||
7976 gameMode == MachinePlaysBlack ||
7977 gameMode == TwoMachinesPlay ||
7978 gameMode == IcsPlayingWhite ||
7979 gameMode == IcsPlayingBlack ||
7980 gameMode == BeginningOfGame) {
7982 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7984 if (first.pr != NoProc) {
7985 SendToProgram(buf, &first);
7987 if (second.pr != NoProc &&
7988 gameMode == TwoMachinesPlay) {
7989 SendToProgram(buf, &second);
7994 if (appData.icsActive) {
7995 if (appData.quietPlay &&
7996 (gameMode == IcsPlayingWhite ||
7997 gameMode == IcsPlayingBlack)) {
7998 SendToICS(ics_prefix);
7999 SendToICS("set shout 1\n");
8001 nextGameMode = IcsIdle;
8002 ics_user_moved = FALSE;
8003 /* clean up premove. It's ugly when the game has ended and the
8004 * premove highlights are still on the board.
8008 ClearPremoveHighlights();
8009 DrawPosition(FALSE, boards[currentMove]);
8011 if (whosays == GE_ICS) {
8014 if (gameMode == IcsPlayingWhite)
8016 else if(gameMode == IcsPlayingBlack)
8020 if (gameMode == IcsPlayingBlack)
8022 else if(gameMode == IcsPlayingWhite)
8029 PlayIcsUnfinishedSound();
8032 } else if (gameMode == EditGame ||
8033 gameMode == PlayFromGameFile ||
8034 gameMode == AnalyzeMode ||
8035 gameMode == AnalyzeFile) {
8036 nextGameMode = gameMode;
8038 nextGameMode = EndOfGame;
8043 nextGameMode = gameMode;
8046 if (appData.noChessProgram) {
8047 gameMode = nextGameMode;
8049 endingGame = 0; /* [HGM] crash */
8054 /* Put first chess program into idle state */
8055 if (first.pr != NoProc &&
8056 (gameMode == MachinePlaysWhite ||
8057 gameMode == MachinePlaysBlack ||
8058 gameMode == TwoMachinesPlay ||
8059 gameMode == IcsPlayingWhite ||
8060 gameMode == IcsPlayingBlack ||
8061 gameMode == BeginningOfGame)) {
8062 SendToProgram("force\n", &first);
8063 if (first.usePing) {
8065 sprintf(buf, "ping %d\n", ++first.lastPing);
8066 SendToProgram(buf, &first);
8069 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8070 /* Kill off first chess program */
8071 if (first.isr != NULL)
8072 RemoveInputSource(first.isr);
8075 if (first.pr != NoProc) {
8077 DoSleep( appData.delayBeforeQuit );
8078 SendToProgram("quit\n", &first);
8079 DoSleep( appData.delayAfterQuit );
8080 DestroyChildProcess(first.pr, first.useSigterm);
8085 /* Put second chess program into idle state */
8086 if (second.pr != NoProc &&
8087 gameMode == TwoMachinesPlay) {
8088 SendToProgram("force\n", &second);
8089 if (second.usePing) {
8091 sprintf(buf, "ping %d\n", ++second.lastPing);
8092 SendToProgram(buf, &second);
8095 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8096 /* Kill off second chess program */
8097 if (second.isr != NULL)
8098 RemoveInputSource(second.isr);
8101 if (second.pr != NoProc) {
8102 DoSleep( appData.delayBeforeQuit );
8103 SendToProgram("quit\n", &second);
8104 DoSleep( appData.delayAfterQuit );
8105 DestroyChildProcess(second.pr, second.useSigterm);
8110 if (matchMode && gameMode == TwoMachinesPlay) {
8113 if (first.twoMachinesColor[0] == 'w') {
8120 if (first.twoMachinesColor[0] == 'b') {
8129 if (matchGame < appData.matchGames) {
8131 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8132 tmp = first.twoMachinesColor;
8133 first.twoMachinesColor = second.twoMachinesColor;
8134 second.twoMachinesColor = tmp;
8136 gameMode = nextGameMode;
8138 if(appData.matchPause>10000 || appData.matchPause<10)
8139 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8140 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8141 endingGame = 0; /* [HGM] crash */
8145 gameMode = nextGameMode;
8146 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8147 first.tidy, second.tidy,
8148 first.matchWins, second.matchWins,
8149 appData.matchGames - (first.matchWins + second.matchWins));
8150 DisplayFatalError(buf, 0, 0);
8153 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8154 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8156 gameMode = nextGameMode;
8158 endingGame = 0; /* [HGM] crash */
8161 /* Assumes program was just initialized (initString sent).
8162 Leaves program in force mode. */
8164 FeedMovesToProgram(cps, upto)
8165 ChessProgramState *cps;
8170 if (appData.debugMode)
8171 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8172 startedFromSetupPosition ? "position and " : "",
8173 backwardMostMove, upto, cps->which);
8174 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8175 // [HGM] variantswitch: make engine aware of new variant
8176 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8177 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8178 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8179 SendToProgram(buf, cps);
8180 currentlyInitializedVariant = gameInfo.variant;
8182 SendToProgram("force\n", cps);
8183 if (startedFromSetupPosition) {
8184 SendBoard(cps, backwardMostMove);
8185 if (appData.debugMode) {
8186 fprintf(debugFP, "feedMoves\n");
8189 for (i = backwardMostMove; i < upto; i++) {
8190 SendMoveToProgram(i, cps);
8196 ResurrectChessProgram()
8198 /* The chess program may have exited.
8199 If so, restart it and feed it all the moves made so far. */
8201 if (appData.noChessProgram || first.pr != NoProc) return;
8203 StartChessProgram(&first);
8204 InitChessProgram(&first, FALSE);
8205 FeedMovesToProgram(&first, currentMove);
8207 if (!first.sendTime) {
8208 /* can't tell gnuchess what its clock should read,
8209 so we bow to its notion. */
8211 timeRemaining[0][currentMove] = whiteTimeRemaining;
8212 timeRemaining[1][currentMove] = blackTimeRemaining;
8215 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8216 appData.icsEngineAnalyze) && first.analysisSupport) {
8217 SendToProgram("analyze\n", &first);
8218 first.analyzing = TRUE;
8231 if (appData.debugMode) {
8232 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8233 redraw, init, gameMode);
8235 pausing = pauseExamInvalid = FALSE;
8236 startedFromSetupPosition = blackPlaysFirst = FALSE;
8238 whiteFlag = blackFlag = FALSE;
8239 userOfferedDraw = FALSE;
8240 hintRequested = bookRequested = FALSE;
8241 first.maybeThinking = FALSE;
8242 second.maybeThinking = FALSE;
8243 first.bookSuspend = FALSE; // [HGM] book
8244 second.bookSuspend = FALSE;
8245 thinkOutput[0] = NULLCHAR;
8246 lastHint[0] = NULLCHAR;
8247 ClearGameInfo(&gameInfo);
8248 gameInfo.variant = StringToVariant(appData.variant);
8249 ics_user_moved = ics_clock_paused = FALSE;
8250 ics_getting_history = H_FALSE;
8252 white_holding[0] = black_holding[0] = NULLCHAR;
8253 ClearProgramStats();
8254 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8258 flipView = appData.flipView;
8259 ClearPremoveHighlights();
8261 alarmSounded = FALSE;
8263 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8264 if(appData.serverMovesName != NULL) {
8265 /* [HGM] prepare to make moves file for broadcasting */
8266 clock_t t = clock();
8267 if(serverMoves != NULL) fclose(serverMoves);
8268 serverMoves = fopen(appData.serverMovesName, "r");
8269 if(serverMoves != NULL) {
8270 fclose(serverMoves);
8271 /* delay 15 sec before overwriting, so all clients can see end */
8272 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8274 serverMoves = fopen(appData.serverMovesName, "w");
8278 gameMode = BeginningOfGame;
8280 if(appData.icsActive) gameInfo.variant = VariantNormal;
8281 InitPosition(redraw);
8282 for (i = 0; i < MAX_MOVES; i++) {
8283 if (commentList[i] != NULL) {
8284 free(commentList[i]);
8285 commentList[i] = NULL;
8289 timeRemaining[0][0] = whiteTimeRemaining;
8290 timeRemaining[1][0] = blackTimeRemaining;
8291 if (first.pr == NULL) {
8292 StartChessProgram(&first);
8295 InitChessProgram(&first, startedFromSetupPosition);
8298 DisplayMessage("", "");
8299 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8300 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8307 if (!AutoPlayOneMove())
8309 if (matchMode || appData.timeDelay == 0)
8311 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8313 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8322 int fromX, fromY, toX, toY;
8324 if (appData.debugMode) {
8325 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8328 if (gameMode != PlayFromGameFile)
8331 if (currentMove >= forwardMostMove) {
8332 gameMode = EditGame;
8335 /* [AS] Clear current move marker at the end of a game */
8336 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8341 toX = moveList[currentMove][2] - AAA;
8342 toY = moveList[currentMove][3] - ONE;
8344 if (moveList[currentMove][1] == '@') {
8345 if (appData.highlightLastMove) {
8346 SetHighlights(-1, -1, toX, toY);
8349 fromX = moveList[currentMove][0] - AAA;
8350 fromY = moveList[currentMove][1] - ONE;
8352 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8354 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8356 if (appData.highlightLastMove) {
8357 SetHighlights(fromX, fromY, toX, toY);
8360 DisplayMove(currentMove);
8361 SendMoveToProgram(currentMove++, &first);
8362 DisplayBothClocks();
8363 DrawPosition(FALSE, boards[currentMove]);
8364 // [HGM] PV info: always display, routine tests if empty
8365 DisplayComment(currentMove - 1, commentList[currentMove]);
8371 LoadGameOneMove(readAhead)
8372 ChessMove readAhead;
8374 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8375 char promoChar = NULLCHAR;
8380 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8381 gameMode != AnalyzeMode && gameMode != Training) {
8386 yyboardindex = forwardMostMove;
8387 if (readAhead != (ChessMove)0) {
8388 moveType = readAhead;
8390 if (gameFileFP == NULL)
8392 moveType = (ChessMove) yylex();
8398 if (appData.debugMode)
8399 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8401 if (*p == '{' || *p == '[' || *p == '(') {
8402 p[strlen(p) - 1] = NULLCHAR;
8406 /* append the comment but don't display it */
8407 while (*p == '\n') p++;
8408 AppendComment(currentMove, p);
8411 case WhiteCapturesEnPassant:
8412 case BlackCapturesEnPassant:
8413 case WhitePromotionChancellor:
8414 case BlackPromotionChancellor:
8415 case WhitePromotionArchbishop:
8416 case BlackPromotionArchbishop:
8417 case WhitePromotionCentaur:
8418 case BlackPromotionCentaur:
8419 case WhitePromotionQueen:
8420 case BlackPromotionQueen:
8421 case WhitePromotionRook:
8422 case BlackPromotionRook:
8423 case WhitePromotionBishop:
8424 case BlackPromotionBishop:
8425 case WhitePromotionKnight:
8426 case BlackPromotionKnight:
8427 case WhitePromotionKing:
8428 case BlackPromotionKing:
8430 case WhiteKingSideCastle:
8431 case WhiteQueenSideCastle:
8432 case BlackKingSideCastle:
8433 case BlackQueenSideCastle:
8434 case WhiteKingSideCastleWild:
8435 case WhiteQueenSideCastleWild:
8436 case BlackKingSideCastleWild:
8437 case BlackQueenSideCastleWild:
8439 case WhiteHSideCastleFR:
8440 case WhiteASideCastleFR:
8441 case BlackHSideCastleFR:
8442 case BlackASideCastleFR:
8444 if (appData.debugMode)
8445 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8446 fromX = currentMoveString[0] - AAA;
8447 fromY = currentMoveString[1] - ONE;
8448 toX = currentMoveString[2] - AAA;
8449 toY = currentMoveString[3] - ONE;
8450 promoChar = currentMoveString[4];
8455 if (appData.debugMode)
8456 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8457 fromX = moveType == WhiteDrop ?
8458 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8459 (int) CharToPiece(ToLower(currentMoveString[0]));
8461 toX = currentMoveString[2] - AAA;
8462 toY = currentMoveString[3] - ONE;
8468 case GameUnfinished:
8469 if (appData.debugMode)
8470 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8471 p = strchr(yy_text, '{');
8472 if (p == NULL) p = strchr(yy_text, '(');
8475 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8477 q = strchr(p, *p == '{' ? '}' : ')');
8478 if (q != NULL) *q = NULLCHAR;
8481 GameEnds(moveType, p, GE_FILE);
8483 if (cmailMsgLoaded) {
8485 flipView = WhiteOnMove(currentMove);
8486 if (moveType == GameUnfinished) flipView = !flipView;
8487 if (appData.debugMode)
8488 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8492 case (ChessMove) 0: /* end of file */
8493 if (appData.debugMode)
8494 fprintf(debugFP, "Parser hit end of file\n");
8495 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8496 EP_UNKNOWN, castlingRights[currentMove]) ) {
8502 if (WhiteOnMove(currentMove)) {
8503 GameEnds(BlackWins, "Black mates", GE_FILE);
8505 GameEnds(WhiteWins, "White mates", GE_FILE);
8509 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8516 if (lastLoadGameStart == GNUChessGame) {
8517 /* GNUChessGames have numbers, but they aren't move numbers */
8518 if (appData.debugMode)
8519 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8520 yy_text, (int) moveType);
8521 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8523 /* else fall thru */
8528 /* Reached start of next game in file */
8529 if (appData.debugMode)
8530 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8531 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8532 EP_UNKNOWN, castlingRights[currentMove]) ) {
8538 if (WhiteOnMove(currentMove)) {
8539 GameEnds(BlackWins, "Black mates", GE_FILE);
8541 GameEnds(WhiteWins, "White mates", GE_FILE);
8545 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8551 case PositionDiagram: /* should not happen; ignore */
8552 case ElapsedTime: /* ignore */
8553 case NAG: /* ignore */
8554 if (appData.debugMode)
8555 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8556 yy_text, (int) moveType);
8557 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8560 if (appData.testLegality) {
8561 if (appData.debugMode)
8562 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8563 sprintf(move, _("Illegal move: %d.%s%s"),
8564 (forwardMostMove / 2) + 1,
8565 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8566 DisplayError(move, 0);
8569 if (appData.debugMode)
8570 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8571 yy_text, currentMoveString);
8572 fromX = currentMoveString[0] - AAA;
8573 fromY = currentMoveString[1] - ONE;
8574 toX = currentMoveString[2] - AAA;
8575 toY = currentMoveString[3] - ONE;
8576 promoChar = currentMoveString[4];
8581 if (appData.debugMode)
8582 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8583 sprintf(move, _("Ambiguous move: %d.%s%s"),
8584 (forwardMostMove / 2) + 1,
8585 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8586 DisplayError(move, 0);
8591 case ImpossibleMove:
8592 if (appData.debugMode)
8593 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8594 sprintf(move, _("Illegal move: %d.%s%s"),
8595 (forwardMostMove / 2) + 1,
8596 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8597 DisplayError(move, 0);
8603 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8604 DrawPosition(FALSE, boards[currentMove]);
8605 DisplayBothClocks();
8606 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8607 DisplayComment(currentMove - 1, commentList[currentMove]);
8609 (void) StopLoadGameTimer();
8611 cmailOldMove = forwardMostMove;
8614 /* currentMoveString is set as a side-effect of yylex */
8615 strcat(currentMoveString, "\n");
8616 strcpy(moveList[forwardMostMove], currentMoveString);
8618 thinkOutput[0] = NULLCHAR;
8619 MakeMove(fromX, fromY, toX, toY, promoChar);
8620 currentMove = forwardMostMove;
8625 /* Load the nth game from the given file */
8627 LoadGameFromFile(filename, n, title, useList)
8631 /*Boolean*/ int useList;
8636 if (strcmp(filename, "-") == 0) {
8640 f = fopen(filename, "rb");
8642 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8643 DisplayError(buf, errno);
8647 if (fseek(f, 0, 0) == -1) {
8648 /* f is not seekable; probably a pipe */
8651 if (useList && n == 0) {
8652 int error = GameListBuild(f);
8654 DisplayError(_("Cannot build game list"), error);
8655 } else if (!ListEmpty(&gameList) &&
8656 ((ListGame *) gameList.tailPred)->number > 1) {
8657 GameListPopUp(f, title);
8664 return LoadGame(f, n, title, FALSE);
8669 MakeRegisteredMove()
8671 int fromX, fromY, toX, toY;
8673 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8674 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8677 if (appData.debugMode)
8678 fprintf(debugFP, "Restoring %s for game %d\n",
8679 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8681 thinkOutput[0] = NULLCHAR;
8682 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8683 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8684 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8685 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8686 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8687 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8688 MakeMove(fromX, fromY, toX, toY, promoChar);
8689 ShowMove(fromX, fromY, toX, toY);
8691 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8692 EP_UNKNOWN, castlingRights[currentMove]) ) {
8699 if (WhiteOnMove(currentMove)) {
8700 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8702 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8707 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8714 if (WhiteOnMove(currentMove)) {
8715 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8717 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8722 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8733 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8735 CmailLoadGame(f, gameNumber, title, useList)
8743 if (gameNumber > nCmailGames) {
8744 DisplayError(_("No more games in this message"), 0);
8747 if (f == lastLoadGameFP) {
8748 int offset = gameNumber - lastLoadGameNumber;
8750 cmailMsg[0] = NULLCHAR;
8751 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8752 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8753 nCmailMovesRegistered--;
8755 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8756 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8757 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8760 if (! RegisterMove()) return FALSE;
8764 retVal = LoadGame(f, gameNumber, title, useList);
8766 /* Make move registered during previous look at this game, if any */
8767 MakeRegisteredMove();
8769 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8770 commentList[currentMove]
8771 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8772 DisplayComment(currentMove - 1, commentList[currentMove]);
8778 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8783 int gameNumber = lastLoadGameNumber + offset;
8784 if (lastLoadGameFP == NULL) {
8785 DisplayError(_("No game has been loaded yet"), 0);
8788 if (gameNumber <= 0) {
8789 DisplayError(_("Can't back up any further"), 0);
8792 if (cmailMsgLoaded) {
8793 return CmailLoadGame(lastLoadGameFP, gameNumber,
8794 lastLoadGameTitle, lastLoadGameUseList);
8796 return LoadGame(lastLoadGameFP, gameNumber,
8797 lastLoadGameTitle, lastLoadGameUseList);
8803 /* Load the nth game from open file f */
8805 LoadGame(f, gameNumber, title, useList)
8813 int gn = gameNumber;
8814 ListGame *lg = NULL;
8817 GameMode oldGameMode;
8818 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8820 if (appData.debugMode)
8821 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8823 if (gameMode == Training )
8824 SetTrainingModeOff();
8826 oldGameMode = gameMode;
8827 if (gameMode != BeginningOfGame) {
8832 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8833 fclose(lastLoadGameFP);
8837 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8840 fseek(f, lg->offset, 0);
8841 GameListHighlight(gameNumber);
8845 DisplayError(_("Game number out of range"), 0);
8850 if (fseek(f, 0, 0) == -1) {
8851 if (f == lastLoadGameFP ?
8852 gameNumber == lastLoadGameNumber + 1 :
8856 DisplayError(_("Can't seek on game file"), 0);
8862 lastLoadGameNumber = gameNumber;
8863 strcpy(lastLoadGameTitle, title);
8864 lastLoadGameUseList = useList;
8868 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8869 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8870 lg->gameInfo.black);
8872 } else if (*title != NULLCHAR) {
8873 if (gameNumber > 1) {
8874 sprintf(buf, "%s %d", title, gameNumber);
8877 DisplayTitle(title);
8881 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8882 gameMode = PlayFromGameFile;
8886 currentMove = forwardMostMove = backwardMostMove = 0;
8887 CopyBoard(boards[0], initialPosition);
8891 * Skip the first gn-1 games in the file.
8892 * Also skip over anything that precedes an identifiable
8893 * start of game marker, to avoid being confused by
8894 * garbage at the start of the file. Currently
8895 * recognized start of game markers are the move number "1",
8896 * the pattern "gnuchess .* game", the pattern
8897 * "^[#;%] [^ ]* game file", and a PGN tag block.
8898 * A game that starts with one of the latter two patterns
8899 * will also have a move number 1, possibly
8900 * following a position diagram.
8901 * 5-4-02: Let's try being more lenient and allowing a game to
8902 * start with an unnumbered move. Does that break anything?
8904 cm = lastLoadGameStart = (ChessMove) 0;
8906 yyboardindex = forwardMostMove;
8907 cm = (ChessMove) yylex();
8910 if (cmailMsgLoaded) {
8911 nCmailGames = CMAIL_MAX_GAMES - gn;
8914 DisplayError(_("Game not found in file"), 0);
8921 lastLoadGameStart = cm;
8925 switch (lastLoadGameStart) {
8932 gn--; /* count this game */
8933 lastLoadGameStart = cm;
8942 switch (lastLoadGameStart) {
8947 gn--; /* count this game */
8948 lastLoadGameStart = cm;
8951 lastLoadGameStart = cm; /* game counted already */
8959 yyboardindex = forwardMostMove;
8960 cm = (ChessMove) yylex();
8961 } while (cm == PGNTag || cm == Comment);
8968 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8969 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8970 != CMAIL_OLD_RESULT) {
8972 cmailResult[ CMAIL_MAX_GAMES
8973 - gn - 1] = CMAIL_OLD_RESULT;
8979 /* Only a NormalMove can be at the start of a game
8980 * without a position diagram. */
8981 if (lastLoadGameStart == (ChessMove) 0) {
8983 lastLoadGameStart = MoveNumberOne;
8992 if (appData.debugMode)
8993 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8995 if (cm == XBoardGame) {
8996 /* Skip any header junk before position diagram and/or move 1 */
8998 yyboardindex = forwardMostMove;
8999 cm = (ChessMove) yylex();
9001 if (cm == (ChessMove) 0 ||
9002 cm == GNUChessGame || cm == XBoardGame) {
9003 /* Empty game; pretend end-of-file and handle later */
9008 if (cm == MoveNumberOne || cm == PositionDiagram ||
9009 cm == PGNTag || cm == Comment)
9012 } else if (cm == GNUChessGame) {
9013 if (gameInfo.event != NULL) {
9014 free(gameInfo.event);
9016 gameInfo.event = StrSave(yy_text);
9019 startedFromSetupPosition = FALSE;
9020 while (cm == PGNTag) {
9021 if (appData.debugMode)
9022 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9023 err = ParsePGNTag(yy_text, &gameInfo);
9024 if (!err) numPGNTags++;
9026 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9027 if(gameInfo.variant != oldVariant) {
9028 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9030 oldVariant = gameInfo.variant;
9031 if (appData.debugMode)
9032 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9036 if (gameInfo.fen != NULL) {
9037 Board initial_position;
9038 startedFromSetupPosition = TRUE;
9039 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9041 DisplayError(_("Bad FEN position in file"), 0);
9044 CopyBoard(boards[0], initial_position);
9045 if (blackPlaysFirst) {
9046 currentMove = forwardMostMove = backwardMostMove = 1;
9047 CopyBoard(boards[1], initial_position);
9048 strcpy(moveList[0], "");
9049 strcpy(parseList[0], "");
9050 timeRemaining[0][1] = whiteTimeRemaining;
9051 timeRemaining[1][1] = blackTimeRemaining;
9052 if (commentList[0] != NULL) {
9053 commentList[1] = commentList[0];
9054 commentList[0] = NULL;
9057 currentMove = forwardMostMove = backwardMostMove = 0;
9059 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9061 initialRulePlies = FENrulePlies;
9062 epStatus[forwardMostMove] = FENepStatus;
9063 for( i=0; i< nrCastlingRights; i++ )
9064 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9066 yyboardindex = forwardMostMove;
9068 gameInfo.fen = NULL;
9071 yyboardindex = forwardMostMove;
9072 cm = (ChessMove) yylex();
9074 /* Handle comments interspersed among the tags */
9075 while (cm == Comment) {
9077 if (appData.debugMode)
9078 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9080 if (*p == '{' || *p == '[' || *p == '(') {
9081 p[strlen(p) - 1] = NULLCHAR;
9084 while (*p == '\n') p++;
9085 AppendComment(currentMove, p);
9086 yyboardindex = forwardMostMove;
9087 cm = (ChessMove) yylex();
9091 /* don't rely on existence of Event tag since if game was
9092 * pasted from clipboard the Event tag may not exist
9094 if (numPGNTags > 0){
9096 if (gameInfo.variant == VariantNormal) {
9097 gameInfo.variant = StringToVariant(gameInfo.event);
9100 if( appData.autoDisplayTags ) {
9101 tags = PGNTags(&gameInfo);
9102 TagsPopUp(tags, CmailMsg());
9107 /* Make something up, but don't display it now */
9112 if (cm == PositionDiagram) {
9115 Board initial_position;
9117 if (appData.debugMode)
9118 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9120 if (!startedFromSetupPosition) {
9122 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9123 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9133 initial_position[i][j++] = CharToPiece(*p);
9136 while (*p == ' ' || *p == '\t' ||
9137 *p == '\n' || *p == '\r') p++;
9139 if (strncmp(p, "black", strlen("black"))==0)
9140 blackPlaysFirst = TRUE;
9142 blackPlaysFirst = FALSE;
9143 startedFromSetupPosition = TRUE;
9145 CopyBoard(boards[0], initial_position);
9146 if (blackPlaysFirst) {
9147 currentMove = forwardMostMove = backwardMostMove = 1;
9148 CopyBoard(boards[1], initial_position);
9149 strcpy(moveList[0], "");
9150 strcpy(parseList[0], "");
9151 timeRemaining[0][1] = whiteTimeRemaining;
9152 timeRemaining[1][1] = blackTimeRemaining;
9153 if (commentList[0] != NULL) {
9154 commentList[1] = commentList[0];
9155 commentList[0] = NULL;
9158 currentMove = forwardMostMove = backwardMostMove = 0;
9161 yyboardindex = forwardMostMove;
9162 cm = (ChessMove) yylex();
9165 if (first.pr == NoProc) {
9166 StartChessProgram(&first);
9168 InitChessProgram(&first, FALSE);
9169 SendToProgram("force\n", &first);
9170 if (startedFromSetupPosition) {
9171 SendBoard(&first, forwardMostMove);
9172 if (appData.debugMode) {
9173 fprintf(debugFP, "Load Game\n");
9175 DisplayBothClocks();
9178 /* [HGM] server: flag to write setup moves in broadcast file as one */
9179 loadFlag = appData.suppressLoadMoves;
9181 while (cm == Comment) {
9183 if (appData.debugMode)
9184 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9186 if (*p == '{' || *p == '[' || *p == '(') {
9187 p[strlen(p) - 1] = NULLCHAR;
9190 while (*p == '\n') p++;
9191 AppendComment(currentMove, p);
9192 yyboardindex = forwardMostMove;
9193 cm = (ChessMove) yylex();
9196 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9197 cm == WhiteWins || cm == BlackWins ||
9198 cm == GameIsDrawn || cm == GameUnfinished) {
9199 DisplayMessage("", _("No moves in game"));
9200 if (cmailMsgLoaded) {
9201 if (appData.debugMode)
9202 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9206 DrawPosition(FALSE, boards[currentMove]);
9207 DisplayBothClocks();
9208 gameMode = EditGame;
9215 // [HGM] PV info: routine tests if comment empty
9216 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9217 DisplayComment(currentMove - 1, commentList[currentMove]);
9219 if (!matchMode && appData.timeDelay != 0)
9220 DrawPosition(FALSE, boards[currentMove]);
9222 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9223 programStats.ok_to_send = 1;
9226 /* if the first token after the PGN tags is a move
9227 * and not move number 1, retrieve it from the parser
9229 if (cm != MoveNumberOne)
9230 LoadGameOneMove(cm);
9232 /* load the remaining moves from the file */
9233 while (LoadGameOneMove((ChessMove)0)) {
9234 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9235 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9238 /* rewind to the start of the game */
9239 currentMove = backwardMostMove;
9241 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9243 if (oldGameMode == AnalyzeFile ||
9244 oldGameMode == AnalyzeMode) {
9248 if (matchMode || appData.timeDelay == 0) {
9250 gameMode = EditGame;
9252 } else if (appData.timeDelay > 0) {
9256 if (appData.debugMode)
9257 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9259 loadFlag = 0; /* [HGM] true game starts */
9263 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9265 ReloadPosition(offset)
9268 int positionNumber = lastLoadPositionNumber + offset;
9269 if (lastLoadPositionFP == NULL) {
9270 DisplayError(_("No position has been loaded yet"), 0);
9273 if (positionNumber <= 0) {
9274 DisplayError(_("Can't back up any further"), 0);
9277 return LoadPosition(lastLoadPositionFP, positionNumber,
9278 lastLoadPositionTitle);
9281 /* Load the nth position from the given file */
9283 LoadPositionFromFile(filename, n, title)
9291 if (strcmp(filename, "-") == 0) {
9292 return LoadPosition(stdin, n, "stdin");
9294 f = fopen(filename, "rb");
9296 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9297 DisplayError(buf, errno);
9300 return LoadPosition(f, n, title);
9305 /* Load the nth position from the given open file, and close it */
9307 LoadPosition(f, positionNumber, title)
9312 char *p, line[MSG_SIZ];
9313 Board initial_position;
9314 int i, j, fenMode, pn;
9316 if (gameMode == Training )
9317 SetTrainingModeOff();
9319 if (gameMode != BeginningOfGame) {
9322 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9323 fclose(lastLoadPositionFP);
9325 if (positionNumber == 0) positionNumber = 1;
9326 lastLoadPositionFP = f;
9327 lastLoadPositionNumber = positionNumber;
9328 strcpy(lastLoadPositionTitle, title);
9329 if (first.pr == NoProc) {
9330 StartChessProgram(&first);
9331 InitChessProgram(&first, FALSE);
9333 pn = positionNumber;
9334 if (positionNumber < 0) {
9335 /* Negative position number means to seek to that byte offset */
9336 if (fseek(f, -positionNumber, 0) == -1) {
9337 DisplayError(_("Can't seek on position file"), 0);
9342 if (fseek(f, 0, 0) == -1) {
9343 if (f == lastLoadPositionFP ?
9344 positionNumber == lastLoadPositionNumber + 1 :
9345 positionNumber == 1) {
9348 DisplayError(_("Can't seek on position file"), 0);
9353 /* See if this file is FEN or old-style xboard */
9354 if (fgets(line, MSG_SIZ, f) == NULL) {
9355 DisplayError(_("Position not found in file"), 0);
9364 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9365 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9366 case '1': case '2': case '3': case '4': case '5': case '6':
9367 case '7': case '8': case '9':
9368 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9369 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9370 case 'C': case 'W': case 'c': case 'w':
9375 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9376 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9380 if (fenMode || line[0] == '#') pn--;
9382 /* skip positions before number pn */
9383 if (fgets(line, MSG_SIZ, f) == NULL) {
9385 DisplayError(_("Position not found in file"), 0);
9388 if (fenMode || line[0] == '#') pn--;
9393 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9394 DisplayError(_("Bad FEN position in file"), 0);
9398 (void) fgets(line, MSG_SIZ, f);
9399 (void) fgets(line, MSG_SIZ, f);
9401 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9402 (void) fgets(line, MSG_SIZ, f);
9403 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9406 initial_position[i][j++] = CharToPiece(*p);
9410 blackPlaysFirst = FALSE;
9412 (void) fgets(line, MSG_SIZ, f);
9413 if (strncmp(line, "black", strlen("black"))==0)
9414 blackPlaysFirst = TRUE;
9417 startedFromSetupPosition = TRUE;
9419 SendToProgram("force\n", &first);
9420 CopyBoard(boards[0], initial_position);
9421 if (blackPlaysFirst) {
9422 currentMove = forwardMostMove = backwardMostMove = 1;
9423 strcpy(moveList[0], "");
9424 strcpy(parseList[0], "");
9425 CopyBoard(boards[1], initial_position);
9426 DisplayMessage("", _("Black to play"));
9428 currentMove = forwardMostMove = backwardMostMove = 0;
9429 DisplayMessage("", _("White to play"));
9431 /* [HGM] copy FEN attributes as well */
9433 initialRulePlies = FENrulePlies;
9434 epStatus[forwardMostMove] = FENepStatus;
9435 for( i=0; i< nrCastlingRights; i++ )
9436 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9438 SendBoard(&first, forwardMostMove);
9439 if (appData.debugMode) {
9441 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9442 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9443 fprintf(debugFP, "Load Position\n");
9446 if (positionNumber > 1) {
9447 sprintf(line, "%s %d", title, positionNumber);
9450 DisplayTitle(title);
9452 gameMode = EditGame;
9455 timeRemaining[0][1] = whiteTimeRemaining;
9456 timeRemaining[1][1] = blackTimeRemaining;
9457 DrawPosition(FALSE, boards[currentMove]);
9464 CopyPlayerNameIntoFileName(dest, src)
9467 while (*src != NULLCHAR && *src != ',') {
9472 *(*dest)++ = *src++;
9477 char *DefaultFileName(ext)
9480 static char def[MSG_SIZ];
9483 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9485 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9487 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9496 /* Save the current game to the given file */
9498 SaveGameToFile(filename, append)
9505 if (strcmp(filename, "-") == 0) {
9506 return SaveGame(stdout, 0, NULL);
9508 f = fopen(filename, append ? "a" : "w");
9510 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9511 DisplayError(buf, errno);
9514 return SaveGame(f, 0, NULL);
9523 static char buf[MSG_SIZ];
9526 p = strchr(str, ' ');
9527 if (p == NULL) return str;
9528 strncpy(buf, str, p - str);
9529 buf[p - str] = NULLCHAR;
9533 #define PGN_MAX_LINE 75
9535 #define PGN_SIDE_WHITE 0
9536 #define PGN_SIDE_BLACK 1
9539 static int FindFirstMoveOutOfBook( int side )
9543 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9544 int index = backwardMostMove;
9545 int has_book_hit = 0;
9547 if( (index % 2) != side ) {
9551 while( index < forwardMostMove ) {
9552 /* Check to see if engine is in book */
9553 int depth = pvInfoList[index].depth;
9554 int score = pvInfoList[index].score;
9560 else if( score == 0 && depth == 63 ) {
9561 in_book = 1; /* Zappa */
9563 else if( score == 2 && depth == 99 ) {
9564 in_book = 1; /* Abrok */
9567 has_book_hit += in_book;
9583 void GetOutOfBookInfo( char * buf )
9587 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9589 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9590 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9594 if( oob[0] >= 0 || oob[1] >= 0 ) {
9595 for( i=0; i<2; i++ ) {
9599 if( i > 0 && oob[0] >= 0 ) {
9603 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9604 sprintf( buf+strlen(buf), "%s%.2f",
9605 pvInfoList[idx].score >= 0 ? "+" : "",
9606 pvInfoList[idx].score / 100.0 );
9612 /* Save game in PGN style and close the file */
9617 int i, offset, linelen, newblock;
9621 int movelen, numlen, blank;
9622 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9624 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9626 tm = time((time_t *) NULL);
9628 PrintPGNTags(f, &gameInfo);
9630 if (backwardMostMove > 0 || startedFromSetupPosition) {
9631 char *fen = PositionToFEN(backwardMostMove, NULL);
9632 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9633 fprintf(f, "\n{--------------\n");
9634 PrintPosition(f, backwardMostMove);
9635 fprintf(f, "--------------}\n");
9639 /* [AS] Out of book annotation */
9640 if( appData.saveOutOfBookInfo ) {
9643 GetOutOfBookInfo( buf );
9645 if( buf[0] != '\0' ) {
9646 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9653 i = backwardMostMove;
9657 while (i < forwardMostMove) {
9658 /* Print comments preceding this move */
9659 if (commentList[i] != NULL) {
9660 if (linelen > 0) fprintf(f, "\n");
9661 fprintf(f, "{\n%s}\n", commentList[i]);
9666 /* Format move number */
9668 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9671 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9673 numtext[0] = NULLCHAR;
9676 numlen = strlen(numtext);
9679 /* Print move number */
9680 blank = linelen > 0 && numlen > 0;
9681 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9690 fprintf(f, numtext);
9694 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9695 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9697 // SavePart already does this!
9698 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9699 int p = movelen - 1;
9700 if(move_buffer[p] == ' ') p--;
9701 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9702 while(p && move_buffer[--p] != '(');
9703 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9708 blank = linelen > 0 && movelen > 0;
9709 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9718 fprintf(f, move_buffer);
9721 /* [AS] Add PV info if present */
9722 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9723 /* [HGM] add time */
9724 char buf[MSG_SIZ]; int seconds = 0;
9727 if(i >= backwardMostMove) {
9729 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9730 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9732 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9733 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9735 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9737 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9740 if( seconds <= 0) buf[0] = 0; else
9741 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9742 seconds = (seconds + 4)/10; // round to full seconds
9743 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9744 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9747 sprintf( move_buffer, "{%s%.2f/%d%s}",
9748 pvInfoList[i].score >= 0 ? "+" : "",
9749 pvInfoList[i].score / 100.0,
9750 pvInfoList[i].depth,
9753 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9755 /* Print score/depth */
9756 blank = linelen > 0 && movelen > 0;
9757 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9766 fprintf(f, move_buffer);
9773 /* Start a new line */
9774 if (linelen > 0) fprintf(f, "\n");
9776 /* Print comments after last move */
9777 if (commentList[i] != NULL) {
9778 fprintf(f, "{\n%s}\n", commentList[i]);
9782 if (gameInfo.resultDetails != NULL &&
9783 gameInfo.resultDetails[0] != NULLCHAR) {
9784 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9785 PGNResult(gameInfo.result));
9787 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9791 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9795 /* Save game in old style and close the file */
9803 tm = time((time_t *) NULL);
9805 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9808 if (backwardMostMove > 0 || startedFromSetupPosition) {
9809 fprintf(f, "\n[--------------\n");
9810 PrintPosition(f, backwardMostMove);
9811 fprintf(f, "--------------]\n");
9816 i = backwardMostMove;
9817 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9819 while (i < forwardMostMove) {
9820 if (commentList[i] != NULL) {
9821 fprintf(f, "[%s]\n", commentList[i]);
9825 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9828 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9830 if (commentList[i] != NULL) {
9834 if (i >= forwardMostMove) {
9838 fprintf(f, "%s\n", parseList[i]);
9843 if (commentList[i] != NULL) {
9844 fprintf(f, "[%s]\n", commentList[i]);
9847 /* This isn't really the old style, but it's close enough */
9848 if (gameInfo.resultDetails != NULL &&
9849 gameInfo.resultDetails[0] != NULLCHAR) {
9850 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9851 gameInfo.resultDetails);
9853 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9860 /* Save the current game to open file f and close the file */
9862 SaveGame(f, dummy, dummy2)
9867 if (gameMode == EditPosition) EditPositionDone();
9868 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9869 if (appData.oldSaveStyle)
9870 return SaveGameOldStyle(f);
9872 return SaveGamePGN(f);
9875 /* Save the current position to the given file */
9877 SavePositionToFile(filename)
9883 if (strcmp(filename, "-") == 0) {
9884 return SavePosition(stdout, 0, NULL);
9886 f = fopen(filename, "a");
9888 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9889 DisplayError(buf, errno);
9892 SavePosition(f, 0, NULL);
9898 /* Save the current position to the given open file and close the file */
9900 SavePosition(f, dummy, dummy2)
9908 if (appData.oldSaveStyle) {
9909 tm = time((time_t *) NULL);
9911 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9913 fprintf(f, "[--------------\n");
9914 PrintPosition(f, currentMove);
9915 fprintf(f, "--------------]\n");
9917 fen = PositionToFEN(currentMove, NULL);
9918 fprintf(f, "%s\n", fen);
9926 ReloadCmailMsgEvent(unregister)
9930 static char *inFilename = NULL;
9931 static char *outFilename;
9933 struct stat inbuf, outbuf;
9936 /* Any registered moves are unregistered if unregister is set, */
9937 /* i.e. invoked by the signal handler */
9939 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9940 cmailMoveRegistered[i] = FALSE;
9941 if (cmailCommentList[i] != NULL) {
9942 free(cmailCommentList[i]);
9943 cmailCommentList[i] = NULL;
9946 nCmailMovesRegistered = 0;
9949 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9950 cmailResult[i] = CMAIL_NOT_RESULT;
9954 if (inFilename == NULL) {
9955 /* Because the filenames are static they only get malloced once */
9956 /* and they never get freed */
9957 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9958 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9960 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9961 sprintf(outFilename, "%s.out", appData.cmailGameName);
9964 status = stat(outFilename, &outbuf);
9966 cmailMailedMove = FALSE;
9968 status = stat(inFilename, &inbuf);
9969 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9972 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9973 counts the games, notes how each one terminated, etc.
9975 It would be nice to remove this kludge and instead gather all
9976 the information while building the game list. (And to keep it
9977 in the game list nodes instead of having a bunch of fixed-size
9978 parallel arrays.) Note this will require getting each game's
9979 termination from the PGN tags, as the game list builder does
9980 not process the game moves. --mann
9982 cmailMsgLoaded = TRUE;
9983 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9985 /* Load first game in the file or popup game menu */
9986 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9996 char string[MSG_SIZ];
9998 if ( cmailMailedMove
9999 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10000 return TRUE; /* Allow free viewing */
10003 /* Unregister move to ensure that we don't leave RegisterMove */
10004 /* with the move registered when the conditions for registering no */
10006 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10007 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10008 nCmailMovesRegistered --;
10010 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10012 free(cmailCommentList[lastLoadGameNumber - 1]);
10013 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10017 if (cmailOldMove == -1) {
10018 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10022 if (currentMove > cmailOldMove + 1) {
10023 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10027 if (currentMove < cmailOldMove) {
10028 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10032 if (forwardMostMove > currentMove) {
10033 /* Silently truncate extra moves */
10037 if ( (currentMove == cmailOldMove + 1)
10038 || ( (currentMove == cmailOldMove)
10039 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10040 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10041 if (gameInfo.result != GameUnfinished) {
10042 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10045 if (commentList[currentMove] != NULL) {
10046 cmailCommentList[lastLoadGameNumber - 1]
10047 = StrSave(commentList[currentMove]);
10049 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10051 if (appData.debugMode)
10052 fprintf(debugFP, "Saving %s for game %d\n",
10053 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10056 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10058 f = fopen(string, "w");
10059 if (appData.oldSaveStyle) {
10060 SaveGameOldStyle(f); /* also closes the file */
10062 sprintf(string, "%s.pos.out", appData.cmailGameName);
10063 f = fopen(string, "w");
10064 SavePosition(f, 0, NULL); /* also closes the file */
10066 fprintf(f, "{--------------\n");
10067 PrintPosition(f, currentMove);
10068 fprintf(f, "--------------}\n\n");
10070 SaveGame(f, 0, NULL); /* also closes the file*/
10073 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10074 nCmailMovesRegistered ++;
10075 } else if (nCmailGames == 1) {
10076 DisplayError(_("You have not made a move yet"), 0);
10087 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10088 FILE *commandOutput;
10089 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10090 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10096 if (! cmailMsgLoaded) {
10097 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10101 if (nCmailGames == nCmailResults) {
10102 DisplayError(_("No unfinished games"), 0);
10106 #if CMAIL_PROHIBIT_REMAIL
10107 if (cmailMailedMove) {
10108 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);
10109 DisplayError(msg, 0);
10114 if (! (cmailMailedMove || RegisterMove())) return;
10116 if ( cmailMailedMove
10117 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10118 sprintf(string, partCommandString,
10119 appData.debugMode ? " -v" : "", appData.cmailGameName);
10120 commandOutput = popen(string, "r");
10122 if (commandOutput == NULL) {
10123 DisplayError(_("Failed to invoke cmail"), 0);
10125 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10126 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10128 if (nBuffers > 1) {
10129 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10130 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10131 nBytes = MSG_SIZ - 1;
10133 (void) memcpy(msg, buffer, nBytes);
10135 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10137 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10138 cmailMailedMove = TRUE; /* Prevent >1 moves */
10141 for (i = 0; i < nCmailGames; i ++) {
10142 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10147 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10149 sprintf(buffer, "%s/%s.%s.archive",
10151 appData.cmailGameName,
10153 LoadGameFromFile(buffer, 1, buffer, FALSE);
10154 cmailMsgLoaded = FALSE;
10158 DisplayInformation(msg);
10159 pclose(commandOutput);
10162 if ((*cmailMsg) != '\0') {
10163 DisplayInformation(cmailMsg);
10168 #endif /* !WIN32 */
10177 int prependComma = 0;
10179 char string[MSG_SIZ]; /* Space for game-list */
10182 if (!cmailMsgLoaded) return "";
10184 if (cmailMailedMove) {
10185 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10187 /* Create a list of games left */
10188 sprintf(string, "[");
10189 for (i = 0; i < nCmailGames; i ++) {
10190 if (! ( cmailMoveRegistered[i]
10191 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10192 if (prependComma) {
10193 sprintf(number, ",%d", i + 1);
10195 sprintf(number, "%d", i + 1);
10199 strcat(string, number);
10202 strcat(string, "]");
10204 if (nCmailMovesRegistered + nCmailResults == 0) {
10205 switch (nCmailGames) {
10208 _("Still need to make move for game\n"));
10213 _("Still need to make moves for both games\n"));
10218 _("Still need to make moves for all %d games\n"),
10223 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10226 _("Still need to make a move for game %s\n"),
10231 if (nCmailResults == nCmailGames) {
10232 sprintf(cmailMsg, _("No unfinished games\n"));
10234 sprintf(cmailMsg, _("Ready to send mail\n"));
10240 _("Still need to make moves for games %s\n"),
10252 if (gameMode == Training)
10253 SetTrainingModeOff();
10256 cmailMsgLoaded = FALSE;
10257 if (appData.icsActive) {
10258 SendToICS(ics_prefix);
10259 SendToICS("refresh\n");
10269 /* Give up on clean exit */
10273 /* Keep trying for clean exit */
10277 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10279 if (telnetISR != NULL) {
10280 RemoveInputSource(telnetISR);
10282 if (icsPR != NoProc) {
10283 DestroyChildProcess(icsPR, TRUE);
10286 /* Save game if resource set and not already saved by GameEnds() */
10287 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10288 && forwardMostMove > 0) {
10289 if (*appData.saveGameFile != NULLCHAR) {
10290 SaveGameToFile(appData.saveGameFile, TRUE);
10291 } else if (appData.autoSaveGames) {
10294 if (*appData.savePositionFile != NULLCHAR) {
10295 SavePositionToFile(appData.savePositionFile);
10298 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10300 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10301 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10303 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10304 /* make sure this other one finishes before killing it! */
10305 if(endingGame) { int count = 0;
10306 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10307 while(endingGame && count++ < 10) DoSleep(1);
10308 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10311 /* Kill off chess programs */
10312 if (first.pr != NoProc) {
10315 DoSleep( appData.delayBeforeQuit );
10316 SendToProgram("quit\n", &first);
10317 DoSleep( appData.delayAfterQuit );
10318 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10320 if (second.pr != NoProc) {
10321 DoSleep( appData.delayBeforeQuit );
10322 SendToProgram("quit\n", &second);
10323 DoSleep( appData.delayAfterQuit );
10324 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10326 if (first.isr != NULL) {
10327 RemoveInputSource(first.isr);
10329 if (second.isr != NULL) {
10330 RemoveInputSource(second.isr);
10333 ShutDownFrontEnd();
10340 if (appData.debugMode)
10341 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10345 if (gameMode == MachinePlaysWhite ||
10346 gameMode == MachinePlaysBlack) {
10349 DisplayBothClocks();
10351 if (gameMode == PlayFromGameFile) {
10352 if (appData.timeDelay >= 0)
10353 AutoPlayGameLoop();
10354 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10355 Reset(FALSE, TRUE);
10356 SendToICS(ics_prefix);
10357 SendToICS("refresh\n");
10358 } else if (currentMove < forwardMostMove) {
10359 ForwardInner(forwardMostMove);
10361 pauseExamInvalid = FALSE;
10363 switch (gameMode) {
10367 pauseExamForwardMostMove = forwardMostMove;
10368 pauseExamInvalid = FALSE;
10371 case IcsPlayingWhite:
10372 case IcsPlayingBlack:
10376 case PlayFromGameFile:
10377 (void) StopLoadGameTimer();
10381 case BeginningOfGame:
10382 if (appData.icsActive) return;
10383 /* else fall through */
10384 case MachinePlaysWhite:
10385 case MachinePlaysBlack:
10386 case TwoMachinesPlay:
10387 if (forwardMostMove == 0)
10388 return; /* don't pause if no one has moved */
10389 if ((gameMode == MachinePlaysWhite &&
10390 !WhiteOnMove(forwardMostMove)) ||
10391 (gameMode == MachinePlaysBlack &&
10392 WhiteOnMove(forwardMostMove))) {
10405 char title[MSG_SIZ];
10407 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10408 strcpy(title, _("Edit comment"));
10410 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10411 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10412 parseList[currentMove - 1]);
10415 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10422 char *tags = PGNTags(&gameInfo);
10423 EditTagsPopUp(tags);
10430 if (appData.noChessProgram || gameMode == AnalyzeMode)
10433 if (gameMode != AnalyzeFile) {
10434 if (!appData.icsEngineAnalyze) {
10436 if (gameMode != EditGame) return;
10438 ResurrectChessProgram();
10439 SendToProgram("analyze\n", &first);
10440 first.analyzing = TRUE;
10441 /*first.maybeThinking = TRUE;*/
10442 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10443 AnalysisPopUp(_("Analysis"),
10444 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10446 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10451 StartAnalysisClock();
10452 GetTimeMark(&lastNodeCountTime);
10459 if (appData.noChessProgram || gameMode == AnalyzeFile)
10462 if (gameMode != AnalyzeMode) {
10464 if (gameMode != EditGame) return;
10465 ResurrectChessProgram();
10466 SendToProgram("analyze\n", &first);
10467 first.analyzing = TRUE;
10468 /*first.maybeThinking = TRUE;*/
10469 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10470 AnalysisPopUp(_("Analysis"),
10471 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10473 gameMode = AnalyzeFile;
10478 StartAnalysisClock();
10479 GetTimeMark(&lastNodeCountTime);
10484 MachineWhiteEvent()
10487 char *bookHit = NULL;
10489 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10493 if (gameMode == PlayFromGameFile ||
10494 gameMode == TwoMachinesPlay ||
10495 gameMode == Training ||
10496 gameMode == AnalyzeMode ||
10497 gameMode == EndOfGame)
10500 if (gameMode == EditPosition)
10501 EditPositionDone();
10503 if (!WhiteOnMove(currentMove)) {
10504 DisplayError(_("It is not White's turn"), 0);
10508 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10511 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10512 gameMode == AnalyzeFile)
10515 ResurrectChessProgram(); /* in case it isn't running */
10516 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10517 gameMode = MachinePlaysWhite;
10520 gameMode = MachinePlaysWhite;
10524 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10526 if (first.sendName) {
10527 sprintf(buf, "name %s\n", gameInfo.black);
10528 SendToProgram(buf, &first);
10530 if (first.sendTime) {
10531 if (first.useColors) {
10532 SendToProgram("black\n", &first); /*gnu kludge*/
10534 SendTimeRemaining(&first, TRUE);
10536 if (first.useColors) {
10537 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10539 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10540 SetMachineThinkingEnables();
10541 first.maybeThinking = TRUE;
10544 if (appData.autoFlipView && !flipView) {
10545 flipView = !flipView;
10546 DrawPosition(FALSE, NULL);
10547 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10550 if(bookHit) { // [HGM] book: simulate book reply
10551 static char bookMove[MSG_SIZ]; // a bit generous?
10553 programStats.nodes = programStats.depth = programStats.time =
10554 programStats.score = programStats.got_only_move = 0;
10555 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10557 strcpy(bookMove, "move ");
10558 strcat(bookMove, bookHit);
10559 HandleMachineMove(bookMove, &first);
10564 MachineBlackEvent()
10567 char *bookHit = NULL;
10569 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10573 if (gameMode == PlayFromGameFile ||
10574 gameMode == TwoMachinesPlay ||
10575 gameMode == Training ||
10576 gameMode == AnalyzeMode ||
10577 gameMode == EndOfGame)
10580 if (gameMode == EditPosition)
10581 EditPositionDone();
10583 if (WhiteOnMove(currentMove)) {
10584 DisplayError(_("It is not Black's turn"), 0);
10588 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10591 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10592 gameMode == AnalyzeFile)
10595 ResurrectChessProgram(); /* in case it isn't running */
10596 gameMode = MachinePlaysBlack;
10600 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10602 if (first.sendName) {
10603 sprintf(buf, "name %s\n", gameInfo.white);
10604 SendToProgram(buf, &first);
10606 if (first.sendTime) {
10607 if (first.useColors) {
10608 SendToProgram("white\n", &first); /*gnu kludge*/
10610 SendTimeRemaining(&first, FALSE);
10612 if (first.useColors) {
10613 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10615 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10616 SetMachineThinkingEnables();
10617 first.maybeThinking = TRUE;
10620 if (appData.autoFlipView && flipView) {
10621 flipView = !flipView;
10622 DrawPosition(FALSE, NULL);
10623 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10625 if(bookHit) { // [HGM] book: simulate book reply
10626 static char bookMove[MSG_SIZ]; // a bit generous?
10628 programStats.nodes = programStats.depth = programStats.time =
10629 programStats.score = programStats.got_only_move = 0;
10630 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10632 strcpy(bookMove, "move ");
10633 strcat(bookMove, bookHit);
10634 HandleMachineMove(bookMove, &first);
10640 DisplayTwoMachinesTitle()
10643 if (appData.matchGames > 0) {
10644 if (first.twoMachinesColor[0] == 'w') {
10645 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10646 gameInfo.white, gameInfo.black,
10647 first.matchWins, second.matchWins,
10648 matchGame - 1 - (first.matchWins + second.matchWins));
10650 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10651 gameInfo.white, gameInfo.black,
10652 second.matchWins, first.matchWins,
10653 matchGame - 1 - (first.matchWins + second.matchWins));
10656 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10662 TwoMachinesEvent P((void))
10666 ChessProgramState *onmove;
10667 char *bookHit = NULL;
10669 if (appData.noChessProgram) return;
10671 switch (gameMode) {
10672 case TwoMachinesPlay:
10674 case MachinePlaysWhite:
10675 case MachinePlaysBlack:
10676 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10677 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10681 case BeginningOfGame:
10682 case PlayFromGameFile:
10685 if (gameMode != EditGame) return;
10688 EditPositionDone();
10699 forwardMostMove = currentMove;
10700 ResurrectChessProgram(); /* in case first program isn't running */
10702 if (second.pr == NULL) {
10703 StartChessProgram(&second);
10704 if (second.protocolVersion == 1) {
10705 TwoMachinesEventIfReady();
10707 /* kludge: allow timeout for initial "feature" command */
10709 DisplayMessage("", _("Starting second chess program"));
10710 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10714 DisplayMessage("", "");
10715 InitChessProgram(&second, FALSE);
10716 SendToProgram("force\n", &second);
10717 if (startedFromSetupPosition) {
10718 SendBoard(&second, backwardMostMove);
10719 if (appData.debugMode) {
10720 fprintf(debugFP, "Two Machines\n");
10723 for (i = backwardMostMove; i < forwardMostMove; i++) {
10724 SendMoveToProgram(i, &second);
10727 gameMode = TwoMachinesPlay;
10731 DisplayTwoMachinesTitle();
10733 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10739 SendToProgram(first.computerString, &first);
10740 if (first.sendName) {
10741 sprintf(buf, "name %s\n", second.tidy);
10742 SendToProgram(buf, &first);
10744 SendToProgram(second.computerString, &second);
10745 if (second.sendName) {
10746 sprintf(buf, "name %s\n", first.tidy);
10747 SendToProgram(buf, &second);
10751 if (!first.sendTime || !second.sendTime) {
10752 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10753 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10755 if (onmove->sendTime) {
10756 if (onmove->useColors) {
10757 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10759 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10761 if (onmove->useColors) {
10762 SendToProgram(onmove->twoMachinesColor, onmove);
10764 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10765 // SendToProgram("go\n", onmove);
10766 onmove->maybeThinking = TRUE;
10767 SetMachineThinkingEnables();
10771 if(bookHit) { // [HGM] book: simulate book reply
10772 static char bookMove[MSG_SIZ]; // a bit generous?
10774 programStats.nodes = programStats.depth = programStats.time =
10775 programStats.score = programStats.got_only_move = 0;
10776 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10778 strcpy(bookMove, "move ");
10779 strcat(bookMove, bookHit);
10780 HandleMachineMove(bookMove, &first);
10787 if (gameMode == Training) {
10788 SetTrainingModeOff();
10789 gameMode = PlayFromGameFile;
10790 DisplayMessage("", _("Training mode off"));
10792 gameMode = Training;
10793 animateTraining = appData.animate;
10795 /* make sure we are not already at the end of the game */
10796 if (currentMove < forwardMostMove) {
10797 SetTrainingModeOn();
10798 DisplayMessage("", _("Training mode on"));
10800 gameMode = PlayFromGameFile;
10801 DisplayError(_("Already at end of game"), 0);
10810 if (!appData.icsActive) return;
10811 switch (gameMode) {
10812 case IcsPlayingWhite:
10813 case IcsPlayingBlack:
10816 case BeginningOfGame:
10824 EditPositionDone();
10837 gameMode = IcsIdle;
10848 switch (gameMode) {
10850 SetTrainingModeOff();
10852 case MachinePlaysWhite:
10853 case MachinePlaysBlack:
10854 case BeginningOfGame:
10855 SendToProgram("force\n", &first);
10856 SetUserThinkingEnables();
10858 case PlayFromGameFile:
10859 (void) StopLoadGameTimer();
10860 if (gameFileFP != NULL) {
10865 EditPositionDone();
10870 SendToProgram("force\n", &first);
10872 case TwoMachinesPlay:
10873 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10874 ResurrectChessProgram();
10875 SetUserThinkingEnables();
10878 ResurrectChessProgram();
10880 case IcsPlayingBlack:
10881 case IcsPlayingWhite:
10882 DisplayError(_("Warning: You are still playing a game"), 0);
10885 DisplayError(_("Warning: You are still observing a game"), 0);
10888 DisplayError(_("Warning: You are still examining a game"), 0);
10899 first.offeredDraw = second.offeredDraw = 0;
10901 if (gameMode == PlayFromGameFile) {
10902 whiteTimeRemaining = timeRemaining[0][currentMove];
10903 blackTimeRemaining = timeRemaining[1][currentMove];
10907 if (gameMode == MachinePlaysWhite ||
10908 gameMode == MachinePlaysBlack ||
10909 gameMode == TwoMachinesPlay ||
10910 gameMode == EndOfGame) {
10911 i = forwardMostMove;
10912 while (i > currentMove) {
10913 SendToProgram("undo\n", &first);
10916 whiteTimeRemaining = timeRemaining[0][currentMove];
10917 blackTimeRemaining = timeRemaining[1][currentMove];
10918 DisplayBothClocks();
10919 if (whiteFlag || blackFlag) {
10920 whiteFlag = blackFlag = 0;
10925 gameMode = EditGame;
10932 EditPositionEvent()
10934 if (gameMode == EditPosition) {
10940 if (gameMode != EditGame) return;
10942 gameMode = EditPosition;
10945 if (currentMove > 0)
10946 CopyBoard(boards[0], boards[currentMove]);
10948 blackPlaysFirst = !WhiteOnMove(currentMove);
10950 currentMove = forwardMostMove = backwardMostMove = 0;
10951 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10958 /* [DM] icsEngineAnalyze - possible call from other functions */
10959 if (appData.icsEngineAnalyze) {
10960 appData.icsEngineAnalyze = FALSE;
10962 DisplayMessage("",_("Close ICS engine analyze..."));
10964 if (first.analysisSupport && first.analyzing) {
10965 SendToProgram("exit\n", &first);
10966 first.analyzing = FALSE;
10969 thinkOutput[0] = NULLCHAR;
10975 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10977 startedFromSetupPosition = TRUE;
10978 InitChessProgram(&first, FALSE);
10979 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10980 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10981 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10982 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10983 } else castlingRights[0][2] = -1;
10984 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10985 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10986 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10987 } else castlingRights[0][5] = -1;
10988 SendToProgram("force\n", &first);
10989 if (blackPlaysFirst) {
10990 strcpy(moveList[0], "");
10991 strcpy(parseList[0], "");
10992 currentMove = forwardMostMove = backwardMostMove = 1;
10993 CopyBoard(boards[1], boards[0]);
10994 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10996 epStatus[1] = epStatus[0];
10997 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11000 currentMove = forwardMostMove = backwardMostMove = 0;
11002 SendBoard(&first, forwardMostMove);
11003 if (appData.debugMode) {
11004 fprintf(debugFP, "EditPosDone\n");
11007 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11008 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11009 gameMode = EditGame;
11011 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11012 ClearHighlights(); /* [AS] */
11015 /* Pause for `ms' milliseconds */
11016 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11026 } while (SubtractTimeMarks(&m2, &m1) < ms);
11029 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11031 SendMultiLineToICS(buf)
11034 char temp[MSG_SIZ+1], *p;
11041 strncpy(temp, buf, len);
11046 if (*p == '\n' || *p == '\r')
11051 strcat(temp, "\n");
11053 SendToPlayer(temp, strlen(temp));
11057 SetWhiteToPlayEvent()
11059 if (gameMode == EditPosition) {
11060 blackPlaysFirst = FALSE;
11061 DisplayBothClocks(); /* works because currentMove is 0 */
11062 } else if (gameMode == IcsExamining) {
11063 SendToICS(ics_prefix);
11064 SendToICS("tomove white\n");
11069 SetBlackToPlayEvent()
11071 if (gameMode == EditPosition) {
11072 blackPlaysFirst = TRUE;
11073 currentMove = 1; /* kludge */
11074 DisplayBothClocks();
11076 } else if (gameMode == IcsExamining) {
11077 SendToICS(ics_prefix);
11078 SendToICS("tomove black\n");
11083 EditPositionMenuEvent(selection, x, y)
11084 ChessSquare selection;
11088 ChessSquare piece = boards[0][y][x];
11090 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11092 switch (selection) {
11094 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11095 SendToICS(ics_prefix);
11096 SendToICS("bsetup clear\n");
11097 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11098 SendToICS(ics_prefix);
11099 SendToICS("clearboard\n");
11101 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11102 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11103 for (y = 0; y < BOARD_HEIGHT; y++) {
11104 if (gameMode == IcsExamining) {
11105 if (boards[currentMove][y][x] != EmptySquare) {
11106 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11111 boards[0][y][x] = p;
11116 if (gameMode == EditPosition) {
11117 DrawPosition(FALSE, boards[0]);
11122 SetWhiteToPlayEvent();
11126 SetBlackToPlayEvent();
11130 if (gameMode == IcsExamining) {
11131 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11134 boards[0][y][x] = EmptySquare;
11135 DrawPosition(FALSE, boards[0]);
11140 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11141 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11142 selection = (ChessSquare) (PROMOTED piece);
11143 } else if(piece == EmptySquare) selection = WhiteSilver;
11144 else selection = (ChessSquare)((int)piece - 1);
11148 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11149 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11150 selection = (ChessSquare) (DEMOTED piece);
11151 } else if(piece == EmptySquare) selection = BlackSilver;
11152 else selection = (ChessSquare)((int)piece + 1);
11157 if(gameInfo.variant == VariantShatranj ||
11158 gameInfo.variant == VariantXiangqi ||
11159 gameInfo.variant == VariantCourier )
11160 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11165 if(gameInfo.variant == VariantXiangqi)
11166 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11167 if(gameInfo.variant == VariantKnightmate)
11168 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11171 if (gameMode == IcsExamining) {
11172 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11173 PieceToChar(selection), AAA + x, ONE + y);
11176 boards[0][y][x] = selection;
11177 DrawPosition(FALSE, boards[0]);
11185 DropMenuEvent(selection, x, y)
11186 ChessSquare selection;
11189 ChessMove moveType;
11191 switch (gameMode) {
11192 case IcsPlayingWhite:
11193 case MachinePlaysBlack:
11194 if (!WhiteOnMove(currentMove)) {
11195 DisplayMoveError(_("It is Black's turn"));
11198 moveType = WhiteDrop;
11200 case IcsPlayingBlack:
11201 case MachinePlaysWhite:
11202 if (WhiteOnMove(currentMove)) {
11203 DisplayMoveError(_("It is White's turn"));
11206 moveType = BlackDrop;
11209 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11215 if (moveType == BlackDrop && selection < BlackPawn) {
11216 selection = (ChessSquare) ((int) selection
11217 + (int) BlackPawn - (int) WhitePawn);
11219 if (boards[currentMove][y][x] != EmptySquare) {
11220 DisplayMoveError(_("That square is occupied"));
11224 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11230 /* Accept a pending offer of any kind from opponent */
11232 if (appData.icsActive) {
11233 SendToICS(ics_prefix);
11234 SendToICS("accept\n");
11235 } else if (cmailMsgLoaded) {
11236 if (currentMove == cmailOldMove &&
11237 commentList[cmailOldMove] != NULL &&
11238 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11239 "Black offers a draw" : "White offers a draw")) {
11241 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11242 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11244 DisplayError(_("There is no pending offer on this move"), 0);
11245 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11248 /* Not used for offers from chess program */
11255 /* Decline a pending offer of any kind from opponent */
11257 if (appData.icsActive) {
11258 SendToICS(ics_prefix);
11259 SendToICS("decline\n");
11260 } else if (cmailMsgLoaded) {
11261 if (currentMove == cmailOldMove &&
11262 commentList[cmailOldMove] != NULL &&
11263 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11264 "Black offers a draw" : "White offers a draw")) {
11266 AppendComment(cmailOldMove, "Draw declined");
11267 DisplayComment(cmailOldMove - 1, "Draw declined");
11270 DisplayError(_("There is no pending offer on this move"), 0);
11273 /* Not used for offers from chess program */
11280 /* Issue ICS rematch command */
11281 if (appData.icsActive) {
11282 SendToICS(ics_prefix);
11283 SendToICS("rematch\n");
11290 /* Call your opponent's flag (claim a win on time) */
11291 if (appData.icsActive) {
11292 SendToICS(ics_prefix);
11293 SendToICS("flag\n");
11295 switch (gameMode) {
11298 case MachinePlaysWhite:
11301 GameEnds(GameIsDrawn, "Both players ran out of time",
11304 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11306 DisplayError(_("Your opponent is not out of time"), 0);
11309 case MachinePlaysBlack:
11312 GameEnds(GameIsDrawn, "Both players ran out of time",
11315 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11317 DisplayError(_("Your opponent is not out of time"), 0);
11327 /* Offer draw or accept pending draw offer from opponent */
11329 if (appData.icsActive) {
11330 /* Note: tournament rules require draw offers to be
11331 made after you make your move but before you punch
11332 your clock. Currently ICS doesn't let you do that;
11333 instead, you immediately punch your clock after making
11334 a move, but you can offer a draw at any time. */
11336 SendToICS(ics_prefix);
11337 SendToICS("draw\n");
11338 } else if (cmailMsgLoaded) {
11339 if (currentMove == cmailOldMove &&
11340 commentList[cmailOldMove] != NULL &&
11341 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11342 "Black offers a draw" : "White offers a draw")) {
11343 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11344 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11345 } else if (currentMove == cmailOldMove + 1) {
11346 char *offer = WhiteOnMove(cmailOldMove) ?
11347 "White offers a draw" : "Black offers a draw";
11348 AppendComment(currentMove, offer);
11349 DisplayComment(currentMove - 1, offer);
11350 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11352 DisplayError(_("You must make your move before offering a draw"), 0);
11353 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11355 } else if (first.offeredDraw) {
11356 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11358 if (first.sendDrawOffers) {
11359 SendToProgram("draw\n", &first);
11360 userOfferedDraw = TRUE;
11368 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11370 if (appData.icsActive) {
11371 SendToICS(ics_prefix);
11372 SendToICS("adjourn\n");
11374 /* Currently GNU Chess doesn't offer or accept Adjourns */
11382 /* Offer Abort or accept pending Abort offer from opponent */
11384 if (appData.icsActive) {
11385 SendToICS(ics_prefix);
11386 SendToICS("abort\n");
11388 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11395 /* Resign. You can do this even if it's not your turn. */
11397 if (appData.icsActive) {
11398 SendToICS(ics_prefix);
11399 SendToICS("resign\n");
11401 switch (gameMode) {
11402 case MachinePlaysWhite:
11403 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11405 case MachinePlaysBlack:
11406 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11409 if (cmailMsgLoaded) {
11411 if (WhiteOnMove(cmailOldMove)) {
11412 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11414 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11416 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11427 StopObservingEvent()
11429 /* Stop observing current games */
11430 SendToICS(ics_prefix);
11431 SendToICS("unobserve\n");
11435 StopExaminingEvent()
11437 /* Stop observing current game */
11438 SendToICS(ics_prefix);
11439 SendToICS("unexamine\n");
11443 ForwardInner(target)
11448 if (appData.debugMode)
11449 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11450 target, currentMove, forwardMostMove);
11452 if (gameMode == EditPosition)
11455 if (gameMode == PlayFromGameFile && !pausing)
11458 if (gameMode == IcsExamining && pausing)
11459 limit = pauseExamForwardMostMove;
11461 limit = forwardMostMove;
11463 if (target > limit) target = limit;
11465 if (target > 0 && moveList[target - 1][0]) {
11466 int fromX, fromY, toX, toY;
11467 toX = moveList[target - 1][2] - AAA;
11468 toY = moveList[target - 1][3] - ONE;
11469 if (moveList[target - 1][1] == '@') {
11470 if (appData.highlightLastMove) {
11471 SetHighlights(-1, -1, toX, toY);
11474 fromX = moveList[target - 1][0] - AAA;
11475 fromY = moveList[target - 1][1] - ONE;
11476 if (target == currentMove + 1) {
11477 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11479 if (appData.highlightLastMove) {
11480 SetHighlights(fromX, fromY, toX, toY);
11484 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11485 gameMode == Training || gameMode == PlayFromGameFile ||
11486 gameMode == AnalyzeFile) {
11487 while (currentMove < target) {
11488 SendMoveToProgram(currentMove++, &first);
11491 currentMove = target;
11494 if (gameMode == EditGame || gameMode == EndOfGame) {
11495 whiteTimeRemaining = timeRemaining[0][currentMove];
11496 blackTimeRemaining = timeRemaining[1][currentMove];
11498 DisplayBothClocks();
11499 DisplayMove(currentMove - 1);
11500 DrawPosition(FALSE, boards[currentMove]);
11501 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11502 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11503 DisplayComment(currentMove - 1, commentList[currentMove]);
11511 if (gameMode == IcsExamining && !pausing) {
11512 SendToICS(ics_prefix);
11513 SendToICS("forward\n");
11515 ForwardInner(currentMove + 1);
11522 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11523 /* to optimze, we temporarily turn off analysis mode while we feed
11524 * the remaining moves to the engine. Otherwise we get analysis output
11527 if (first.analysisSupport) {
11528 SendToProgram("exit\nforce\n", &first);
11529 first.analyzing = FALSE;
11533 if (gameMode == IcsExamining && !pausing) {
11534 SendToICS(ics_prefix);
11535 SendToICS("forward 999999\n");
11537 ForwardInner(forwardMostMove);
11540 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11541 /* we have fed all the moves, so reactivate analysis mode */
11542 SendToProgram("analyze\n", &first);
11543 first.analyzing = TRUE;
11544 /*first.maybeThinking = TRUE;*/
11545 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11550 BackwardInner(target)
11553 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11555 if (appData.debugMode)
11556 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11557 target, currentMove, forwardMostMove);
11559 if (gameMode == EditPosition) return;
11560 if (currentMove <= backwardMostMove) {
11562 DrawPosition(full_redraw, boards[currentMove]);
11565 if (gameMode == PlayFromGameFile && !pausing)
11568 if (moveList[target][0]) {
11569 int fromX, fromY, toX, toY;
11570 toX = moveList[target][2] - AAA;
11571 toY = moveList[target][3] - ONE;
11572 if (moveList[target][1] == '@') {
11573 if (appData.highlightLastMove) {
11574 SetHighlights(-1, -1, toX, toY);
11577 fromX = moveList[target][0] - AAA;
11578 fromY = moveList[target][1] - ONE;
11579 if (target == currentMove - 1) {
11580 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11582 if (appData.highlightLastMove) {
11583 SetHighlights(fromX, fromY, toX, toY);
11587 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11588 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11589 while (currentMove > target) {
11590 SendToProgram("undo\n", &first);
11594 currentMove = target;
11597 if (gameMode == EditGame || gameMode == EndOfGame) {
11598 whiteTimeRemaining = timeRemaining[0][currentMove];
11599 blackTimeRemaining = timeRemaining[1][currentMove];
11601 DisplayBothClocks();
11602 DisplayMove(currentMove - 1);
11603 DrawPosition(full_redraw, boards[currentMove]);
11604 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11605 // [HGM] PV info: routine tests if comment empty
11606 DisplayComment(currentMove - 1, commentList[currentMove]);
11612 if (gameMode == IcsExamining && !pausing) {
11613 SendToICS(ics_prefix);
11614 SendToICS("backward\n");
11616 BackwardInner(currentMove - 1);
11623 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11624 /* to optimze, we temporarily turn off analysis mode while we undo
11625 * all the moves. Otherwise we get analysis output after each undo.
11627 if (first.analysisSupport) {
11628 SendToProgram("exit\nforce\n", &first);
11629 first.analyzing = FALSE;
11633 if (gameMode == IcsExamining && !pausing) {
11634 SendToICS(ics_prefix);
11635 SendToICS("backward 999999\n");
11637 BackwardInner(backwardMostMove);
11640 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11641 /* we have fed all the moves, so reactivate analysis mode */
11642 SendToProgram("analyze\n", &first);
11643 first.analyzing = TRUE;
11644 /*first.maybeThinking = TRUE;*/
11645 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11652 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11653 if (to >= forwardMostMove) to = forwardMostMove;
11654 if (to <= backwardMostMove) to = backwardMostMove;
11655 if (to < currentMove) {
11665 if (gameMode != IcsExamining) {
11666 DisplayError(_("You are not examining a game"), 0);
11670 DisplayError(_("You can't revert while pausing"), 0);
11673 SendToICS(ics_prefix);
11674 SendToICS("revert\n");
11680 switch (gameMode) {
11681 case MachinePlaysWhite:
11682 case MachinePlaysBlack:
11683 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11684 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11687 if (forwardMostMove < 2) return;
11688 currentMove = forwardMostMove = forwardMostMove - 2;
11689 whiteTimeRemaining = timeRemaining[0][currentMove];
11690 blackTimeRemaining = timeRemaining[1][currentMove];
11691 DisplayBothClocks();
11692 DisplayMove(currentMove - 1);
11693 ClearHighlights();/*!! could figure this out*/
11694 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11695 SendToProgram("remove\n", &first);
11696 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11699 case BeginningOfGame:
11703 case IcsPlayingWhite:
11704 case IcsPlayingBlack:
11705 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11706 SendToICS(ics_prefix);
11707 SendToICS("takeback 2\n");
11709 SendToICS(ics_prefix);
11710 SendToICS("takeback 1\n");
11719 ChessProgramState *cps;
11721 switch (gameMode) {
11722 case MachinePlaysWhite:
11723 if (!WhiteOnMove(forwardMostMove)) {
11724 DisplayError(_("It is your turn"), 0);
11729 case MachinePlaysBlack:
11730 if (WhiteOnMove(forwardMostMove)) {
11731 DisplayError(_("It is your turn"), 0);
11736 case TwoMachinesPlay:
11737 if (WhiteOnMove(forwardMostMove) ==
11738 (first.twoMachinesColor[0] == 'w')) {
11744 case BeginningOfGame:
11748 SendToProgram("?\n", cps);
11752 TruncateGameEvent()
11755 if (gameMode != EditGame) return;
11762 if (forwardMostMove > currentMove) {
11763 if (gameInfo.resultDetails != NULL) {
11764 free(gameInfo.resultDetails);
11765 gameInfo.resultDetails = NULL;
11766 gameInfo.result = GameUnfinished;
11768 forwardMostMove = currentMove;
11769 HistorySet(parseList, backwardMostMove, forwardMostMove,
11777 if (appData.noChessProgram) return;
11778 switch (gameMode) {
11779 case MachinePlaysWhite:
11780 if (WhiteOnMove(forwardMostMove)) {
11781 DisplayError(_("Wait until your turn"), 0);
11785 case BeginningOfGame:
11786 case MachinePlaysBlack:
11787 if (!WhiteOnMove(forwardMostMove)) {
11788 DisplayError(_("Wait until your turn"), 0);
11793 DisplayError(_("No hint available"), 0);
11796 SendToProgram("hint\n", &first);
11797 hintRequested = TRUE;
11803 if (appData.noChessProgram) return;
11804 switch (gameMode) {
11805 case MachinePlaysWhite:
11806 if (WhiteOnMove(forwardMostMove)) {
11807 DisplayError(_("Wait until your turn"), 0);
11811 case BeginningOfGame:
11812 case MachinePlaysBlack:
11813 if (!WhiteOnMove(forwardMostMove)) {
11814 DisplayError(_("Wait until your turn"), 0);
11819 EditPositionDone();
11821 case TwoMachinesPlay:
11826 SendToProgram("bk\n", &first);
11827 bookOutput[0] = NULLCHAR;
11828 bookRequested = TRUE;
11834 char *tags = PGNTags(&gameInfo);
11835 TagsPopUp(tags, CmailMsg());
11839 /* end button procedures */
11842 PrintPosition(fp, move)
11848 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11849 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11850 char c = PieceToChar(boards[move][i][j]);
11851 fputc(c == 'x' ? '.' : c, fp);
11852 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11855 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11856 fprintf(fp, "white to play\n");
11858 fprintf(fp, "black to play\n");
11865 if (gameInfo.white != NULL) {
11866 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11872 /* Find last component of program's own name, using some heuristics */
11874 TidyProgramName(prog, host, buf)
11875 char *prog, *host, buf[MSG_SIZ];
11878 int local = (strcmp(host, "localhost") == 0);
11879 while (!local && (p = strchr(prog, ';')) != NULL) {
11881 while (*p == ' ') p++;
11884 if (*prog == '"' || *prog == '\'') {
11885 q = strchr(prog + 1, *prog);
11887 q = strchr(prog, ' ');
11889 if (q == NULL) q = prog + strlen(prog);
11891 while (p >= prog && *p != '/' && *p != '\\') p--;
11893 if(p == prog && *p == '"') p++;
11894 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11895 memcpy(buf, p, q - p);
11896 buf[q - p] = NULLCHAR;
11904 TimeControlTagValue()
11907 if (!appData.clockMode) {
11909 } else if (movesPerSession > 0) {
11910 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11911 } else if (timeIncrement == 0) {
11912 sprintf(buf, "%ld", timeControl/1000);
11914 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11916 return StrSave(buf);
11922 /* This routine is used only for certain modes */
11923 VariantClass v = gameInfo.variant;
11924 ClearGameInfo(&gameInfo);
11925 gameInfo.variant = v;
11927 switch (gameMode) {
11928 case MachinePlaysWhite:
11929 gameInfo.event = StrSave( appData.pgnEventHeader );
11930 gameInfo.site = StrSave(HostName());
11931 gameInfo.date = PGNDate();
11932 gameInfo.round = StrSave("-");
11933 gameInfo.white = StrSave(first.tidy);
11934 gameInfo.black = StrSave(UserName());
11935 gameInfo.timeControl = TimeControlTagValue();
11938 case MachinePlaysBlack:
11939 gameInfo.event = StrSave( appData.pgnEventHeader );
11940 gameInfo.site = StrSave(HostName());
11941 gameInfo.date = PGNDate();
11942 gameInfo.round = StrSave("-");
11943 gameInfo.white = StrSave(UserName());
11944 gameInfo.black = StrSave(first.tidy);
11945 gameInfo.timeControl = TimeControlTagValue();
11948 case TwoMachinesPlay:
11949 gameInfo.event = StrSave( appData.pgnEventHeader );
11950 gameInfo.site = StrSave(HostName());
11951 gameInfo.date = PGNDate();
11952 if (matchGame > 0) {
11954 sprintf(buf, "%d", matchGame);
11955 gameInfo.round = StrSave(buf);
11957 gameInfo.round = StrSave("-");
11959 if (first.twoMachinesColor[0] == 'w') {
11960 gameInfo.white = StrSave(first.tidy);
11961 gameInfo.black = StrSave(second.tidy);
11963 gameInfo.white = StrSave(second.tidy);
11964 gameInfo.black = StrSave(first.tidy);
11966 gameInfo.timeControl = TimeControlTagValue();
11970 gameInfo.event = StrSave("Edited game");
11971 gameInfo.site = StrSave(HostName());
11972 gameInfo.date = PGNDate();
11973 gameInfo.round = StrSave("-");
11974 gameInfo.white = StrSave("-");
11975 gameInfo.black = StrSave("-");
11979 gameInfo.event = StrSave("Edited position");
11980 gameInfo.site = StrSave(HostName());
11981 gameInfo.date = PGNDate();
11982 gameInfo.round = StrSave("-");
11983 gameInfo.white = StrSave("-");
11984 gameInfo.black = StrSave("-");
11987 case IcsPlayingWhite:
11988 case IcsPlayingBlack:
11993 case PlayFromGameFile:
11994 gameInfo.event = StrSave("Game from non-PGN file");
11995 gameInfo.site = StrSave(HostName());
11996 gameInfo.date = PGNDate();
11997 gameInfo.round = StrSave("-");
11998 gameInfo.white = StrSave("?");
11999 gameInfo.black = StrSave("?");
12008 ReplaceComment(index, text)
12014 while (*text == '\n') text++;
12015 len = strlen(text);
12016 while (len > 0 && text[len - 1] == '\n') len--;
12018 if (commentList[index] != NULL)
12019 free(commentList[index]);
12022 commentList[index] = NULL;
12025 commentList[index] = (char *) malloc(len + 2);
12026 strncpy(commentList[index], text, len);
12027 commentList[index][len] = '\n';
12028 commentList[index][len + 1] = NULLCHAR;
12041 if (ch == '\r') continue;
12043 } while (ch != '\0');
12047 AppendComment(index, text)
12054 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12057 while (*text == '\n') text++;
12058 len = strlen(text);
12059 while (len > 0 && text[len - 1] == '\n') len--;
12061 if (len == 0) return;
12063 if (commentList[index] != NULL) {
12064 old = commentList[index];
12065 oldlen = strlen(old);
12066 commentList[index] = (char *) malloc(oldlen + len + 2);
12067 strcpy(commentList[index], old);
12069 strncpy(&commentList[index][oldlen], text, len);
12070 commentList[index][oldlen + len] = '\n';
12071 commentList[index][oldlen + len + 1] = NULLCHAR;
12073 commentList[index] = (char *) malloc(len + 2);
12074 strncpy(commentList[index], text, len);
12075 commentList[index][len] = '\n';
12076 commentList[index][len + 1] = NULLCHAR;
12080 static char * FindStr( char * text, char * sub_text )
12082 char * result = strstr( text, sub_text );
12084 if( result != NULL ) {
12085 result += strlen( sub_text );
12091 /* [AS] Try to extract PV info from PGN comment */
12092 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12093 char *GetInfoFromComment( int index, char * text )
12097 if( text != NULL && index > 0 ) {
12100 int time = -1, sec = 0, deci;
12101 char * s_eval = FindStr( text, "[%eval " );
12102 char * s_emt = FindStr( text, "[%emt " );
12104 if( s_eval != NULL || s_emt != NULL ) {
12108 if( s_eval != NULL ) {
12109 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12113 if( delim != ']' ) {
12118 if( s_emt != NULL ) {
12122 /* We expect something like: [+|-]nnn.nn/dd */
12125 sep = strchr( text, '/' );
12126 if( sep == NULL || sep < (text+4) ) {
12130 time = -1; sec = -1; deci = -1;
12131 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12132 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12133 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12134 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12138 if( score_lo < 0 || score_lo >= 100 ) {
12142 if(sec >= 0) time = 600*time + 10*sec; else
12143 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12145 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12147 /* [HGM] PV time: now locate end of PV info */
12148 while( *++sep >= '0' && *sep <= '9'); // strip depth
12150 while( *++sep >= '0' && *sep <= '9'); // strip time
12152 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12154 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12155 while(*sep == ' ') sep++;
12166 pvInfoList[index-1].depth = depth;
12167 pvInfoList[index-1].score = score;
12168 pvInfoList[index-1].time = 10*time; // centi-sec
12174 SendToProgram(message, cps)
12176 ChessProgramState *cps;
12178 int count, outCount, error;
12181 if (cps->pr == NULL) return;
12184 if (appData.debugMode) {
12187 fprintf(debugFP, "%ld >%-6s: %s",
12188 SubtractTimeMarks(&now, &programStartTime),
12189 cps->which, message);
12192 count = strlen(message);
12193 outCount = OutputToProcess(cps->pr, message, count, &error);
12194 if (outCount < count && !exiting
12195 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12196 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12197 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12198 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12199 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12200 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12202 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12204 gameInfo.resultDetails = buf;
12206 DisplayFatalError(buf, error, 1);
12211 ReceiveFromProgram(isr, closure, message, count, error)
12212 InputSourceRef isr;
12220 ChessProgramState *cps = (ChessProgramState *)closure;
12222 if (isr != cps->isr) return; /* Killed intentionally */
12226 _("Error: %s chess program (%s) exited unexpectedly"),
12227 cps->which, cps->program);
12228 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12229 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12230 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12231 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12233 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12235 gameInfo.resultDetails = buf;
12237 RemoveInputSource(cps->isr);
12238 DisplayFatalError(buf, 0, 1);
12241 _("Error reading from %s chess program (%s)"),
12242 cps->which, cps->program);
12243 RemoveInputSource(cps->isr);
12245 /* [AS] Program is misbehaving badly... kill it */
12246 if( count == -2 ) {
12247 DestroyChildProcess( cps->pr, 9 );
12251 DisplayFatalError(buf, error, 1);
12256 if ((end_str = strchr(message, '\r')) != NULL)
12257 *end_str = NULLCHAR;
12258 if ((end_str = strchr(message, '\n')) != NULL)
12259 *end_str = NULLCHAR;
12261 if (appData.debugMode) {
12262 TimeMark now; int print = 1;
12263 char *quote = ""; char c; int i;
12265 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12266 char start = message[0];
12267 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12268 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12269 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12270 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12271 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12272 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12273 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
12274 { quote = "# "; print = (appData.engineComments == 2); }
12275 message[0] = start; // restore original message
12279 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12280 SubtractTimeMarks(&now, &programStartTime), cps->which,
12286 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12287 if (appData.icsEngineAnalyze) {
12288 if (strstr(message, "whisper") != NULL ||
12289 strstr(message, "kibitz") != NULL ||
12290 strstr(message, "tellics") != NULL) return;
12293 HandleMachineMove(message, cps);
12298 SendTimeControl(cps, mps, tc, inc, sd, st)
12299 ChessProgramState *cps;
12300 int mps, inc, sd, st;
12306 if( timeControl_2 > 0 ) {
12307 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12308 tc = timeControl_2;
12311 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12312 inc /= cps->timeOdds;
12313 st /= cps->timeOdds;
12315 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12318 /* Set exact time per move, normally using st command */
12319 if (cps->stKludge) {
12320 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12322 if (seconds == 0) {
12323 sprintf(buf, "level 1 %d\n", st/60);
12325 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12328 sprintf(buf, "st %d\n", st);
12331 /* Set conventional or incremental time control, using level command */
12332 if (seconds == 0) {
12333 /* Note old gnuchess bug -- minutes:seconds used to not work.
12334 Fixed in later versions, but still avoid :seconds
12335 when seconds is 0. */
12336 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12338 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12339 seconds, inc/1000);
12342 SendToProgram(buf, cps);
12344 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12345 /* Orthogonally, limit search to given depth */
12347 if (cps->sdKludge) {
12348 sprintf(buf, "depth\n%d\n", sd);
12350 sprintf(buf, "sd %d\n", sd);
12352 SendToProgram(buf, cps);
12355 if(cps->nps > 0) { /* [HGM] nps */
12356 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12358 sprintf(buf, "nps %d\n", cps->nps);
12359 SendToProgram(buf, cps);
12364 ChessProgramState *WhitePlayer()
12365 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12367 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12368 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12374 SendTimeRemaining(cps, machineWhite)
12375 ChessProgramState *cps;
12376 int /*boolean*/ machineWhite;
12378 char message[MSG_SIZ];
12381 /* Note: this routine must be called when the clocks are stopped
12382 or when they have *just* been set or switched; otherwise
12383 it will be off by the time since the current tick started.
12385 if (machineWhite) {
12386 time = whiteTimeRemaining / 10;
12387 otime = blackTimeRemaining / 10;
12389 time = blackTimeRemaining / 10;
12390 otime = whiteTimeRemaining / 10;
12392 /* [HGM] translate opponent's time by time-odds factor */
12393 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12394 if (appData.debugMode) {
12395 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12398 if (time <= 0) time = 1;
12399 if (otime <= 0) otime = 1;
12401 sprintf(message, "time %ld\n", time);
12402 SendToProgram(message, cps);
12404 sprintf(message, "otim %ld\n", otime);
12405 SendToProgram(message, cps);
12409 BoolFeature(p, name, loc, cps)
12413 ChessProgramState *cps;
12416 int len = strlen(name);
12418 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12420 sscanf(*p, "%d", &val);
12422 while (**p && **p != ' ') (*p)++;
12423 sprintf(buf, "accepted %s\n", name);
12424 SendToProgram(buf, cps);
12431 IntFeature(p, name, loc, cps)
12435 ChessProgramState *cps;
12438 int len = strlen(name);
12439 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12441 sscanf(*p, "%d", loc);
12442 while (**p && **p != ' ') (*p)++;
12443 sprintf(buf, "accepted %s\n", name);
12444 SendToProgram(buf, cps);
12451 StringFeature(p, name, loc, cps)
12455 ChessProgramState *cps;
12458 int len = strlen(name);
12459 if (strncmp((*p), name, len) == 0
12460 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12462 sscanf(*p, "%[^\"]", loc);
12463 while (**p && **p != '\"') (*p)++;
12464 if (**p == '\"') (*p)++;
12465 sprintf(buf, "accepted %s\n", name);
12466 SendToProgram(buf, cps);
12473 ParseOption(Option *opt, ChessProgramState *cps)
12474 // [HGM] options: process the string that defines an engine option, and determine
12475 // name, type, default value, and allowed value range
12477 char *p, *q, buf[MSG_SIZ];
12478 int n, min = (-1)<<31, max = 1<<31, def;
12480 if(p = strstr(opt->name, " -spin ")) {
12481 if((n = sscanf(p, " -spin %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;
12489 } else if((p = strstr(opt->name, " -slider "))) {
12490 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12491 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12492 if(max < min) max = min; // enforce consistency
12493 if(def < min) def = min;
12494 if(def > max) def = max;
12498 opt->type = Spin; // Slider;
12499 } else if((p = strstr(opt->name, " -string "))) {
12500 opt->textValue = p+9;
12501 opt->type = TextBox;
12502 } else if((p = strstr(opt->name, " -file "))) {
12503 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12504 opt->textValue = p+7;
12505 opt->type = TextBox; // FileName;
12506 } else if((p = strstr(opt->name, " -path "))) {
12507 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12508 opt->textValue = p+7;
12509 opt->type = TextBox; // PathName;
12510 } else if(p = strstr(opt->name, " -check ")) {
12511 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12512 opt->value = (def != 0);
12513 opt->type = CheckBox;
12514 } else if(p = strstr(opt->name, " -combo ")) {
12515 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12516 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12517 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12518 opt->value = n = 0;
12519 while(q = StrStr(q, " /// ")) {
12520 n++; *q = 0; // count choices, and null-terminate each of them
12522 if(*q == '*') { // remember default, which is marked with * prefix
12526 cps->comboList[cps->comboCnt++] = q;
12528 cps->comboList[cps->comboCnt++] = NULL;
12530 opt->type = ComboBox;
12531 } else if(p = strstr(opt->name, " -button")) {
12532 opt->type = Button;
12533 } else if(p = strstr(opt->name, " -save")) {
12534 opt->type = SaveButton;
12535 } else return FALSE;
12536 *p = 0; // terminate option name
12537 // now look if the command-line options define a setting for this engine option.
12538 if(cps->optionSettings && cps->optionSettings[0])
12539 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12540 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12541 sprintf(buf, "option %s", p);
12542 if(p = strstr(buf, ",")) *p = 0;
12544 SendToProgram(buf, cps);
12550 FeatureDone(cps, val)
12551 ChessProgramState* cps;
12554 DelayedEventCallback cb = GetDelayedEvent();
12555 if ((cb == InitBackEnd3 && cps == &first) ||
12556 (cb == TwoMachinesEventIfReady && cps == &second)) {
12557 CancelDelayedEvent();
12558 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12560 cps->initDone = val;
12563 /* Parse feature command from engine */
12565 ParseFeatures(args, cps)
12567 ChessProgramState *cps;
12575 while (*p == ' ') p++;
12576 if (*p == NULLCHAR) return;
12578 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12579 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12580 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12581 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12582 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12583 if (BoolFeature(&p, "reuse", &val, cps)) {
12584 /* Engine can disable reuse, but can't enable it if user said no */
12585 if (!val) cps->reuse = FALSE;
12588 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12589 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12590 if (gameMode == TwoMachinesPlay) {
12591 DisplayTwoMachinesTitle();
12597 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12598 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12599 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12600 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12601 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12602 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12603 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12604 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12605 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12606 if (IntFeature(&p, "done", &val, cps)) {
12607 FeatureDone(cps, val);
12610 /* Added by Tord: */
12611 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12612 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12613 /* End of additions by Tord */
12615 /* [HGM] added features: */
12616 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12617 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12618 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12619 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12620 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12621 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12622 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12623 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12624 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12625 SendToProgram(buf, cps);
12628 if(cps->nrOptions >= MAX_OPTIONS) {
12630 sprintf(buf, "%s engine has too many options\n", cps->which);
12631 DisplayError(buf, 0);
12635 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12636 /* End of additions by HGM */
12638 /* unknown feature: complain and skip */
12640 while (*q && *q != '=') q++;
12641 sprintf(buf, "rejected %.*s\n", q-p, p);
12642 SendToProgram(buf, cps);
12648 while (*p && *p != '\"') p++;
12649 if (*p == '\"') p++;
12651 while (*p && *p != ' ') p++;
12659 PeriodicUpdatesEvent(newState)
12662 if (newState == appData.periodicUpdates)
12665 appData.periodicUpdates=newState;
12667 /* Display type changes, so update it now */
12670 /* Get the ball rolling again... */
12672 AnalysisPeriodicEvent(1);
12673 StartAnalysisClock();
12678 PonderNextMoveEvent(newState)
12681 if (newState == appData.ponderNextMove) return;
12682 if (gameMode == EditPosition) EditPositionDone();
12684 SendToProgram("hard\n", &first);
12685 if (gameMode == TwoMachinesPlay) {
12686 SendToProgram("hard\n", &second);
12689 SendToProgram("easy\n", &first);
12690 thinkOutput[0] = NULLCHAR;
12691 if (gameMode == TwoMachinesPlay) {
12692 SendToProgram("easy\n", &second);
12695 appData.ponderNextMove = newState;
12699 NewSettingEvent(option, command, value)
12705 if (gameMode == EditPosition) EditPositionDone();
12706 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12707 SendToProgram(buf, &first);
12708 if (gameMode == TwoMachinesPlay) {
12709 SendToProgram(buf, &second);
12714 ShowThinkingEvent()
12715 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12717 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12718 int newState = appData.showThinking
12719 // [HGM] thinking: other features now need thinking output as well
12720 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12722 if (oldState == newState) return;
12723 oldState = newState;
12724 if (gameMode == EditPosition) EditPositionDone();
12726 SendToProgram("post\n", &first);
12727 if (gameMode == TwoMachinesPlay) {
12728 SendToProgram("post\n", &second);
12731 SendToProgram("nopost\n", &first);
12732 thinkOutput[0] = NULLCHAR;
12733 if (gameMode == TwoMachinesPlay) {
12734 SendToProgram("nopost\n", &second);
12737 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12741 AskQuestionEvent(title, question, replyPrefix, which)
12742 char *title; char *question; char *replyPrefix; char *which;
12744 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12745 if (pr == NoProc) return;
12746 AskQuestion(title, question, replyPrefix, pr);
12750 DisplayMove(moveNumber)
12753 char message[MSG_SIZ];
12755 char cpThinkOutput[MSG_SIZ];
12757 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12759 if (moveNumber == forwardMostMove - 1 ||
12760 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12762 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12764 if (strchr(cpThinkOutput, '\n')) {
12765 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12768 *cpThinkOutput = NULLCHAR;
12771 /* [AS] Hide thinking from human user */
12772 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12773 *cpThinkOutput = NULLCHAR;
12774 if( thinkOutput[0] != NULLCHAR ) {
12777 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12778 cpThinkOutput[i] = '.';
12780 cpThinkOutput[i] = NULLCHAR;
12781 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12785 if (moveNumber == forwardMostMove - 1 &&
12786 gameInfo.resultDetails != NULL) {
12787 if (gameInfo.resultDetails[0] == NULLCHAR) {
12788 sprintf(res, " %s", PGNResult(gameInfo.result));
12790 sprintf(res, " {%s} %s",
12791 gameInfo.resultDetails, PGNResult(gameInfo.result));
12797 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12798 DisplayMessage(res, cpThinkOutput);
12800 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12801 WhiteOnMove(moveNumber) ? " " : ".. ",
12802 parseList[moveNumber], res);
12803 DisplayMessage(message, cpThinkOutput);
12808 DisplayAnalysisText(text)
12813 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12814 || appData.icsEngineAnalyze) {
12815 sprintf(buf, "Analysis (%s)", first.tidy);
12816 AnalysisPopUp(buf, text);
12824 while (*str && isspace(*str)) ++str;
12825 while (*str && !isspace(*str)) ++str;
12826 if (!*str) return 1;
12827 while (*str && isspace(*str)) ++str;
12828 if (!*str) return 1;
12836 char lst[MSG_SIZ / 2];
12838 static char *xtra[] = { "", " (--)", " (++)" };
12841 if (programStats.time == 0) {
12842 programStats.time = 1;
12845 if (programStats.got_only_move) {
12846 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12848 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12850 nps = (u64ToDouble(programStats.nodes) /
12851 ((double)programStats.time /100.0));
12853 cs = programStats.time % 100;
12854 s = programStats.time / 100;
12860 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12861 if (programStats.move_name[0] != NULLCHAR) {
12862 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12863 programStats.depth,
12864 programStats.nr_moves-programStats.moves_left,
12865 programStats.nr_moves, programStats.move_name,
12866 ((float)programStats.score)/100.0, lst,
12867 only_one_move(lst)?
12868 xtra[programStats.got_fail] : "",
12869 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12871 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12872 programStats.depth,
12873 programStats.nr_moves-programStats.moves_left,
12874 programStats.nr_moves, ((float)programStats.score)/100.0,
12876 only_one_move(lst)?
12877 xtra[programStats.got_fail] : "",
12878 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12881 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12882 programStats.depth,
12883 ((float)programStats.score)/100.0,
12885 only_one_move(lst)?
12886 xtra[programStats.got_fail] : "",
12887 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12890 DisplayAnalysisText(buf);
12894 DisplayComment(moveNumber, text)
12898 char title[MSG_SIZ];
12899 char buf[8000]; // comment can be long!
12902 if( appData.autoDisplayComment ) {
12903 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12904 strcpy(title, "Comment");
12906 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12907 WhiteOnMove(moveNumber) ? " " : ".. ",
12908 parseList[moveNumber]);
12910 // [HGM] PV info: display PV info together with (or as) comment
12911 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12912 if(text == NULL) text = "";
12913 score = pvInfoList[moveNumber].score;
12914 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12915 depth, (pvInfoList[moveNumber].time+50)/100, text);
12918 } else title[0] = 0;
12921 CommentPopUp(title, text);
12924 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12925 * might be busy thinking or pondering. It can be omitted if your
12926 * gnuchess is configured to stop thinking immediately on any user
12927 * input. However, that gnuchess feature depends on the FIONREAD
12928 * ioctl, which does not work properly on some flavors of Unix.
12932 ChessProgramState *cps;
12935 if (!cps->useSigint) return;
12936 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12937 switch (gameMode) {
12938 case MachinePlaysWhite:
12939 case MachinePlaysBlack:
12940 case TwoMachinesPlay:
12941 case IcsPlayingWhite:
12942 case IcsPlayingBlack:
12945 /* Skip if we know it isn't thinking */
12946 if (!cps->maybeThinking) return;
12947 if (appData.debugMode)
12948 fprintf(debugFP, "Interrupting %s\n", cps->which);
12949 InterruptChildProcess(cps->pr);
12950 cps->maybeThinking = FALSE;
12955 #endif /*ATTENTION*/
12961 if (whiteTimeRemaining <= 0) {
12964 if (appData.icsActive) {
12965 if (appData.autoCallFlag &&
12966 gameMode == IcsPlayingBlack && !blackFlag) {
12967 SendToICS(ics_prefix);
12968 SendToICS("flag\n");
12972 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12974 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12975 if (appData.autoCallFlag) {
12976 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12983 if (blackTimeRemaining <= 0) {
12986 if (appData.icsActive) {
12987 if (appData.autoCallFlag &&
12988 gameMode == IcsPlayingWhite && !whiteFlag) {
12989 SendToICS(ics_prefix);
12990 SendToICS("flag\n");
12994 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12996 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12997 if (appData.autoCallFlag) {
12998 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13011 if (!appData.clockMode || appData.icsActive ||
13012 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13015 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13017 if ( !WhiteOnMove(forwardMostMove) )
13018 /* White made time control */
13019 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13020 /* [HGM] time odds: correct new time quota for time odds! */
13021 / WhitePlayer()->timeOdds;
13023 /* Black made time control */
13024 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13025 / WhitePlayer()->other->timeOdds;
13029 DisplayBothClocks()
13031 int wom = gameMode == EditPosition ?
13032 !blackPlaysFirst : WhiteOnMove(currentMove);
13033 DisplayWhiteClock(whiteTimeRemaining, wom);
13034 DisplayBlackClock(blackTimeRemaining, !wom);
13038 /* Timekeeping seems to be a portability nightmare. I think everyone
13039 has ftime(), but I'm really not sure, so I'm including some ifdefs
13040 to use other calls if you don't. Clocks will be less accurate if
13041 you have neither ftime nor gettimeofday.
13044 /* VS 2008 requires the #include outside of the function */
13045 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13046 #include <sys/timeb.h>
13049 /* Get the current time as a TimeMark */
13054 #if HAVE_GETTIMEOFDAY
13056 struct timeval timeVal;
13057 struct timezone timeZone;
13059 gettimeofday(&timeVal, &timeZone);
13060 tm->sec = (long) timeVal.tv_sec;
13061 tm->ms = (int) (timeVal.tv_usec / 1000L);
13063 #else /*!HAVE_GETTIMEOFDAY*/
13066 // include <sys/timeb.h> / moved to just above start of function
13067 struct timeb timeB;
13070 tm->sec = (long) timeB.time;
13071 tm->ms = (int) timeB.millitm;
13073 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13074 tm->sec = (long) time(NULL);
13080 /* Return the difference in milliseconds between two
13081 time marks. We assume the difference will fit in a long!
13084 SubtractTimeMarks(tm2, tm1)
13085 TimeMark *tm2, *tm1;
13087 return 1000L*(tm2->sec - tm1->sec) +
13088 (long) (tm2->ms - tm1->ms);
13093 * Code to manage the game clocks.
13095 * In tournament play, black starts the clock and then white makes a move.
13096 * We give the human user a slight advantage if he is playing white---the
13097 * clocks don't run until he makes his first move, so it takes zero time.
13098 * Also, we don't account for network lag, so we could get out of sync
13099 * with GNU Chess's clock -- but then, referees are always right.
13102 static TimeMark tickStartTM;
13103 static long intendedTickLength;
13106 NextTickLength(timeRemaining)
13107 long timeRemaining;
13109 long nominalTickLength, nextTickLength;
13111 if (timeRemaining > 0L && timeRemaining <= 10000L)
13112 nominalTickLength = 100L;
13114 nominalTickLength = 1000L;
13115 nextTickLength = timeRemaining % nominalTickLength;
13116 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13118 return nextTickLength;
13121 /* Adjust clock one minute up or down */
13123 AdjustClock(Boolean which, int dir)
13125 if(which) blackTimeRemaining += 60000*dir;
13126 else whiteTimeRemaining += 60000*dir;
13127 DisplayBothClocks();
13130 /* Stop clocks and reset to a fresh time control */
13134 (void) StopClockTimer();
13135 if (appData.icsActive) {
13136 whiteTimeRemaining = blackTimeRemaining = 0;
13137 } else { /* [HGM] correct new time quote for time odds */
13138 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13139 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13141 if (whiteFlag || blackFlag) {
13143 whiteFlag = blackFlag = FALSE;
13145 DisplayBothClocks();
13148 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13150 /* Decrement running clock by amount of time that has passed */
13154 long timeRemaining;
13155 long lastTickLength, fudge;
13158 if (!appData.clockMode) return;
13159 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13163 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13165 /* Fudge if we woke up a little too soon */
13166 fudge = intendedTickLength - lastTickLength;
13167 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13169 if (WhiteOnMove(forwardMostMove)) {
13170 if(whiteNPS >= 0) lastTickLength = 0;
13171 timeRemaining = whiteTimeRemaining -= lastTickLength;
13172 DisplayWhiteClock(whiteTimeRemaining - fudge,
13173 WhiteOnMove(currentMove));
13175 if(blackNPS >= 0) lastTickLength = 0;
13176 timeRemaining = blackTimeRemaining -= lastTickLength;
13177 DisplayBlackClock(blackTimeRemaining - fudge,
13178 !WhiteOnMove(currentMove));
13181 if (CheckFlags()) return;
13184 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13185 StartClockTimer(intendedTickLength);
13187 /* if the time remaining has fallen below the alarm threshold, sound the
13188 * alarm. if the alarm has sounded and (due to a takeback or time control
13189 * with increment) the time remaining has increased to a level above the
13190 * threshold, reset the alarm so it can sound again.
13193 if (appData.icsActive && appData.icsAlarm) {
13195 /* make sure we are dealing with the user's clock */
13196 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13197 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13200 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13201 alarmSounded = FALSE;
13202 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13204 alarmSounded = TRUE;
13210 /* A player has just moved, so stop the previously running
13211 clock and (if in clock mode) start the other one.
13212 We redisplay both clocks in case we're in ICS mode, because
13213 ICS gives us an update to both clocks after every move.
13214 Note that this routine is called *after* forwardMostMove
13215 is updated, so the last fractional tick must be subtracted
13216 from the color that is *not* on move now.
13221 long lastTickLength;
13223 int flagged = FALSE;
13227 if (StopClockTimer() && appData.clockMode) {
13228 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13229 if (WhiteOnMove(forwardMostMove)) {
13230 if(blackNPS >= 0) lastTickLength = 0;
13231 blackTimeRemaining -= lastTickLength;
13232 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13233 // if(pvInfoList[forwardMostMove-1].time == -1)
13234 pvInfoList[forwardMostMove-1].time = // use GUI time
13235 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13237 if(whiteNPS >= 0) lastTickLength = 0;
13238 whiteTimeRemaining -= lastTickLength;
13239 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13240 // if(pvInfoList[forwardMostMove-1].time == -1)
13241 pvInfoList[forwardMostMove-1].time =
13242 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13244 flagged = CheckFlags();
13246 CheckTimeControl();
13248 if (flagged || !appData.clockMode) return;
13250 switch (gameMode) {
13251 case MachinePlaysBlack:
13252 case MachinePlaysWhite:
13253 case BeginningOfGame:
13254 if (pausing) return;
13258 case PlayFromGameFile:
13267 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13268 whiteTimeRemaining : blackTimeRemaining);
13269 StartClockTimer(intendedTickLength);
13273 /* Stop both clocks */
13277 long lastTickLength;
13280 if (!StopClockTimer()) return;
13281 if (!appData.clockMode) return;
13285 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13286 if (WhiteOnMove(forwardMostMove)) {
13287 if(whiteNPS >= 0) lastTickLength = 0;
13288 whiteTimeRemaining -= lastTickLength;
13289 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13291 if(blackNPS >= 0) lastTickLength = 0;
13292 blackTimeRemaining -= lastTickLength;
13293 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13298 /* Start clock of player on move. Time may have been reset, so
13299 if clock is already running, stop and restart it. */
13303 (void) StopClockTimer(); /* in case it was running already */
13304 DisplayBothClocks();
13305 if (CheckFlags()) return;
13307 if (!appData.clockMode) return;
13308 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13310 GetTimeMark(&tickStartTM);
13311 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13312 whiteTimeRemaining : blackTimeRemaining);
13314 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13315 whiteNPS = blackNPS = -1;
13316 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13317 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13318 whiteNPS = first.nps;
13319 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13320 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13321 blackNPS = first.nps;
13322 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13323 whiteNPS = second.nps;
13324 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13325 blackNPS = second.nps;
13326 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13328 StartClockTimer(intendedTickLength);
13335 long second, minute, hour, day;
13337 static char buf[32];
13339 if (ms > 0 && ms <= 9900) {
13340 /* convert milliseconds to tenths, rounding up */
13341 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13343 sprintf(buf, " %03.1f ", tenths/10.0);
13347 /* convert milliseconds to seconds, rounding up */
13348 /* use floating point to avoid strangeness of integer division
13349 with negative dividends on many machines */
13350 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13357 day = second / (60 * 60 * 24);
13358 second = second % (60 * 60 * 24);
13359 hour = second / (60 * 60);
13360 second = second % (60 * 60);
13361 minute = second / 60;
13362 second = second % 60;
13365 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13366 sign, day, hour, minute, second);
13368 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13370 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13377 * This is necessary because some C libraries aren't ANSI C compliant yet.
13380 StrStr(string, match)
13381 char *string, *match;
13385 length = strlen(match);
13387 for (i = strlen(string) - length; i >= 0; i--, string++)
13388 if (!strncmp(match, string, length))
13395 StrCaseStr(string, match)
13396 char *string, *match;
13400 length = strlen(match);
13402 for (i = strlen(string) - length; i >= 0; i--, string++) {
13403 for (j = 0; j < length; j++) {
13404 if (ToLower(match[j]) != ToLower(string[j]))
13407 if (j == length) return string;
13421 c1 = ToLower(*s1++);
13422 c2 = ToLower(*s2++);
13423 if (c1 > c2) return 1;
13424 if (c1 < c2) return -1;
13425 if (c1 == NULLCHAR) return 0;
13434 return isupper(c) ? tolower(c) : c;
13442 return islower(c) ? toupper(c) : c;
13444 #endif /* !_amigados */
13452 if ((ret = (char *) malloc(strlen(s) + 1))) {
13459 StrSavePtr(s, savePtr)
13460 char *s, **savePtr;
13465 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13466 strcpy(*savePtr, s);
13478 clock = time((time_t *)NULL);
13479 tm = localtime(&clock);
13480 sprintf(buf, "%04d.%02d.%02d",
13481 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13482 return StrSave(buf);
13487 PositionToFEN(move, overrideCastling)
13489 char *overrideCastling;
13491 int i, j, fromX, fromY, toX, toY;
13498 whiteToPlay = (gameMode == EditPosition) ?
13499 !blackPlaysFirst : (move % 2 == 0);
13502 /* Piece placement data */
13503 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13505 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13506 if (boards[move][i][j] == EmptySquare) {
13508 } else { ChessSquare piece = boards[move][i][j];
13509 if (emptycount > 0) {
13510 if(emptycount<10) /* [HGM] can be >= 10 */
13511 *p++ = '0' + emptycount;
13512 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13515 if(PieceToChar(piece) == '+') {
13516 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13518 piece = (ChessSquare)(DEMOTED piece);
13520 *p++ = PieceToChar(piece);
13522 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13523 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13528 if (emptycount > 0) {
13529 if(emptycount<10) /* [HGM] can be >= 10 */
13530 *p++ = '0' + emptycount;
13531 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13538 /* [HGM] print Crazyhouse or Shogi holdings */
13539 if( gameInfo.holdingsWidth ) {
13540 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13542 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13543 piece = boards[move][i][BOARD_WIDTH-1];
13544 if( piece != EmptySquare )
13545 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13546 *p++ = PieceToChar(piece);
13548 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13549 piece = boards[move][BOARD_HEIGHT-i-1][0];
13550 if( piece != EmptySquare )
13551 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13552 *p++ = PieceToChar(piece);
13555 if( q == p ) *p++ = '-';
13561 *p++ = whiteToPlay ? 'w' : 'b';
13564 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13565 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13567 if(nrCastlingRights) {
13569 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13570 /* [HGM] write directly from rights */
13571 if(castlingRights[move][2] >= 0 &&
13572 castlingRights[move][0] >= 0 )
13573 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13574 if(castlingRights[move][2] >= 0 &&
13575 castlingRights[move][1] >= 0 )
13576 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13577 if(castlingRights[move][5] >= 0 &&
13578 castlingRights[move][3] >= 0 )
13579 *p++ = castlingRights[move][3] + AAA;
13580 if(castlingRights[move][5] >= 0 &&
13581 castlingRights[move][4] >= 0 )
13582 *p++ = castlingRights[move][4] + AAA;
13585 /* [HGM] write true castling rights */
13586 if( nrCastlingRights == 6 ) {
13587 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13588 castlingRights[move][2] >= 0 ) *p++ = 'K';
13589 if(castlingRights[move][1] == BOARD_LEFT &&
13590 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13591 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13592 castlingRights[move][5] >= 0 ) *p++ = 'k';
13593 if(castlingRights[move][4] == BOARD_LEFT &&
13594 castlingRights[move][5] >= 0 ) *p++ = 'q';
13597 if (q == p) *p++ = '-'; /* No castling rights */
13601 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13602 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13603 /* En passant target square */
13604 if (move > backwardMostMove) {
13605 fromX = moveList[move - 1][0] - AAA;
13606 fromY = moveList[move - 1][1] - ONE;
13607 toX = moveList[move - 1][2] - AAA;
13608 toY = moveList[move - 1][3] - ONE;
13609 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13610 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13611 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13613 /* 2-square pawn move just happened */
13615 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13626 /* [HGM] find reversible plies */
13627 { int i = 0, j=move;
13629 if (appData.debugMode) { int k;
13630 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13631 for(k=backwardMostMove; k<=forwardMostMove; k++)
13632 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13636 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13637 if( j == backwardMostMove ) i += initialRulePlies;
13638 sprintf(p, "%d ", i);
13639 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13641 /* Fullmove number */
13642 sprintf(p, "%d", (move / 2) + 1);
13644 return StrSave(buf);
13648 ParseFEN(board, blackPlaysFirst, fen)
13650 int *blackPlaysFirst;
13660 /* [HGM] by default clear Crazyhouse holdings, if present */
13661 if(gameInfo.holdingsWidth) {
13662 for(i=0; i<BOARD_HEIGHT; i++) {
13663 board[i][0] = EmptySquare; /* black holdings */
13664 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13665 board[i][1] = (ChessSquare) 0; /* black counts */
13666 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13670 /* Piece placement data */
13671 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13674 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13675 if (*p == '/') p++;
13676 emptycount = gameInfo.boardWidth - j;
13677 while (emptycount--)
13678 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13680 #if(BOARD_SIZE >= 10)
13681 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13682 p++; emptycount=10;
13683 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13684 while (emptycount--)
13685 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13687 } else if (isdigit(*p)) {
13688 emptycount = *p++ - '0';
13689 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13690 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13691 while (emptycount--)
13692 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13693 } else if (*p == '+' || isalpha(*p)) {
13694 if (j >= gameInfo.boardWidth) return FALSE;
13696 piece = CharToPiece(*++p);
13697 if(piece == EmptySquare) return FALSE; /* unknown piece */
13698 piece = (ChessSquare) (PROMOTED piece ); p++;
13699 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13700 } else piece = CharToPiece(*p++);
13702 if(piece==EmptySquare) return FALSE; /* unknown piece */
13703 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13704 piece = (ChessSquare) (PROMOTED piece);
13705 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13708 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13714 while (*p == '/' || *p == ' ') p++;
13716 /* [HGM] look for Crazyhouse holdings here */
13717 while(*p==' ') p++;
13718 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13720 if(*p == '-' ) *p++; /* empty holdings */ else {
13721 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13722 /* if we would allow FEN reading to set board size, we would */
13723 /* have to add holdings and shift the board read so far here */
13724 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13726 if((int) piece >= (int) BlackPawn ) {
13727 i = (int)piece - (int)BlackPawn;
13728 i = PieceToNumber((ChessSquare)i);
13729 if( i >= gameInfo.holdingsSize ) return FALSE;
13730 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13731 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13733 i = (int)piece - (int)WhitePawn;
13734 i = PieceToNumber((ChessSquare)i);
13735 if( i >= gameInfo.holdingsSize ) return FALSE;
13736 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13737 board[i][BOARD_WIDTH-2]++; /* black holdings */
13741 if(*p == ']') *p++;
13744 while(*p == ' ') p++;
13749 *blackPlaysFirst = FALSE;
13752 *blackPlaysFirst = TRUE;
13758 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13759 /* return the extra info in global variiables */
13761 /* set defaults in case FEN is incomplete */
13762 FENepStatus = EP_UNKNOWN;
13763 for(i=0; i<nrCastlingRights; i++ ) {
13764 FENcastlingRights[i] =
13765 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13766 } /* assume possible unless obviously impossible */
13767 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13768 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13769 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13770 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13771 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13772 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13775 while(*p==' ') p++;
13776 if(nrCastlingRights) {
13777 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13778 /* castling indicator present, so default becomes no castlings */
13779 for(i=0; i<nrCastlingRights; i++ ) {
13780 FENcastlingRights[i] = -1;
13783 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13784 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13785 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13786 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13787 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13789 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13790 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13791 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13795 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13796 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13797 FENcastlingRights[2] = whiteKingFile;
13800 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13801 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13802 FENcastlingRights[2] = whiteKingFile;
13805 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13806 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13807 FENcastlingRights[5] = blackKingFile;
13810 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13811 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13812 FENcastlingRights[5] = blackKingFile;
13815 default: /* FRC castlings */
13816 if(c >= 'a') { /* black rights */
13817 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13818 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13819 if(i == BOARD_RGHT) break;
13820 FENcastlingRights[5] = i;
13822 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13823 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13825 FENcastlingRights[3] = c;
13827 FENcastlingRights[4] = c;
13828 } else { /* white rights */
13829 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13830 if(board[0][i] == WhiteKing) break;
13831 if(i == BOARD_RGHT) break;
13832 FENcastlingRights[2] = i;
13833 c -= AAA - 'a' + 'A';
13834 if(board[0][c] >= WhiteKing) break;
13836 FENcastlingRights[0] = c;
13838 FENcastlingRights[1] = c;
13842 if (appData.debugMode) {
13843 fprintf(debugFP, "FEN castling rights:");
13844 for(i=0; i<nrCastlingRights; i++)
13845 fprintf(debugFP, " %d", FENcastlingRights[i]);
13846 fprintf(debugFP, "\n");
13849 while(*p==' ') p++;
13852 /* read e.p. field in games that know e.p. capture */
13853 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13854 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13856 p++; FENepStatus = EP_NONE;
13858 char c = *p++ - AAA;
13860 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13861 if(*p >= '0' && *p <='9') *p++;
13867 if(sscanf(p, "%d", &i) == 1) {
13868 FENrulePlies = i; /* 50-move ply counter */
13869 /* (The move number is still ignored) */
13876 EditPositionPasteFEN(char *fen)
13879 Board initial_position;
13881 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13882 DisplayError(_("Bad FEN position in clipboard"), 0);
13885 int savedBlackPlaysFirst = blackPlaysFirst;
13886 EditPositionEvent();
13887 blackPlaysFirst = savedBlackPlaysFirst;
13888 CopyBoard(boards[0], initial_position);
13889 /* [HGM] copy FEN attributes as well */
13891 initialRulePlies = FENrulePlies;
13892 epStatus[0] = FENepStatus;
13893 for( i=0; i<nrCastlingRights; i++ )
13894 castlingRights[0][i] = FENcastlingRights[i];
13896 EditPositionDone();
13897 DisplayBothClocks();
13898 DrawPosition(FALSE, boards[currentMove]);