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)
472 int mute; // mute all sounds
474 ChessSquare FIDEArray[2][BOARD_SIZE] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499 BlackKing, BlackBishop, BlackKnight, BlackRook }
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
529 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
534 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
536 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
541 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
543 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
549 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
551 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
554 #define GothicArray CapablancaArray
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
560 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
562 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
565 #define FalconArray CapablancaArray
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
587 Board initialPosition;
590 /* Convert str to a rating. Checks for special cases of "----",
592 "++++", etc. Also strips ()'s */
594 string_to_rating(str)
597 while(*str && !isdigit(*str)) ++str;
599 return 0; /* One of the special "no rating" cases */
607 /* Init programStats */
608 programStats.movelist[0] = 0;
609 programStats.depth = 0;
610 programStats.nr_moves = 0;
611 programStats.moves_left = 0;
612 programStats.nodes = 0;
613 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
614 programStats.score = 0;
615 programStats.got_only_move = 0;
616 programStats.got_fail = 0;
617 programStats.line_is_book = 0;
623 int matched, min, sec;
625 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
627 GetTimeMark(&programStartTime);
628 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
631 programStats.ok_to_send = 1;
632 programStats.seen_stat = 0;
635 * Initialize game list
641 * Internet chess server status
643 if (appData.icsActive) {
644 appData.matchMode = FALSE;
645 appData.matchGames = 0;
647 appData.noChessProgram = !appData.zippyPlay;
649 appData.zippyPlay = FALSE;
650 appData.zippyTalk = FALSE;
651 appData.noChessProgram = TRUE;
653 if (*appData.icsHelper != NULLCHAR) {
654 appData.useTelnet = TRUE;
655 appData.telnetProgram = appData.icsHelper;
658 appData.zippyTalk = appData.zippyPlay = FALSE;
661 /* [AS] Initialize pv info list [HGM] and game state */
665 for( i=0; i<MAX_MOVES; i++ ) {
666 pvInfoList[i].depth = -1;
668 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
673 * Parse timeControl resource
675 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676 appData.movesPerSession)) {
678 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679 DisplayFatalError(buf, 0, 2);
683 * Parse searchTime resource
685 if (*appData.searchTime != NULLCHAR) {
686 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
688 searchTime = min * 60;
689 } else if (matched == 2) {
690 searchTime = min * 60 + sec;
693 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694 DisplayFatalError(buf, 0, 2);
698 /* [AS] Adjudication threshold */
699 adjudicateLossThreshold = appData.adjudicateLossThreshold;
701 first.which = "first";
702 second.which = "second";
703 first.maybeThinking = second.maybeThinking = FALSE;
704 first.pr = second.pr = NoProc;
705 first.isr = second.isr = NULL;
706 first.sendTime = second.sendTime = 2;
707 first.sendDrawOffers = 1;
708 if (appData.firstPlaysBlack) {
709 first.twoMachinesColor = "black\n";
710 second.twoMachinesColor = "white\n";
712 first.twoMachinesColor = "white\n";
713 second.twoMachinesColor = "black\n";
715 first.program = appData.firstChessProgram;
716 second.program = appData.secondChessProgram;
717 first.host = appData.firstHost;
718 second.host = appData.secondHost;
719 first.dir = appData.firstDirectory;
720 second.dir = appData.secondDirectory;
721 first.other = &second;
722 second.other = &first;
723 first.initString = appData.initString;
724 second.initString = appData.secondInitString;
725 first.computerString = appData.firstComputerString;
726 second.computerString = appData.secondComputerString;
727 first.useSigint = second.useSigint = TRUE;
728 first.useSigterm = second.useSigterm = TRUE;
729 first.reuse = appData.reuseFirst;
730 second.reuse = appData.reuseSecond;
731 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
732 second.nps = appData.secondNPS;
733 first.useSetboard = second.useSetboard = FALSE;
734 first.useSAN = second.useSAN = FALSE;
735 first.usePing = second.usePing = FALSE;
736 first.lastPing = second.lastPing = 0;
737 first.lastPong = second.lastPong = 0;
738 first.usePlayother = second.usePlayother = FALSE;
739 first.useColors = second.useColors = TRUE;
740 first.useUsermove = second.useUsermove = FALSE;
741 first.sendICS = second.sendICS = FALSE;
742 first.sendName = second.sendName = appData.icsActive;
743 first.sdKludge = second.sdKludge = FALSE;
744 first.stKludge = second.stKludge = FALSE;
745 TidyProgramName(first.program, first.host, first.tidy);
746 TidyProgramName(second.program, second.host, second.tidy);
747 first.matchWins = second.matchWins = 0;
748 strcpy(first.variants, appData.variant);
749 strcpy(second.variants, appData.variant);
750 first.analysisSupport = second.analysisSupport = 2; /* detect */
751 first.analyzing = second.analyzing = FALSE;
752 first.initDone = second.initDone = FALSE;
754 /* New features added by Tord: */
755 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757 /* End of new features added by Tord. */
758 first.fenOverride = appData.fenOverride1;
759 second.fenOverride = appData.fenOverride2;
761 /* [HGM] time odds: set factor for each machine */
762 first.timeOdds = appData.firstTimeOdds;
763 second.timeOdds = appData.secondTimeOdds;
765 if(appData.timeOddsMode) {
766 norm = first.timeOdds;
767 if(norm > second.timeOdds) norm = second.timeOdds;
769 first.timeOdds /= norm;
770 second.timeOdds /= norm;
773 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774 first.accumulateTC = appData.firstAccumulateTC;
775 second.accumulateTC = appData.secondAccumulateTC;
776 first.maxNrOfSessions = second.maxNrOfSessions = 1;
779 first.debug = second.debug = FALSE;
780 first.supportsNPS = second.supportsNPS = UNKNOWN;
783 first.optionSettings = appData.firstOptions;
784 second.optionSettings = appData.secondOptions;
786 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788 first.isUCI = appData.firstIsUCI; /* [AS] */
789 second.isUCI = appData.secondIsUCI; /* [AS] */
790 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
793 if (appData.firstProtocolVersion > PROTOVER ||
794 appData.firstProtocolVersion < 1) {
796 sprintf(buf, _("protocol version %d not supported"),
797 appData.firstProtocolVersion);
798 DisplayFatalError(buf, 0, 2);
800 first.protocolVersion = appData.firstProtocolVersion;
803 if (appData.secondProtocolVersion > PROTOVER ||
804 appData.secondProtocolVersion < 1) {
806 sprintf(buf, _("protocol version %d not supported"),
807 appData.secondProtocolVersion);
808 DisplayFatalError(buf, 0, 2);
810 second.protocolVersion = appData.secondProtocolVersion;
813 if (appData.icsActive) {
814 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
815 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816 appData.clockMode = FALSE;
817 first.sendTime = second.sendTime = 0;
821 /* Override some settings from environment variables, for backward
822 compatibility. Unfortunately it's not feasible to have the env
823 vars just set defaults, at least in xboard. Ugh.
825 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
830 if (appData.noChessProgram) {
831 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832 sprintf(programVersion, "%s", PACKAGE_STRING);
837 while (*q != ' ' && *q != NULLCHAR) q++;
839 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841 sprintf(programVersion, "%s + ", PACKAGE_STRING);
842 strncat(programVersion, p, q - p);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
850 if (!appData.icsActive) {
852 /* Check for variants that are supported only in ICS mode,
853 or not at all. Some that are accepted here nevertheless
854 have bugs; see comments below.
856 VariantClass variant = StringToVariant(appData.variant);
858 case VariantBughouse: /* need four players and two boards */
859 case VariantKriegspiel: /* need to hide pieces and move details */
860 /* case VariantFischeRandom: (Fabien: moved below) */
861 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862 DisplayFatalError(buf, 0, 2);
866 case VariantLoadable:
876 sprintf(buf, _("Unknown variant name %s"), appData.variant);
877 DisplayFatalError(buf, 0, 2);
880 case VariantXiangqi: /* [HGM] repetition rules not implemented */
881 case VariantFairy: /* [HGM] TestLegality definitely off! */
882 case VariantGothic: /* [HGM] should work */
883 case VariantCapablanca: /* [HGM] should work */
884 case VariantCourier: /* [HGM] initial forced moves not implemented */
885 case VariantShogi: /* [HGM] drops not tested for legality */
886 case VariantKnightmate: /* [HGM] should work */
887 case VariantCylinder: /* [HGM] untested */
888 case VariantFalcon: /* [HGM] untested */
889 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890 offboard interposition not understood */
891 case VariantNormal: /* definitely works! */
892 case VariantWildCastle: /* pieces not automatically shuffled */
893 case VariantNoCastle: /* pieces not automatically shuffled */
894 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895 case VariantLosers: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantSuicide: /* should work except for win condition,
898 and doesn't know captures are mandatory */
899 case VariantGiveaway: /* should work except for win condition,
900 and doesn't know captures are mandatory */
901 case VariantTwoKings: /* should work */
902 case VariantAtomic: /* should work except for win condition */
903 case Variant3Check: /* should work except for win condition */
904 case VariantShatranj: /* should work except for all win conditions */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1014 int matched, min, sec;
1016 matched = sscanf(tc, "%d:%d", &min, &sec);
1018 timeControl = min * 60 * 1000;
1019 } else if (matched == 2) {
1020 timeControl = (min * 60 + sec) * 1000;
1029 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033 else sprintf(buf, "+%s+%d", tc, ti);
1036 sprintf(buf, "+%d/%s", mps, tc);
1037 else sprintf(buf, "+%s", tc);
1039 fullTimeControlString = StrSave(buf);
1041 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1046 /* Parse second time control */
1049 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1057 timeControl_2 = tc2 * 1000;
1067 timeControl = tc1 * 1000;
1071 timeIncrement = ti * 1000; /* convert to ms */
1072 movesPerSession = 0;
1075 movesPerSession = mps;
1083 if (appData.debugMode) {
1084 fprintf(debugFP, "%s\n", programVersion);
1087 if (appData.matchGames > 0) {
1088 appData.matchMode = TRUE;
1089 } else if (appData.matchMode) {
1090 appData.matchGames = 1;
1092 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093 appData.matchGames = appData.sameColorGames;
1094 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1102 /* kludge: allow timeout for initial "feature" commands */
1104 DisplayMessage("", _("Starting chess program"));
1105 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 InitBackEnd3 P((void))
1112 GameMode initialMode;
1116 InitChessProgram(&first, startedFromSetupPosition);
1119 if (appData.icsActive) {
1121 /* [DM] Make a console window if needed [HGM] merged ifs */
1126 if (*appData.icsCommPort != NULLCHAR) {
1127 sprintf(buf, _("Could not open comm port %s"),
1128 appData.icsCommPort);
1130 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131 appData.icsHost, appData.icsPort);
1133 DisplayFatalError(buf, err, 1);
1138 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1140 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141 } else if (appData.noChessProgram) {
1147 if (*appData.cmailGameName != NULLCHAR) {
1149 OpenLoopback(&cmailPR);
1151 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1155 DisplayMessage("", "");
1156 if (StrCaseCmp(appData.initialMode, "") == 0) {
1157 initialMode = BeginningOfGame;
1158 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159 initialMode = TwoMachinesPlay;
1160 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161 initialMode = AnalyzeFile;
1162 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163 initialMode = AnalyzeMode;
1164 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165 initialMode = MachinePlaysWhite;
1166 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167 initialMode = MachinePlaysBlack;
1168 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169 initialMode = EditGame;
1170 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171 initialMode = EditPosition;
1172 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173 initialMode = Training;
1175 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176 DisplayFatalError(buf, 0, 2);
1180 if (appData.matchMode) {
1181 /* Set up machine vs. machine match */
1182 if (appData.noChessProgram) {
1183 DisplayFatalError(_("Can't have a match with no chess programs"),
1189 if (*appData.loadGameFile != NULLCHAR) {
1190 int index = appData.loadGameIndex; // [HGM] autoinc
1191 if(index<0) lastIndex = index = 1;
1192 if (!LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameFile, FALSE)) {
1195 DisplayFatalError(_("Bad game file"), 0, 1);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 int index = appData.loadPositionIndex; // [HGM] autoinc
1200 if(index<0) lastIndex = index = 1;
1201 if (!LoadPositionFromFile(appData.loadPositionFile,
1203 appData.loadPositionFile)) {
1204 DisplayFatalError(_("Bad position file"), 0, 1);
1209 } else if (*appData.cmailGameName != NULLCHAR) {
1210 /* Set up cmail mode */
1211 ReloadCmailMsgEvent(TRUE);
1213 /* Set up other modes */
1214 if (initialMode == AnalyzeFile) {
1215 if (*appData.loadGameFile == NULLCHAR) {
1216 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1220 if (*appData.loadGameFile != NULLCHAR) {
1221 (void) LoadGameFromFile(appData.loadGameFile,
1222 appData.loadGameIndex,
1223 appData.loadGameFile, TRUE);
1224 } else if (*appData.loadPositionFile != NULLCHAR) {
1225 (void) LoadPositionFromFile(appData.loadPositionFile,
1226 appData.loadPositionIndex,
1227 appData.loadPositionFile);
1228 /* [HGM] try to make self-starting even after FEN load */
1229 /* to allow automatic setup of fairy variants with wtm */
1230 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231 gameMode = BeginningOfGame;
1232 setboardSpoiledMachineBlack = 1;
1234 /* [HGM] loadPos: make that every new game uses the setup */
1235 /* from file as long as we do not switch variant */
1236 if(!blackPlaysFirst) { int i;
1237 startedFromPositionFile = TRUE;
1238 CopyBoard(filePosition, boards[0]);
1239 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1242 if (initialMode == AnalyzeMode) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1252 } else if (initialMode == AnalyzeFile) {
1253 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254 ShowThinkingEvent();
1256 AnalysisPeriodicEvent(1);
1257 } else if (initialMode == MachinePlaysWhite) {
1258 if (appData.noChessProgram) {
1259 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1263 if (appData.icsActive) {
1264 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1268 MachineWhiteEvent();
1269 } else if (initialMode == MachinePlaysBlack) {
1270 if (appData.noChessProgram) {
1271 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1275 if (appData.icsActive) {
1276 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1280 MachineBlackEvent();
1281 } else if (initialMode == TwoMachinesPlay) {
1282 if (appData.noChessProgram) {
1283 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1287 if (appData.icsActive) {
1288 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1293 } else if (initialMode == EditGame) {
1295 } else if (initialMode == EditPosition) {
1296 EditPositionEvent();
1297 } else if (initialMode == Training) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308 * Establish will establish a contact to a remote host.port.
1309 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310 * used to talk to the host.
1311 * Returns 0 if okay, error code if not.
1318 if (*appData.icsCommPort != NULLCHAR) {
1319 /* Talk to the host through a serial comm port */
1320 return OpenCommPort(appData.icsCommPort, &icsPR);
1322 } else if (*appData.gateway != NULLCHAR) {
1323 if (*appData.remoteShell == NULLCHAR) {
1324 /* Use the rcmd protocol to run telnet program on a gateway host */
1325 snprintf(buf, sizeof(buf), "%s %s %s",
1326 appData.telnetProgram, appData.icsHost, appData.icsPort);
1327 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1330 /* Use the rsh program to run telnet program on a gateway host */
1331 if (*appData.remoteUser == NULLCHAR) {
1332 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333 appData.gateway, appData.telnetProgram,
1334 appData.icsHost, appData.icsPort);
1336 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337 appData.remoteShell, appData.gateway,
1338 appData.remoteUser, appData.telnetProgram,
1339 appData.icsHost, appData.icsPort);
1341 return StartChildProcess(buf, "", &icsPR);
1344 } else if (appData.useTelnet) {
1345 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1348 /* TCP socket interface differs somewhat between
1349 Unix and NT; handle details in the front end.
1351 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1356 show_bytes(fp, buf, count)
1362 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363 fprintf(fp, "\\%03o", *buf & 0xff);
1372 /* Returns an errno value */
1374 OutputMaybeTelnet(pr, message, count, outError)
1380 char buf[8192], *p, *q, *buflim;
1381 int left, newcount, outcount;
1383 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384 *appData.gateway != NULLCHAR) {
1385 if (appData.debugMode) {
1386 fprintf(debugFP, ">ICS: ");
1387 show_bytes(debugFP, message, count);
1388 fprintf(debugFP, "\n");
1390 return OutputToProcess(pr, message, count, outError);
1393 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1400 if (appData.debugMode) {
1401 fprintf(debugFP, ">ICS: ");
1402 show_bytes(debugFP, buf, newcount);
1403 fprintf(debugFP, "\n");
1405 outcount = OutputToProcess(pr, buf, newcount, outError);
1406 if (outcount < newcount) return -1; /* to be sure */
1413 } else if (((unsigned char) *p) == TN_IAC) {
1414 *q++ = (char) TN_IAC;
1421 if (appData.debugMode) {
1422 fprintf(debugFP, ">ICS: ");
1423 show_bytes(debugFP, buf, newcount);
1424 fprintf(debugFP, "\n");
1426 outcount = OutputToProcess(pr, buf, newcount, outError);
1427 if (outcount < newcount) return -1; /* to be sure */
1432 read_from_player(isr, closure, message, count, error)
1439 int outError, outCount;
1440 static int gotEof = 0;
1442 /* Pass data read from player on to ICS */
1445 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 } else if (count < 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452 } else if (gotEof++ > 0) {
1453 RemoveInputSource(isr);
1454 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1460 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461 SendToICS("date\n");
1462 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469 int count, outCount, outError;
1471 if (icsPR == NULL) return;
1474 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475 if (outCount < count) {
1476 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480 /* This is used for sending logon scripts to the ICS. Sending
1481 without a delay causes problems when using timestamp on ICC
1482 (at least on my machine). */
1484 SendToICSDelayed(s,msdelay)
1488 int count, outCount, outError;
1490 if (icsPR == NULL) return;
1493 if (appData.debugMode) {
1494 fprintf(debugFP, ">ICS: ");
1495 show_bytes(debugFP, s, count);
1496 fprintf(debugFP, "\n");
1498 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1500 if (outCount < count) {
1501 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 /* Remove all highlighting escape sequences in s
1507 Also deletes any suffix starting with '('
1510 StripHighlightAndTitle(s)
1513 static char retbuf[MSG_SIZ];
1516 while (*s != NULLCHAR) {
1517 while (*s == '\033') {
1518 while (*s != NULLCHAR && !isalpha(*s)) s++;
1519 if (*s != NULLCHAR) s++;
1521 while (*s != NULLCHAR && *s != '\033') {
1522 if (*s == '(' || *s == '[') {
1533 /* Remove all highlighting escape sequences in s */
1538 static char retbuf[MSG_SIZ];
1541 while (*s != NULLCHAR) {
1542 while (*s == '\033') {
1543 while (*s != NULLCHAR && !isalpha(*s)) s++;
1544 if (*s != NULLCHAR) s++;
1546 while (*s != NULLCHAR && *s != '\033') {
1554 char *variantNames[] = VARIANT_NAMES;
1559 return variantNames[v];
1563 /* Identify a variant from the strings the chess servers use or the
1564 PGN Variant tag names we use. */
1571 VariantClass v = VariantNormal;
1572 int i, found = FALSE;
1577 /* [HGM] skip over optional board-size prefixes */
1578 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580 while( *e++ != '_');
1583 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584 if (StrCaseStr(e, variantNames[i])) {
1585 v = (VariantClass) i;
1592 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593 || StrCaseStr(e, "wild/fr")
1594 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595 v = VariantFischeRandom;
1596 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597 (i = 1, p = StrCaseStr(e, "w"))) {
1599 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606 case 0: /* FICS only, actually */
1608 /* Castling legal even if K starts on d-file */
1609 v = VariantWildCastle;
1614 /* Castling illegal even if K & R happen to start in
1615 normal positions. */
1616 v = VariantNoCastle;
1629 /* Castling legal iff K & R start in normal positions */
1635 /* Special wilds for position setup; unclear what to do here */
1636 v = VariantLoadable;
1639 /* Bizarre ICC game */
1640 v = VariantTwoKings;
1643 v = VariantKriegspiel;
1649 v = VariantFischeRandom;
1652 v = VariantCrazyhouse;
1655 v = VariantBughouse;
1661 /* Not quite the same as FICS suicide! */
1662 v = VariantGiveaway;
1668 v = VariantShatranj;
1671 /* Temporary names for future ICC types. The name *will* change in
1672 the next xboard/WinBoard release after ICC defines it. */
1710 v = VariantCapablanca;
1713 v = VariantKnightmate;
1719 v = VariantCylinder;
1725 v = VariantCapaRandom;
1728 v = VariantBerolina;
1740 /* Found "wild" or "w" in the string but no number;
1741 must assume it's normal chess. */
1745 sprintf(buf, _("Unknown wild type %d"), wnum);
1746 DisplayError(buf, 0);
1752 if (appData.debugMode) {
1753 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754 e, wnum, VariantName(v));
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763 advance *index beyond it, and set leftover_start to the new value of
1764 *index; else return FALSE. If pattern contains the character '*', it
1765 matches any sequence of characters not containing '\r', '\n', or the
1766 character following the '*' (if any), and the matched sequence(s) are
1767 copied into star_match.
1770 looking_at(buf, index, pattern)
1775 char *bufp = &buf[*index], *patternp = pattern;
1777 char *matchp = star_match[0];
1780 if (*patternp == NULLCHAR) {
1781 *index = leftover_start = bufp - buf;
1785 if (*bufp == NULLCHAR) return FALSE;
1786 if (*patternp == '*') {
1787 if (*bufp == *(patternp + 1)) {
1789 matchp = star_match[++star_count];
1793 } else if (*bufp == '\n' || *bufp == '\r') {
1795 if (*patternp == NULLCHAR)
1800 *matchp++ = *bufp++;
1804 if (*patternp != *bufp) return FALSE;
1811 SendToPlayer(data, length)
1815 int error, outCount;
1816 outCount = OutputToProcess(NoProc, data, length, &error);
1817 if (outCount < length) {
1818 DisplayFatalError(_("Error writing to display"), error, 1);
1823 PackHolding(packed, holding)
1835 switch (runlength) {
1846 sprintf(q, "%d", runlength);
1858 /* Telnet protocol requests from the front end */
1860 TelnetRequest(ddww, option)
1861 unsigned char ddww, option;
1863 unsigned char msg[3];
1864 int outCount, outError;
1866 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1868 if (appData.debugMode) {
1869 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1885 sprintf(buf1, "%d", ddww);
1894 sprintf(buf2, "%d", option);
1897 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1902 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1904 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911 if (!appData.icsActive) return;
1912 TelnetRequest(TN_DO, TN_ECHO);
1918 if (!appData.icsActive) return;
1919 TelnetRequest(TN_DONT, TN_ECHO);
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1925 /* put the holdings sent to us by the server on the board holdings area */
1926 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1930 if(gameInfo.holdingsWidth < 2) return;
1932 if( (int)lowestPiece >= BlackPawn ) {
1935 holdingsStartRow = BOARD_HEIGHT-1;
1938 holdingsColumn = BOARD_WIDTH-1;
1939 countsColumn = BOARD_WIDTH-2;
1940 holdingsStartRow = 0;
1944 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945 board[i][holdingsColumn] = EmptySquare;
1946 board[i][countsColumn] = (ChessSquare) 0;
1948 while( (p=*holdings++) != NULLCHAR ) {
1949 piece = CharToPiece( ToUpper(p) );
1950 if(piece == EmptySquare) continue;
1951 /*j = (int) piece - (int) WhitePawn;*/
1952 j = PieceToNumber(piece);
1953 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954 if(j < 0) continue; /* should not happen */
1955 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957 board[holdingsStartRow+j*direction][countsColumn]++;
1964 VariantSwitch(Board board, VariantClass newVariant)
1966 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1970 startedFromPositionFile = FALSE;
1971 if(gameInfo.variant == newVariant) return;
1973 /* [HGM] This routine is called each time an assignment is made to
1974 * gameInfo.variant during a game, to make sure the board sizes
1975 * are set to match the new variant. If that means adding or deleting
1976 * holdings, we shift the playing board accordingly
1977 * This kludge is needed because in ICS observe mode, we get boards
1978 * of an ongoing game without knowing the variant, and learn about the
1979 * latter only later. This can be because of the move list we requested,
1980 * in which case the game history is refilled from the beginning anyway,
1981 * but also when receiving holdings of a crazyhouse game. In the latter
1982 * case we want to add those holdings to the already received position.
1986 if (appData.debugMode) {
1987 fprintf(debugFP, "Switch board from %s to %s\n",
1988 VariantName(gameInfo.variant), VariantName(newVariant));
1989 setbuf(debugFP, NULL);
1991 shuffleOpenings = 0; /* [HGM] shuffle */
1992 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993 switch(newVariant) {
1995 newWidth = 9; newHeight = 9;
1996 gameInfo.holdingsSize = 7;
1997 case VariantBughouse:
1998 case VariantCrazyhouse:
1999 newHoldingsWidth = 2; break;
2001 newHoldingsWidth = gameInfo.holdingsSize = 0;
2004 if(newWidth != gameInfo.boardWidth ||
2005 newHeight != gameInfo.boardHeight ||
2006 newHoldingsWidth != gameInfo.holdingsWidth ) {
2008 /* shift position to new playing area, if needed */
2009 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010 for(i=0; i<BOARD_HEIGHT; i++)
2011 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 for(i=0; i<newHeight; i++) {
2015 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019 for(i=0; i<BOARD_HEIGHT; i++)
2020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2031 /* [HGM] The following should definitely be solved in a better way */
2033 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035 saveEP = epStatus[0];
2037 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2039 epStatus[0] = saveEP;
2040 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041 CopyBoard(tempBoard, board); /* restore position received from ICS */
2043 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2045 forwardMostMove = oldForwardMostMove;
2046 backwardMostMove = oldBackwardMostMove;
2047 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2050 static int loggedOn = FALSE;
2052 /*-- Game start info cache: --*/
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2065 read_from_ics(isr, closure, data, count, error)
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2082 static int started = STARTED_NONE;
2083 static char parse[20000];
2084 static int parse_pos = 0;
2085 static char buf[BUF_SIZE + 1];
2086 static int firstTime = TRUE, intfSet = FALSE;
2087 static ColorClass prevColor = ColorNormal;
2088 static int savingComment = FALSE;
2094 int backup; /* [DM] For zippy color lines */
2096 char talker[MSG_SIZ]; // [HGM] chat
2099 if (appData.debugMode) {
2101 fprintf(debugFP, "<ICS: ");
2102 show_bytes(debugFP, data, count);
2103 fprintf(debugFP, "\n");
2107 if (appData.debugMode) { int f = forwardMostMove;
2108 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2112 /* If last read ended with a partial line that we couldn't parse,
2113 prepend it to the new read and try again. */
2114 if (leftover_len > 0) {
2115 for (i=0; i<leftover_len; i++)
2116 buf[i] = buf[leftover_start + i];
2119 /* Copy in new characters, removing nulls and \r's */
2120 buf_len = leftover_len;
2121 for (i = 0; i < count; i++) {
2122 if (data[i] != NULLCHAR && data[i] != '\r')
2123 buf[buf_len++] = data[i];
2124 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2125 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2126 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2131 buf[buf_len] = NULLCHAR;
2132 next_out = leftover_len;
2136 while (i < buf_len) {
2137 /* Deal with part of the TELNET option negotiation
2138 protocol. We refuse to do anything beyond the
2139 defaults, except that we allow the WILL ECHO option,
2140 which ICS uses to turn off password echoing when we are
2141 directly connected to it. We reject this option
2142 if localLineEditing mode is on (always on in xboard)
2143 and we are talking to port 23, which might be a real
2144 telnet server that will try to keep WILL ECHO on permanently.
2146 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148 unsigned char option;
2150 switch ((unsigned char) buf[++i]) {
2152 if (appData.debugMode)
2153 fprintf(debugFP, "\n<WILL ");
2154 switch (option = (unsigned char) buf[++i]) {
2156 if (appData.debugMode)
2157 fprintf(debugFP, "ECHO ");
2158 /* Reply only if this is a change, according
2159 to the protocol rules. */
2160 if (remoteEchoOption) break;
2161 if (appData.localLineEditing &&
2162 atoi(appData.icsPort) == TN_PORT) {
2163 TelnetRequest(TN_DONT, TN_ECHO);
2166 TelnetRequest(TN_DO, TN_ECHO);
2167 remoteEchoOption = TRUE;
2171 if (appData.debugMode)
2172 fprintf(debugFP, "%d ", option);
2173 /* Whatever this is, we don't want it. */
2174 TelnetRequest(TN_DONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<WONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "ECHO ");
2185 /* Reply only if this is a change, according
2186 to the protocol rules. */
2187 if (!remoteEchoOption) break;
2189 TelnetRequest(TN_DONT, TN_ECHO);
2190 remoteEchoOption = FALSE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", (unsigned char) option);
2195 /* Whatever this is, it must already be turned
2196 off, because we never agree to turn on
2197 anything non-default, so according to the
2198 protocol rules, we don't reply. */
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DO ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 /* Whatever this is, we refuse to do it. */
2208 if (appData.debugMode)
2209 fprintf(debugFP, "%d ", option);
2210 TelnetRequest(TN_WONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<DONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "%d ", option);
2221 /* Whatever this is, we are already not doing
2222 it, because we never agree to do anything
2223 non-default, so according to the protocol
2224 rules, we don't reply. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "\n<IAC ");
2231 /* Doubled IAC; pass it through */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237 /* Drop all other telnet commands on the floor */
2240 if (oldi > next_out)
2241 SendToPlayer(&buf[next_out], oldi - next_out);
2247 /* OK, this at least will *usually* work */
2248 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2252 if (loggedOn && !intfSet) {
2253 if (ics_type == ICS_ICC) {
2255 "/set-quietly interface %s\n/set-quietly style 12\n",
2258 } else if (ics_type == ICS_CHESSNET) {
2259 sprintf(str, "/style 12\n");
2261 strcpy(str, "alias $ @\n$set interface ");
2262 strcat(str, programVersion);
2263 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2265 strcat(str, "$iset nohighlight 1\n");
2267 strcat(str, "$iset lock 1\n$style 12\n");
2273 if (started == STARTED_COMMENT) {
2274 /* Accumulate characters in comment */
2275 parse[parse_pos++] = buf[i];
2276 if (buf[i] == '\n') {
2277 parse[parse_pos] = NULLCHAR;
2278 if(chattingPartner>=0) {
2280 sprintf(mess, "%s%s", talker, parse);
2281 OutputChatMessage(chattingPartner, mess);
2282 chattingPartner = -1;
2284 if(!suppressKibitz) // [HGM] kibitz
2285 AppendComment(forwardMostMove, StripHighlight(parse));
2286 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287 int nrDigit = 0, nrAlph = 0, i;
2288 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290 parse[parse_pos] = NULLCHAR;
2291 // try to be smart: if it does not look like search info, it should go to
2292 // ICS interaction window after all, not to engine-output window.
2293 for(i=0; i<parse_pos; i++) { // count letters and digits
2294 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2296 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2298 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299 int depth=0; float score;
2300 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302 pvInfoList[forwardMostMove-1].depth = depth;
2303 pvInfoList[forwardMostMove-1].score = 100*score;
2305 OutputKibitz(suppressKibitz, parse);
2308 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309 SendToPlayer(tmp, strlen(tmp));
2312 started = STARTED_NONE;
2314 /* Don't match patterns against characters in chatter */
2319 if (started == STARTED_CHATTER) {
2320 if (buf[i] != '\n') {
2321 /* Don't match patterns against characters in chatter */
2325 started = STARTED_NONE;
2328 /* Kludge to deal with rcmd protocol */
2329 if (firstTime && looking_at(buf, &i, "\001*")) {
2330 DisplayFatalError(&buf[1], 0, 1);
2336 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2339 if (appData.debugMode)
2340 fprintf(debugFP, "ics_type %d\n", ics_type);
2343 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344 ics_type = ICS_FICS;
2346 if (appData.debugMode)
2347 fprintf(debugFP, "ics_type %d\n", ics_type);
2350 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351 ics_type = ICS_CHESSNET;
2353 if (appData.debugMode)
2354 fprintf(debugFP, "ics_type %d\n", ics_type);
2359 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360 looking_at(buf, &i, "Logging you in as \"*\"") ||
2361 looking_at(buf, &i, "will be \"*\""))) {
2362 strcpy(ics_handle, star_match[0]);
2366 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2368 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369 DisplayIcsInteractionTitle(buf);
2370 have_set_title = TRUE;
2373 /* skip finger notes */
2374 if (started == STARTED_NONE &&
2375 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376 (buf[i] == '1' && buf[i+1] == '0')) &&
2377 buf[i+2] == ':' && buf[i+3] == ' ') {
2378 started = STARTED_CHATTER;
2383 /* skip formula vars */
2384 if (started == STARTED_NONE &&
2385 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386 started = STARTED_CHATTER;
2392 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393 if (appData.autoKibitz && started == STARTED_NONE &&
2394 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2395 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396 if(looking_at(buf, &i, "* kibitzes: ") &&
2397 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2398 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2399 suppressKibitz = TRUE;
2400 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401 && (gameMode == IcsPlayingWhite)) ||
2402 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2404 started = STARTED_CHATTER; // own kibitz we simply discard
2406 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407 parse_pos = 0; parse[0] = NULLCHAR;
2408 savingComment = TRUE;
2409 suppressKibitz = gameMode != IcsObserving ? 2 :
2410 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2414 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415 started = STARTED_CHATTER;
2416 suppressKibitz = TRUE;
2418 } // [HGM] kibitz: end of patch
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2422 // [HGM] chat: intercept tells by users for which we have an open chat window
2424 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2425 looking_at(buf, &i, "* whispers:") ||
2426 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2429 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430 chattingPartner = -1;
2432 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433 for(p=0; p<MAX_CHAT; p++) {
2434 if(channel == atoi(chatPartner[p])) {
2435 talker[0] = '['; strcat(talker, "]");
2436 chattingPartner = p; break;
2439 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440 for(p=0; p<MAX_CHAT; p++) {
2441 if(!strcmp("WHISPER", chatPartner[p])) {
2442 talker[0] = '['; strcat(talker, "]");
2443 chattingPartner = p; break;
2446 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2449 chattingPartner = p; break;
2451 if(chattingPartner<0) i = oldi; else {
2452 started = STARTED_COMMENT;
2453 parse_pos = 0; parse[0] = NULLCHAR;
2454 savingComment = TRUE;
2455 suppressKibitz = TRUE;
2457 } // [HGM] chat: end of patch
2459 if (appData.zippyTalk || appData.zippyPlay) {
2460 /* [DM] Backup address for color zippy lines */
2464 if (loggedOn == TRUE)
2465 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2468 if (ZippyControl(buf, &i) ||
2469 ZippyConverse(buf, &i) ||
2470 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2472 if (!appData.colorize) continue;
2476 } // [DM] 'else { ' deleted
2478 /* Regular tells and says */
2479 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480 looking_at(buf, &i, "* (your partner) tells you: ") ||
2481 looking_at(buf, &i, "* says: ") ||
2482 /* Don't color "message" or "messages" output */
2483 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484 looking_at(buf, &i, "*. * at *:*: ") ||
2485 looking_at(buf, &i, "--* (*:*): ") ||
2486 /* Message notifications (same color as tells) */
2487 looking_at(buf, &i, "* has left a message ") ||
2488 looking_at(buf, &i, "* just sent you a message:\n") ||
2489 /* Whispers and kibitzes */
2490 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491 looking_at(buf, &i, "* kibitzes: ") ||
2493 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2495 if (tkind == 1 && strchr(star_match[0], ':')) {
2496 /* Avoid "tells you:" spoofs in channels */
2499 if (star_match[0][0] == NULLCHAR ||
2500 strchr(star_match[0], ' ') ||
2501 (tkind == 3 && strchr(star_match[1], ' '))) {
2502 /* Reject bogus matches */
2505 if (appData.colorize) {
2506 if (oldi > next_out) {
2507 SendToPlayer(&buf[next_out], oldi - next_out);
2512 Colorize(ColorTell, FALSE);
2513 curColor = ColorTell;
2516 Colorize(ColorKibitz, FALSE);
2517 curColor = ColorKibitz;
2520 p = strrchr(star_match[1], '(');
2527 Colorize(ColorChannel1, FALSE);
2528 curColor = ColorChannel1;
2530 Colorize(ColorChannel, FALSE);
2531 curColor = ColorChannel;
2535 curColor = ColorNormal;
2539 if (started == STARTED_NONE && appData.autoComment &&
2540 (gameMode == IcsObserving ||
2541 gameMode == IcsPlayingWhite ||
2542 gameMode == IcsPlayingBlack)) {
2543 parse_pos = i - oldi;
2544 memcpy(parse, &buf[oldi], parse_pos);
2545 parse[parse_pos] = NULLCHAR;
2546 started = STARTED_COMMENT;
2547 savingComment = TRUE;
2549 started = STARTED_CHATTER;
2550 savingComment = FALSE;
2557 if (looking_at(buf, &i, "* s-shouts: ") ||
2558 looking_at(buf, &i, "* c-shouts: ")) {
2559 if (appData.colorize) {
2560 if (oldi > next_out) {
2561 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorSShout, FALSE);
2565 curColor = ColorSShout;
2568 started = STARTED_CHATTER;
2572 if (looking_at(buf, &i, "--->")) {
2577 if (looking_at(buf, &i, "* shouts: ") ||
2578 looking_at(buf, &i, "--> ")) {
2579 if (appData.colorize) {
2580 if (oldi > next_out) {
2581 SendToPlayer(&buf[next_out], oldi - next_out);
2584 Colorize(ColorShout, FALSE);
2585 curColor = ColorShout;
2588 started = STARTED_CHATTER;
2592 if (looking_at( buf, &i, "Challenge:")) {
2593 if (appData.colorize) {
2594 if (oldi > next_out) {
2595 SendToPlayer(&buf[next_out], oldi - next_out);
2598 Colorize(ColorChallenge, FALSE);
2599 curColor = ColorChallenge;
2605 if (looking_at(buf, &i, "* offers you") ||
2606 looking_at(buf, &i, "* offers to be") ||
2607 looking_at(buf, &i, "* would like to") ||
2608 looking_at(buf, &i, "* requests to") ||
2609 looking_at(buf, &i, "Your opponent offers") ||
2610 looking_at(buf, &i, "Your opponent requests")) {
2612 if (appData.colorize) {
2613 if (oldi > next_out) {
2614 SendToPlayer(&buf[next_out], oldi - next_out);
2617 Colorize(ColorRequest, FALSE);
2618 curColor = ColorRequest;
2623 if (looking_at(buf, &i, "* (*) seeking")) {
2624 if (appData.colorize) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 Colorize(ColorSeek, FALSE);
2630 curColor = ColorSeek;
2635 if (looking_at(buf, &i, "\\ ")) {
2636 if (prevColor != ColorNormal) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(prevColor, TRUE);
2642 curColor = prevColor;
2644 if (savingComment) {
2645 parse_pos = i - oldi;
2646 memcpy(parse, &buf[oldi], parse_pos);
2647 parse[parse_pos] = NULLCHAR;
2648 started = STARTED_COMMENT;
2650 started = STARTED_CHATTER;
2655 if (looking_at(buf, &i, "Black Strength :") ||
2656 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657 looking_at(buf, &i, "<10>") ||
2658 looking_at(buf, &i, "#@#")) {
2659 /* Wrong board style */
2661 SendToICS(ics_prefix);
2662 SendToICS("set style 12\n");
2663 SendToICS(ics_prefix);
2664 SendToICS("refresh\n");
2668 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2670 have_sent_ICS_logon = 1;
2674 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2675 (looking_at(buf, &i, "\n<12> ") ||
2676 looking_at(buf, &i, "<12> "))) {
2678 if (oldi > next_out) {
2679 SendToPlayer(&buf[next_out], oldi - next_out);
2682 started = STARTED_BOARD;
2687 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688 looking_at(buf, &i, "<b1> ")) {
2689 if (oldi > next_out) {
2690 SendToPlayer(&buf[next_out], oldi - next_out);
2693 started = STARTED_HOLDINGS;
2698 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2700 /* Header for a move list -- first line */
2702 switch (ics_getting_history) {
2706 case BeginningOfGame:
2707 /* User typed "moves" or "oldmoves" while we
2708 were idle. Pretend we asked for these
2709 moves and soak them up so user can step
2710 through them and/or save them.
2713 gameMode = IcsObserving;
2716 ics_getting_history = H_GOT_UNREQ_HEADER;
2718 case EditGame: /*?*/
2719 case EditPosition: /*?*/
2720 /* Should above feature work in these modes too? */
2721 /* For now it doesn't */
2722 ics_getting_history = H_GOT_UNWANTED_HEADER;
2725 ics_getting_history = H_GOT_UNWANTED_HEADER;
2730 /* Is this the right one? */
2731 if (gameInfo.white && gameInfo.black &&
2732 strcmp(gameInfo.white, star_match[0]) == 0 &&
2733 strcmp(gameInfo.black, star_match[2]) == 0) {
2735 ics_getting_history = H_GOT_REQ_HEADER;
2738 case H_GOT_REQ_HEADER:
2739 case H_GOT_UNREQ_HEADER:
2740 case H_GOT_UNWANTED_HEADER:
2741 case H_GETTING_MOVES:
2742 /* Should not happen */
2743 DisplayError(_("Error gathering move list: two headers"), 0);
2744 ics_getting_history = H_FALSE;
2748 /* Save player ratings into gameInfo if needed */
2749 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751 (gameInfo.whiteRating == -1 ||
2752 gameInfo.blackRating == -1)) {
2754 gameInfo.whiteRating = string_to_rating(star_match[1]);
2755 gameInfo.blackRating = string_to_rating(star_match[3]);
2756 if (appData.debugMode)
2757 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2758 gameInfo.whiteRating, gameInfo.blackRating);
2763 if (looking_at(buf, &i,
2764 "* * match, initial time: * minute*, increment: * second")) {
2765 /* Header for a move list -- second line */
2766 /* Initial board will follow if this is a wild game */
2767 if (gameInfo.event != NULL) free(gameInfo.event);
2768 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769 gameInfo.event = StrSave(str);
2770 /* [HGM] we switched variant. Translate boards if needed. */
2771 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2775 if (looking_at(buf, &i, "Move ")) {
2776 /* Beginning of a move list */
2777 switch (ics_getting_history) {
2779 /* Normally should not happen */
2780 /* Maybe user hit reset while we were parsing */
2783 /* Happens if we are ignoring a move list that is not
2784 * the one we just requested. Common if the user
2785 * tries to observe two games without turning off
2788 case H_GETTING_MOVES:
2789 /* Should not happen */
2790 DisplayError(_("Error gathering move list: nested"), 0);
2791 ics_getting_history = H_FALSE;
2793 case H_GOT_REQ_HEADER:
2794 ics_getting_history = H_GETTING_MOVES;
2795 started = STARTED_MOVES;
2797 if (oldi > next_out) {
2798 SendToPlayer(&buf[next_out], oldi - next_out);
2801 case H_GOT_UNREQ_HEADER:
2802 ics_getting_history = H_GETTING_MOVES;
2803 started = STARTED_MOVES_NOHIDE;
2806 case H_GOT_UNWANTED_HEADER:
2807 ics_getting_history = H_FALSE;
2813 if (looking_at(buf, &i, "% ") ||
2814 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816 savingComment = FALSE;
2819 case STARTED_MOVES_NOHIDE:
2820 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821 parse[parse_pos + i - oldi] = NULLCHAR;
2822 ParseGameHistory(parse);
2824 if (appData.zippyPlay && first.initDone) {
2825 FeedMovesToProgram(&first, forwardMostMove);
2826 if (gameMode == IcsPlayingWhite) {
2827 if (WhiteOnMove(forwardMostMove)) {
2828 if (first.sendTime) {
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 SendTimeRemaining(&first, TRUE);
2835 if (first.useColors) {
2836 SendToProgram("white\ngo\n", &first);
2838 SendToProgram("go\n", &first);
2841 if (first.useColors) {
2842 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2844 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2846 first.maybeThinking = TRUE;
2848 if (first.usePlayother) {
2849 if (first.sendTime) {
2850 SendTimeRemaining(&first, TRUE);
2852 SendToProgram("playother\n", &first);
2858 } else if (gameMode == IcsPlayingBlack) {
2859 if (!WhiteOnMove(forwardMostMove)) {
2860 if (first.sendTime) {
2861 if (first.useColors) {
2862 SendToProgram("white\n", &first);
2864 SendTimeRemaining(&first, FALSE);
2867 if (first.useColors) {
2868 SendToProgram("black\ngo\n", &first);
2870 SendToProgram("go\n", &first);
2873 if (first.useColors) {
2874 SendToProgram("black\n", &first);
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2878 first.maybeThinking = TRUE;
2880 if (first.usePlayother) {
2881 if (first.sendTime) {
2882 SendTimeRemaining(&first, FALSE);
2884 SendToProgram("playother\n", &first);
2893 if (gameMode == IcsObserving && ics_gamenum == -1) {
2894 /* Moves came from oldmoves or moves command
2895 while we weren't doing anything else.
2897 currentMove = forwardMostMove;
2898 ClearHighlights();/*!!could figure this out*/
2899 flipView = appData.flipView;
2900 DrawPosition(FALSE, boards[currentMove]);
2901 DisplayBothClocks();
2902 sprintf(str, "%s vs. %s",
2903 gameInfo.white, gameInfo.black);
2907 /* Moves were history of an active game */
2908 if (gameInfo.resultDetails != NULL) {
2909 free(gameInfo.resultDetails);
2910 gameInfo.resultDetails = NULL;
2913 HistorySet(parseList, backwardMostMove,
2914 forwardMostMove, currentMove-1);
2915 DisplayMove(currentMove - 1);
2916 if (started == STARTED_MOVES) next_out = i;
2917 started = STARTED_NONE;
2918 ics_getting_history = H_FALSE;
2921 case STARTED_OBSERVE:
2922 started = STARTED_NONE;
2923 SendToICS(ics_prefix);
2924 SendToICS("refresh\n");
2930 if(bookHit) { // [HGM] book: simulate book reply
2931 static char bookMove[MSG_SIZ]; // a bit generous?
2933 programStats.nodes = programStats.depth = programStats.time =
2934 programStats.score = programStats.got_only_move = 0;
2935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2937 strcpy(bookMove, "move ");
2938 strcat(bookMove, bookHit);
2939 HandleMachineMove(bookMove, &first);
2944 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945 started == STARTED_HOLDINGS ||
2946 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947 /* Accumulate characters in move list or board */
2948 parse[parse_pos++] = buf[i];
2951 /* Start of game messages. Mostly we detect start of game
2952 when the first board image arrives. On some versions
2953 of the ICS, though, we need to do a "refresh" after starting
2954 to observe in order to get the current board right away. */
2955 if (looking_at(buf, &i, "Adding game * to observation list")) {
2956 started = STARTED_OBSERVE;
2960 /* Handle auto-observe */
2961 if (appData.autoObserve &&
2962 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2965 /* Choose the player that was highlighted, if any. */
2966 if (star_match[0][0] == '\033' ||
2967 star_match[1][0] != '\033') {
2968 player = star_match[0];
2970 player = star_match[2];
2972 sprintf(str, "%sobserve %s\n",
2973 ics_prefix, StripHighlightAndTitle(player));
2976 /* Save ratings from notify string */
2977 strcpy(player1Name, star_match[0]);
2978 player1Rating = string_to_rating(star_match[1]);
2979 strcpy(player2Name, star_match[2]);
2980 player2Rating = string_to_rating(star_match[3]);
2982 if (appData.debugMode)
2984 "Ratings from 'Game notification:' %s %d, %s %d\n",
2985 player1Name, player1Rating,
2986 player2Name, player2Rating);
2991 /* Deal with automatic examine mode after a game,
2992 and with IcsObserving -> IcsExamining transition */
2993 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994 looking_at(buf, &i, "has made you an examiner of game *")) {
2996 int gamenum = atoi(star_match[0]);
2997 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998 gamenum == ics_gamenum) {
2999 /* We were already playing or observing this game;
3000 no need to refetch history */
3001 gameMode = IcsExamining;
3003 pauseExamForwardMostMove = forwardMostMove;
3004 } else if (currentMove < forwardMostMove) {
3005 ForwardInner(forwardMostMove);
3008 /* I don't think this case really can happen */
3009 SendToICS(ics_prefix);
3010 SendToICS("refresh\n");
3015 /* Error messages */
3016 // if (ics_user_moved) {
3017 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018 if (looking_at(buf, &i, "Illegal move") ||
3019 looking_at(buf, &i, "Not a legal move") ||
3020 looking_at(buf, &i, "Your king is in check") ||
3021 looking_at(buf, &i, "It isn't your turn") ||
3022 looking_at(buf, &i, "It is not your move")) {
3024 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025 currentMove = --forwardMostMove;
3026 DisplayMove(currentMove - 1); /* before DMError */
3027 DrawPosition(FALSE, boards[currentMove]);
3029 DisplayBothClocks();
3031 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037 if (looking_at(buf, &i, "still have time") ||
3038 looking_at(buf, &i, "not out of time") ||
3039 looking_at(buf, &i, "either player is out of time") ||
3040 looking_at(buf, &i, "has timeseal; checking")) {
3041 /* We must have called his flag a little too soon */
3042 whiteFlag = blackFlag = FALSE;
3046 if (looking_at(buf, &i, "added * seconds to") ||
3047 looking_at(buf, &i, "seconds were added to")) {
3048 /* Update the clocks */
3049 SendToICS(ics_prefix);
3050 SendToICS("refresh\n");
3054 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055 ics_clock_paused = TRUE;
3060 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061 ics_clock_paused = FALSE;
3066 /* Grab player ratings from the Creating: message.
3067 Note we have to check for the special case when
3068 the ICS inserts things like [white] or [black]. */
3069 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3072 0 player 1 name (not necessarily white)
3074 2 empty, white, or black (IGNORED)
3075 3 player 2 name (not necessarily black)
3078 The names/ratings are sorted out when the game
3079 actually starts (below).
3081 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082 player1Rating = string_to_rating(star_match[1]);
3083 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084 player2Rating = string_to_rating(star_match[4]);
3086 if (appData.debugMode)
3088 "Ratings from 'Creating:' %s %d, %s %d\n",
3089 player1Name, player1Rating,
3090 player2Name, player2Rating);
3095 /* Improved generic start/end-of-game messages */
3096 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098 /* If tkind == 0: */
3099 /* star_match[0] is the game number */
3100 /* [1] is the white player's name */
3101 /* [2] is the black player's name */
3102 /* For end-of-game: */
3103 /* [3] is the reason for the game end */
3104 /* [4] is a PGN end game-token, preceded by " " */
3105 /* For start-of-game: */
3106 /* [3] begins with "Creating" or "Continuing" */
3107 /* [4] is " *" or empty (don't care). */
3108 int gamenum = atoi(star_match[0]);
3109 char *whitename, *blackname, *why, *endtoken;
3110 ChessMove endtype = (ChessMove) 0;
3113 whitename = star_match[1];
3114 blackname = star_match[2];
3115 why = star_match[3];
3116 endtoken = star_match[4];
3118 whitename = star_match[1];
3119 blackname = star_match[3];
3120 why = star_match[5];
3121 endtoken = star_match[6];
3124 /* Game start messages */
3125 if (strncmp(why, "Creating ", 9) == 0 ||
3126 strncmp(why, "Continuing ", 11) == 0) {
3127 gs_gamenum = gamenum;
3128 strcpy(gs_kind, strchr(why, ' ') + 1);
3130 if (appData.zippyPlay) {
3131 ZippyGameStart(whitename, blackname);
3137 /* Game end messages */
3138 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139 ics_gamenum != gamenum) {
3142 while (endtoken[0] == ' ') endtoken++;
3143 switch (endtoken[0]) {
3146 endtype = GameUnfinished;
3149 endtype = BlackWins;
3152 if (endtoken[1] == '/')
3153 endtype = GameIsDrawn;
3155 endtype = WhiteWins;
3158 GameEnds(endtype, why, GE_ICS);
3160 if (appData.zippyPlay && first.initDone) {
3161 ZippyGameEnd(endtype, why);
3162 if (first.pr == NULL) {
3163 /* Start the next process early so that we'll
3164 be ready for the next challenge */
3165 StartChessProgram(&first);
3167 /* Send "new" early, in case this command takes
3168 a long time to finish, so that we'll be ready
3169 for the next challenge. */
3170 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177 if (looking_at(buf, &i, "Removing game * from observation") ||
3178 looking_at(buf, &i, "no longer observing game *") ||
3179 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180 if (gameMode == IcsObserving &&
3181 atoi(star_match[0]) == ics_gamenum)
3183 /* icsEngineAnalyze */
3184 if (appData.icsEngineAnalyze) {
3191 ics_user_moved = FALSE;
3196 if (looking_at(buf, &i, "no longer examining game *")) {
3197 if (gameMode == IcsExamining &&
3198 atoi(star_match[0]) == ics_gamenum)
3202 ics_user_moved = FALSE;
3207 /* Advance leftover_start past any newlines we find,
3208 so only partial lines can get reparsed */
3209 if (looking_at(buf, &i, "\n")) {
3210 prevColor = curColor;
3211 if (curColor != ColorNormal) {
3212 if (oldi > next_out) {
3213 SendToPlayer(&buf[next_out], oldi - next_out);
3216 Colorize(ColorNormal, FALSE);
3217 curColor = ColorNormal;
3219 if (started == STARTED_BOARD) {
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 ParseBoard12(parse);
3225 /* Send premove here */
3226 if (appData.premove) {
3228 if (currentMove == 0 &&
3229 gameMode == IcsPlayingWhite &&
3230 appData.premoveWhite) {
3231 sprintf(str, "%s%s\n", ics_prefix,
3232 appData.premoveWhiteText);
3233 if (appData.debugMode)
3234 fprintf(debugFP, "Sending premove:\n");
3236 } else if (currentMove == 1 &&
3237 gameMode == IcsPlayingBlack &&
3238 appData.premoveBlack) {
3239 sprintf(str, "%s%s\n", ics_prefix,
3240 appData.premoveBlackText);
3241 if (appData.debugMode)
3242 fprintf(debugFP, "Sending premove:\n");
3244 } else if (gotPremove) {
3246 ClearPremoveHighlights();
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3249 UserMoveEvent(premoveFromX, premoveFromY,
3250 premoveToX, premoveToY,
3255 /* Usually suppress following prompt */
3256 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3262 } else if (started == STARTED_HOLDINGS) {
3264 char new_piece[MSG_SIZ];
3265 started = STARTED_NONE;
3266 parse[parse_pos] = NULLCHAR;
3267 if (appData.debugMode)
3268 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269 parse, currentMove);
3270 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271 gamenum == ics_gamenum) {
3272 if (gameInfo.variant == VariantNormal) {
3273 /* [HGM] We seem to switch variant during a game!
3274 * Presumably no holdings were displayed, so we have
3275 * to move the position two files to the right to
3276 * create room for them!
3278 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279 /* Get a move list just to see the header, which
3280 will tell us whether this is really bug or zh */
3281 if (ics_getting_history == H_FALSE) {
3282 ics_getting_history = H_REQUESTED;
3283 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3287 new_piece[0] = NULLCHAR;
3288 sscanf(parse, "game %d white [%s black [%s <- %s",
3289 &gamenum, white_holding, black_holding,
3291 white_holding[strlen(white_holding)-1] = NULLCHAR;
3292 black_holding[strlen(black_holding)-1] = NULLCHAR;
3293 /* [HGM] copy holdings to board holdings area */
3294 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3297 if (appData.zippyPlay && first.initDone) {
3298 ZippyHoldings(white_holding, black_holding,
3302 if (tinyLayout || smallLayout) {
3303 char wh[16], bh[16];
3304 PackHolding(wh, white_holding);
3305 PackHolding(bh, black_holding);
3306 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307 gameInfo.white, gameInfo.black);
3309 sprintf(str, "%s [%s] vs. %s [%s]",
3310 gameInfo.white, white_holding,
3311 gameInfo.black, black_holding);
3314 DrawPosition(FALSE, boards[currentMove]);
3317 /* Suppress following prompt */
3318 if (looking_at(buf, &i, "*% ")) {
3319 savingComment = FALSE;
3326 i++; /* skip unparsed character and loop back */
3329 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330 started != STARTED_HOLDINGS && i > next_out) {
3331 SendToPlayer(&buf[next_out], i - next_out);
3334 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3336 leftover_len = buf_len - leftover_start;
3337 /* if buffer ends with something we couldn't parse,
3338 reparse it after appending the next read */
3340 } else if (count == 0) {
3341 RemoveInputSource(isr);
3342 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3344 DisplayFatalError(_("Error reading from ICS"), error, 1);
3349 /* Board style 12 looks like this:
3351 <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
3353 * The "<12> " is stripped before it gets to this routine. The two
3354 * trailing 0's (flip state and clock ticking) are later addition, and
3355 * some chess servers may not have them, or may have only the first.
3356 * Additional trailing fields may be added in the future.
3359 #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"
3361 #define RELATION_OBSERVING_PLAYED 0
3362 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE 1
3364 #define RELATION_PLAYING_NOTMYMOVE -1
3365 #define RELATION_EXAMINING 2
3366 #define RELATION_ISOLATED_BOARD -3
3367 #define RELATION_STARTING_POSITION -4 /* FICS only */
3370 ParseBoard12(string)
3373 GameMode newGameMode;
3374 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377 char to_play, board_chars[200];
3378 char move_str[500], str[500], elapsed_time[500];
3379 char black[32], white[32];
3381 int prevMove = currentMove;
3384 int fromX, fromY, toX, toY;
3386 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387 char *bookHit = NULL; // [HGM] book
3389 fromX = fromY = toX = toY = -1;
3393 if (appData.debugMode)
3394 fprintf(debugFP, _("Parsing board: %s\n"), string);
3396 move_str[0] = NULLCHAR;
3397 elapsed_time[0] = NULLCHAR;
3398 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3400 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401 if(string[i] == ' ') { ranks++; files = 0; }
3405 for(j = 0; j <i; j++) board_chars[j] = string[j];
3406 board_chars[i] = '\0';
3409 n = sscanf(string, PATTERN, &to_play, &double_push,
3410 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411 &gamenum, white, black, &relation, &basetime, &increment,
3412 &white_stren, &black_stren, &white_time, &black_time,
3413 &moveNum, str, elapsed_time, move_str, &ics_flip,
3417 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418 DisplayError(str, 0);
3422 /* Convert the move number to internal form */
3423 moveNum = (moveNum - 1) * 2;
3424 if (to_play == 'B') moveNum++;
3425 if (moveNum >= MAX_MOVES) {
3426 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3432 case RELATION_OBSERVING_PLAYED:
3433 case RELATION_OBSERVING_STATIC:
3434 if (gamenum == -1) {
3435 /* Old ICC buglet */
3436 relation = RELATION_OBSERVING_STATIC;
3438 newGameMode = IcsObserving;
3440 case RELATION_PLAYING_MYMOVE:
3441 case RELATION_PLAYING_NOTMYMOVE:
3443 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444 IcsPlayingWhite : IcsPlayingBlack;
3446 case RELATION_EXAMINING:
3447 newGameMode = IcsExamining;
3449 case RELATION_ISOLATED_BOARD:
3451 /* Just display this board. If user was doing something else,
3452 we will forget about it until the next board comes. */
3453 newGameMode = IcsIdle;
3455 case RELATION_STARTING_POSITION:
3456 newGameMode = gameMode;
3460 /* Modify behavior for initial board display on move listing
3463 switch (ics_getting_history) {
3467 case H_GOT_REQ_HEADER:
3468 case H_GOT_UNREQ_HEADER:
3469 /* This is the initial position of the current game */
3470 gamenum = ics_gamenum;
3471 moveNum = 0; /* old ICS bug workaround */
3472 if (to_play == 'B') {
3473 startedFromSetupPosition = TRUE;
3474 blackPlaysFirst = TRUE;
3476 if (forwardMostMove == 0) forwardMostMove = 1;
3477 if (backwardMostMove == 0) backwardMostMove = 1;
3478 if (currentMove == 0) currentMove = 1;
3480 newGameMode = gameMode;
3481 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3483 case H_GOT_UNWANTED_HEADER:
3484 /* This is an initial board that we don't want */
3486 case H_GETTING_MOVES:
3487 /* Should not happen */
3488 DisplayError(_("Error gathering move list: extra board"), 0);
3489 ics_getting_history = H_FALSE;
3493 /* Take action if this is the first board of a new game, or of a
3494 different game than is currently being displayed. */
3495 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496 relation == RELATION_ISOLATED_BOARD) {
3498 /* Forget the old game and get the history (if any) of the new one */
3499 if (gameMode != BeginningOfGame) {
3503 if (appData.autoRaiseBoard) BoardToTop();
3505 if (gamenum == -1) {
3506 newGameMode = IcsIdle;
3507 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508 appData.getMoveList) {
3509 /* Need to get game history */
3510 ics_getting_history = H_REQUESTED;
3511 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3515 /* Initially flip the board to have black on the bottom if playing
3516 black or if the ICS flip flag is set, but let the user change
3517 it with the Flip View button. */
3518 flipView = appData.autoFlipView ?
3519 (newGameMode == IcsPlayingBlack) || ics_flip :
3522 /* Done with values from previous mode; copy in new ones */
3523 gameMode = newGameMode;
3525 ics_gamenum = gamenum;
3526 if (gamenum == gs_gamenum) {
3527 int klen = strlen(gs_kind);
3528 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529 sprintf(str, "ICS %s", gs_kind);
3530 gameInfo.event = StrSave(str);
3532 gameInfo.event = StrSave("ICS game");
3534 gameInfo.site = StrSave(appData.icsHost);
3535 gameInfo.date = PGNDate();
3536 gameInfo.round = StrSave("-");
3537 gameInfo.white = StrSave(white);
3538 gameInfo.black = StrSave(black);
3539 timeControl = basetime * 60 * 1000;
3541 timeIncrement = increment * 1000;
3542 movesPerSession = 0;
3543 gameInfo.timeControl = TimeControlTagValue();
3544 VariantSwitch(board, StringToVariant(gameInfo.event) );
3545 if (appData.debugMode) {
3546 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548 setbuf(debugFP, NULL);
3551 gameInfo.outOfBook = NULL;
3553 /* Do we have the ratings? */
3554 if (strcmp(player1Name, white) == 0 &&
3555 strcmp(player2Name, black) == 0) {
3556 if (appData.debugMode)
3557 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558 player1Rating, player2Rating);
3559 gameInfo.whiteRating = player1Rating;
3560 gameInfo.blackRating = player2Rating;
3561 } else if (strcmp(player2Name, white) == 0 &&
3562 strcmp(player1Name, black) == 0) {
3563 if (appData.debugMode)
3564 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565 player2Rating, player1Rating);
3566 gameInfo.whiteRating = player2Rating;
3567 gameInfo.blackRating = player1Rating;
3569 player1Name[0] = player2Name[0] = NULLCHAR;
3571 /* Silence shouts if requested */
3572 if (appData.quietPlay &&
3573 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574 SendToICS(ics_prefix);
3575 SendToICS("set shout 0\n");
3579 /* Deal with midgame name changes */
3581 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582 if (gameInfo.white) free(gameInfo.white);
3583 gameInfo.white = StrSave(white);
3585 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586 if (gameInfo.black) free(gameInfo.black);
3587 gameInfo.black = StrSave(black);
3591 /* Throw away game result if anything actually changes in examine mode */
3592 if (gameMode == IcsExamining && !newGame) {
3593 gameInfo.result = GameUnfinished;
3594 if (gameInfo.resultDetails != NULL) {
3595 free(gameInfo.resultDetails);
3596 gameInfo.resultDetails = NULL;
3600 /* In pausing && IcsExamining mode, we ignore boards coming
3601 in if they are in a different variation than we are. */
3602 if (pauseExamInvalid) return;
3603 if (pausing && gameMode == IcsExamining) {
3604 if (moveNum <= pauseExamForwardMostMove) {
3605 pauseExamInvalid = TRUE;
3606 forwardMostMove = pauseExamForwardMostMove;
3611 if (appData.debugMode) {
3612 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3614 /* Parse the board */
3615 for (k = 0; k < ranks; k++) {
3616 for (j = 0; j < files; j++)
3617 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618 if(gameInfo.holdingsWidth > 1) {
3619 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3623 CopyBoard(boards[moveNum], board);
3625 startedFromSetupPosition =
3626 !CompareBoards(board, initialPosition);
3627 if(startedFromSetupPosition)
3628 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3631 /* [HGM] Set castling rights. Take the outermost Rooks,
3632 to make it also work for FRC opening positions. Note that board12
3633 is really defective for later FRC positions, as it has no way to
3634 indicate which Rook can castle if they are on the same side of King.
3635 For the initial position we grant rights to the outermost Rooks,
3636 and remember thos rights, and we then copy them on positions
3637 later in an FRC game. This means WB might not recognize castlings with
3638 Rooks that have moved back to their original position as illegal,
3639 but in ICS mode that is not its job anyway.
3641 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3644 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645 if(board[0][i] == WhiteRook) j = i;
3646 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648 if(board[0][i] == WhiteRook) j = i;
3649 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3657 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661 if(board[BOARD_HEIGHT-1][k] == bKing)
3662 initialRights[5] = castlingRights[moveNum][5] = k;
3664 r = castlingRights[moveNum][0] = initialRights[0];
3665 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666 r = castlingRights[moveNum][1] = initialRights[1];
3667 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668 r = castlingRights[moveNum][3] = initialRights[3];
3669 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670 r = castlingRights[moveNum][4] = initialRights[4];
3671 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672 /* wildcastle kludge: always assume King has rights */
3673 r = castlingRights[moveNum][2] = initialRights[2];
3674 r = castlingRights[moveNum][5] = initialRights[5];
3676 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3680 if (ics_getting_history == H_GOT_REQ_HEADER ||
3681 ics_getting_history == H_GOT_UNREQ_HEADER) {
3682 /* This was an initial position from a move list, not
3683 the current position */
3687 /* Update currentMove and known move number limits */
3688 newMove = newGame || moveNum > forwardMostMove;
3690 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692 takeback = forwardMostMove - moveNum;
3693 for (i = 0; i < takeback; i++) {
3694 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695 SendToProgram("undo\n", &first);
3700 forwardMostMove = backwardMostMove = currentMove = moveNum;
3701 if (gameMode == IcsExamining && moveNum == 0) {
3702 /* Workaround for ICS limitation: we are not told the wild
3703 type when starting to examine a game. But if we ask for
3704 the move list, the move list header will tell us */
3705 ics_getting_history = H_REQUESTED;
3706 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3709 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711 forwardMostMove = moveNum;
3712 if (!pausing || currentMove > forwardMostMove)
3713 currentMove = forwardMostMove;
3715 /* New part of history that is not contiguous with old part */
3716 if (pausing && gameMode == IcsExamining) {
3717 pauseExamInvalid = TRUE;
3718 forwardMostMove = pauseExamForwardMostMove;
3721 forwardMostMove = backwardMostMove = currentMove = moveNum;
3722 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723 ics_getting_history = H_REQUESTED;
3724 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3729 /* Update the clocks */
3730 if (strchr(elapsed_time, '.')) {
3732 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3735 /* Time is in seconds */
3736 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3742 if (appData.zippyPlay && newGame &&
3743 gameMode != IcsObserving && gameMode != IcsIdle &&
3744 gameMode != IcsExamining)
3745 ZippyFirstBoard(moveNum, basetime, increment);
3748 /* Put the move on the move list, first converting
3749 to canonical algebraic form. */
3751 if (appData.debugMode) {
3752 if (appData.debugMode) { int f = forwardMostMove;
3753 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3756 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757 fprintf(debugFP, "moveNum = %d\n", moveNum);
3758 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759 setbuf(debugFP, NULL);
3761 if (moveNum <= backwardMostMove) {
3762 /* We don't know what the board looked like before
3764 strcpy(parseList[moveNum - 1], move_str);
3765 strcat(parseList[moveNum - 1], " ");
3766 strcat(parseList[moveNum - 1], elapsed_time);
3767 moveList[moveNum - 1][0] = NULLCHAR;
3768 } else if (strcmp(move_str, "none") == 0) {
3769 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770 /* Again, we don't know what the board looked like;
3771 this is really the start of the game. */
3772 parseList[moveNum - 1][0] = NULLCHAR;
3773 moveList[moveNum - 1][0] = NULLCHAR;
3774 backwardMostMove = moveNum;
3775 startedFromSetupPosition = TRUE;
3776 fromX = fromY = toX = toY = -1;
3778 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3779 // So we parse the long-algebraic move string in stead of the SAN move
3780 int valid; char buf[MSG_SIZ], *prom;
3782 // str looks something like "Q/a1-a2"; kill the slash
3784 sprintf(buf, "%c%s", str[0], str+2);
3785 else strcpy(buf, str); // might be castling
3786 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3787 strcat(buf, prom); // long move lacks promo specification!
3788 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789 if(appData.debugMode)
3790 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791 strcpy(move_str, buf);
3793 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794 &fromX, &fromY, &toX, &toY, &promoChar)
3795 || ParseOneMove(buf, moveNum - 1, &moveType,
3796 &fromX, &fromY, &toX, &toY, &promoChar);
3797 // end of long SAN patch
3799 (void) CoordsToAlgebraic(boards[moveNum - 1],
3800 PosFlags(moveNum - 1), EP_UNKNOWN,
3801 fromY, fromX, toY, toX, promoChar,
3802 parseList[moveNum-1]);
3803 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804 castlingRights[moveNum]) ) {
3810 if(gameInfo.variant != VariantShogi)
3811 strcat(parseList[moveNum - 1], "+");
3814 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815 strcat(parseList[moveNum - 1], "#");
3818 strcat(parseList[moveNum - 1], " ");
3819 strcat(parseList[moveNum - 1], elapsed_time);
3820 /* currentMoveString is set as a side-effect of ParseOneMove */
3821 strcpy(moveList[moveNum - 1], currentMoveString);
3822 strcat(moveList[moveNum - 1], "\n");
3824 /* Move from ICS was illegal!? Punt. */
3825 if (appData.debugMode) {
3826 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3830 if (appData.testLegality && appData.debugMode) {
3831 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832 DisplayError(str, 0);
3835 strcpy(parseList[moveNum - 1], move_str);
3836 strcat(parseList[moveNum - 1], " ");
3837 strcat(parseList[moveNum - 1], elapsed_time);
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 fromX = fromY = toX = toY = -1;
3842 if (appData.debugMode) {
3843 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844 setbuf(debugFP, NULL);
3848 /* Send move to chess program (BEFORE animating it). */
3849 if (appData.zippyPlay && !newGame && newMove &&
3850 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3852 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3857 DisplayError(str, 0);
3859 if (first.sendTime) {
3860 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3862 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863 if (firstMove && !bookHit) {
3865 if (first.useColors) {
3866 SendToProgram(gameMode == IcsPlayingWhite ?
3868 "black\ngo\n", &first);
3870 SendToProgram("go\n", &first);
3872 first.maybeThinking = TRUE;
3875 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876 if (moveList[moveNum - 1][0] == NULLCHAR) {
3877 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878 DisplayError(str, 0);
3880 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881 SendMoveToProgram(moveNum - 1, &first);
3888 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889 /* If move comes from a remote source, animate it. If it
3890 isn't remote, it will have already been animated. */
3891 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3894 if (!pausing && appData.highlightLastMove) {
3895 SetHighlights(fromX, fromY, toX, toY);
3899 /* Start the clocks */
3900 whiteFlag = blackFlag = FALSE;
3901 appData.clockMode = !(basetime == 0 && increment == 0);
3903 ics_clock_paused = TRUE;
3905 } else if (ticking == 1) {
3906 ics_clock_paused = FALSE;
3908 if (gameMode == IcsIdle ||
3909 relation == RELATION_OBSERVING_STATIC ||
3910 relation == RELATION_EXAMINING ||
3912 DisplayBothClocks();
3916 /* Display opponents and material strengths */
3917 if (gameInfo.variant != VariantBughouse &&
3918 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919 if (tinyLayout || smallLayout) {
3920 if(gameInfo.variant == VariantNormal)
3921 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3922 gameInfo.white, white_stren, gameInfo.black, black_stren,
3923 basetime, increment);
3925 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3926 gameInfo.white, white_stren, gameInfo.black, black_stren,
3927 basetime, increment, (int) gameInfo.variant);
3929 if(gameInfo.variant == VariantNormal)
3930 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3931 gameInfo.white, white_stren, gameInfo.black, black_stren,
3932 basetime, increment);
3934 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3935 gameInfo.white, white_stren, gameInfo.black, black_stren,
3936 basetime, increment, VariantName(gameInfo.variant));
3939 if (appData.debugMode) {
3940 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3945 /* Display the board */
3946 if (!pausing && !appData.noGUI) {
3948 if (appData.premove)
3950 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952 ClearPremoveHighlights();
3954 DrawPosition(FALSE, boards[currentMove]);
3955 DisplayMove(moveNum - 1);
3956 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3959 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3963 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3965 if(bookHit) { // [HGM] book: simulate book reply
3966 static char bookMove[MSG_SIZ]; // a bit generous?
3968 programStats.nodes = programStats.depth = programStats.time =
3969 programStats.score = programStats.got_only_move = 0;
3970 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3972 strcpy(bookMove, "move ");
3973 strcat(bookMove, bookHit);
3974 HandleMachineMove(bookMove, &first);
3983 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984 ics_getting_history = H_REQUESTED;
3985 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3991 AnalysisPeriodicEvent(force)
3994 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995 && !force) || !appData.periodicUpdates)
3998 /* Send . command to Crafty to collect stats */
3999 SendToProgram(".\n", &first);
4001 /* Don't send another until we get a response (this makes
4002 us stop sending to old Crafty's which don't understand
4003 the "." command (sending illegal cmds resets node count & time,
4004 which looks bad)) */
4005 programStats.ok_to_send = 0;
4009 SendMoveToProgram(moveNum, cps)
4011 ChessProgramState *cps;
4015 if (cps->useUsermove) {
4016 SendToProgram("usermove ", cps);
4020 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021 int len = space - parseList[moveNum];
4022 memcpy(buf, parseList[moveNum], len);
4024 buf[len] = NULLCHAR;
4026 sprintf(buf, "%s\n", parseList[moveNum]);
4028 SendToProgram(buf, cps);
4030 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031 AlphaRank(moveList[moveNum], 4);
4032 SendToProgram(moveList[moveNum], cps);
4033 AlphaRank(moveList[moveNum], 4); // and back
4035 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036 * the engine. It would be nice to have a better way to identify castle
4038 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039 && cps->useOOCastle) {
4040 int fromX = moveList[moveNum][0] - AAA;
4041 int fromY = moveList[moveNum][1] - ONE;
4042 int toX = moveList[moveNum][2] - AAA;
4043 int toY = moveList[moveNum][3] - ONE;
4044 if((boards[moveNum][fromY][fromX] == WhiteKing
4045 && boards[moveNum][toY][toX] == WhiteRook)
4046 || (boards[moveNum][fromY][fromX] == BlackKing
4047 && boards[moveNum][toY][toX] == BlackRook)) {
4048 if(toX > fromX) SendToProgram("O-O\n", cps);
4049 else SendToProgram("O-O-O\n", cps);
4051 else SendToProgram(moveList[moveNum], cps);
4053 else SendToProgram(moveList[moveNum], cps);
4054 /* End of additions by Tord */
4057 /* [HGM] setting up the opening has brought engine in force mode! */
4058 /* Send 'go' if we are in a mode where machine should play. */
4059 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060 (gameMode == TwoMachinesPlay ||
4062 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4064 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065 SendToProgram("go\n", cps);
4066 if (appData.debugMode) {
4067 fprintf(debugFP, "(extra)\n");
4070 setboardSpoiledMachineBlack = 0;
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4076 int fromX, fromY, toX, toY;
4078 char user_move[MSG_SIZ];
4082 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083 (int)moveType, fromX, fromY, toX, toY);
4084 DisplayError(user_move + strlen("say "), 0);
4086 case WhiteKingSideCastle:
4087 case BlackKingSideCastle:
4088 case WhiteQueenSideCastleWild:
4089 case BlackQueenSideCastleWild:
4091 case WhiteHSideCastleFR:
4092 case BlackHSideCastleFR:
4094 sprintf(user_move, "o-o\n");
4096 case WhiteQueenSideCastle:
4097 case BlackQueenSideCastle:
4098 case WhiteKingSideCastleWild:
4099 case BlackKingSideCastleWild:
4101 case WhiteASideCastleFR:
4102 case BlackASideCastleFR:
4104 sprintf(user_move, "o-o-o\n");
4106 case WhitePromotionQueen:
4107 case BlackPromotionQueen:
4108 case WhitePromotionRook:
4109 case BlackPromotionRook:
4110 case WhitePromotionBishop:
4111 case BlackPromotionBishop:
4112 case WhitePromotionKnight:
4113 case BlackPromotionKnight:
4114 case WhitePromotionKing:
4115 case BlackPromotionKing:
4116 case WhitePromotionChancellor:
4117 case BlackPromotionChancellor:
4118 case WhitePromotionArchbishop:
4119 case BlackPromotionArchbishop:
4120 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121 sprintf(user_move, "%c%c%c%c=%c\n",
4122 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123 PieceToChar(WhiteFerz));
4124 else if(gameInfo.variant == VariantGreat)
4125 sprintf(user_move, "%c%c%c%c=%c\n",
4126 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127 PieceToChar(WhiteMan));
4129 sprintf(user_move, "%c%c%c%c=%c\n",
4130 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131 PieceToChar(PromoPiece(moveType)));
4135 sprintf(user_move, "%c@%c%c\n",
4136 ToUpper(PieceToChar((ChessSquare) fromX)),
4137 AAA + toX, ONE + toY);
4140 case WhiteCapturesEnPassant:
4141 case BlackCapturesEnPassant:
4142 case IllegalMove: /* could be a variant we don't quite understand */
4143 sprintf(user_move, "%c%c%c%c\n",
4144 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4147 SendToICS(user_move);
4148 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4158 if (rf == DROP_RANK) {
4159 sprintf(move, "%c@%c%c\n",
4160 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4162 if (promoChar == 'x' || promoChar == NULLCHAR) {
4163 sprintf(move, "%c%c%c%c\n",
4164 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4166 sprintf(move, "%c%c%c%c%c\n",
4167 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4173 ProcessICSInitScript(f)
4178 while (fgets(buf, MSG_SIZ, f)) {
4179 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4188 AlphaRank(char *move, int n)
4190 // char *p = move, c; int x, y;
4192 if (appData.debugMode) {
4193 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4197 move[2]>='0' && move[2]<='9' &&
4198 move[3]>='a' && move[3]<='x' ) {
4200 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4201 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4203 if(move[0]>='0' && move[0]<='9' &&
4204 move[1]>='a' && move[1]<='x' &&
4205 move[2]>='0' && move[2]<='9' &&
4206 move[3]>='a' && move[3]<='x' ) {
4207 /* input move, Shogi -> normal */
4208 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4209 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4211 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4214 move[3]>='0' && move[3]<='9' &&
4215 move[2]>='a' && move[2]<='x' ) {
4217 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4221 move[0]>='a' && move[0]<='x' &&
4222 move[3]>='0' && move[3]<='9' &&
4223 move[2]>='a' && move[2]<='x' ) {
4224 /* output move, normal -> Shogi */
4225 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4231 if (appData.debugMode) {
4232 fprintf(debugFP, " out = '%s'\n", move);
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4241 ChessMove *moveType;
4242 int *fromX, *fromY, *toX, *toY;
4245 if (appData.debugMode) {
4246 fprintf(debugFP, "move to parse: %s\n", move);
4248 *moveType = yylexstr(moveNum, move);
4250 switch (*moveType) {
4251 case WhitePromotionChancellor:
4252 case BlackPromotionChancellor:
4253 case WhitePromotionArchbishop:
4254 case BlackPromotionArchbishop:
4255 case WhitePromotionQueen:
4256 case BlackPromotionQueen:
4257 case WhitePromotionRook:
4258 case BlackPromotionRook:
4259 case WhitePromotionBishop:
4260 case BlackPromotionBishop:
4261 case WhitePromotionKnight:
4262 case BlackPromotionKnight:
4263 case WhitePromotionKing:
4264 case BlackPromotionKing:
4266 case WhiteCapturesEnPassant:
4267 case BlackCapturesEnPassant:
4268 case WhiteKingSideCastle:
4269 case WhiteQueenSideCastle:
4270 case BlackKingSideCastle:
4271 case BlackQueenSideCastle:
4272 case WhiteKingSideCastleWild:
4273 case WhiteQueenSideCastleWild:
4274 case BlackKingSideCastleWild:
4275 case BlackQueenSideCastleWild:
4276 /* Code added by Tord: */
4277 case WhiteHSideCastleFR:
4278 case WhiteASideCastleFR:
4279 case BlackHSideCastleFR:
4280 case BlackASideCastleFR:
4281 /* End of code added by Tord */
4282 case IllegalMove: /* bug or odd chess variant */
4283 *fromX = currentMoveString[0] - AAA;
4284 *fromY = currentMoveString[1] - ONE;
4285 *toX = currentMoveString[2] - AAA;
4286 *toY = currentMoveString[3] - ONE;
4287 *promoChar = currentMoveString[4];
4288 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290 if (appData.debugMode) {
4291 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4293 *fromX = *fromY = *toX = *toY = 0;
4296 if (appData.testLegality) {
4297 return (*moveType != IllegalMove);
4299 return !(fromX == fromY && toX == toY);
4304 *fromX = *moveType == WhiteDrop ?
4305 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306 (int) CharToPiece(ToLower(currentMoveString[0]));
4308 *toX = currentMoveString[2] - AAA;
4309 *toY = currentMoveString[3] - ONE;
4310 *promoChar = NULLCHAR;
4314 case ImpossibleMove:
4315 case (ChessMove) 0: /* end of file */
4324 if (appData.debugMode) {
4325 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4328 *fromX = *fromY = *toX = *toY = 0;
4329 *promoChar = NULLCHAR;
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4345 void GetPositionNumber()
4346 { // sets global variable seed
4349 seed = appData.defaultFrcPosition;
4350 if(seed < 0) { // randomize based on time for negative FRC position numbers
4351 for(i=0; i<50; i++) seed += random();
4352 seed = random() ^ random() >> 8 ^ random() << 8;
4353 if(seed<0) seed = -seed;
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4362 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364 board[rank][i] = (ChessSquare) pieceType;
4365 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4367 piecesLeft[pieceType]--;
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4380 i = seed % squaresLeft[shade];
4381 nrOfShuffles *= squaresLeft[shade];
4382 seed /= squaresLeft[shade];
4383 put(board, pieceType, rank, i, shade);
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4389 int i, n=squaresLeft[ANY], j=n-1, k;
4391 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392 i = seed % k; // pick one
4395 while(i >= j) i -= j--;
4396 j = n - 1 - j; i += j;
4397 put(board, pieceType, rank, j, ANY);
4398 put(board, pieceType, rank, i, ANY);
4401 void SetUpShuffle(Board board, int number)
4405 GetPositionNumber(); nrOfShuffles = 1;
4407 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4409 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4411 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4413 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414 p = (int) board[0][i];
4415 if(p < (int) BlackPawn) piecesLeft[p] ++;
4416 board[0][i] = EmptySquare;
4419 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420 // shuffles restricted to allow normal castling put KRR first
4421 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428 put(board, WhiteRook, 0, 0, ANY);
4429 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4432 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436 while(piecesLeft[p] >= 2) {
4437 AddOnePiece(board, p, 0, LITE);
4438 AddOnePiece(board, p, 0, DARK);
4440 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4443 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445 // but we leave King and Rooks for last, to possibly obey FRC restriction
4446 if(p == (int)WhiteRook) continue;
4447 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4451 // now everything is placed, except perhaps King (Unicorn) and Rooks
4453 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454 // Last King gets castling rights
4455 while(piecesLeft[(int)WhiteUnicorn]) {
4456 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4460 while(piecesLeft[(int)WhiteKing]) {
4461 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4467 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4468 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4471 // Only Rooks can be left; simply place them all
4472 while(piecesLeft[(int)WhiteRook]) {
4473 i = put(board, WhiteRook, 0, 0, ANY);
4474 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4477 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4479 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4482 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4486 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /* Basically a safe strcpy that uses the last character as King */
4493 int result = FALSE; int NrPieces;
4495 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4496 && NrPieces >= 12 && !(NrPieces&1)) {
4497 int i; /* [HGM] Accept even length from 12 to 34 */
4499 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500 for( i=0; i<NrPieces/2-1; i++ ) {
4502 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4504 table[(int) WhiteKing] = map[NrPieces/2-1];
4505 table[(int) BlackKing] = map[NrPieces-1];
4513 void Prelude(Board board)
4514 { // [HGM] superchess: random selection of exo-pieces
4515 int i, j, k; ChessSquare p;
4516 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4518 GetPositionNumber(); // use FRC position number
4520 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521 SetCharTable(pieceToChar, appData.pieceToCharTable);
4522 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4523 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4526 j = seed%4; seed /= 4;
4527 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4529 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4530 j = seed%3 + (seed%3 >= j); seed /= 3;
4531 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4533 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4534 j = seed%3; seed /= 3;
4535 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4537 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4538 j = seed%2 + (seed%2 >= j); seed /= 2;
4539 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4541 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4542 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4543 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4544 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545 put(board, exoPieces[0], 0, 0, ANY);
4546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4550 InitPosition(redraw)
4553 ChessSquare (* pieces)[BOARD_SIZE];
4554 int i, j, pawnRow, overrule,
4555 oldx = gameInfo.boardWidth,
4556 oldy = gameInfo.boardHeight,
4557 oldh = gameInfo.holdingsWidth,
4558 oldv = gameInfo.variant;
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;
5054 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5055 int fromX, fromY, toX, toY;
5060 ChessSquare pdown, pup;
5062 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064 /* [HGM] suppress all moves into holdings area and guard band */
5065 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5066 return ImpossibleMove;
5068 /* [HGM] <sameColor> moved to here from winboard.c */
5069 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5070 pdown = boards[currentMove][fromY][fromX];
5071 pup = boards[currentMove][toY][toX];
5072 if ( gameMode != EditPosition && !captureOwn &&
5073 (WhitePawn <= pdown && pdown < BlackPawn &&
5074 WhitePawn <= pup && pup < BlackPawn ||
5075 BlackPawn <= pdown && pdown < EmptySquare &&
5076 BlackPawn <= pup && pup < EmptySquare
5077 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5078 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5079 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5080 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5081 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5085 /* Check if the user is playing in turn. This is complicated because we
5086 let the user "pick up" a piece before it is his turn. So the piece he
5087 tried to pick up may have been captured by the time he puts it down!
5088 Therefore we use the color the user is supposed to be playing in this
5089 test, not the color of the piece that is currently on the starting
5090 square---except in EditGame mode, where the user is playing both
5091 sides; fortunately there the capture race can't happen. (It can
5092 now happen in IcsExamining mode, but that's just too bad. The user
5093 will get a somewhat confusing message in that case.)
5097 case PlayFromGameFile:
5099 case TwoMachinesPlay:
5103 /* We switched into a game mode where moves are not accepted,
5104 perhaps while the mouse button was down. */
5105 return ImpossibleMove;
5107 case MachinePlaysWhite:
5108 /* User is moving for Black */
5109 if (WhiteOnMove(currentMove)) {
5110 DisplayMoveError(_("It is White's turn"));
5111 return ImpossibleMove;
5115 case MachinePlaysBlack:
5116 /* User is moving for White */
5117 if (!WhiteOnMove(currentMove)) {
5118 DisplayMoveError(_("It is Black's turn"));
5119 return ImpossibleMove;
5125 case BeginningOfGame:
5128 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5129 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5130 /* User is moving for Black */
5131 if (WhiteOnMove(currentMove)) {
5132 DisplayMoveError(_("It is White's turn"));
5133 return ImpossibleMove;
5136 /* User is moving for White */
5137 if (!WhiteOnMove(currentMove)) {
5138 DisplayMoveError(_("It is Black's turn"));
5139 return ImpossibleMove;
5144 case IcsPlayingBlack:
5145 /* User is moving for Black */
5146 if (WhiteOnMove(currentMove)) {
5147 if (!appData.premove) {
5148 DisplayMoveError(_("It is White's turn"));
5149 } else if (toX >= 0 && toY >= 0) {
5152 premoveFromX = fromX;
5153 premoveFromY = fromY;
5154 premovePromoChar = promoChar;
5156 if (appData.debugMode)
5157 fprintf(debugFP, "Got premove: fromX %d,"
5158 "fromY %d, toX %d, toY %d\n",
5159 fromX, fromY, toX, toY);
5161 return ImpossibleMove;
5165 case IcsPlayingWhite:
5166 /* User is moving for White */
5167 if (!WhiteOnMove(currentMove)) {
5168 if (!appData.premove) {
5169 DisplayMoveError(_("It is Black's turn"));
5170 } else if (toX >= 0 && toY >= 0) {
5173 premoveFromX = fromX;
5174 premoveFromY = fromY;
5175 premovePromoChar = promoChar;
5177 if (appData.debugMode)
5178 fprintf(debugFP, "Got premove: fromX %d,"
5179 "fromY %d, toX %d, toY %d\n",
5180 fromX, fromY, toX, toY);
5182 return ImpossibleMove;
5190 /* EditPosition, empty square, or different color piece;
5191 click-click move is possible */
5192 if (toX == -2 || toY == -2) {
5193 boards[0][fromY][fromX] = EmptySquare;
5194 return AmbiguousMove;
5195 } else if (toX >= 0 && toY >= 0) {
5196 boards[0][toY][toX] = boards[0][fromY][fromX];
5197 boards[0][fromY][fromX] = EmptySquare;
5198 return AmbiguousMove;
5200 return ImpossibleMove;
5203 /* [HGM] If move started in holdings, it means a drop */
5204 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5205 if( pup != EmptySquare ) return ImpossibleMove;
5206 if(appData.testLegality) {
5207 /* it would be more logical if LegalityTest() also figured out
5208 * which drops are legal. For now we forbid pawns on back rank.
5209 * Shogi is on its own here...
5211 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5212 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5213 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5215 return WhiteDrop; /* Not needed to specify white or black yet */
5218 userOfferedDraw = FALSE;
5220 /* [HGM] always test for legality, to get promotion info */
5221 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5222 epStatus[currentMove], castlingRights[currentMove],
5223 fromY, fromX, toY, toX, promoChar);
5224 /* [HGM] but possibly ignore an IllegalMove result */
5225 if (appData.testLegality) {
5226 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5227 DisplayMoveError(_("Illegal move"));
5228 return ImpossibleMove;
5231 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5233 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5234 function is made into one that returns an OK move type if FinishMove
5235 should be called. This to give the calling driver routine the
5236 opportunity to finish the userMove input with a promotion popup,
5237 without bothering the user with this for invalid or illegal moves */
5239 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5242 /* Common tail of UserMoveEvent and DropMenuEvent */
5244 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5246 int fromX, fromY, toX, toY;
5247 /*char*/int promoChar;
5250 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5251 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5252 // [HGM] superchess: suppress promotions to non-available piece
5253 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5254 if(WhiteOnMove(currentMove)) {
5255 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5257 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5261 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5262 move type in caller when we know the move is a legal promotion */
5263 if(moveType == NormalMove && promoChar)
5264 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5265 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5266 /* [HGM] convert drag-and-drop piece drops to standard form */
5267 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5268 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5269 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5270 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5271 // fromX = boards[currentMove][fromY][fromX];
5272 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5273 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5274 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5275 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5279 /* [HGM] <popupFix> The following if has been moved here from
5280 UserMoveEvent(). Because it seemed to belon here (why not allow
5281 piece drops in training games?), and because it can only be
5282 performed after it is known to what we promote. */
5283 if (gameMode == Training) {
5284 /* compare the move played on the board to the next move in the
5285 * game. If they match, display the move and the opponent's response.
5286 * If they don't match, display an error message.
5289 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5290 CopyBoard(testBoard, boards[currentMove]);
5291 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5293 if (CompareBoards(testBoard, boards[currentMove+1])) {
5294 ForwardInner(currentMove+1);
5296 /* Autoplay the opponent's response.
5297 * if appData.animate was TRUE when Training mode was entered,
5298 * the response will be animated.
5300 saveAnimate = appData.animate;
5301 appData.animate = animateTraining;
5302 ForwardInner(currentMove+1);
5303 appData.animate = saveAnimate;
5305 /* check for the end of the game */
5306 if (currentMove >= forwardMostMove) {
5307 gameMode = PlayFromGameFile;
5309 SetTrainingModeOff();
5310 DisplayInformation(_("End of game"));
5313 DisplayError(_("Incorrect move"), 0);
5318 /* Ok, now we know that the move is good, so we can kill
5319 the previous line in Analysis Mode */
5320 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5321 forwardMostMove = currentMove;
5324 /* If we need the chess program but it's dead, restart it */
5325 ResurrectChessProgram();
5327 /* A user move restarts a paused game*/
5331 thinkOutput[0] = NULLCHAR;
5333 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5335 if (gameMode == BeginningOfGame) {
5336 if (appData.noChessProgram) {
5337 gameMode = EditGame;
5341 gameMode = MachinePlaysBlack;
5344 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5346 if (first.sendName) {
5347 sprintf(buf, "name %s\n", gameInfo.white);
5348 SendToProgram(buf, &first);
5354 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5355 /* Relay move to ICS or chess engine */
5356 if (appData.icsActive) {
5357 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5358 gameMode == IcsExamining) {
5359 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5363 if (first.sendTime && (gameMode == BeginningOfGame ||
5364 gameMode == MachinePlaysWhite ||
5365 gameMode == MachinePlaysBlack)) {
5366 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5368 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5369 // [HGM] book: if program might be playing, let it use book
5370 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5371 first.maybeThinking = TRUE;
5372 } else SendMoveToProgram(forwardMostMove-1, &first);
5373 if (currentMove == cmailOldMove + 1) {
5374 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5378 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5382 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5383 EP_UNKNOWN, castlingRights[currentMove]) ) {
5389 if (WhiteOnMove(currentMove)) {
5390 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5392 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5396 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5401 case MachinePlaysBlack:
5402 case MachinePlaysWhite:
5403 /* disable certain menu options while machine is thinking */
5404 SetMachineThinkingEnables();
5411 if(bookHit) { // [HGM] book: simulate book reply
5412 static char bookMove[MSG_SIZ]; // a bit generous?
5414 programStats.nodes = programStats.depth = programStats.time =
5415 programStats.score = programStats.got_only_move = 0;
5416 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5418 strcpy(bookMove, "move ");
5419 strcat(bookMove, bookHit);
5420 HandleMachineMove(bookMove, &first);
5426 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5427 int fromX, fromY, toX, toY;
5430 /* [HGM] This routine was added to allow calling of its two logical
5431 parts from other modules in the old way. Before, UserMoveEvent()
5432 automatically called FinishMove() if the move was OK, and returned
5433 otherwise. I separated the two, in order to make it possible to
5434 slip a promotion popup in between. But that it always needs two
5435 calls, to the first part, (now called UserMoveTest() ), and to
5436 FinishMove if the first part succeeded. Calls that do not need
5437 to do anything in between, can call this routine the old way.
5439 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5440 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5441 if(moveType == AmbiguousMove)
5442 DrawPosition(FALSE, boards[currentMove]);
5443 else if(moveType != ImpossibleMove && moveType != Comment)
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(appData.forceIllegal) {
6380 // [HGM] illegal: machine refused move; force position after move into it
6381 SendToProgram("force\n", cps);
6382 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6383 // we have a real problem now, as SendBoard will use the a2a3 kludge
6384 // when black is to move, while there might be nothing on a2 or black
6385 // might already have the move. So send the board as if white has the move.
6386 // But first we must change the stm of the engine, as it refused the last move
6387 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6388 if(WhiteOnMove(forwardMostMove)) {
6389 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6390 SendBoard(cps, forwardMostMove); // kludgeless board
6392 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6393 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6394 SendBoard(cps, forwardMostMove+1); // kludgeless board
6396 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6397 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6398 gameMode == TwoMachinesPlay)
6399 SendToProgram("go\n", cps);
6402 if (gameMode == PlayFromGameFile) {
6403 /* Stop reading this game file */
6404 gameMode = EditGame;
6407 currentMove = --forwardMostMove;
6408 DisplayMove(currentMove-1); /* before DisplayMoveError */
6410 DisplayBothClocks();
6411 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6412 parseList[currentMove], cps->which);
6413 DisplayMoveError(buf1);
6414 DrawPosition(FALSE, boards[currentMove]);
6416 /* [HGM] illegal-move claim should forfeit game when Xboard */
6417 /* only passes fully legal moves */
6418 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6419 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6420 "False illegal-move claim", GE_XBOARD );
6424 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6425 /* Program has a broken "time" command that
6426 outputs a string not ending in newline.
6432 * If chess program startup fails, exit with an error message.
6433 * Attempts to recover here are futile.
6435 if ((StrStr(message, "unknown host") != NULL)
6436 || (StrStr(message, "No remote directory") != NULL)
6437 || (StrStr(message, "not found") != NULL)
6438 || (StrStr(message, "No such file") != NULL)
6439 || (StrStr(message, "can't alloc") != NULL)
6440 || (StrStr(message, "Permission denied") != NULL)) {
6442 cps->maybeThinking = FALSE;
6443 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6444 cps->which, cps->program, cps->host, message);
6445 RemoveInputSource(cps->isr);
6446 DisplayFatalError(buf1, 0, 1);
6451 * Look for hint output
6453 if (sscanf(message, "Hint: %s", buf1) == 1) {
6454 if (cps == &first && hintRequested) {
6455 hintRequested = FALSE;
6456 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6457 &fromX, &fromY, &toX, &toY, &promoChar)) {
6458 (void) CoordsToAlgebraic(boards[forwardMostMove],
6459 PosFlags(forwardMostMove), EP_UNKNOWN,
6460 fromY, fromX, toY, toX, promoChar, buf1);
6461 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6462 DisplayInformation(buf2);
6464 /* Hint move could not be parsed!? */
6465 snprintf(buf2, sizeof(buf2),
6466 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6468 DisplayError(buf2, 0);
6471 strcpy(lastHint, buf1);
6477 * Ignore other messages if game is not in progress
6479 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6480 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6483 * look for win, lose, draw, or draw offer
6485 if (strncmp(message, "1-0", 3) == 0) {
6486 char *p, *q, *r = "";
6487 p = strchr(message, '{');
6495 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6497 } else if (strncmp(message, "0-1", 3) == 0) {
6498 char *p, *q, *r = "";
6499 p = strchr(message, '{');
6507 /* Kludge for Arasan 4.1 bug */
6508 if (strcmp(r, "Black resigns") == 0) {
6509 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6512 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6514 } else if (strncmp(message, "1/2", 3) == 0) {
6515 char *p, *q, *r = "";
6516 p = strchr(message, '{');
6525 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6528 } else if (strncmp(message, "White resign", 12) == 0) {
6529 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6531 } else if (strncmp(message, "Black resign", 12) == 0) {
6532 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6534 } else if (strncmp(message, "White matches", 13) == 0 ||
6535 strncmp(message, "Black matches", 13) == 0 ) {
6536 /* [HGM] ignore GNUShogi noises */
6538 } else if (strncmp(message, "White", 5) == 0 &&
6539 message[5] != '(' &&
6540 StrStr(message, "Black") == NULL) {
6541 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6543 } else if (strncmp(message, "Black", 5) == 0 &&
6544 message[5] != '(') {
6545 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6547 } else if (strcmp(message, "resign") == 0 ||
6548 strcmp(message, "computer resigns") == 0) {
6550 case MachinePlaysBlack:
6551 case IcsPlayingBlack:
6552 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6554 case MachinePlaysWhite:
6555 case IcsPlayingWhite:
6556 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6558 case TwoMachinesPlay:
6559 if (cps->twoMachinesColor[0] == 'w')
6560 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6562 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6569 } else if (strncmp(message, "opponent mates", 14) == 0) {
6571 case MachinePlaysBlack:
6572 case IcsPlayingBlack:
6573 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6575 case MachinePlaysWhite:
6576 case IcsPlayingWhite:
6577 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6579 case TwoMachinesPlay:
6580 if (cps->twoMachinesColor[0] == 'w')
6581 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6583 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6590 } else if (strncmp(message, "computer mates", 14) == 0) {
6592 case MachinePlaysBlack:
6593 case IcsPlayingBlack:
6594 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6596 case MachinePlaysWhite:
6597 case IcsPlayingWhite:
6598 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6600 case TwoMachinesPlay:
6601 if (cps->twoMachinesColor[0] == 'w')
6602 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6604 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6611 } else if (strncmp(message, "checkmate", 9) == 0) {
6612 if (WhiteOnMove(forwardMostMove)) {
6613 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6615 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6618 } else if (strstr(message, "Draw") != NULL ||
6619 strstr(message, "game is a draw") != NULL) {
6620 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6622 } else if (strstr(message, "offer") != NULL &&
6623 strstr(message, "draw") != NULL) {
6625 if (appData.zippyPlay && first.initDone) {
6626 /* Relay offer to ICS */
6627 SendToICS(ics_prefix);
6628 SendToICS("draw\n");
6631 cps->offeredDraw = 2; /* valid until this engine moves twice */
6632 if (gameMode == TwoMachinesPlay) {
6633 if (cps->other->offeredDraw) {
6634 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6635 /* [HGM] in two-machine mode we delay relaying draw offer */
6636 /* until after we also have move, to see if it is really claim */
6640 if (cps->other->sendDrawOffers) {
6641 SendToProgram("draw\n", cps->other);
6645 } else if (gameMode == MachinePlaysWhite ||
6646 gameMode == MachinePlaysBlack) {
6647 if (userOfferedDraw) {
6648 DisplayInformation(_("Machine accepts your draw offer"));
6649 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6651 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6658 * Look for thinking output
6660 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6661 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6663 int plylev, mvleft, mvtot, curscore, time;
6664 char mvname[MOVE_LEN];
6668 int prefixHint = FALSE;
6669 mvname[0] = NULLCHAR;
6672 case MachinePlaysBlack:
6673 case IcsPlayingBlack:
6674 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6676 case MachinePlaysWhite:
6677 case IcsPlayingWhite:
6678 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6683 case IcsObserving: /* [DM] icsEngineAnalyze */
6684 if (!appData.icsEngineAnalyze) ignore = TRUE;
6686 case TwoMachinesPlay:
6687 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6698 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6699 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6701 if (plyext != ' ' && plyext != '\t') {
6705 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6706 if( cps->scoreIsAbsolute &&
6707 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6709 curscore = -curscore;
6713 programStats.depth = plylev;
6714 programStats.nodes = nodes;
6715 programStats.time = time;
6716 programStats.score = curscore;
6717 programStats.got_only_move = 0;
6719 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6722 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6723 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6724 if(WhiteOnMove(forwardMostMove))
6725 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6726 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6729 /* Buffer overflow protection */
6730 if (buf1[0] != NULLCHAR) {
6731 if (strlen(buf1) >= sizeof(programStats.movelist)
6732 && appData.debugMode) {
6734 "PV is too long; using the first %d bytes.\n",
6735 sizeof(programStats.movelist) - 1);
6738 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6740 sprintf(programStats.movelist, " no PV\n");
6743 if (programStats.seen_stat) {
6744 programStats.ok_to_send = 1;
6747 if (strchr(programStats.movelist, '(') != NULL) {
6748 programStats.line_is_book = 1;
6749 programStats.nr_moves = 0;
6750 programStats.moves_left = 0;
6752 programStats.line_is_book = 0;
6755 SendProgramStatsToFrontend( cps, &programStats );
6758 [AS] Protect the thinkOutput buffer from overflow... this
6759 is only useful if buf1 hasn't overflowed first!
6761 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6763 (gameMode == TwoMachinesPlay ?
6764 ToUpper(cps->twoMachinesColor[0]) : ' '),
6765 ((double) curscore) / 100.0,
6766 prefixHint ? lastHint : "",
6767 prefixHint ? " " : "" );
6769 if( buf1[0] != NULLCHAR ) {
6770 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6772 if( strlen(buf1) > max_len ) {
6773 if( appData.debugMode) {
6774 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6776 buf1[max_len+1] = '\0';
6779 strcat( thinkOutput, buf1 );
6782 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6783 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6784 DisplayMove(currentMove - 1);
6789 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6790 /* crafty (9.25+) says "(only move) <move>"
6791 * if there is only 1 legal move
6793 sscanf(p, "(only move) %s", buf1);
6794 sprintf(thinkOutput, "%s (only move)", buf1);
6795 sprintf(programStats.movelist, "%s (only move)", buf1);
6796 programStats.depth = 1;
6797 programStats.nr_moves = 1;
6798 programStats.moves_left = 1;
6799 programStats.nodes = 1;
6800 programStats.time = 1;
6801 programStats.got_only_move = 1;
6803 /* Not really, but we also use this member to
6804 mean "line isn't going to change" (Crafty
6805 isn't searching, so stats won't change) */
6806 programStats.line_is_book = 1;
6808 SendProgramStatsToFrontend( cps, &programStats );
6810 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6811 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6812 DisplayMove(currentMove - 1);
6816 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6817 &time, &nodes, &plylev, &mvleft,
6818 &mvtot, mvname) >= 5) {
6819 /* The stat01: line is from Crafty (9.29+) in response
6820 to the "." command */
6821 programStats.seen_stat = 1;
6822 cps->maybeThinking = TRUE;
6824 if (programStats.got_only_move || !appData.periodicUpdates)
6827 programStats.depth = plylev;
6828 programStats.time = time;
6829 programStats.nodes = nodes;
6830 programStats.moves_left = mvleft;
6831 programStats.nr_moves = mvtot;
6832 strcpy(programStats.move_name, mvname);
6833 programStats.ok_to_send = 1;
6834 programStats.movelist[0] = '\0';
6836 SendProgramStatsToFrontend( cps, &programStats );
6841 } else if (strncmp(message,"++",2) == 0) {
6842 /* Crafty 9.29+ outputs this */
6843 programStats.got_fail = 2;
6846 } else if (strncmp(message,"--",2) == 0) {
6847 /* Crafty 9.29+ outputs this */
6848 programStats.got_fail = 1;
6851 } else if (thinkOutput[0] != NULLCHAR &&
6852 strncmp(message, " ", 4) == 0) {
6853 unsigned message_len;
6856 while (*p && *p == ' ') p++;
6858 message_len = strlen( p );
6860 /* [AS] Avoid buffer overflow */
6861 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6862 strcat(thinkOutput, " ");
6863 strcat(thinkOutput, p);
6866 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6867 strcat(programStats.movelist, " ");
6868 strcat(programStats.movelist, p);
6871 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6872 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6873 DisplayMove(currentMove - 1);
6882 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6883 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6885 ChessProgramStats cpstats;
6887 if (plyext != ' ' && plyext != '\t') {
6891 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6892 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6893 curscore = -curscore;
6896 cpstats.depth = plylev;
6897 cpstats.nodes = nodes;
6898 cpstats.time = time;
6899 cpstats.score = curscore;
6900 cpstats.got_only_move = 0;
6901 cpstats.movelist[0] = '\0';
6903 if (buf1[0] != NULLCHAR) {
6904 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6907 cpstats.ok_to_send = 0;
6908 cpstats.line_is_book = 0;
6909 cpstats.nr_moves = 0;
6910 cpstats.moves_left = 0;
6912 SendProgramStatsToFrontend( cps, &cpstats );
6919 /* Parse a game score from the character string "game", and
6920 record it as the history of the current game. The game
6921 score is NOT assumed to start from the standard position.
6922 The display is not updated in any way.
6925 ParseGameHistory(game)
6929 int fromX, fromY, toX, toY, boardIndex;
6934 if (appData.debugMode)
6935 fprintf(debugFP, "Parsing game history: %s\n", game);
6937 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6938 gameInfo.site = StrSave(appData.icsHost);
6939 gameInfo.date = PGNDate();
6940 gameInfo.round = StrSave("-");
6942 /* Parse out names of players */
6943 while (*game == ' ') game++;
6945 while (*game != ' ') *p++ = *game++;
6947 gameInfo.white = StrSave(buf);
6948 while (*game == ' ') game++;
6950 while (*game != ' ' && *game != '\n') *p++ = *game++;
6952 gameInfo.black = StrSave(buf);
6955 boardIndex = blackPlaysFirst ? 1 : 0;
6958 yyboardindex = boardIndex;
6959 moveType = (ChessMove) yylex();
6961 case IllegalMove: /* maybe suicide chess, etc. */
6962 if (appData.debugMode) {
6963 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6964 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6965 setbuf(debugFP, NULL);
6967 case WhitePromotionChancellor:
6968 case BlackPromotionChancellor:
6969 case WhitePromotionArchbishop:
6970 case BlackPromotionArchbishop:
6971 case WhitePromotionQueen:
6972 case BlackPromotionQueen:
6973 case WhitePromotionRook:
6974 case BlackPromotionRook:
6975 case WhitePromotionBishop:
6976 case BlackPromotionBishop:
6977 case WhitePromotionKnight:
6978 case BlackPromotionKnight:
6979 case WhitePromotionKing:
6980 case BlackPromotionKing:
6982 case WhiteCapturesEnPassant:
6983 case BlackCapturesEnPassant:
6984 case WhiteKingSideCastle:
6985 case WhiteQueenSideCastle:
6986 case BlackKingSideCastle:
6987 case BlackQueenSideCastle:
6988 case WhiteKingSideCastleWild:
6989 case WhiteQueenSideCastleWild:
6990 case BlackKingSideCastleWild:
6991 case BlackQueenSideCastleWild:
6993 case WhiteHSideCastleFR:
6994 case WhiteASideCastleFR:
6995 case BlackHSideCastleFR:
6996 case BlackASideCastleFR:
6998 fromX = currentMoveString[0] - AAA;
6999 fromY = currentMoveString[1] - ONE;
7000 toX = currentMoveString[2] - AAA;
7001 toY = currentMoveString[3] - ONE;
7002 promoChar = currentMoveString[4];
7006 fromX = moveType == WhiteDrop ?
7007 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7008 (int) CharToPiece(ToLower(currentMoveString[0]));
7010 toX = currentMoveString[2] - AAA;
7011 toY = currentMoveString[3] - ONE;
7012 promoChar = NULLCHAR;
7016 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7017 if (appData.debugMode) {
7018 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7019 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7020 setbuf(debugFP, NULL);
7022 DisplayError(buf, 0);
7024 case ImpossibleMove:
7026 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7027 if (appData.debugMode) {
7028 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7029 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7030 setbuf(debugFP, NULL);
7032 DisplayError(buf, 0);
7034 case (ChessMove) 0: /* end of file */
7035 if (boardIndex < backwardMostMove) {
7036 /* Oops, gap. How did that happen? */
7037 DisplayError(_("Gap in move list"), 0);
7040 backwardMostMove = blackPlaysFirst ? 1 : 0;
7041 if (boardIndex > forwardMostMove) {
7042 forwardMostMove = boardIndex;
7046 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7047 strcat(parseList[boardIndex-1], " ");
7048 strcat(parseList[boardIndex-1], yy_text);
7060 case GameUnfinished:
7061 if (gameMode == IcsExamining) {
7062 if (boardIndex < backwardMostMove) {
7063 /* Oops, gap. How did that happen? */
7066 backwardMostMove = blackPlaysFirst ? 1 : 0;
7069 gameInfo.result = moveType;
7070 p = strchr(yy_text, '{');
7071 if (p == NULL) p = strchr(yy_text, '(');
7074 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7076 q = strchr(p, *p == '{' ? '}' : ')');
7077 if (q != NULL) *q = NULLCHAR;
7080 gameInfo.resultDetails = StrSave(p);
7083 if (boardIndex >= forwardMostMove &&
7084 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7085 backwardMostMove = blackPlaysFirst ? 1 : 0;
7088 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7089 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7090 parseList[boardIndex]);
7091 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7092 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7093 /* currentMoveString is set as a side-effect of yylex */
7094 strcpy(moveList[boardIndex], currentMoveString);
7095 strcat(moveList[boardIndex], "\n");
7097 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7098 castlingRights[boardIndex], &epStatus[boardIndex]);
7099 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7100 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7106 if(gameInfo.variant != VariantShogi)
7107 strcat(parseList[boardIndex - 1], "+");
7111 strcat(parseList[boardIndex - 1], "#");
7118 /* Apply a move to the given board */
7120 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7121 int fromX, fromY, toX, toY;
7127 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7129 /* [HGM] compute & store e.p. status and castling rights for new position */
7130 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7133 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7137 if( board[toY][toX] != EmptySquare )
7140 if( board[fromY][fromX] == WhitePawn ) {
7141 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7144 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7145 gameInfo.variant != VariantBerolina || toX < fromX)
7146 *ep = toX | berolina;
7147 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7148 gameInfo.variant != VariantBerolina || toX > fromX)
7152 if( board[fromY][fromX] == BlackPawn ) {
7153 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7155 if( toY-fromY== -2) {
7156 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7157 gameInfo.variant != VariantBerolina || toX < fromX)
7158 *ep = toX | berolina;
7159 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7160 gameInfo.variant != VariantBerolina || toX > fromX)
7165 for(i=0; i<nrCastlingRights; i++) {
7166 if(castling[i] == fromX && castlingRank[i] == fromY ||
7167 castling[i] == toX && castlingRank[i] == toY
7168 ) castling[i] = -1; // revoke for moved or captured piece
7173 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7174 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7175 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7177 if (fromX == toX && fromY == toY) return;
7179 if (fromY == DROP_RANK) {
7181 piece = board[toY][toX] = (ChessSquare) fromX;
7183 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7184 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7185 if(gameInfo.variant == VariantKnightmate)
7186 king += (int) WhiteUnicorn - (int) WhiteKing;
7188 /* Code added by Tord: */
7189 /* FRC castling assumed when king captures friendly rook. */
7190 if (board[fromY][fromX] == WhiteKing &&
7191 board[toY][toX] == WhiteRook) {
7192 board[fromY][fromX] = EmptySquare;
7193 board[toY][toX] = EmptySquare;
7195 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7197 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7199 } else if (board[fromY][fromX] == BlackKing &&
7200 board[toY][toX] == BlackRook) {
7201 board[fromY][fromX] = EmptySquare;
7202 board[toY][toX] = EmptySquare;
7204 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7206 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7208 /* End of code added by Tord */
7210 } else if (board[fromY][fromX] == king
7211 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7212 && toY == fromY && toX > fromX+1) {
7213 board[fromY][fromX] = EmptySquare;
7214 board[toY][toX] = king;
7215 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7216 board[fromY][BOARD_RGHT-1] = EmptySquare;
7217 } else if (board[fromY][fromX] == king
7218 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7219 && toY == fromY && toX < fromX-1) {
7220 board[fromY][fromX] = EmptySquare;
7221 board[toY][toX] = king;
7222 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7223 board[fromY][BOARD_LEFT] = EmptySquare;
7224 } else if (board[fromY][fromX] == WhitePawn
7225 && toY == BOARD_HEIGHT-1
7226 && gameInfo.variant != VariantXiangqi
7228 /* white pawn promotion */
7229 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7230 if (board[toY][toX] == EmptySquare) {
7231 board[toY][toX] = WhiteQueen;
7233 if(gameInfo.variant==VariantBughouse ||
7234 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7235 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7236 board[fromY][fromX] = EmptySquare;
7237 } else if ((fromY == BOARD_HEIGHT-4)
7239 && gameInfo.variant != VariantXiangqi
7240 && gameInfo.variant != VariantBerolina
7241 && (board[fromY][fromX] == WhitePawn)
7242 && (board[toY][toX] == EmptySquare)) {
7243 board[fromY][fromX] = EmptySquare;
7244 board[toY][toX] = WhitePawn;
7245 captured = board[toY - 1][toX];
7246 board[toY - 1][toX] = EmptySquare;
7247 } else if ((fromY == BOARD_HEIGHT-4)
7249 && gameInfo.variant == VariantBerolina
7250 && (board[fromY][fromX] == WhitePawn)
7251 && (board[toY][toX] == EmptySquare)) {
7252 board[fromY][fromX] = EmptySquare;
7253 board[toY][toX] = WhitePawn;
7254 if(oldEP & EP_BEROLIN_A) {
7255 captured = board[fromY][fromX-1];
7256 board[fromY][fromX-1] = EmptySquare;
7257 }else{ captured = board[fromY][fromX+1];
7258 board[fromY][fromX+1] = EmptySquare;
7260 } else if (board[fromY][fromX] == king
7261 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7262 && toY == fromY && toX > fromX+1) {
7263 board[fromY][fromX] = EmptySquare;
7264 board[toY][toX] = king;
7265 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7266 board[fromY][BOARD_RGHT-1] = EmptySquare;
7267 } else if (board[fromY][fromX] == king
7268 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7269 && toY == fromY && toX < fromX-1) {
7270 board[fromY][fromX] = EmptySquare;
7271 board[toY][toX] = king;
7272 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7273 board[fromY][BOARD_LEFT] = EmptySquare;
7274 } else if (fromY == 7 && fromX == 3
7275 && board[fromY][fromX] == BlackKing
7276 && toY == 7 && toX == 5) {
7277 board[fromY][fromX] = EmptySquare;
7278 board[toY][toX] = BlackKing;
7279 board[fromY][7] = EmptySquare;
7280 board[toY][4] = BlackRook;
7281 } else if (fromY == 7 && fromX == 3
7282 && board[fromY][fromX] == BlackKing
7283 && toY == 7 && toX == 1) {
7284 board[fromY][fromX] = EmptySquare;
7285 board[toY][toX] = BlackKing;
7286 board[fromY][0] = EmptySquare;
7287 board[toY][2] = BlackRook;
7288 } else if (board[fromY][fromX] == BlackPawn
7290 && gameInfo.variant != VariantXiangqi
7292 /* black pawn promotion */
7293 board[0][toX] = CharToPiece(ToLower(promoChar));
7294 if (board[0][toX] == EmptySquare) {
7295 board[0][toX] = BlackQueen;
7297 if(gameInfo.variant==VariantBughouse ||
7298 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7299 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7300 board[fromY][fromX] = EmptySquare;
7301 } else if ((fromY == 3)
7303 && gameInfo.variant != VariantXiangqi
7304 && gameInfo.variant != VariantBerolina
7305 && (board[fromY][fromX] == BlackPawn)
7306 && (board[toY][toX] == EmptySquare)) {
7307 board[fromY][fromX] = EmptySquare;
7308 board[toY][toX] = BlackPawn;
7309 captured = board[toY + 1][toX];
7310 board[toY + 1][toX] = EmptySquare;
7311 } else if ((fromY == 3)
7313 && gameInfo.variant == VariantBerolina
7314 && (board[fromY][fromX] == BlackPawn)
7315 && (board[toY][toX] == EmptySquare)) {
7316 board[fromY][fromX] = EmptySquare;
7317 board[toY][toX] = BlackPawn;
7318 if(oldEP & EP_BEROLIN_A) {
7319 captured = board[fromY][fromX-1];
7320 board[fromY][fromX-1] = EmptySquare;
7321 }else{ captured = board[fromY][fromX+1];
7322 board[fromY][fromX+1] = EmptySquare;
7325 board[toY][toX] = board[fromY][fromX];
7326 board[fromY][fromX] = EmptySquare;
7329 /* [HGM] now we promote for Shogi, if needed */
7330 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7331 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7334 if (gameInfo.holdingsWidth != 0) {
7336 /* !!A lot more code needs to be written to support holdings */
7337 /* [HGM] OK, so I have written it. Holdings are stored in the */
7338 /* penultimate board files, so they are automaticlly stored */
7339 /* in the game history. */
7340 if (fromY == DROP_RANK) {
7341 /* Delete from holdings, by decreasing count */
7342 /* and erasing image if necessary */
7344 if(p < (int) BlackPawn) { /* white drop */
7345 p -= (int)WhitePawn;
7346 if(p >= gameInfo.holdingsSize) p = 0;
7347 if(--board[p][BOARD_WIDTH-2] == 0)
7348 board[p][BOARD_WIDTH-1] = EmptySquare;
7349 } else { /* black drop */
7350 p -= (int)BlackPawn;
7351 if(p >= gameInfo.holdingsSize) p = 0;
7352 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7353 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7356 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7357 && gameInfo.variant != VariantBughouse ) {
7358 /* [HGM] holdings: Add to holdings, if holdings exist */
7359 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7360 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7361 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7364 if (p >= (int) BlackPawn) {
7365 p -= (int)BlackPawn;
7366 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7367 /* in Shogi restore piece to its original first */
7368 captured = (ChessSquare) (DEMOTED captured);
7371 p = PieceToNumber((ChessSquare)p);
7372 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7373 board[p][BOARD_WIDTH-2]++;
7374 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7376 p -= (int)WhitePawn;
7377 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7378 captured = (ChessSquare) (DEMOTED captured);
7381 p = PieceToNumber((ChessSquare)p);
7382 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7383 board[BOARD_HEIGHT-1-p][1]++;
7384 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7388 } else if (gameInfo.variant == VariantAtomic) {
7389 if (captured != EmptySquare) {
7391 for (y = toY-1; y <= toY+1; y++) {
7392 for (x = toX-1; x <= toX+1; x++) {
7393 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7394 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7395 board[y][x] = EmptySquare;
7399 board[toY][toX] = EmptySquare;
7402 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7403 /* [HGM] Shogi promotions */
7404 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7407 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7408 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7409 // [HGM] superchess: take promotion piece out of holdings
7410 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7411 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7412 if(!--board[k][BOARD_WIDTH-2])
7413 board[k][BOARD_WIDTH-1] = EmptySquare;
7415 if(!--board[BOARD_HEIGHT-1-k][1])
7416 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7422 /* Updates forwardMostMove */
7424 MakeMove(fromX, fromY, toX, toY, promoChar)
7425 int fromX, fromY, toX, toY;
7428 // forwardMostMove++; // [HGM] bare: moved downstream
7430 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7431 int timeLeft; static int lastLoadFlag=0; int king, piece;
7432 piece = boards[forwardMostMove][fromY][fromX];
7433 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7434 if(gameInfo.variant == VariantKnightmate)
7435 king += (int) WhiteUnicorn - (int) WhiteKing;
7436 if(forwardMostMove == 0) {
7438 fprintf(serverMoves, "%s;", second.tidy);
7439 fprintf(serverMoves, "%s;", first.tidy);
7440 if(!blackPlaysFirst)
7441 fprintf(serverMoves, "%s;", second.tidy);
7442 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7443 lastLoadFlag = loadFlag;
7445 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7446 // print castling suffix
7447 if( toY == fromY && piece == king ) {
7449 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7451 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7454 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7455 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7456 boards[forwardMostMove][toY][toX] == EmptySquare
7458 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7460 if(promoChar != NULLCHAR)
7461 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7463 fprintf(serverMoves, "/%d/%d",
7464 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7465 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7466 else timeLeft = blackTimeRemaining/1000;
7467 fprintf(serverMoves, "/%d", timeLeft);
7469 fflush(serverMoves);
7472 if (forwardMostMove+1 >= MAX_MOVES) {
7473 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7477 if (commentList[forwardMostMove+1] != NULL) {
7478 free(commentList[forwardMostMove+1]);
7479 commentList[forwardMostMove+1] = NULL;
7481 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7482 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7483 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7484 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7485 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7486 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7487 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7488 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7489 gameInfo.result = GameUnfinished;
7490 if (gameInfo.resultDetails != NULL) {
7491 free(gameInfo.resultDetails);
7492 gameInfo.resultDetails = NULL;
7494 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7495 moveList[forwardMostMove - 1]);
7496 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7497 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7498 fromY, fromX, toY, toX, promoChar,
7499 parseList[forwardMostMove - 1]);
7500 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7501 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7502 castlingRights[forwardMostMove]) ) {
7508 if(gameInfo.variant != VariantShogi)
7509 strcat(parseList[forwardMostMove - 1], "+");
7513 strcat(parseList[forwardMostMove - 1], "#");
7516 if (appData.debugMode) {
7517 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7522 /* Updates currentMove if not pausing */
7524 ShowMove(fromX, fromY, toX, toY)
7526 int instant = (gameMode == PlayFromGameFile) ?
7527 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7528 if(appData.noGUI) return;
7529 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7531 if (forwardMostMove == currentMove + 1) {
7532 AnimateMove(boards[forwardMostMove - 1],
7533 fromX, fromY, toX, toY);
7535 if (appData.highlightLastMove) {
7536 SetHighlights(fromX, fromY, toX, toY);
7539 currentMove = forwardMostMove;
7542 if (instant) return;
7544 DisplayMove(currentMove - 1);
7545 DrawPosition(FALSE, boards[currentMove]);
7546 DisplayBothClocks();
7547 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7550 void SendEgtPath(ChessProgramState *cps)
7551 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7552 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7554 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7557 char c, *q = name+1, *r, *s;
7559 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7560 while(*p && *p != ',') *q++ = *p++;
7562 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7563 strcmp(name, ",nalimov:") == 0 ) {
7564 // take nalimov path from the menu-changeable option first, if it is defined
7565 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7566 SendToProgram(buf,cps); // send egtbpath command for nalimov
7568 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7569 (s = StrStr(appData.egtFormats, name)) != NULL) {
7570 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7571 s = r = StrStr(s, ":") + 1; // beginning of path info
7572 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7573 c = *r; *r = 0; // temporarily null-terminate path info
7574 *--q = 0; // strip of trailig ':' from name
7575 sprintf(buf, "egtpath %s %s\n", name+1, s);
7577 SendToProgram(buf,cps); // send egtbpath command for this format
7579 if(*p == ',') p++; // read away comma to position for next format name
7584 InitChessProgram(cps, setup)
7585 ChessProgramState *cps;
7586 int setup; /* [HGM] needed to setup FRC opening position */
7588 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7589 if (appData.noChessProgram) return;
7590 hintRequested = FALSE;
7591 bookRequested = FALSE;
7593 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7594 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7595 if(cps->memSize) { /* [HGM] memory */
7596 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7597 SendToProgram(buf, cps);
7599 SendEgtPath(cps); /* [HGM] EGT */
7600 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7601 sprintf(buf, "cores %d\n", appData.smpCores);
7602 SendToProgram(buf, cps);
7605 SendToProgram(cps->initString, cps);
7606 if (gameInfo.variant != VariantNormal &&
7607 gameInfo.variant != VariantLoadable
7608 /* [HGM] also send variant if board size non-standard */
7609 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7611 char *v = VariantName(gameInfo.variant);
7612 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7613 /* [HGM] in protocol 1 we have to assume all variants valid */
7614 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7615 DisplayFatalError(buf, 0, 1);
7619 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7620 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7621 if( gameInfo.variant == VariantXiangqi )
7622 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7623 if( gameInfo.variant == VariantShogi )
7624 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7625 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7626 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7627 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7628 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7629 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7630 if( gameInfo.variant == VariantCourier )
7631 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7632 if( gameInfo.variant == VariantSuper )
7633 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7634 if( gameInfo.variant == VariantGreat )
7635 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7638 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7639 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7640 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7641 if(StrStr(cps->variants, b) == NULL) {
7642 // specific sized variant not known, check if general sizing allowed
7643 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7644 if(StrStr(cps->variants, "boardsize") == NULL) {
7645 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7646 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7647 DisplayFatalError(buf, 0, 1);
7650 /* [HGM] here we really should compare with the maximum supported board size */
7653 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7654 sprintf(buf, "variant %s\n", b);
7655 SendToProgram(buf, cps);
7657 currentlyInitializedVariant = gameInfo.variant;
7659 /* [HGM] send opening position in FRC to first engine */
7661 SendToProgram("force\n", cps);
7663 /* engine is now in force mode! Set flag to wake it up after first move. */
7664 setboardSpoiledMachineBlack = 1;
7668 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7669 SendToProgram(buf, cps);
7671 cps->maybeThinking = FALSE;
7672 cps->offeredDraw = 0;
7673 if (!appData.icsActive) {
7674 SendTimeControl(cps, movesPerSession, timeControl,
7675 timeIncrement, appData.searchDepth,
7678 if (appData.showThinking
7679 // [HGM] thinking: four options require thinking output to be sent
7680 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7682 SendToProgram("post\n", cps);
7684 SendToProgram("hard\n", cps);
7685 if (!appData.ponderNextMove) {
7686 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7687 it without being sure what state we are in first. "hard"
7688 is not a toggle, so that one is OK.
7690 SendToProgram("easy\n", cps);
7693 sprintf(buf, "ping %d\n", ++cps->lastPing);
7694 SendToProgram(buf, cps);
7696 cps->initDone = TRUE;
7701 StartChessProgram(cps)
7702 ChessProgramState *cps;
7707 if (appData.noChessProgram) return;
7708 cps->initDone = FALSE;
7710 if (strcmp(cps->host, "localhost") == 0) {
7711 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7712 } else if (*appData.remoteShell == NULLCHAR) {
7713 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7715 if (*appData.remoteUser == NULLCHAR) {
7716 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7719 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7720 cps->host, appData.remoteUser, cps->program);
7722 err = StartChildProcess(buf, "", &cps->pr);
7726 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7727 DisplayFatalError(buf, err, 1);
7733 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7734 if (cps->protocolVersion > 1) {
7735 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7736 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7737 cps->comboCnt = 0; // and values of combo boxes
7738 SendToProgram(buf, cps);
7740 SendToProgram("xboard\n", cps);
7746 TwoMachinesEventIfReady P((void))
7748 if (first.lastPing != first.lastPong) {
7749 DisplayMessage("", _("Waiting for first chess program"));
7750 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7753 if (second.lastPing != second.lastPong) {
7754 DisplayMessage("", _("Waiting for second chess program"));
7755 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7763 NextMatchGame P((void))
7765 int index; /* [HGM] autoinc: step lod index during match */
7767 if (*appData.loadGameFile != NULLCHAR) {
7768 index = appData.loadGameIndex;
7769 if(index < 0) { // [HGM] autoinc
7770 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7771 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7773 LoadGameFromFile(appData.loadGameFile,
7775 appData.loadGameFile, FALSE);
7776 } else if (*appData.loadPositionFile != NULLCHAR) {
7777 index = appData.loadPositionIndex;
7778 if(index < 0) { // [HGM] autoinc
7779 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7780 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7782 LoadPositionFromFile(appData.loadPositionFile,
7784 appData.loadPositionFile);
7786 TwoMachinesEventIfReady();
7789 void UserAdjudicationEvent( int result )
7791 ChessMove gameResult = GameIsDrawn;
7794 gameResult = WhiteWins;
7796 else if( result < 0 ) {
7797 gameResult = BlackWins;
7800 if( gameMode == TwoMachinesPlay ) {
7801 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7806 // [HGM] save: calculate checksum of game to make games easily identifiable
7807 int StringCheckSum(char *s)
7810 if(s==NULL) return 0;
7811 while(*s) i = i*259 + *s++;
7818 for(i=backwardMostMove; i<forwardMostMove; i++) {
7819 sum += pvInfoList[i].depth;
7820 sum += StringCheckSum(parseList[i]);
7821 sum += StringCheckSum(commentList[i]);
7824 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7825 return sum + StringCheckSum(commentList[i]);
7826 } // end of save patch
7829 GameEnds(result, resultDetails, whosays)
7831 char *resultDetails;
7834 GameMode nextGameMode;
7838 if(endingGame) return; /* [HGM] crash: forbid recursion */
7841 if (appData.debugMode) {
7842 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7843 result, resultDetails ? resultDetails : "(null)", whosays);
7846 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7847 /* If we are playing on ICS, the server decides when the
7848 game is over, but the engine can offer to draw, claim
7852 if (appData.zippyPlay && first.initDone) {
7853 if (result == GameIsDrawn) {
7854 /* In case draw still needs to be claimed */
7855 SendToICS(ics_prefix);
7856 SendToICS("draw\n");
7857 } else if (StrCaseStr(resultDetails, "resign")) {
7858 SendToICS(ics_prefix);
7859 SendToICS("resign\n");
7863 endingGame = 0; /* [HGM] crash */
7867 /* If we're loading the game from a file, stop */
7868 if (whosays == GE_FILE) {
7869 (void) StopLoadGameTimer();
7873 /* Cancel draw offers */
7874 first.offeredDraw = second.offeredDraw = 0;
7876 /* If this is an ICS game, only ICS can really say it's done;
7877 if not, anyone can. */
7878 isIcsGame = (gameMode == IcsPlayingWhite ||
7879 gameMode == IcsPlayingBlack ||
7880 gameMode == IcsObserving ||
7881 gameMode == IcsExamining);
7883 if (!isIcsGame || whosays == GE_ICS) {
7884 /* OK -- not an ICS game, or ICS said it was done */
7886 if (!isIcsGame && !appData.noChessProgram)
7887 SetUserThinkingEnables();
7889 /* [HGM] if a machine claims the game end we verify this claim */
7890 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7891 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7893 ChessMove trueResult = (ChessMove) -1;
7895 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7896 first.twoMachinesColor[0] :
7897 second.twoMachinesColor[0] ;
7899 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7900 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7901 /* [HGM] verify: engine mate claims accepted if they were flagged */
7902 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7904 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7905 /* [HGM] verify: engine mate claims accepted if they were flagged */
7906 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7908 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7909 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7912 // now verify win claims, but not in drop games, as we don't understand those yet
7913 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7914 || gameInfo.variant == VariantGreat) &&
7915 (result == WhiteWins && claimer == 'w' ||
7916 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7917 if (appData.debugMode) {
7918 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7919 result, epStatus[forwardMostMove], forwardMostMove);
7921 if(result != trueResult) {
7922 sprintf(buf, "False win claim: '%s'", resultDetails);
7923 result = claimer == 'w' ? BlackWins : WhiteWins;
7924 resultDetails = buf;
7927 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7928 && (forwardMostMove <= backwardMostMove ||
7929 epStatus[forwardMostMove-1] > EP_DRAWS ||
7930 (claimer=='b')==(forwardMostMove&1))
7932 /* [HGM] verify: draws that were not flagged are false claims */
7933 sprintf(buf, "False draw claim: '%s'", resultDetails);
7934 result = claimer == 'w' ? BlackWins : WhiteWins;
7935 resultDetails = buf;
7937 /* (Claiming a loss is accepted no questions asked!) */
7939 /* [HGM] bare: don't allow bare King to win */
7940 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7941 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7942 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7943 && result != GameIsDrawn)
7944 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7945 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7946 int p = (int)boards[forwardMostMove][i][j] - color;
7947 if(p >= 0 && p <= (int)WhiteKing) k++;
7949 if (appData.debugMode) {
7950 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7951 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7954 result = GameIsDrawn;
7955 sprintf(buf, "%s but bare king", resultDetails);
7956 resultDetails = buf;
7962 if(serverMoves != NULL && !loadFlag) { char c = '=';
7963 if(result==WhiteWins) c = '+';
7964 if(result==BlackWins) c = '-';
7965 if(resultDetails != NULL)
7966 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7968 if (resultDetails != NULL) {
7969 gameInfo.result = result;
7970 gameInfo.resultDetails = StrSave(resultDetails);
7972 /* display last move only if game was not loaded from file */
7973 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7974 DisplayMove(currentMove - 1);
7976 if (forwardMostMove != 0) {
7977 if (gameMode != PlayFromGameFile && gameMode != EditGame
7978 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7980 if (*appData.saveGameFile != NULLCHAR) {
7981 SaveGameToFile(appData.saveGameFile, TRUE);
7982 } else if (appData.autoSaveGames) {
7985 if (*appData.savePositionFile != NULLCHAR) {
7986 SavePositionToFile(appData.savePositionFile);
7991 /* Tell program how game ended in case it is learning */
7992 /* [HGM] Moved this to after saving the PGN, just in case */
7993 /* engine died and we got here through time loss. In that */
7994 /* case we will get a fatal error writing the pipe, which */
7995 /* would otherwise lose us the PGN. */
7996 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7997 /* output during GameEnds should never be fatal anymore */
7998 if (gameMode == MachinePlaysWhite ||
7999 gameMode == MachinePlaysBlack ||
8000 gameMode == TwoMachinesPlay ||
8001 gameMode == IcsPlayingWhite ||
8002 gameMode == IcsPlayingBlack ||
8003 gameMode == BeginningOfGame) {
8005 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8007 if (first.pr != NoProc) {
8008 SendToProgram(buf, &first);
8010 if (second.pr != NoProc &&
8011 gameMode == TwoMachinesPlay) {
8012 SendToProgram(buf, &second);
8017 if (appData.icsActive) {
8018 if (appData.quietPlay &&
8019 (gameMode == IcsPlayingWhite ||
8020 gameMode == IcsPlayingBlack)) {
8021 SendToICS(ics_prefix);
8022 SendToICS("set shout 1\n");
8024 nextGameMode = IcsIdle;
8025 ics_user_moved = FALSE;
8026 /* clean up premove. It's ugly when the game has ended and the
8027 * premove highlights are still on the board.
8031 ClearPremoveHighlights();
8032 DrawPosition(FALSE, boards[currentMove]);
8034 if (whosays == GE_ICS) {
8037 if (gameMode == IcsPlayingWhite)
8039 else if(gameMode == IcsPlayingBlack)
8043 if (gameMode == IcsPlayingBlack)
8045 else if(gameMode == IcsPlayingWhite)
8052 PlayIcsUnfinishedSound();
8055 } else if (gameMode == EditGame ||
8056 gameMode == PlayFromGameFile ||
8057 gameMode == AnalyzeMode ||
8058 gameMode == AnalyzeFile) {
8059 nextGameMode = gameMode;
8061 nextGameMode = EndOfGame;
8066 nextGameMode = gameMode;
8069 if (appData.noChessProgram) {
8070 gameMode = nextGameMode;
8072 endingGame = 0; /* [HGM] crash */
8077 /* Put first chess program into idle state */
8078 if (first.pr != NoProc &&
8079 (gameMode == MachinePlaysWhite ||
8080 gameMode == MachinePlaysBlack ||
8081 gameMode == TwoMachinesPlay ||
8082 gameMode == IcsPlayingWhite ||
8083 gameMode == IcsPlayingBlack ||
8084 gameMode == BeginningOfGame)) {
8085 SendToProgram("force\n", &first);
8086 if (first.usePing) {
8088 sprintf(buf, "ping %d\n", ++first.lastPing);
8089 SendToProgram(buf, &first);
8092 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8093 /* Kill off first chess program */
8094 if (first.isr != NULL)
8095 RemoveInputSource(first.isr);
8098 if (first.pr != NoProc) {
8100 DoSleep( appData.delayBeforeQuit );
8101 SendToProgram("quit\n", &first);
8102 DoSleep( appData.delayAfterQuit );
8103 DestroyChildProcess(first.pr, first.useSigterm);
8108 /* Put second chess program into idle state */
8109 if (second.pr != NoProc &&
8110 gameMode == TwoMachinesPlay) {
8111 SendToProgram("force\n", &second);
8112 if (second.usePing) {
8114 sprintf(buf, "ping %d\n", ++second.lastPing);
8115 SendToProgram(buf, &second);
8118 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8119 /* Kill off second chess program */
8120 if (second.isr != NULL)
8121 RemoveInputSource(second.isr);
8124 if (second.pr != NoProc) {
8125 DoSleep( appData.delayBeforeQuit );
8126 SendToProgram("quit\n", &second);
8127 DoSleep( appData.delayAfterQuit );
8128 DestroyChildProcess(second.pr, second.useSigterm);
8133 if (matchMode && gameMode == TwoMachinesPlay) {
8136 if (first.twoMachinesColor[0] == 'w') {
8143 if (first.twoMachinesColor[0] == 'b') {
8152 if (matchGame < appData.matchGames) {
8154 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8155 tmp = first.twoMachinesColor;
8156 first.twoMachinesColor = second.twoMachinesColor;
8157 second.twoMachinesColor = tmp;
8159 gameMode = nextGameMode;
8161 if(appData.matchPause>10000 || appData.matchPause<10)
8162 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8163 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8164 endingGame = 0; /* [HGM] crash */
8168 gameMode = nextGameMode;
8169 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8170 first.tidy, second.tidy,
8171 first.matchWins, second.matchWins,
8172 appData.matchGames - (first.matchWins + second.matchWins));
8173 DisplayFatalError(buf, 0, 0);
8176 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8177 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8179 gameMode = nextGameMode;
8181 endingGame = 0; /* [HGM] crash */
8184 /* Assumes program was just initialized (initString sent).
8185 Leaves program in force mode. */
8187 FeedMovesToProgram(cps, upto)
8188 ChessProgramState *cps;
8193 if (appData.debugMode)
8194 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8195 startedFromSetupPosition ? "position and " : "",
8196 backwardMostMove, upto, cps->which);
8197 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8198 // [HGM] variantswitch: make engine aware of new variant
8199 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8200 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8201 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8202 SendToProgram(buf, cps);
8203 currentlyInitializedVariant = gameInfo.variant;
8205 SendToProgram("force\n", cps);
8206 if (startedFromSetupPosition) {
8207 SendBoard(cps, backwardMostMove);
8208 if (appData.debugMode) {
8209 fprintf(debugFP, "feedMoves\n");
8212 for (i = backwardMostMove; i < upto; i++) {
8213 SendMoveToProgram(i, cps);
8219 ResurrectChessProgram()
8221 /* The chess program may have exited.
8222 If so, restart it and feed it all the moves made so far. */
8224 if (appData.noChessProgram || first.pr != NoProc) return;
8226 StartChessProgram(&first);
8227 InitChessProgram(&first, FALSE);
8228 FeedMovesToProgram(&first, currentMove);
8230 if (!first.sendTime) {
8231 /* can't tell gnuchess what its clock should read,
8232 so we bow to its notion. */
8234 timeRemaining[0][currentMove] = whiteTimeRemaining;
8235 timeRemaining[1][currentMove] = blackTimeRemaining;
8238 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8239 appData.icsEngineAnalyze) && first.analysisSupport) {
8240 SendToProgram("analyze\n", &first);
8241 first.analyzing = TRUE;
8254 if (appData.debugMode) {
8255 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8256 redraw, init, gameMode);
8258 pausing = pauseExamInvalid = FALSE;
8259 startedFromSetupPosition = blackPlaysFirst = FALSE;
8261 whiteFlag = blackFlag = FALSE;
8262 userOfferedDraw = FALSE;
8263 hintRequested = bookRequested = FALSE;
8264 first.maybeThinking = FALSE;
8265 second.maybeThinking = FALSE;
8266 first.bookSuspend = FALSE; // [HGM] book
8267 second.bookSuspend = FALSE;
8268 thinkOutput[0] = NULLCHAR;
8269 lastHint[0] = NULLCHAR;
8270 ClearGameInfo(&gameInfo);
8271 gameInfo.variant = StringToVariant(appData.variant);
8272 ics_user_moved = ics_clock_paused = FALSE;
8273 ics_getting_history = H_FALSE;
8275 white_holding[0] = black_holding[0] = NULLCHAR;
8276 ClearProgramStats();
8277 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8281 flipView = appData.flipView;
8282 ClearPremoveHighlights();
8284 alarmSounded = FALSE;
8286 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8287 if(appData.serverMovesName != NULL) {
8288 /* [HGM] prepare to make moves file for broadcasting */
8289 clock_t t = clock();
8290 if(serverMoves != NULL) fclose(serverMoves);
8291 serverMoves = fopen(appData.serverMovesName, "r");
8292 if(serverMoves != NULL) {
8293 fclose(serverMoves);
8294 /* delay 15 sec before overwriting, so all clients can see end */
8295 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8297 serverMoves = fopen(appData.serverMovesName, "w");
8301 gameMode = BeginningOfGame;
8303 if(appData.icsActive) gameInfo.variant = VariantNormal;
8304 currentMove = forwardMostMove = backwardMostMove = 0;
8305 InitPosition(redraw);
8306 for (i = 0; i < MAX_MOVES; i++) {
8307 if (commentList[i] != NULL) {
8308 free(commentList[i]);
8309 commentList[i] = NULL;
8313 timeRemaining[0][0] = whiteTimeRemaining;
8314 timeRemaining[1][0] = blackTimeRemaining;
8315 if (first.pr == NULL) {
8316 StartChessProgram(&first);
8319 InitChessProgram(&first, startedFromSetupPosition);
8322 DisplayMessage("", "");
8323 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8324 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8331 if (!AutoPlayOneMove())
8333 if (matchMode || appData.timeDelay == 0)
8335 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8337 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8346 int fromX, fromY, toX, toY;
8348 if (appData.debugMode) {
8349 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8352 if (gameMode != PlayFromGameFile)
8355 if (currentMove >= forwardMostMove) {
8356 gameMode = EditGame;
8359 /* [AS] Clear current move marker at the end of a game */
8360 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8365 toX = moveList[currentMove][2] - AAA;
8366 toY = moveList[currentMove][3] - ONE;
8368 if (moveList[currentMove][1] == '@') {
8369 if (appData.highlightLastMove) {
8370 SetHighlights(-1, -1, toX, toY);
8373 fromX = moveList[currentMove][0] - AAA;
8374 fromY = moveList[currentMove][1] - ONE;
8376 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8378 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8380 if (appData.highlightLastMove) {
8381 SetHighlights(fromX, fromY, toX, toY);
8384 DisplayMove(currentMove);
8385 SendMoveToProgram(currentMove++, &first);
8386 DisplayBothClocks();
8387 DrawPosition(FALSE, boards[currentMove]);
8388 // [HGM] PV info: always display, routine tests if empty
8389 DisplayComment(currentMove - 1, commentList[currentMove]);
8395 LoadGameOneMove(readAhead)
8396 ChessMove readAhead;
8398 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8399 char promoChar = NULLCHAR;
8404 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8405 gameMode != AnalyzeMode && gameMode != Training) {
8410 yyboardindex = forwardMostMove;
8411 if (readAhead != (ChessMove)0) {
8412 moveType = readAhead;
8414 if (gameFileFP == NULL)
8416 moveType = (ChessMove) yylex();
8422 if (appData.debugMode)
8423 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8425 if (*p == '{' || *p == '[' || *p == '(') {
8426 p[strlen(p) - 1] = NULLCHAR;
8430 /* append the comment but don't display it */
8431 while (*p == '\n') p++;
8432 AppendComment(currentMove, p);
8435 case WhiteCapturesEnPassant:
8436 case BlackCapturesEnPassant:
8437 case WhitePromotionChancellor:
8438 case BlackPromotionChancellor:
8439 case WhitePromotionArchbishop:
8440 case BlackPromotionArchbishop:
8441 case WhitePromotionCentaur:
8442 case BlackPromotionCentaur:
8443 case WhitePromotionQueen:
8444 case BlackPromotionQueen:
8445 case WhitePromotionRook:
8446 case BlackPromotionRook:
8447 case WhitePromotionBishop:
8448 case BlackPromotionBishop:
8449 case WhitePromotionKnight:
8450 case BlackPromotionKnight:
8451 case WhitePromotionKing:
8452 case BlackPromotionKing:
8454 case WhiteKingSideCastle:
8455 case WhiteQueenSideCastle:
8456 case BlackKingSideCastle:
8457 case BlackQueenSideCastle:
8458 case WhiteKingSideCastleWild:
8459 case WhiteQueenSideCastleWild:
8460 case BlackKingSideCastleWild:
8461 case BlackQueenSideCastleWild:
8463 case WhiteHSideCastleFR:
8464 case WhiteASideCastleFR:
8465 case BlackHSideCastleFR:
8466 case BlackASideCastleFR:
8468 if (appData.debugMode)
8469 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8470 fromX = currentMoveString[0] - AAA;
8471 fromY = currentMoveString[1] - ONE;
8472 toX = currentMoveString[2] - AAA;
8473 toY = currentMoveString[3] - ONE;
8474 promoChar = currentMoveString[4];
8479 if (appData.debugMode)
8480 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8481 fromX = moveType == WhiteDrop ?
8482 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8483 (int) CharToPiece(ToLower(currentMoveString[0]));
8485 toX = currentMoveString[2] - AAA;
8486 toY = currentMoveString[3] - ONE;
8492 case GameUnfinished:
8493 if (appData.debugMode)
8494 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8495 p = strchr(yy_text, '{');
8496 if (p == NULL) p = strchr(yy_text, '(');
8499 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8501 q = strchr(p, *p == '{' ? '}' : ')');
8502 if (q != NULL) *q = NULLCHAR;
8505 GameEnds(moveType, p, GE_FILE);
8507 if (cmailMsgLoaded) {
8509 flipView = WhiteOnMove(currentMove);
8510 if (moveType == GameUnfinished) flipView = !flipView;
8511 if (appData.debugMode)
8512 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8516 case (ChessMove) 0: /* end of file */
8517 if (appData.debugMode)
8518 fprintf(debugFP, "Parser hit end of file\n");
8519 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8520 EP_UNKNOWN, castlingRights[currentMove]) ) {
8526 if (WhiteOnMove(currentMove)) {
8527 GameEnds(BlackWins, "Black mates", GE_FILE);
8529 GameEnds(WhiteWins, "White mates", GE_FILE);
8533 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8540 if (lastLoadGameStart == GNUChessGame) {
8541 /* GNUChessGames have numbers, but they aren't move numbers */
8542 if (appData.debugMode)
8543 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8544 yy_text, (int) moveType);
8545 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8547 /* else fall thru */
8552 /* Reached start of next game in file */
8553 if (appData.debugMode)
8554 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8555 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8556 EP_UNKNOWN, castlingRights[currentMove]) ) {
8562 if (WhiteOnMove(currentMove)) {
8563 GameEnds(BlackWins, "Black mates", GE_FILE);
8565 GameEnds(WhiteWins, "White mates", GE_FILE);
8569 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8575 case PositionDiagram: /* should not happen; ignore */
8576 case ElapsedTime: /* ignore */
8577 case NAG: /* ignore */
8578 if (appData.debugMode)
8579 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8580 yy_text, (int) moveType);
8581 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8584 if (appData.testLegality) {
8585 if (appData.debugMode)
8586 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8587 sprintf(move, _("Illegal move: %d.%s%s"),
8588 (forwardMostMove / 2) + 1,
8589 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8590 DisplayError(move, 0);
8593 if (appData.debugMode)
8594 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8595 yy_text, currentMoveString);
8596 fromX = currentMoveString[0] - AAA;
8597 fromY = currentMoveString[1] - ONE;
8598 toX = currentMoveString[2] - AAA;
8599 toY = currentMoveString[3] - ONE;
8600 promoChar = currentMoveString[4];
8605 if (appData.debugMode)
8606 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8607 sprintf(move, _("Ambiguous move: %d.%s%s"),
8608 (forwardMostMove / 2) + 1,
8609 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8610 DisplayError(move, 0);
8615 case ImpossibleMove:
8616 if (appData.debugMode)
8617 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8618 sprintf(move, _("Illegal move: %d.%s%s"),
8619 (forwardMostMove / 2) + 1,
8620 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8621 DisplayError(move, 0);
8627 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8628 DrawPosition(FALSE, boards[currentMove]);
8629 DisplayBothClocks();
8630 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8631 DisplayComment(currentMove - 1, commentList[currentMove]);
8633 (void) StopLoadGameTimer();
8635 cmailOldMove = forwardMostMove;
8638 /* currentMoveString is set as a side-effect of yylex */
8639 strcat(currentMoveString, "\n");
8640 strcpy(moveList[forwardMostMove], currentMoveString);
8642 thinkOutput[0] = NULLCHAR;
8643 MakeMove(fromX, fromY, toX, toY, promoChar);
8644 currentMove = forwardMostMove;
8649 /* Load the nth game from the given file */
8651 LoadGameFromFile(filename, n, title, useList)
8655 /*Boolean*/ int useList;
8660 if (strcmp(filename, "-") == 0) {
8664 f = fopen(filename, "rb");
8666 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8667 DisplayError(buf, errno);
8671 if (fseek(f, 0, 0) == -1) {
8672 /* f is not seekable; probably a pipe */
8675 if (useList && n == 0) {
8676 int error = GameListBuild(f);
8678 DisplayError(_("Cannot build game list"), error);
8679 } else if (!ListEmpty(&gameList) &&
8680 ((ListGame *) gameList.tailPred)->number > 1) {
8681 GameListPopUp(f, title);
8688 return LoadGame(f, n, title, FALSE);
8693 MakeRegisteredMove()
8695 int fromX, fromY, toX, toY;
8697 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8698 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8701 if (appData.debugMode)
8702 fprintf(debugFP, "Restoring %s for game %d\n",
8703 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8705 thinkOutput[0] = NULLCHAR;
8706 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8707 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8708 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8709 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8710 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8711 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8712 MakeMove(fromX, fromY, toX, toY, promoChar);
8713 ShowMove(fromX, fromY, toX, toY);
8715 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8716 EP_UNKNOWN, castlingRights[currentMove]) ) {
8723 if (WhiteOnMove(currentMove)) {
8724 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8726 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8731 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8738 if (WhiteOnMove(currentMove)) {
8739 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8741 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8746 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8757 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8759 CmailLoadGame(f, gameNumber, title, useList)
8767 if (gameNumber > nCmailGames) {
8768 DisplayError(_("No more games in this message"), 0);
8771 if (f == lastLoadGameFP) {
8772 int offset = gameNumber - lastLoadGameNumber;
8774 cmailMsg[0] = NULLCHAR;
8775 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8776 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8777 nCmailMovesRegistered--;
8779 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8780 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8781 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8784 if (! RegisterMove()) return FALSE;
8788 retVal = LoadGame(f, gameNumber, title, useList);
8790 /* Make move registered during previous look at this game, if any */
8791 MakeRegisteredMove();
8793 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8794 commentList[currentMove]
8795 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8796 DisplayComment(currentMove - 1, commentList[currentMove]);
8802 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8807 int gameNumber = lastLoadGameNumber + offset;
8808 if (lastLoadGameFP == NULL) {
8809 DisplayError(_("No game has been loaded yet"), 0);
8812 if (gameNumber <= 0) {
8813 DisplayError(_("Can't back up any further"), 0);
8816 if (cmailMsgLoaded) {
8817 return CmailLoadGame(lastLoadGameFP, gameNumber,
8818 lastLoadGameTitle, lastLoadGameUseList);
8820 return LoadGame(lastLoadGameFP, gameNumber,
8821 lastLoadGameTitle, lastLoadGameUseList);
8827 /* Load the nth game from open file f */
8829 LoadGame(f, gameNumber, title, useList)
8837 int gn = gameNumber;
8838 ListGame *lg = NULL;
8841 GameMode oldGameMode;
8842 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8844 if (appData.debugMode)
8845 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8847 if (gameMode == Training )
8848 SetTrainingModeOff();
8850 oldGameMode = gameMode;
8851 if (gameMode != BeginningOfGame) {
8856 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8857 fclose(lastLoadGameFP);
8861 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8864 fseek(f, lg->offset, 0);
8865 GameListHighlight(gameNumber);
8869 DisplayError(_("Game number out of range"), 0);
8874 if (fseek(f, 0, 0) == -1) {
8875 if (f == lastLoadGameFP ?
8876 gameNumber == lastLoadGameNumber + 1 :
8880 DisplayError(_("Can't seek on game file"), 0);
8886 lastLoadGameNumber = gameNumber;
8887 strcpy(lastLoadGameTitle, title);
8888 lastLoadGameUseList = useList;
8892 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8893 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8894 lg->gameInfo.black);
8896 } else if (*title != NULLCHAR) {
8897 if (gameNumber > 1) {
8898 sprintf(buf, "%s %d", title, gameNumber);
8901 DisplayTitle(title);
8905 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8906 gameMode = PlayFromGameFile;
8910 currentMove = forwardMostMove = backwardMostMove = 0;
8911 CopyBoard(boards[0], initialPosition);
8915 * Skip the first gn-1 games in the file.
8916 * Also skip over anything that precedes an identifiable
8917 * start of game marker, to avoid being confused by
8918 * garbage at the start of the file. Currently
8919 * recognized start of game markers are the move number "1",
8920 * the pattern "gnuchess .* game", the pattern
8921 * "^[#;%] [^ ]* game file", and a PGN tag block.
8922 * A game that starts with one of the latter two patterns
8923 * will also have a move number 1, possibly
8924 * following a position diagram.
8925 * 5-4-02: Let's try being more lenient and allowing a game to
8926 * start with an unnumbered move. Does that break anything?
8928 cm = lastLoadGameStart = (ChessMove) 0;
8930 yyboardindex = forwardMostMove;
8931 cm = (ChessMove) yylex();
8934 if (cmailMsgLoaded) {
8935 nCmailGames = CMAIL_MAX_GAMES - gn;
8938 DisplayError(_("Game not found in file"), 0);
8945 lastLoadGameStart = cm;
8949 switch (lastLoadGameStart) {
8956 gn--; /* count this game */
8957 lastLoadGameStart = cm;
8966 switch (lastLoadGameStart) {
8971 gn--; /* count this game */
8972 lastLoadGameStart = cm;
8975 lastLoadGameStart = cm; /* game counted already */
8983 yyboardindex = forwardMostMove;
8984 cm = (ChessMove) yylex();
8985 } while (cm == PGNTag || cm == Comment);
8992 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8993 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8994 != CMAIL_OLD_RESULT) {
8996 cmailResult[ CMAIL_MAX_GAMES
8997 - gn - 1] = CMAIL_OLD_RESULT;
9003 /* Only a NormalMove can be at the start of a game
9004 * without a position diagram. */
9005 if (lastLoadGameStart == (ChessMove) 0) {
9007 lastLoadGameStart = MoveNumberOne;
9016 if (appData.debugMode)
9017 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9019 if (cm == XBoardGame) {
9020 /* Skip any header junk before position diagram and/or move 1 */
9022 yyboardindex = forwardMostMove;
9023 cm = (ChessMove) yylex();
9025 if (cm == (ChessMove) 0 ||
9026 cm == GNUChessGame || cm == XBoardGame) {
9027 /* Empty game; pretend end-of-file and handle later */
9032 if (cm == MoveNumberOne || cm == PositionDiagram ||
9033 cm == PGNTag || cm == Comment)
9036 } else if (cm == GNUChessGame) {
9037 if (gameInfo.event != NULL) {
9038 free(gameInfo.event);
9040 gameInfo.event = StrSave(yy_text);
9043 startedFromSetupPosition = FALSE;
9044 while (cm == PGNTag) {
9045 if (appData.debugMode)
9046 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9047 err = ParsePGNTag(yy_text, &gameInfo);
9048 if (!err) numPGNTags++;
9050 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9051 if(gameInfo.variant != oldVariant) {
9052 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9054 oldVariant = gameInfo.variant;
9055 if (appData.debugMode)
9056 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9060 if (gameInfo.fen != NULL) {
9061 Board initial_position;
9062 startedFromSetupPosition = TRUE;
9063 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9065 DisplayError(_("Bad FEN position in file"), 0);
9068 CopyBoard(boards[0], initial_position);
9069 if (blackPlaysFirst) {
9070 currentMove = forwardMostMove = backwardMostMove = 1;
9071 CopyBoard(boards[1], initial_position);
9072 strcpy(moveList[0], "");
9073 strcpy(parseList[0], "");
9074 timeRemaining[0][1] = whiteTimeRemaining;
9075 timeRemaining[1][1] = blackTimeRemaining;
9076 if (commentList[0] != NULL) {
9077 commentList[1] = commentList[0];
9078 commentList[0] = NULL;
9081 currentMove = forwardMostMove = backwardMostMove = 0;
9083 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9085 initialRulePlies = FENrulePlies;
9086 epStatus[forwardMostMove] = FENepStatus;
9087 for( i=0; i< nrCastlingRights; i++ )
9088 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9090 yyboardindex = forwardMostMove;
9092 gameInfo.fen = NULL;
9095 yyboardindex = forwardMostMove;
9096 cm = (ChessMove) yylex();
9098 /* Handle comments interspersed among the tags */
9099 while (cm == Comment) {
9101 if (appData.debugMode)
9102 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9104 if (*p == '{' || *p == '[' || *p == '(') {
9105 p[strlen(p) - 1] = NULLCHAR;
9108 while (*p == '\n') p++;
9109 AppendComment(currentMove, p);
9110 yyboardindex = forwardMostMove;
9111 cm = (ChessMove) yylex();
9115 /* don't rely on existence of Event tag since if game was
9116 * pasted from clipboard the Event tag may not exist
9118 if (numPGNTags > 0){
9120 if (gameInfo.variant == VariantNormal) {
9121 gameInfo.variant = StringToVariant(gameInfo.event);
9124 if( appData.autoDisplayTags ) {
9125 tags = PGNTags(&gameInfo);
9126 TagsPopUp(tags, CmailMsg());
9131 /* Make something up, but don't display it now */
9136 if (cm == PositionDiagram) {
9139 Board initial_position;
9141 if (appData.debugMode)
9142 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9144 if (!startedFromSetupPosition) {
9146 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9147 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9157 initial_position[i][j++] = CharToPiece(*p);
9160 while (*p == ' ' || *p == '\t' ||
9161 *p == '\n' || *p == '\r') p++;
9163 if (strncmp(p, "black", strlen("black"))==0)
9164 blackPlaysFirst = TRUE;
9166 blackPlaysFirst = FALSE;
9167 startedFromSetupPosition = TRUE;
9169 CopyBoard(boards[0], initial_position);
9170 if (blackPlaysFirst) {
9171 currentMove = forwardMostMove = backwardMostMove = 1;
9172 CopyBoard(boards[1], initial_position);
9173 strcpy(moveList[0], "");
9174 strcpy(parseList[0], "");
9175 timeRemaining[0][1] = whiteTimeRemaining;
9176 timeRemaining[1][1] = blackTimeRemaining;
9177 if (commentList[0] != NULL) {
9178 commentList[1] = commentList[0];
9179 commentList[0] = NULL;
9182 currentMove = forwardMostMove = backwardMostMove = 0;
9185 yyboardindex = forwardMostMove;
9186 cm = (ChessMove) yylex();
9189 if (first.pr == NoProc) {
9190 StartChessProgram(&first);
9192 InitChessProgram(&first, FALSE);
9193 SendToProgram("force\n", &first);
9194 if (startedFromSetupPosition) {
9195 SendBoard(&first, forwardMostMove);
9196 if (appData.debugMode) {
9197 fprintf(debugFP, "Load Game\n");
9199 DisplayBothClocks();
9202 /* [HGM] server: flag to write setup moves in broadcast file as one */
9203 loadFlag = appData.suppressLoadMoves;
9205 while (cm == Comment) {
9207 if (appData.debugMode)
9208 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9210 if (*p == '{' || *p == '[' || *p == '(') {
9211 p[strlen(p) - 1] = NULLCHAR;
9214 while (*p == '\n') p++;
9215 AppendComment(currentMove, p);
9216 yyboardindex = forwardMostMove;
9217 cm = (ChessMove) yylex();
9220 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9221 cm == WhiteWins || cm == BlackWins ||
9222 cm == GameIsDrawn || cm == GameUnfinished) {
9223 DisplayMessage("", _("No moves in game"));
9224 if (cmailMsgLoaded) {
9225 if (appData.debugMode)
9226 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9230 DrawPosition(FALSE, boards[currentMove]);
9231 DisplayBothClocks();
9232 gameMode = EditGame;
9239 // [HGM] PV info: routine tests if comment empty
9240 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9241 DisplayComment(currentMove - 1, commentList[currentMove]);
9243 if (!matchMode && appData.timeDelay != 0)
9244 DrawPosition(FALSE, boards[currentMove]);
9246 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9247 programStats.ok_to_send = 1;
9250 /* if the first token after the PGN tags is a move
9251 * and not move number 1, retrieve it from the parser
9253 if (cm != MoveNumberOne)
9254 LoadGameOneMove(cm);
9256 /* load the remaining moves from the file */
9257 while (LoadGameOneMove((ChessMove)0)) {
9258 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9259 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9262 /* rewind to the start of the game */
9263 currentMove = backwardMostMove;
9265 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9267 if (oldGameMode == AnalyzeFile ||
9268 oldGameMode == AnalyzeMode) {
9272 if (matchMode || appData.timeDelay == 0) {
9274 gameMode = EditGame;
9276 } else if (appData.timeDelay > 0) {
9280 if (appData.debugMode)
9281 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9283 loadFlag = 0; /* [HGM] true game starts */
9287 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9289 ReloadPosition(offset)
9292 int positionNumber = lastLoadPositionNumber + offset;
9293 if (lastLoadPositionFP == NULL) {
9294 DisplayError(_("No position has been loaded yet"), 0);
9297 if (positionNumber <= 0) {
9298 DisplayError(_("Can't back up any further"), 0);
9301 return LoadPosition(lastLoadPositionFP, positionNumber,
9302 lastLoadPositionTitle);
9305 /* Load the nth position from the given file */
9307 LoadPositionFromFile(filename, n, title)
9315 if (strcmp(filename, "-") == 0) {
9316 return LoadPosition(stdin, n, "stdin");
9318 f = fopen(filename, "rb");
9320 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9321 DisplayError(buf, errno);
9324 return LoadPosition(f, n, title);
9329 /* Load the nth position from the given open file, and close it */
9331 LoadPosition(f, positionNumber, title)
9336 char *p, line[MSG_SIZ];
9337 Board initial_position;
9338 int i, j, fenMode, pn;
9340 if (gameMode == Training )
9341 SetTrainingModeOff();
9343 if (gameMode != BeginningOfGame) {
9346 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9347 fclose(lastLoadPositionFP);
9349 if (positionNumber == 0) positionNumber = 1;
9350 lastLoadPositionFP = f;
9351 lastLoadPositionNumber = positionNumber;
9352 strcpy(lastLoadPositionTitle, title);
9353 if (first.pr == NoProc) {
9354 StartChessProgram(&first);
9355 InitChessProgram(&first, FALSE);
9357 pn = positionNumber;
9358 if (positionNumber < 0) {
9359 /* Negative position number means to seek to that byte offset */
9360 if (fseek(f, -positionNumber, 0) == -1) {
9361 DisplayError(_("Can't seek on position file"), 0);
9366 if (fseek(f, 0, 0) == -1) {
9367 if (f == lastLoadPositionFP ?
9368 positionNumber == lastLoadPositionNumber + 1 :
9369 positionNumber == 1) {
9372 DisplayError(_("Can't seek on position file"), 0);
9377 /* See if this file is FEN or old-style xboard */
9378 if (fgets(line, MSG_SIZ, f) == NULL) {
9379 DisplayError(_("Position not found in file"), 0);
9388 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9389 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9390 case '1': case '2': case '3': case '4': case '5': case '6':
9391 case '7': case '8': case '9':
9392 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9393 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9394 case 'C': case 'W': case 'c': case 'w':
9399 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9400 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9404 if (fenMode || line[0] == '#') pn--;
9406 /* skip positions before number pn */
9407 if (fgets(line, MSG_SIZ, f) == NULL) {
9409 DisplayError(_("Position not found in file"), 0);
9412 if (fenMode || line[0] == '#') pn--;
9417 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9418 DisplayError(_("Bad FEN position in file"), 0);
9422 (void) fgets(line, MSG_SIZ, f);
9423 (void) fgets(line, MSG_SIZ, f);
9425 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9426 (void) fgets(line, MSG_SIZ, f);
9427 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9430 initial_position[i][j++] = CharToPiece(*p);
9434 blackPlaysFirst = FALSE;
9436 (void) fgets(line, MSG_SIZ, f);
9437 if (strncmp(line, "black", strlen("black"))==0)
9438 blackPlaysFirst = TRUE;
9441 startedFromSetupPosition = TRUE;
9443 SendToProgram("force\n", &first);
9444 CopyBoard(boards[0], initial_position);
9445 if (blackPlaysFirst) {
9446 currentMove = forwardMostMove = backwardMostMove = 1;
9447 strcpy(moveList[0], "");
9448 strcpy(parseList[0], "");
9449 CopyBoard(boards[1], initial_position);
9450 DisplayMessage("", _("Black to play"));
9452 currentMove = forwardMostMove = backwardMostMove = 0;
9453 DisplayMessage("", _("White to play"));
9455 /* [HGM] copy FEN attributes as well */
9457 initialRulePlies = FENrulePlies;
9458 epStatus[forwardMostMove] = FENepStatus;
9459 for( i=0; i< nrCastlingRights; i++ )
9460 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9462 SendBoard(&first, forwardMostMove);
9463 if (appData.debugMode) {
9465 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9466 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9467 fprintf(debugFP, "Load Position\n");
9470 if (positionNumber > 1) {
9471 sprintf(line, "%s %d", title, positionNumber);
9474 DisplayTitle(title);
9476 gameMode = EditGame;
9479 timeRemaining[0][1] = whiteTimeRemaining;
9480 timeRemaining[1][1] = blackTimeRemaining;
9481 DrawPosition(FALSE, boards[currentMove]);
9488 CopyPlayerNameIntoFileName(dest, src)
9491 while (*src != NULLCHAR && *src != ',') {
9496 *(*dest)++ = *src++;
9501 char *DefaultFileName(ext)
9504 static char def[MSG_SIZ];
9507 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9509 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9511 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9520 /* Save the current game to the given file */
9522 SaveGameToFile(filename, append)
9529 if (strcmp(filename, "-") == 0) {
9530 return SaveGame(stdout, 0, NULL);
9532 f = fopen(filename, append ? "a" : "w");
9534 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9535 DisplayError(buf, errno);
9538 return SaveGame(f, 0, NULL);
9547 static char buf[MSG_SIZ];
9550 p = strchr(str, ' ');
9551 if (p == NULL) return str;
9552 strncpy(buf, str, p - str);
9553 buf[p - str] = NULLCHAR;
9557 #define PGN_MAX_LINE 75
9559 #define PGN_SIDE_WHITE 0
9560 #define PGN_SIDE_BLACK 1
9563 static int FindFirstMoveOutOfBook( int side )
9567 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9568 int index = backwardMostMove;
9569 int has_book_hit = 0;
9571 if( (index % 2) != side ) {
9575 while( index < forwardMostMove ) {
9576 /* Check to see if engine is in book */
9577 int depth = pvInfoList[index].depth;
9578 int score = pvInfoList[index].score;
9584 else if( score == 0 && depth == 63 ) {
9585 in_book = 1; /* Zappa */
9587 else if( score == 2 && depth == 99 ) {
9588 in_book = 1; /* Abrok */
9591 has_book_hit += in_book;
9607 void GetOutOfBookInfo( char * buf )
9611 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9613 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9614 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9618 if( oob[0] >= 0 || oob[1] >= 0 ) {
9619 for( i=0; i<2; i++ ) {
9623 if( i > 0 && oob[0] >= 0 ) {
9627 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9628 sprintf( buf+strlen(buf), "%s%.2f",
9629 pvInfoList[idx].score >= 0 ? "+" : "",
9630 pvInfoList[idx].score / 100.0 );
9636 /* Save game in PGN style and close the file */
9641 int i, offset, linelen, newblock;
9645 int movelen, numlen, blank;
9646 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9648 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9650 tm = time((time_t *) NULL);
9652 PrintPGNTags(f, &gameInfo);
9654 if (backwardMostMove > 0 || startedFromSetupPosition) {
9655 char *fen = PositionToFEN(backwardMostMove, NULL);
9656 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9657 fprintf(f, "\n{--------------\n");
9658 PrintPosition(f, backwardMostMove);
9659 fprintf(f, "--------------}\n");
9663 /* [AS] Out of book annotation */
9664 if( appData.saveOutOfBookInfo ) {
9667 GetOutOfBookInfo( buf );
9669 if( buf[0] != '\0' ) {
9670 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9677 i = backwardMostMove;
9681 while (i < forwardMostMove) {
9682 /* Print comments preceding this move */
9683 if (commentList[i] != NULL) {
9684 if (linelen > 0) fprintf(f, "\n");
9685 fprintf(f, "{\n%s}\n", commentList[i]);
9690 /* Format move number */
9692 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9695 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9697 numtext[0] = NULLCHAR;
9700 numlen = strlen(numtext);
9703 /* Print move number */
9704 blank = linelen > 0 && numlen > 0;
9705 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9714 fprintf(f, numtext);
9718 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9719 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9721 // SavePart already does this!
9722 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9723 int p = movelen - 1;
9724 if(move_buffer[p] == ' ') p--;
9725 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9726 while(p && move_buffer[--p] != '(');
9727 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9732 blank = linelen > 0 && movelen > 0;
9733 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9742 fprintf(f, move_buffer);
9745 /* [AS] Add PV info if present */
9746 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9747 /* [HGM] add time */
9748 char buf[MSG_SIZ]; int seconds = 0;
9751 if(i >= backwardMostMove) {
9753 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9754 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9756 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9757 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9759 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9761 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9764 if( seconds <= 0) buf[0] = 0; else
9765 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9766 seconds = (seconds + 4)/10; // round to full seconds
9767 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9768 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9771 sprintf( move_buffer, "{%s%.2f/%d%s}",
9772 pvInfoList[i].score >= 0 ? "+" : "",
9773 pvInfoList[i].score / 100.0,
9774 pvInfoList[i].depth,
9777 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9779 /* Print score/depth */
9780 blank = linelen > 0 && movelen > 0;
9781 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9790 fprintf(f, move_buffer);
9797 /* Start a new line */
9798 if (linelen > 0) fprintf(f, "\n");
9800 /* Print comments after last move */
9801 if (commentList[i] != NULL) {
9802 fprintf(f, "{\n%s}\n", commentList[i]);
9806 if (gameInfo.resultDetails != NULL &&
9807 gameInfo.resultDetails[0] != NULLCHAR) {
9808 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9809 PGNResult(gameInfo.result));
9811 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9815 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9819 /* Save game in old style and close the file */
9827 tm = time((time_t *) NULL);
9829 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9832 if (backwardMostMove > 0 || startedFromSetupPosition) {
9833 fprintf(f, "\n[--------------\n");
9834 PrintPosition(f, backwardMostMove);
9835 fprintf(f, "--------------]\n");
9840 i = backwardMostMove;
9841 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9843 while (i < forwardMostMove) {
9844 if (commentList[i] != NULL) {
9845 fprintf(f, "[%s]\n", commentList[i]);
9849 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9852 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9854 if (commentList[i] != NULL) {
9858 if (i >= forwardMostMove) {
9862 fprintf(f, "%s\n", parseList[i]);
9867 if (commentList[i] != NULL) {
9868 fprintf(f, "[%s]\n", commentList[i]);
9871 /* This isn't really the old style, but it's close enough */
9872 if (gameInfo.resultDetails != NULL &&
9873 gameInfo.resultDetails[0] != NULLCHAR) {
9874 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9875 gameInfo.resultDetails);
9877 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9884 /* Save the current game to open file f and close the file */
9886 SaveGame(f, dummy, dummy2)
9891 if (gameMode == EditPosition) EditPositionDone();
9892 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9893 if (appData.oldSaveStyle)
9894 return SaveGameOldStyle(f);
9896 return SaveGamePGN(f);
9899 /* Save the current position to the given file */
9901 SavePositionToFile(filename)
9907 if (strcmp(filename, "-") == 0) {
9908 return SavePosition(stdout, 0, NULL);
9910 f = fopen(filename, "a");
9912 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9913 DisplayError(buf, errno);
9916 SavePosition(f, 0, NULL);
9922 /* Save the current position to the given open file and close the file */
9924 SavePosition(f, dummy, dummy2)
9932 if (appData.oldSaveStyle) {
9933 tm = time((time_t *) NULL);
9935 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9937 fprintf(f, "[--------------\n");
9938 PrintPosition(f, currentMove);
9939 fprintf(f, "--------------]\n");
9941 fen = PositionToFEN(currentMove, NULL);
9942 fprintf(f, "%s\n", fen);
9950 ReloadCmailMsgEvent(unregister)
9954 static char *inFilename = NULL;
9955 static char *outFilename;
9957 struct stat inbuf, outbuf;
9960 /* Any registered moves are unregistered if unregister is set, */
9961 /* i.e. invoked by the signal handler */
9963 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9964 cmailMoveRegistered[i] = FALSE;
9965 if (cmailCommentList[i] != NULL) {
9966 free(cmailCommentList[i]);
9967 cmailCommentList[i] = NULL;
9970 nCmailMovesRegistered = 0;
9973 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9974 cmailResult[i] = CMAIL_NOT_RESULT;
9978 if (inFilename == NULL) {
9979 /* Because the filenames are static they only get malloced once */
9980 /* and they never get freed */
9981 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9982 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9984 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9985 sprintf(outFilename, "%s.out", appData.cmailGameName);
9988 status = stat(outFilename, &outbuf);
9990 cmailMailedMove = FALSE;
9992 status = stat(inFilename, &inbuf);
9993 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9996 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9997 counts the games, notes how each one terminated, etc.
9999 It would be nice to remove this kludge and instead gather all
10000 the information while building the game list. (And to keep it
10001 in the game list nodes instead of having a bunch of fixed-size
10002 parallel arrays.) Note this will require getting each game's
10003 termination from the PGN tags, as the game list builder does
10004 not process the game moves. --mann
10006 cmailMsgLoaded = TRUE;
10007 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10009 /* Load first game in the file or popup game menu */
10010 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10012 #endif /* !WIN32 */
10020 char string[MSG_SIZ];
10022 if ( cmailMailedMove
10023 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10024 return TRUE; /* Allow free viewing */
10027 /* Unregister move to ensure that we don't leave RegisterMove */
10028 /* with the move registered when the conditions for registering no */
10030 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10031 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10032 nCmailMovesRegistered --;
10034 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10036 free(cmailCommentList[lastLoadGameNumber - 1]);
10037 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10041 if (cmailOldMove == -1) {
10042 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10046 if (currentMove > cmailOldMove + 1) {
10047 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10051 if (currentMove < cmailOldMove) {
10052 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10056 if (forwardMostMove > currentMove) {
10057 /* Silently truncate extra moves */
10061 if ( (currentMove == cmailOldMove + 1)
10062 || ( (currentMove == cmailOldMove)
10063 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10064 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10065 if (gameInfo.result != GameUnfinished) {
10066 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10069 if (commentList[currentMove] != NULL) {
10070 cmailCommentList[lastLoadGameNumber - 1]
10071 = StrSave(commentList[currentMove]);
10073 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10075 if (appData.debugMode)
10076 fprintf(debugFP, "Saving %s for game %d\n",
10077 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10080 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10082 f = fopen(string, "w");
10083 if (appData.oldSaveStyle) {
10084 SaveGameOldStyle(f); /* also closes the file */
10086 sprintf(string, "%s.pos.out", appData.cmailGameName);
10087 f = fopen(string, "w");
10088 SavePosition(f, 0, NULL); /* also closes the file */
10090 fprintf(f, "{--------------\n");
10091 PrintPosition(f, currentMove);
10092 fprintf(f, "--------------}\n\n");
10094 SaveGame(f, 0, NULL); /* also closes the file*/
10097 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10098 nCmailMovesRegistered ++;
10099 } else if (nCmailGames == 1) {
10100 DisplayError(_("You have not made a move yet"), 0);
10111 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10112 FILE *commandOutput;
10113 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10114 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10120 if (! cmailMsgLoaded) {
10121 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10125 if (nCmailGames == nCmailResults) {
10126 DisplayError(_("No unfinished games"), 0);
10130 #if CMAIL_PROHIBIT_REMAIL
10131 if (cmailMailedMove) {
10132 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);
10133 DisplayError(msg, 0);
10138 if (! (cmailMailedMove || RegisterMove())) return;
10140 if ( cmailMailedMove
10141 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10142 sprintf(string, partCommandString,
10143 appData.debugMode ? " -v" : "", appData.cmailGameName);
10144 commandOutput = popen(string, "r");
10146 if (commandOutput == NULL) {
10147 DisplayError(_("Failed to invoke cmail"), 0);
10149 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10150 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10152 if (nBuffers > 1) {
10153 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10154 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10155 nBytes = MSG_SIZ - 1;
10157 (void) memcpy(msg, buffer, nBytes);
10159 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10161 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10162 cmailMailedMove = TRUE; /* Prevent >1 moves */
10165 for (i = 0; i < nCmailGames; i ++) {
10166 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10171 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10173 sprintf(buffer, "%s/%s.%s.archive",
10175 appData.cmailGameName,
10177 LoadGameFromFile(buffer, 1, buffer, FALSE);
10178 cmailMsgLoaded = FALSE;
10182 DisplayInformation(msg);
10183 pclose(commandOutput);
10186 if ((*cmailMsg) != '\0') {
10187 DisplayInformation(cmailMsg);
10192 #endif /* !WIN32 */
10201 int prependComma = 0;
10203 char string[MSG_SIZ]; /* Space for game-list */
10206 if (!cmailMsgLoaded) return "";
10208 if (cmailMailedMove) {
10209 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10211 /* Create a list of games left */
10212 sprintf(string, "[");
10213 for (i = 0; i < nCmailGames; i ++) {
10214 if (! ( cmailMoveRegistered[i]
10215 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10216 if (prependComma) {
10217 sprintf(number, ",%d", i + 1);
10219 sprintf(number, "%d", i + 1);
10223 strcat(string, number);
10226 strcat(string, "]");
10228 if (nCmailMovesRegistered + nCmailResults == 0) {
10229 switch (nCmailGames) {
10232 _("Still need to make move for game\n"));
10237 _("Still need to make moves for both games\n"));
10242 _("Still need to make moves for all %d games\n"),
10247 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10250 _("Still need to make a move for game %s\n"),
10255 if (nCmailResults == nCmailGames) {
10256 sprintf(cmailMsg, _("No unfinished games\n"));
10258 sprintf(cmailMsg, _("Ready to send mail\n"));
10264 _("Still need to make moves for games %s\n"),
10276 if (gameMode == Training)
10277 SetTrainingModeOff();
10280 cmailMsgLoaded = FALSE;
10281 if (appData.icsActive) {
10282 SendToICS(ics_prefix);
10283 SendToICS("refresh\n");
10293 /* Give up on clean exit */
10297 /* Keep trying for clean exit */
10301 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10303 if (telnetISR != NULL) {
10304 RemoveInputSource(telnetISR);
10306 if (icsPR != NoProc) {
10307 DestroyChildProcess(icsPR, TRUE);
10310 /* Save game if resource set and not already saved by GameEnds() */
10311 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10312 && forwardMostMove > 0) {
10313 if (*appData.saveGameFile != NULLCHAR) {
10314 SaveGameToFile(appData.saveGameFile, TRUE);
10315 } else if (appData.autoSaveGames) {
10318 if (*appData.savePositionFile != NULLCHAR) {
10319 SavePositionToFile(appData.savePositionFile);
10322 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10324 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10325 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10327 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10328 /* make sure this other one finishes before killing it! */
10329 if(endingGame) { int count = 0;
10330 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10331 while(endingGame && count++ < 10) DoSleep(1);
10332 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10335 /* Kill off chess programs */
10336 if (first.pr != NoProc) {
10339 DoSleep( appData.delayBeforeQuit );
10340 SendToProgram("quit\n", &first);
10341 DoSleep( appData.delayAfterQuit );
10342 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10344 if (second.pr != NoProc) {
10345 DoSleep( appData.delayBeforeQuit );
10346 SendToProgram("quit\n", &second);
10347 DoSleep( appData.delayAfterQuit );
10348 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10350 if (first.isr != NULL) {
10351 RemoveInputSource(first.isr);
10353 if (second.isr != NULL) {
10354 RemoveInputSource(second.isr);
10357 ShutDownFrontEnd();
10364 if (appData.debugMode)
10365 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10369 if (gameMode == MachinePlaysWhite ||
10370 gameMode == MachinePlaysBlack) {
10373 DisplayBothClocks();
10375 if (gameMode == PlayFromGameFile) {
10376 if (appData.timeDelay >= 0)
10377 AutoPlayGameLoop();
10378 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10379 Reset(FALSE, TRUE);
10380 SendToICS(ics_prefix);
10381 SendToICS("refresh\n");
10382 } else if (currentMove < forwardMostMove) {
10383 ForwardInner(forwardMostMove);
10385 pauseExamInvalid = FALSE;
10387 switch (gameMode) {
10391 pauseExamForwardMostMove = forwardMostMove;
10392 pauseExamInvalid = FALSE;
10395 case IcsPlayingWhite:
10396 case IcsPlayingBlack:
10400 case PlayFromGameFile:
10401 (void) StopLoadGameTimer();
10405 case BeginningOfGame:
10406 if (appData.icsActive) return;
10407 /* else fall through */
10408 case MachinePlaysWhite:
10409 case MachinePlaysBlack:
10410 case TwoMachinesPlay:
10411 if (forwardMostMove == 0)
10412 return; /* don't pause if no one has moved */
10413 if ((gameMode == MachinePlaysWhite &&
10414 !WhiteOnMove(forwardMostMove)) ||
10415 (gameMode == MachinePlaysBlack &&
10416 WhiteOnMove(forwardMostMove))) {
10429 char title[MSG_SIZ];
10431 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10432 strcpy(title, _("Edit comment"));
10434 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10435 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10436 parseList[currentMove - 1]);
10439 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10446 char *tags = PGNTags(&gameInfo);
10447 EditTagsPopUp(tags);
10454 if (appData.noChessProgram || gameMode == AnalyzeMode)
10457 if (gameMode != AnalyzeFile) {
10458 if (!appData.icsEngineAnalyze) {
10460 if (gameMode != EditGame) return;
10462 ResurrectChessProgram();
10463 SendToProgram("analyze\n", &first);
10464 first.analyzing = TRUE;
10465 /*first.maybeThinking = TRUE;*/
10466 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10467 AnalysisPopUp(_("Analysis"),
10468 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10470 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10475 StartAnalysisClock();
10476 GetTimeMark(&lastNodeCountTime);
10483 if (appData.noChessProgram || gameMode == AnalyzeFile)
10486 if (gameMode != AnalyzeMode) {
10488 if (gameMode != EditGame) return;
10489 ResurrectChessProgram();
10490 SendToProgram("analyze\n", &first);
10491 first.analyzing = TRUE;
10492 /*first.maybeThinking = TRUE;*/
10493 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10494 AnalysisPopUp(_("Analysis"),
10495 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10497 gameMode = AnalyzeFile;
10502 StartAnalysisClock();
10503 GetTimeMark(&lastNodeCountTime);
10508 MachineWhiteEvent()
10511 char *bookHit = NULL;
10513 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10517 if (gameMode == PlayFromGameFile ||
10518 gameMode == TwoMachinesPlay ||
10519 gameMode == Training ||
10520 gameMode == AnalyzeMode ||
10521 gameMode == EndOfGame)
10524 if (gameMode == EditPosition)
10525 EditPositionDone();
10527 if (!WhiteOnMove(currentMove)) {
10528 DisplayError(_("It is not White's turn"), 0);
10532 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10535 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10536 gameMode == AnalyzeFile)
10539 ResurrectChessProgram(); /* in case it isn't running */
10540 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10541 gameMode = MachinePlaysWhite;
10544 gameMode = MachinePlaysWhite;
10548 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10550 if (first.sendName) {
10551 sprintf(buf, "name %s\n", gameInfo.black);
10552 SendToProgram(buf, &first);
10554 if (first.sendTime) {
10555 if (first.useColors) {
10556 SendToProgram("black\n", &first); /*gnu kludge*/
10558 SendTimeRemaining(&first, TRUE);
10560 if (first.useColors) {
10561 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10563 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10564 SetMachineThinkingEnables();
10565 first.maybeThinking = TRUE;
10569 if (appData.autoFlipView && !flipView) {
10570 flipView = !flipView;
10571 DrawPosition(FALSE, NULL);
10572 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10575 if(bookHit) { // [HGM] book: simulate book reply
10576 static char bookMove[MSG_SIZ]; // a bit generous?
10578 programStats.nodes = programStats.depth = programStats.time =
10579 programStats.score = programStats.got_only_move = 0;
10580 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10582 strcpy(bookMove, "move ");
10583 strcat(bookMove, bookHit);
10584 HandleMachineMove(bookMove, &first);
10589 MachineBlackEvent()
10592 char *bookHit = NULL;
10594 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10598 if (gameMode == PlayFromGameFile ||
10599 gameMode == TwoMachinesPlay ||
10600 gameMode == Training ||
10601 gameMode == AnalyzeMode ||
10602 gameMode == EndOfGame)
10605 if (gameMode == EditPosition)
10606 EditPositionDone();
10608 if (WhiteOnMove(currentMove)) {
10609 DisplayError(_("It is not Black's turn"), 0);
10613 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10616 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10617 gameMode == AnalyzeFile)
10620 ResurrectChessProgram(); /* in case it isn't running */
10621 gameMode = MachinePlaysBlack;
10625 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10627 if (first.sendName) {
10628 sprintf(buf, "name %s\n", gameInfo.white);
10629 SendToProgram(buf, &first);
10631 if (first.sendTime) {
10632 if (first.useColors) {
10633 SendToProgram("white\n", &first); /*gnu kludge*/
10635 SendTimeRemaining(&first, FALSE);
10637 if (first.useColors) {
10638 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10640 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10641 SetMachineThinkingEnables();
10642 first.maybeThinking = TRUE;
10645 if (appData.autoFlipView && flipView) {
10646 flipView = !flipView;
10647 DrawPosition(FALSE, NULL);
10648 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10650 if(bookHit) { // [HGM] book: simulate book reply
10651 static char bookMove[MSG_SIZ]; // a bit generous?
10653 programStats.nodes = programStats.depth = programStats.time =
10654 programStats.score = programStats.got_only_move = 0;
10655 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10657 strcpy(bookMove, "move ");
10658 strcat(bookMove, bookHit);
10659 HandleMachineMove(bookMove, &first);
10665 DisplayTwoMachinesTitle()
10668 if (appData.matchGames > 0) {
10669 if (first.twoMachinesColor[0] == 'w') {
10670 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10671 gameInfo.white, gameInfo.black,
10672 first.matchWins, second.matchWins,
10673 matchGame - 1 - (first.matchWins + second.matchWins));
10675 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10676 gameInfo.white, gameInfo.black,
10677 second.matchWins, first.matchWins,
10678 matchGame - 1 - (first.matchWins + second.matchWins));
10681 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10687 TwoMachinesEvent P((void))
10691 ChessProgramState *onmove;
10692 char *bookHit = NULL;
10694 if (appData.noChessProgram) return;
10696 switch (gameMode) {
10697 case TwoMachinesPlay:
10699 case MachinePlaysWhite:
10700 case MachinePlaysBlack:
10701 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10702 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10706 case BeginningOfGame:
10707 case PlayFromGameFile:
10710 if (gameMode != EditGame) return;
10713 EditPositionDone();
10724 forwardMostMove = currentMove;
10725 ResurrectChessProgram(); /* in case first program isn't running */
10727 if (second.pr == NULL) {
10728 StartChessProgram(&second);
10729 if (second.protocolVersion == 1) {
10730 TwoMachinesEventIfReady();
10732 /* kludge: allow timeout for initial "feature" command */
10734 DisplayMessage("", _("Starting second chess program"));
10735 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10739 DisplayMessage("", "");
10740 InitChessProgram(&second, FALSE);
10741 SendToProgram("force\n", &second);
10742 if (startedFromSetupPosition) {
10743 SendBoard(&second, backwardMostMove);
10744 if (appData.debugMode) {
10745 fprintf(debugFP, "Two Machines\n");
10748 for (i = backwardMostMove; i < forwardMostMove; i++) {
10749 SendMoveToProgram(i, &second);
10752 gameMode = TwoMachinesPlay;
10756 DisplayTwoMachinesTitle();
10758 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10764 SendToProgram(first.computerString, &first);
10765 if (first.sendName) {
10766 sprintf(buf, "name %s\n", second.tidy);
10767 SendToProgram(buf, &first);
10769 SendToProgram(second.computerString, &second);
10770 if (second.sendName) {
10771 sprintf(buf, "name %s\n", first.tidy);
10772 SendToProgram(buf, &second);
10776 if (!first.sendTime || !second.sendTime) {
10777 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10778 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10780 if (onmove->sendTime) {
10781 if (onmove->useColors) {
10782 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10784 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10786 if (onmove->useColors) {
10787 SendToProgram(onmove->twoMachinesColor, onmove);
10789 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10790 // SendToProgram("go\n", onmove);
10791 onmove->maybeThinking = TRUE;
10792 SetMachineThinkingEnables();
10796 if(bookHit) { // [HGM] book: simulate book reply
10797 static char bookMove[MSG_SIZ]; // a bit generous?
10799 programStats.nodes = programStats.depth = programStats.time =
10800 programStats.score = programStats.got_only_move = 0;
10801 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10803 strcpy(bookMove, "move ");
10804 strcat(bookMove, bookHit);
10805 HandleMachineMove(bookMove, &first);
10812 if (gameMode == Training) {
10813 SetTrainingModeOff();
10814 gameMode = PlayFromGameFile;
10815 DisplayMessage("", _("Training mode off"));
10817 gameMode = Training;
10818 animateTraining = appData.animate;
10820 /* make sure we are not already at the end of the game */
10821 if (currentMove < forwardMostMove) {
10822 SetTrainingModeOn();
10823 DisplayMessage("", _("Training mode on"));
10825 gameMode = PlayFromGameFile;
10826 DisplayError(_("Already at end of game"), 0);
10835 if (!appData.icsActive) return;
10836 switch (gameMode) {
10837 case IcsPlayingWhite:
10838 case IcsPlayingBlack:
10841 case BeginningOfGame:
10849 EditPositionDone();
10862 gameMode = IcsIdle;
10873 switch (gameMode) {
10875 SetTrainingModeOff();
10877 case MachinePlaysWhite:
10878 case MachinePlaysBlack:
10879 case BeginningOfGame:
10880 SendToProgram("force\n", &first);
10881 SetUserThinkingEnables();
10883 case PlayFromGameFile:
10884 (void) StopLoadGameTimer();
10885 if (gameFileFP != NULL) {
10890 EditPositionDone();
10895 SendToProgram("force\n", &first);
10897 case TwoMachinesPlay:
10898 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10899 ResurrectChessProgram();
10900 SetUserThinkingEnables();
10903 ResurrectChessProgram();
10905 case IcsPlayingBlack:
10906 case IcsPlayingWhite:
10907 DisplayError(_("Warning: You are still playing a game"), 0);
10910 DisplayError(_("Warning: You are still observing a game"), 0);
10913 DisplayError(_("Warning: You are still examining a game"), 0);
10924 first.offeredDraw = second.offeredDraw = 0;
10926 if (gameMode == PlayFromGameFile) {
10927 whiteTimeRemaining = timeRemaining[0][currentMove];
10928 blackTimeRemaining = timeRemaining[1][currentMove];
10932 if (gameMode == MachinePlaysWhite ||
10933 gameMode == MachinePlaysBlack ||
10934 gameMode == TwoMachinesPlay ||
10935 gameMode == EndOfGame) {
10936 i = forwardMostMove;
10937 while (i > currentMove) {
10938 SendToProgram("undo\n", &first);
10941 whiteTimeRemaining = timeRemaining[0][currentMove];
10942 blackTimeRemaining = timeRemaining[1][currentMove];
10943 DisplayBothClocks();
10944 if (whiteFlag || blackFlag) {
10945 whiteFlag = blackFlag = 0;
10950 gameMode = EditGame;
10957 EditPositionEvent()
10959 if (gameMode == EditPosition) {
10965 if (gameMode != EditGame) return;
10967 gameMode = EditPosition;
10970 if (currentMove > 0)
10971 CopyBoard(boards[0], boards[currentMove]);
10973 blackPlaysFirst = !WhiteOnMove(currentMove);
10975 currentMove = forwardMostMove = backwardMostMove = 0;
10976 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10983 /* [DM] icsEngineAnalyze - possible call from other functions */
10984 if (appData.icsEngineAnalyze) {
10985 appData.icsEngineAnalyze = FALSE;
10987 DisplayMessage("",_("Close ICS engine analyze..."));
10989 if (first.analysisSupport && first.analyzing) {
10990 SendToProgram("exit\n", &first);
10991 first.analyzing = FALSE;
10994 thinkOutput[0] = NULLCHAR;
11000 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11002 startedFromSetupPosition = TRUE;
11003 InitChessProgram(&first, FALSE);
11004 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11005 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11006 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11007 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11008 } else castlingRights[0][2] = -1;
11009 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11010 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11011 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11012 } else castlingRights[0][5] = -1;
11013 SendToProgram("force\n", &first);
11014 if (blackPlaysFirst) {
11015 strcpy(moveList[0], "");
11016 strcpy(parseList[0], "");
11017 currentMove = forwardMostMove = backwardMostMove = 1;
11018 CopyBoard(boards[1], boards[0]);
11019 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11021 epStatus[1] = epStatus[0];
11022 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11025 currentMove = forwardMostMove = backwardMostMove = 0;
11027 SendBoard(&first, forwardMostMove);
11028 if (appData.debugMode) {
11029 fprintf(debugFP, "EditPosDone\n");
11032 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11033 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11034 gameMode = EditGame;
11036 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11037 ClearHighlights(); /* [AS] */
11040 /* Pause for `ms' milliseconds */
11041 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11051 } while (SubtractTimeMarks(&m2, &m1) < ms);
11054 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11056 SendMultiLineToICS(buf)
11059 char temp[MSG_SIZ+1], *p;
11066 strncpy(temp, buf, len);
11071 if (*p == '\n' || *p == '\r')
11076 strcat(temp, "\n");
11078 SendToPlayer(temp, strlen(temp));
11082 SetWhiteToPlayEvent()
11084 if (gameMode == EditPosition) {
11085 blackPlaysFirst = FALSE;
11086 DisplayBothClocks(); /* works because currentMove is 0 */
11087 } else if (gameMode == IcsExamining) {
11088 SendToICS(ics_prefix);
11089 SendToICS("tomove white\n");
11094 SetBlackToPlayEvent()
11096 if (gameMode == EditPosition) {
11097 blackPlaysFirst = TRUE;
11098 currentMove = 1; /* kludge */
11099 DisplayBothClocks();
11101 } else if (gameMode == IcsExamining) {
11102 SendToICS(ics_prefix);
11103 SendToICS("tomove black\n");
11108 EditPositionMenuEvent(selection, x, y)
11109 ChessSquare selection;
11113 ChessSquare piece = boards[0][y][x];
11115 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11117 switch (selection) {
11119 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11120 SendToICS(ics_prefix);
11121 SendToICS("bsetup clear\n");
11122 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11123 SendToICS(ics_prefix);
11124 SendToICS("clearboard\n");
11126 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11127 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11128 for (y = 0; y < BOARD_HEIGHT; y++) {
11129 if (gameMode == IcsExamining) {
11130 if (boards[currentMove][y][x] != EmptySquare) {
11131 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11136 boards[0][y][x] = p;
11141 if (gameMode == EditPosition) {
11142 DrawPosition(FALSE, boards[0]);
11147 SetWhiteToPlayEvent();
11151 SetBlackToPlayEvent();
11155 if (gameMode == IcsExamining) {
11156 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11159 boards[0][y][x] = EmptySquare;
11160 DrawPosition(FALSE, boards[0]);
11165 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11166 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11167 selection = (ChessSquare) (PROMOTED piece);
11168 } else if(piece == EmptySquare) selection = WhiteSilver;
11169 else selection = (ChessSquare)((int)piece - 1);
11173 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11174 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11175 selection = (ChessSquare) (DEMOTED piece);
11176 } else if(piece == EmptySquare) selection = BlackSilver;
11177 else selection = (ChessSquare)((int)piece + 1);
11182 if(gameInfo.variant == VariantShatranj ||
11183 gameInfo.variant == VariantXiangqi ||
11184 gameInfo.variant == VariantCourier )
11185 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11190 if(gameInfo.variant == VariantXiangqi)
11191 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11192 if(gameInfo.variant == VariantKnightmate)
11193 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11196 if (gameMode == IcsExamining) {
11197 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11198 PieceToChar(selection), AAA + x, ONE + y);
11201 boards[0][y][x] = selection;
11202 DrawPosition(FALSE, boards[0]);
11210 DropMenuEvent(selection, x, y)
11211 ChessSquare selection;
11214 ChessMove moveType;
11216 switch (gameMode) {
11217 case IcsPlayingWhite:
11218 case MachinePlaysBlack:
11219 if (!WhiteOnMove(currentMove)) {
11220 DisplayMoveError(_("It is Black's turn"));
11223 moveType = WhiteDrop;
11225 case IcsPlayingBlack:
11226 case MachinePlaysWhite:
11227 if (WhiteOnMove(currentMove)) {
11228 DisplayMoveError(_("It is White's turn"));
11231 moveType = BlackDrop;
11234 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11240 if (moveType == BlackDrop && selection < BlackPawn) {
11241 selection = (ChessSquare) ((int) selection
11242 + (int) BlackPawn - (int) WhitePawn);
11244 if (boards[currentMove][y][x] != EmptySquare) {
11245 DisplayMoveError(_("That square is occupied"));
11249 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11255 /* Accept a pending offer of any kind from opponent */
11257 if (appData.icsActive) {
11258 SendToICS(ics_prefix);
11259 SendToICS("accept\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 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11267 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11269 DisplayError(_("There is no pending offer on this move"), 0);
11270 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11273 /* Not used for offers from chess program */
11280 /* Decline a pending offer of any kind from opponent */
11282 if (appData.icsActive) {
11283 SendToICS(ics_prefix);
11284 SendToICS("decline\n");
11285 } else if (cmailMsgLoaded) {
11286 if (currentMove == cmailOldMove &&
11287 commentList[cmailOldMove] != NULL &&
11288 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11289 "Black offers a draw" : "White offers a draw")) {
11291 AppendComment(cmailOldMove, "Draw declined");
11292 DisplayComment(cmailOldMove - 1, "Draw declined");
11295 DisplayError(_("There is no pending offer on this move"), 0);
11298 /* Not used for offers from chess program */
11305 /* Issue ICS rematch command */
11306 if (appData.icsActive) {
11307 SendToICS(ics_prefix);
11308 SendToICS("rematch\n");
11315 /* Call your opponent's flag (claim a win on time) */
11316 if (appData.icsActive) {
11317 SendToICS(ics_prefix);
11318 SendToICS("flag\n");
11320 switch (gameMode) {
11323 case MachinePlaysWhite:
11326 GameEnds(GameIsDrawn, "Both players ran out of time",
11329 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11331 DisplayError(_("Your opponent is not out of time"), 0);
11334 case MachinePlaysBlack:
11337 GameEnds(GameIsDrawn, "Both players ran out of time",
11340 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11342 DisplayError(_("Your opponent is not out of time"), 0);
11352 /* Offer draw or accept pending draw offer from opponent */
11354 if (appData.icsActive) {
11355 /* Note: tournament rules require draw offers to be
11356 made after you make your move but before you punch
11357 your clock. Currently ICS doesn't let you do that;
11358 instead, you immediately punch your clock after making
11359 a move, but you can offer a draw at any time. */
11361 SendToICS(ics_prefix);
11362 SendToICS("draw\n");
11363 } else if (cmailMsgLoaded) {
11364 if (currentMove == cmailOldMove &&
11365 commentList[cmailOldMove] != NULL &&
11366 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11367 "Black offers a draw" : "White offers a draw")) {
11368 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11369 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11370 } else if (currentMove == cmailOldMove + 1) {
11371 char *offer = WhiteOnMove(cmailOldMove) ?
11372 "White offers a draw" : "Black offers a draw";
11373 AppendComment(currentMove, offer);
11374 DisplayComment(currentMove - 1, offer);
11375 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11377 DisplayError(_("You must make your move before offering a draw"), 0);
11378 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11380 } else if (first.offeredDraw) {
11381 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11383 if (first.sendDrawOffers) {
11384 SendToProgram("draw\n", &first);
11385 userOfferedDraw = TRUE;
11393 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11395 if (appData.icsActive) {
11396 SendToICS(ics_prefix);
11397 SendToICS("adjourn\n");
11399 /* Currently GNU Chess doesn't offer or accept Adjourns */
11407 /* Offer Abort or accept pending Abort offer from opponent */
11409 if (appData.icsActive) {
11410 SendToICS(ics_prefix);
11411 SendToICS("abort\n");
11413 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11420 /* Resign. You can do this even if it's not your turn. */
11422 if (appData.icsActive) {
11423 SendToICS(ics_prefix);
11424 SendToICS("resign\n");
11426 switch (gameMode) {
11427 case MachinePlaysWhite:
11428 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11430 case MachinePlaysBlack:
11431 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11434 if (cmailMsgLoaded) {
11436 if (WhiteOnMove(cmailOldMove)) {
11437 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11439 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11441 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11452 StopObservingEvent()
11454 /* Stop observing current games */
11455 SendToICS(ics_prefix);
11456 SendToICS("unobserve\n");
11460 StopExaminingEvent()
11462 /* Stop observing current game */
11463 SendToICS(ics_prefix);
11464 SendToICS("unexamine\n");
11468 ForwardInner(target)
11473 if (appData.debugMode)
11474 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11475 target, currentMove, forwardMostMove);
11477 if (gameMode == EditPosition)
11480 if (gameMode == PlayFromGameFile && !pausing)
11483 if (gameMode == IcsExamining && pausing)
11484 limit = pauseExamForwardMostMove;
11486 limit = forwardMostMove;
11488 if (target > limit) target = limit;
11490 if (target > 0 && moveList[target - 1][0]) {
11491 int fromX, fromY, toX, toY;
11492 toX = moveList[target - 1][2] - AAA;
11493 toY = moveList[target - 1][3] - ONE;
11494 if (moveList[target - 1][1] == '@') {
11495 if (appData.highlightLastMove) {
11496 SetHighlights(-1, -1, toX, toY);
11499 fromX = moveList[target - 1][0] - AAA;
11500 fromY = moveList[target - 1][1] - ONE;
11501 if (target == currentMove + 1) {
11502 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11504 if (appData.highlightLastMove) {
11505 SetHighlights(fromX, fromY, toX, toY);
11509 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11510 gameMode == Training || gameMode == PlayFromGameFile ||
11511 gameMode == AnalyzeFile) {
11512 while (currentMove < target) {
11513 SendMoveToProgram(currentMove++, &first);
11516 currentMove = target;
11519 if (gameMode == EditGame || gameMode == EndOfGame) {
11520 whiteTimeRemaining = timeRemaining[0][currentMove];
11521 blackTimeRemaining = timeRemaining[1][currentMove];
11523 DisplayBothClocks();
11524 DisplayMove(currentMove - 1);
11525 DrawPosition(FALSE, boards[currentMove]);
11526 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11527 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11528 DisplayComment(currentMove - 1, commentList[currentMove]);
11536 if (gameMode == IcsExamining && !pausing) {
11537 SendToICS(ics_prefix);
11538 SendToICS("forward\n");
11540 ForwardInner(currentMove + 1);
11547 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11548 /* to optimze, we temporarily turn off analysis mode while we feed
11549 * the remaining moves to the engine. Otherwise we get analysis output
11552 if (first.analysisSupport) {
11553 SendToProgram("exit\nforce\n", &first);
11554 first.analyzing = FALSE;
11558 if (gameMode == IcsExamining && !pausing) {
11559 SendToICS(ics_prefix);
11560 SendToICS("forward 999999\n");
11562 ForwardInner(forwardMostMove);
11565 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11566 /* we have fed all the moves, so reactivate analysis mode */
11567 SendToProgram("analyze\n", &first);
11568 first.analyzing = TRUE;
11569 /*first.maybeThinking = TRUE;*/
11570 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11575 BackwardInner(target)
11578 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11580 if (appData.debugMode)
11581 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11582 target, currentMove, forwardMostMove);
11584 if (gameMode == EditPosition) return;
11585 if (currentMove <= backwardMostMove) {
11587 DrawPosition(full_redraw, boards[currentMove]);
11590 if (gameMode == PlayFromGameFile && !pausing)
11593 if (moveList[target][0]) {
11594 int fromX, fromY, toX, toY;
11595 toX = moveList[target][2] - AAA;
11596 toY = moveList[target][3] - ONE;
11597 if (moveList[target][1] == '@') {
11598 if (appData.highlightLastMove) {
11599 SetHighlights(-1, -1, toX, toY);
11602 fromX = moveList[target][0] - AAA;
11603 fromY = moveList[target][1] - ONE;
11604 if (target == currentMove - 1) {
11605 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11607 if (appData.highlightLastMove) {
11608 SetHighlights(fromX, fromY, toX, toY);
11612 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11613 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11614 while (currentMove > target) {
11615 SendToProgram("undo\n", &first);
11619 currentMove = target;
11622 if (gameMode == EditGame || gameMode == EndOfGame) {
11623 whiteTimeRemaining = timeRemaining[0][currentMove];
11624 blackTimeRemaining = timeRemaining[1][currentMove];
11626 DisplayBothClocks();
11627 DisplayMove(currentMove - 1);
11628 DrawPosition(full_redraw, boards[currentMove]);
11629 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11630 // [HGM] PV info: routine tests if comment empty
11631 DisplayComment(currentMove - 1, commentList[currentMove]);
11637 if (gameMode == IcsExamining && !pausing) {
11638 SendToICS(ics_prefix);
11639 SendToICS("backward\n");
11641 BackwardInner(currentMove - 1);
11648 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11649 /* to optimze, we temporarily turn off analysis mode while we undo
11650 * all the moves. Otherwise we get analysis output after each undo.
11652 if (first.analysisSupport) {
11653 SendToProgram("exit\nforce\n", &first);
11654 first.analyzing = FALSE;
11658 if (gameMode == IcsExamining && !pausing) {
11659 SendToICS(ics_prefix);
11660 SendToICS("backward 999999\n");
11662 BackwardInner(backwardMostMove);
11665 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11666 /* we have fed all the moves, so reactivate analysis mode */
11667 SendToProgram("analyze\n", &first);
11668 first.analyzing = TRUE;
11669 /*first.maybeThinking = TRUE;*/
11670 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11677 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11678 if (to >= forwardMostMove) to = forwardMostMove;
11679 if (to <= backwardMostMove) to = backwardMostMove;
11680 if (to < currentMove) {
11690 if (gameMode != IcsExamining) {
11691 DisplayError(_("You are not examining a game"), 0);
11695 DisplayError(_("You can't revert while pausing"), 0);
11698 SendToICS(ics_prefix);
11699 SendToICS("revert\n");
11705 switch (gameMode) {
11706 case MachinePlaysWhite:
11707 case MachinePlaysBlack:
11708 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11709 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11712 if (forwardMostMove < 2) return;
11713 currentMove = forwardMostMove = forwardMostMove - 2;
11714 whiteTimeRemaining = timeRemaining[0][currentMove];
11715 blackTimeRemaining = timeRemaining[1][currentMove];
11716 DisplayBothClocks();
11717 DisplayMove(currentMove - 1);
11718 ClearHighlights();/*!! could figure this out*/
11719 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11720 SendToProgram("remove\n", &first);
11721 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11724 case BeginningOfGame:
11728 case IcsPlayingWhite:
11729 case IcsPlayingBlack:
11730 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11731 SendToICS(ics_prefix);
11732 SendToICS("takeback 2\n");
11734 SendToICS(ics_prefix);
11735 SendToICS("takeback 1\n");
11744 ChessProgramState *cps;
11746 switch (gameMode) {
11747 case MachinePlaysWhite:
11748 if (!WhiteOnMove(forwardMostMove)) {
11749 DisplayError(_("It is your turn"), 0);
11754 case MachinePlaysBlack:
11755 if (WhiteOnMove(forwardMostMove)) {
11756 DisplayError(_("It is your turn"), 0);
11761 case TwoMachinesPlay:
11762 if (WhiteOnMove(forwardMostMove) ==
11763 (first.twoMachinesColor[0] == 'w')) {
11769 case BeginningOfGame:
11773 SendToProgram("?\n", cps);
11777 TruncateGameEvent()
11780 if (gameMode != EditGame) return;
11787 if (forwardMostMove > currentMove) {
11788 if (gameInfo.resultDetails != NULL) {
11789 free(gameInfo.resultDetails);
11790 gameInfo.resultDetails = NULL;
11791 gameInfo.result = GameUnfinished;
11793 forwardMostMove = currentMove;
11794 HistorySet(parseList, backwardMostMove, forwardMostMove,
11802 if (appData.noChessProgram) return;
11803 switch (gameMode) {
11804 case MachinePlaysWhite:
11805 if (WhiteOnMove(forwardMostMove)) {
11806 DisplayError(_("Wait until your turn"), 0);
11810 case BeginningOfGame:
11811 case MachinePlaysBlack:
11812 if (!WhiteOnMove(forwardMostMove)) {
11813 DisplayError(_("Wait until your turn"), 0);
11818 DisplayError(_("No hint available"), 0);
11821 SendToProgram("hint\n", &first);
11822 hintRequested = TRUE;
11828 if (appData.noChessProgram) return;
11829 switch (gameMode) {
11830 case MachinePlaysWhite:
11831 if (WhiteOnMove(forwardMostMove)) {
11832 DisplayError(_("Wait until your turn"), 0);
11836 case BeginningOfGame:
11837 case MachinePlaysBlack:
11838 if (!WhiteOnMove(forwardMostMove)) {
11839 DisplayError(_("Wait until your turn"), 0);
11844 EditPositionDone();
11846 case TwoMachinesPlay:
11851 SendToProgram("bk\n", &first);
11852 bookOutput[0] = NULLCHAR;
11853 bookRequested = TRUE;
11859 char *tags = PGNTags(&gameInfo);
11860 TagsPopUp(tags, CmailMsg());
11864 /* end button procedures */
11867 PrintPosition(fp, move)
11873 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11874 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11875 char c = PieceToChar(boards[move][i][j]);
11876 fputc(c == 'x' ? '.' : c, fp);
11877 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11880 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11881 fprintf(fp, "white to play\n");
11883 fprintf(fp, "black to play\n");
11890 if (gameInfo.white != NULL) {
11891 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11897 /* Find last component of program's own name, using some heuristics */
11899 TidyProgramName(prog, host, buf)
11900 char *prog, *host, buf[MSG_SIZ];
11903 int local = (strcmp(host, "localhost") == 0);
11904 while (!local && (p = strchr(prog, ';')) != NULL) {
11906 while (*p == ' ') p++;
11909 if (*prog == '"' || *prog == '\'') {
11910 q = strchr(prog + 1, *prog);
11912 q = strchr(prog, ' ');
11914 if (q == NULL) q = prog + strlen(prog);
11916 while (p >= prog && *p != '/' && *p != '\\') p--;
11918 if(p == prog && *p == '"') p++;
11919 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11920 memcpy(buf, p, q - p);
11921 buf[q - p] = NULLCHAR;
11929 TimeControlTagValue()
11932 if (!appData.clockMode) {
11934 } else if (movesPerSession > 0) {
11935 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11936 } else if (timeIncrement == 0) {
11937 sprintf(buf, "%ld", timeControl/1000);
11939 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11941 return StrSave(buf);
11947 /* This routine is used only for certain modes */
11948 VariantClass v = gameInfo.variant;
11949 ClearGameInfo(&gameInfo);
11950 gameInfo.variant = v;
11952 switch (gameMode) {
11953 case MachinePlaysWhite:
11954 gameInfo.event = StrSave( appData.pgnEventHeader );
11955 gameInfo.site = StrSave(HostName());
11956 gameInfo.date = PGNDate();
11957 gameInfo.round = StrSave("-");
11958 gameInfo.white = StrSave(first.tidy);
11959 gameInfo.black = StrSave(UserName());
11960 gameInfo.timeControl = TimeControlTagValue();
11963 case MachinePlaysBlack:
11964 gameInfo.event = StrSave( appData.pgnEventHeader );
11965 gameInfo.site = StrSave(HostName());
11966 gameInfo.date = PGNDate();
11967 gameInfo.round = StrSave("-");
11968 gameInfo.white = StrSave(UserName());
11969 gameInfo.black = StrSave(first.tidy);
11970 gameInfo.timeControl = TimeControlTagValue();
11973 case TwoMachinesPlay:
11974 gameInfo.event = StrSave( appData.pgnEventHeader );
11975 gameInfo.site = StrSave(HostName());
11976 gameInfo.date = PGNDate();
11977 if (matchGame > 0) {
11979 sprintf(buf, "%d", matchGame);
11980 gameInfo.round = StrSave(buf);
11982 gameInfo.round = StrSave("-");
11984 if (first.twoMachinesColor[0] == 'w') {
11985 gameInfo.white = StrSave(first.tidy);
11986 gameInfo.black = StrSave(second.tidy);
11988 gameInfo.white = StrSave(second.tidy);
11989 gameInfo.black = StrSave(first.tidy);
11991 gameInfo.timeControl = TimeControlTagValue();
11995 gameInfo.event = StrSave("Edited game");
11996 gameInfo.site = StrSave(HostName());
11997 gameInfo.date = PGNDate();
11998 gameInfo.round = StrSave("-");
11999 gameInfo.white = StrSave("-");
12000 gameInfo.black = StrSave("-");
12004 gameInfo.event = StrSave("Edited position");
12005 gameInfo.site = StrSave(HostName());
12006 gameInfo.date = PGNDate();
12007 gameInfo.round = StrSave("-");
12008 gameInfo.white = StrSave("-");
12009 gameInfo.black = StrSave("-");
12012 case IcsPlayingWhite:
12013 case IcsPlayingBlack:
12018 case PlayFromGameFile:
12019 gameInfo.event = StrSave("Game from non-PGN file");
12020 gameInfo.site = StrSave(HostName());
12021 gameInfo.date = PGNDate();
12022 gameInfo.round = StrSave("-");
12023 gameInfo.white = StrSave("?");
12024 gameInfo.black = StrSave("?");
12033 ReplaceComment(index, text)
12039 while (*text == '\n') text++;
12040 len = strlen(text);
12041 while (len > 0 && text[len - 1] == '\n') len--;
12043 if (commentList[index] != NULL)
12044 free(commentList[index]);
12047 commentList[index] = NULL;
12050 commentList[index] = (char *) malloc(len + 2);
12051 strncpy(commentList[index], text, len);
12052 commentList[index][len] = '\n';
12053 commentList[index][len + 1] = NULLCHAR;
12066 if (ch == '\r') continue;
12068 } while (ch != '\0');
12072 AppendComment(index, text)
12079 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12082 while (*text == '\n') text++;
12083 len = strlen(text);
12084 while (len > 0 && text[len - 1] == '\n') len--;
12086 if (len == 0) return;
12088 if (commentList[index] != NULL) {
12089 old = commentList[index];
12090 oldlen = strlen(old);
12091 commentList[index] = (char *) malloc(oldlen + len + 2);
12092 strcpy(commentList[index], old);
12094 strncpy(&commentList[index][oldlen], text, len);
12095 commentList[index][oldlen + len] = '\n';
12096 commentList[index][oldlen + len + 1] = NULLCHAR;
12098 commentList[index] = (char *) malloc(len + 2);
12099 strncpy(commentList[index], text, len);
12100 commentList[index][len] = '\n';
12101 commentList[index][len + 1] = NULLCHAR;
12105 static char * FindStr( char * text, char * sub_text )
12107 char * result = strstr( text, sub_text );
12109 if( result != NULL ) {
12110 result += strlen( sub_text );
12116 /* [AS] Try to extract PV info from PGN comment */
12117 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12118 char *GetInfoFromComment( int index, char * text )
12122 if( text != NULL && index > 0 ) {
12125 int time = -1, sec = 0, deci;
12126 char * s_eval = FindStr( text, "[%eval " );
12127 char * s_emt = FindStr( text, "[%emt " );
12129 if( s_eval != NULL || s_emt != NULL ) {
12133 if( s_eval != NULL ) {
12134 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12138 if( delim != ']' ) {
12143 if( s_emt != NULL ) {
12147 /* We expect something like: [+|-]nnn.nn/dd */
12150 sep = strchr( text, '/' );
12151 if( sep == NULL || sep < (text+4) ) {
12155 time = -1; sec = -1; deci = -1;
12156 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12157 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12158 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12159 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12163 if( score_lo < 0 || score_lo >= 100 ) {
12167 if(sec >= 0) time = 600*time + 10*sec; else
12168 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12170 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12172 /* [HGM] PV time: now locate end of PV info */
12173 while( *++sep >= '0' && *sep <= '9'); // strip depth
12175 while( *++sep >= '0' && *sep <= '9'); // strip time
12177 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12179 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12180 while(*sep == ' ') sep++;
12191 pvInfoList[index-1].depth = depth;
12192 pvInfoList[index-1].score = score;
12193 pvInfoList[index-1].time = 10*time; // centi-sec
12199 SendToProgram(message, cps)
12201 ChessProgramState *cps;
12203 int count, outCount, error;
12206 if (cps->pr == NULL) return;
12209 if (appData.debugMode) {
12212 fprintf(debugFP, "%ld >%-6s: %s",
12213 SubtractTimeMarks(&now, &programStartTime),
12214 cps->which, message);
12217 count = strlen(message);
12218 outCount = OutputToProcess(cps->pr, message, count, &error);
12219 if (outCount < count && !exiting
12220 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12221 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12222 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12223 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12224 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12225 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12227 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12229 gameInfo.resultDetails = buf;
12231 DisplayFatalError(buf, error, 1);
12236 ReceiveFromProgram(isr, closure, message, count, error)
12237 InputSourceRef isr;
12245 ChessProgramState *cps = (ChessProgramState *)closure;
12247 if (isr != cps->isr) return; /* Killed intentionally */
12251 _("Error: %s chess program (%s) exited unexpectedly"),
12252 cps->which, cps->program);
12253 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12254 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12255 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12256 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12258 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12260 gameInfo.resultDetails = buf;
12262 RemoveInputSource(cps->isr);
12263 DisplayFatalError(buf, 0, 1);
12266 _("Error reading from %s chess program (%s)"),
12267 cps->which, cps->program);
12268 RemoveInputSource(cps->isr);
12270 /* [AS] Program is misbehaving badly... kill it */
12271 if( count == -2 ) {
12272 DestroyChildProcess( cps->pr, 9 );
12276 DisplayFatalError(buf, error, 1);
12281 if ((end_str = strchr(message, '\r')) != NULL)
12282 *end_str = NULLCHAR;
12283 if ((end_str = strchr(message, '\n')) != NULL)
12284 *end_str = NULLCHAR;
12286 if (appData.debugMode) {
12287 TimeMark now; int print = 1;
12288 char *quote = ""; char c; int i;
12290 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12291 char start = message[0];
12292 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12293 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12294 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12295 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12296 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12297 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12298 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12299 sscanf(message, "pong %c", &c)!=1 && start != '#')
12300 { quote = "# "; print = (appData.engineComments == 2); }
12301 message[0] = start; // restore original message
12305 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12306 SubtractTimeMarks(&now, &programStartTime), cps->which,
12312 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12313 if (appData.icsEngineAnalyze) {
12314 if (strstr(message, "whisper") != NULL ||
12315 strstr(message, "kibitz") != NULL ||
12316 strstr(message, "tellics") != NULL) return;
12319 HandleMachineMove(message, cps);
12324 SendTimeControl(cps, mps, tc, inc, sd, st)
12325 ChessProgramState *cps;
12326 int mps, inc, sd, st;
12332 if( timeControl_2 > 0 ) {
12333 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12334 tc = timeControl_2;
12337 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12338 inc /= cps->timeOdds;
12339 st /= cps->timeOdds;
12341 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12344 /* Set exact time per move, normally using st command */
12345 if (cps->stKludge) {
12346 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12348 if (seconds == 0) {
12349 sprintf(buf, "level 1 %d\n", st/60);
12351 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12354 sprintf(buf, "st %d\n", st);
12357 /* Set conventional or incremental time control, using level command */
12358 if (seconds == 0) {
12359 /* Note old gnuchess bug -- minutes:seconds used to not work.
12360 Fixed in later versions, but still avoid :seconds
12361 when seconds is 0. */
12362 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12364 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12365 seconds, inc/1000);
12368 SendToProgram(buf, cps);
12370 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12371 /* Orthogonally, limit search to given depth */
12373 if (cps->sdKludge) {
12374 sprintf(buf, "depth\n%d\n", sd);
12376 sprintf(buf, "sd %d\n", sd);
12378 SendToProgram(buf, cps);
12381 if(cps->nps > 0) { /* [HGM] nps */
12382 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12384 sprintf(buf, "nps %d\n", cps->nps);
12385 SendToProgram(buf, cps);
12390 ChessProgramState *WhitePlayer()
12391 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12393 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12394 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12400 SendTimeRemaining(cps, machineWhite)
12401 ChessProgramState *cps;
12402 int /*boolean*/ machineWhite;
12404 char message[MSG_SIZ];
12407 /* Note: this routine must be called when the clocks are stopped
12408 or when they have *just* been set or switched; otherwise
12409 it will be off by the time since the current tick started.
12411 if (machineWhite) {
12412 time = whiteTimeRemaining / 10;
12413 otime = blackTimeRemaining / 10;
12415 time = blackTimeRemaining / 10;
12416 otime = whiteTimeRemaining / 10;
12418 /* [HGM] translate opponent's time by time-odds factor */
12419 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12420 if (appData.debugMode) {
12421 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12424 if (time <= 0) time = 1;
12425 if (otime <= 0) otime = 1;
12427 sprintf(message, "time %ld\n", time);
12428 SendToProgram(message, cps);
12430 sprintf(message, "otim %ld\n", otime);
12431 SendToProgram(message, cps);
12435 BoolFeature(p, name, loc, cps)
12439 ChessProgramState *cps;
12442 int len = strlen(name);
12444 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12446 sscanf(*p, "%d", &val);
12448 while (**p && **p != ' ') (*p)++;
12449 sprintf(buf, "accepted %s\n", name);
12450 SendToProgram(buf, cps);
12457 IntFeature(p, name, loc, cps)
12461 ChessProgramState *cps;
12464 int len = strlen(name);
12465 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12467 sscanf(*p, "%d", loc);
12468 while (**p && **p != ' ') (*p)++;
12469 sprintf(buf, "accepted %s\n", name);
12470 SendToProgram(buf, cps);
12477 StringFeature(p, name, loc, cps)
12481 ChessProgramState *cps;
12484 int len = strlen(name);
12485 if (strncmp((*p), name, len) == 0
12486 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12488 sscanf(*p, "%[^\"]", loc);
12489 while (**p && **p != '\"') (*p)++;
12490 if (**p == '\"') (*p)++;
12491 sprintf(buf, "accepted %s\n", name);
12492 SendToProgram(buf, cps);
12499 ParseOption(Option *opt, ChessProgramState *cps)
12500 // [HGM] options: process the string that defines an engine option, and determine
12501 // name, type, default value, and allowed value range
12503 char *p, *q, buf[MSG_SIZ];
12504 int n, min = (-1)<<31, max = 1<<31, def;
12506 if(p = strstr(opt->name, " -spin ")) {
12507 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12508 if(max < min) max = min; // enforce consistency
12509 if(def < min) def = min;
12510 if(def > max) def = max;
12515 } else if((p = strstr(opt->name, " -slider "))) {
12516 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12517 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12518 if(max < min) max = min; // enforce consistency
12519 if(def < min) def = min;
12520 if(def > max) def = max;
12524 opt->type = Spin; // Slider;
12525 } else if((p = strstr(opt->name, " -string "))) {
12526 opt->textValue = p+9;
12527 opt->type = TextBox;
12528 } else if((p = strstr(opt->name, " -file "))) {
12529 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12530 opt->textValue = p+7;
12531 opt->type = TextBox; // FileName;
12532 } else if((p = strstr(opt->name, " -path "))) {
12533 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12534 opt->textValue = p+7;
12535 opt->type = TextBox; // PathName;
12536 } else if(p = strstr(opt->name, " -check ")) {
12537 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12538 opt->value = (def != 0);
12539 opt->type = CheckBox;
12540 } else if(p = strstr(opt->name, " -combo ")) {
12541 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12542 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12543 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12544 opt->value = n = 0;
12545 while(q = StrStr(q, " /// ")) {
12546 n++; *q = 0; // count choices, and null-terminate each of them
12548 if(*q == '*') { // remember default, which is marked with * prefix
12552 cps->comboList[cps->comboCnt++] = q;
12554 cps->comboList[cps->comboCnt++] = NULL;
12556 opt->type = ComboBox;
12557 } else if(p = strstr(opt->name, " -button")) {
12558 opt->type = Button;
12559 } else if(p = strstr(opt->name, " -save")) {
12560 opt->type = SaveButton;
12561 } else return FALSE;
12562 *p = 0; // terminate option name
12563 // now look if the command-line options define a setting for this engine option.
12564 if(cps->optionSettings && cps->optionSettings[0])
12565 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12566 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12567 sprintf(buf, "option %s", p);
12568 if(p = strstr(buf, ",")) *p = 0;
12570 SendToProgram(buf, cps);
12576 FeatureDone(cps, val)
12577 ChessProgramState* cps;
12580 DelayedEventCallback cb = GetDelayedEvent();
12581 if ((cb == InitBackEnd3 && cps == &first) ||
12582 (cb == TwoMachinesEventIfReady && cps == &second)) {
12583 CancelDelayedEvent();
12584 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12586 cps->initDone = val;
12589 /* Parse feature command from engine */
12591 ParseFeatures(args, cps)
12593 ChessProgramState *cps;
12601 while (*p == ' ') p++;
12602 if (*p == NULLCHAR) return;
12604 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12605 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12606 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12607 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12608 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12609 if (BoolFeature(&p, "reuse", &val, cps)) {
12610 /* Engine can disable reuse, but can't enable it if user said no */
12611 if (!val) cps->reuse = FALSE;
12614 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12615 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12616 if (gameMode == TwoMachinesPlay) {
12617 DisplayTwoMachinesTitle();
12623 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12624 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12625 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12626 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12627 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12628 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12629 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12630 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12631 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12632 if (IntFeature(&p, "done", &val, cps)) {
12633 FeatureDone(cps, val);
12636 /* Added by Tord: */
12637 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12638 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12639 /* End of additions by Tord */
12641 /* [HGM] added features: */
12642 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12643 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12644 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12645 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12646 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12647 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12648 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12649 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12650 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12651 SendToProgram(buf, cps);
12654 if(cps->nrOptions >= MAX_OPTIONS) {
12656 sprintf(buf, "%s engine has too many options\n", cps->which);
12657 DisplayError(buf, 0);
12661 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12662 /* End of additions by HGM */
12664 /* unknown feature: complain and skip */
12666 while (*q && *q != '=') q++;
12667 sprintf(buf, "rejected %.*s\n", q-p, p);
12668 SendToProgram(buf, cps);
12674 while (*p && *p != '\"') p++;
12675 if (*p == '\"') p++;
12677 while (*p && *p != ' ') p++;
12685 PeriodicUpdatesEvent(newState)
12688 if (newState == appData.periodicUpdates)
12691 appData.periodicUpdates=newState;
12693 /* Display type changes, so update it now */
12696 /* Get the ball rolling again... */
12698 AnalysisPeriodicEvent(1);
12699 StartAnalysisClock();
12704 PonderNextMoveEvent(newState)
12707 if (newState == appData.ponderNextMove) return;
12708 if (gameMode == EditPosition) EditPositionDone();
12710 SendToProgram("hard\n", &first);
12711 if (gameMode == TwoMachinesPlay) {
12712 SendToProgram("hard\n", &second);
12715 SendToProgram("easy\n", &first);
12716 thinkOutput[0] = NULLCHAR;
12717 if (gameMode == TwoMachinesPlay) {
12718 SendToProgram("easy\n", &second);
12721 appData.ponderNextMove = newState;
12725 NewSettingEvent(option, command, value)
12731 if (gameMode == EditPosition) EditPositionDone();
12732 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12733 SendToProgram(buf, &first);
12734 if (gameMode == TwoMachinesPlay) {
12735 SendToProgram(buf, &second);
12740 ShowThinkingEvent()
12741 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12743 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12744 int newState = appData.showThinking
12745 // [HGM] thinking: other features now need thinking output as well
12746 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12748 if (oldState == newState) return;
12749 oldState = newState;
12750 if (gameMode == EditPosition) EditPositionDone();
12752 SendToProgram("post\n", &first);
12753 if (gameMode == TwoMachinesPlay) {
12754 SendToProgram("post\n", &second);
12757 SendToProgram("nopost\n", &first);
12758 thinkOutput[0] = NULLCHAR;
12759 if (gameMode == TwoMachinesPlay) {
12760 SendToProgram("nopost\n", &second);
12763 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12767 AskQuestionEvent(title, question, replyPrefix, which)
12768 char *title; char *question; char *replyPrefix; char *which;
12770 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12771 if (pr == NoProc) return;
12772 AskQuestion(title, question, replyPrefix, pr);
12776 DisplayMove(moveNumber)
12779 char message[MSG_SIZ];
12781 char cpThinkOutput[MSG_SIZ];
12783 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12785 if (moveNumber == forwardMostMove - 1 ||
12786 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12788 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12790 if (strchr(cpThinkOutput, '\n')) {
12791 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12794 *cpThinkOutput = NULLCHAR;
12797 /* [AS] Hide thinking from human user */
12798 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12799 *cpThinkOutput = NULLCHAR;
12800 if( thinkOutput[0] != NULLCHAR ) {
12803 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12804 cpThinkOutput[i] = '.';
12806 cpThinkOutput[i] = NULLCHAR;
12807 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12811 if (moveNumber == forwardMostMove - 1 &&
12812 gameInfo.resultDetails != NULL) {
12813 if (gameInfo.resultDetails[0] == NULLCHAR) {
12814 sprintf(res, " %s", PGNResult(gameInfo.result));
12816 sprintf(res, " {%s} %s",
12817 gameInfo.resultDetails, PGNResult(gameInfo.result));
12823 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12824 DisplayMessage(res, cpThinkOutput);
12826 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12827 WhiteOnMove(moveNumber) ? " " : ".. ",
12828 parseList[moveNumber], res);
12829 DisplayMessage(message, cpThinkOutput);
12834 DisplayAnalysisText(text)
12839 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12840 || appData.icsEngineAnalyze) {
12841 sprintf(buf, "Analysis (%s)", first.tidy);
12842 AnalysisPopUp(buf, text);
12850 while (*str && isspace(*str)) ++str;
12851 while (*str && !isspace(*str)) ++str;
12852 if (!*str) return 1;
12853 while (*str && isspace(*str)) ++str;
12854 if (!*str) return 1;
12862 char lst[MSG_SIZ / 2];
12864 static char *xtra[] = { "", " (--)", " (++)" };
12867 if (programStats.time == 0) {
12868 programStats.time = 1;
12871 if (programStats.got_only_move) {
12872 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12874 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12876 nps = (u64ToDouble(programStats.nodes) /
12877 ((double)programStats.time /100.0));
12879 cs = programStats.time % 100;
12880 s = programStats.time / 100;
12886 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12887 if (programStats.move_name[0] != NULLCHAR) {
12888 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12889 programStats.depth,
12890 programStats.nr_moves-programStats.moves_left,
12891 programStats.nr_moves, programStats.move_name,
12892 ((float)programStats.score)/100.0, lst,
12893 only_one_move(lst)?
12894 xtra[programStats.got_fail] : "",
12895 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12897 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12898 programStats.depth,
12899 programStats.nr_moves-programStats.moves_left,
12900 programStats.nr_moves, ((float)programStats.score)/100.0,
12902 only_one_move(lst)?
12903 xtra[programStats.got_fail] : "",
12904 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12907 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12908 programStats.depth,
12909 ((float)programStats.score)/100.0,
12911 only_one_move(lst)?
12912 xtra[programStats.got_fail] : "",
12913 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12916 DisplayAnalysisText(buf);
12920 DisplayComment(moveNumber, text)
12924 char title[MSG_SIZ];
12925 char buf[8000]; // comment can be long!
12928 if( appData.autoDisplayComment ) {
12929 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12930 strcpy(title, "Comment");
12932 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12933 WhiteOnMove(moveNumber) ? " " : ".. ",
12934 parseList[moveNumber]);
12936 // [HGM] PV info: display PV info together with (or as) comment
12937 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12938 if(text == NULL) text = "";
12939 score = pvInfoList[moveNumber].score;
12940 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12941 depth, (pvInfoList[moveNumber].time+50)/100, text);
12944 } else title[0] = 0;
12947 CommentPopUp(title, text);
12950 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12951 * might be busy thinking or pondering. It can be omitted if your
12952 * gnuchess is configured to stop thinking immediately on any user
12953 * input. However, that gnuchess feature depends on the FIONREAD
12954 * ioctl, which does not work properly on some flavors of Unix.
12958 ChessProgramState *cps;
12961 if (!cps->useSigint) return;
12962 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12963 switch (gameMode) {
12964 case MachinePlaysWhite:
12965 case MachinePlaysBlack:
12966 case TwoMachinesPlay:
12967 case IcsPlayingWhite:
12968 case IcsPlayingBlack:
12971 /* Skip if we know it isn't thinking */
12972 if (!cps->maybeThinking) return;
12973 if (appData.debugMode)
12974 fprintf(debugFP, "Interrupting %s\n", cps->which);
12975 InterruptChildProcess(cps->pr);
12976 cps->maybeThinking = FALSE;
12981 #endif /*ATTENTION*/
12987 if (whiteTimeRemaining <= 0) {
12990 if (appData.icsActive) {
12991 if (appData.autoCallFlag &&
12992 gameMode == IcsPlayingBlack && !blackFlag) {
12993 SendToICS(ics_prefix);
12994 SendToICS("flag\n");
12998 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13000 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13001 if (appData.autoCallFlag) {
13002 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13009 if (blackTimeRemaining <= 0) {
13012 if (appData.icsActive) {
13013 if (appData.autoCallFlag &&
13014 gameMode == IcsPlayingWhite && !whiteFlag) {
13015 SendToICS(ics_prefix);
13016 SendToICS("flag\n");
13020 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13022 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13023 if (appData.autoCallFlag) {
13024 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13037 if (!appData.clockMode || appData.icsActive ||
13038 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13041 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13043 if ( !WhiteOnMove(forwardMostMove) )
13044 /* White made time control */
13045 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13046 /* [HGM] time odds: correct new time quota for time odds! */
13047 / WhitePlayer()->timeOdds;
13049 /* Black made time control */
13050 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13051 / WhitePlayer()->other->timeOdds;
13055 DisplayBothClocks()
13057 int wom = gameMode == EditPosition ?
13058 !blackPlaysFirst : WhiteOnMove(currentMove);
13059 DisplayWhiteClock(whiteTimeRemaining, wom);
13060 DisplayBlackClock(blackTimeRemaining, !wom);
13064 /* Timekeeping seems to be a portability nightmare. I think everyone
13065 has ftime(), but I'm really not sure, so I'm including some ifdefs
13066 to use other calls if you don't. Clocks will be less accurate if
13067 you have neither ftime nor gettimeofday.
13070 /* VS 2008 requires the #include outside of the function */
13071 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13072 #include <sys/timeb.h>
13075 /* Get the current time as a TimeMark */
13080 #if HAVE_GETTIMEOFDAY
13082 struct timeval timeVal;
13083 struct timezone timeZone;
13085 gettimeofday(&timeVal, &timeZone);
13086 tm->sec = (long) timeVal.tv_sec;
13087 tm->ms = (int) (timeVal.tv_usec / 1000L);
13089 #else /*!HAVE_GETTIMEOFDAY*/
13092 // include <sys/timeb.h> / moved to just above start of function
13093 struct timeb timeB;
13096 tm->sec = (long) timeB.time;
13097 tm->ms = (int) timeB.millitm;
13099 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13100 tm->sec = (long) time(NULL);
13106 /* Return the difference in milliseconds between two
13107 time marks. We assume the difference will fit in a long!
13110 SubtractTimeMarks(tm2, tm1)
13111 TimeMark *tm2, *tm1;
13113 return 1000L*(tm2->sec - tm1->sec) +
13114 (long) (tm2->ms - tm1->ms);
13119 * Code to manage the game clocks.
13121 * In tournament play, black starts the clock and then white makes a move.
13122 * We give the human user a slight advantage if he is playing white---the
13123 * clocks don't run until he makes his first move, so it takes zero time.
13124 * Also, we don't account for network lag, so we could get out of sync
13125 * with GNU Chess's clock -- but then, referees are always right.
13128 static TimeMark tickStartTM;
13129 static long intendedTickLength;
13132 NextTickLength(timeRemaining)
13133 long timeRemaining;
13135 long nominalTickLength, nextTickLength;
13137 if (timeRemaining > 0L && timeRemaining <= 10000L)
13138 nominalTickLength = 100L;
13140 nominalTickLength = 1000L;
13141 nextTickLength = timeRemaining % nominalTickLength;
13142 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13144 return nextTickLength;
13147 /* Adjust clock one minute up or down */
13149 AdjustClock(Boolean which, int dir)
13151 if(which) blackTimeRemaining += 60000*dir;
13152 else whiteTimeRemaining += 60000*dir;
13153 DisplayBothClocks();
13156 /* Stop clocks and reset to a fresh time control */
13160 (void) StopClockTimer();
13161 if (appData.icsActive) {
13162 whiteTimeRemaining = blackTimeRemaining = 0;
13163 } else { /* [HGM] correct new time quote for time odds */
13164 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13165 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13167 if (whiteFlag || blackFlag) {
13169 whiteFlag = blackFlag = FALSE;
13171 DisplayBothClocks();
13174 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13176 /* Decrement running clock by amount of time that has passed */
13180 long timeRemaining;
13181 long lastTickLength, fudge;
13184 if (!appData.clockMode) return;
13185 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13189 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13191 /* Fudge if we woke up a little too soon */
13192 fudge = intendedTickLength - lastTickLength;
13193 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13195 if (WhiteOnMove(forwardMostMove)) {
13196 if(whiteNPS >= 0) lastTickLength = 0;
13197 timeRemaining = whiteTimeRemaining -= lastTickLength;
13198 DisplayWhiteClock(whiteTimeRemaining - fudge,
13199 WhiteOnMove(currentMove));
13201 if(blackNPS >= 0) lastTickLength = 0;
13202 timeRemaining = blackTimeRemaining -= lastTickLength;
13203 DisplayBlackClock(blackTimeRemaining - fudge,
13204 !WhiteOnMove(currentMove));
13207 if (CheckFlags()) return;
13210 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13211 StartClockTimer(intendedTickLength);
13213 /* if the time remaining has fallen below the alarm threshold, sound the
13214 * alarm. if the alarm has sounded and (due to a takeback or time control
13215 * with increment) the time remaining has increased to a level above the
13216 * threshold, reset the alarm so it can sound again.
13219 if (appData.icsActive && appData.icsAlarm) {
13221 /* make sure we are dealing with the user's clock */
13222 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13223 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13226 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13227 alarmSounded = FALSE;
13228 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13230 alarmSounded = TRUE;
13236 /* A player has just moved, so stop the previously running
13237 clock and (if in clock mode) start the other one.
13238 We redisplay both clocks in case we're in ICS mode, because
13239 ICS gives us an update to both clocks after every move.
13240 Note that this routine is called *after* forwardMostMove
13241 is updated, so the last fractional tick must be subtracted
13242 from the color that is *not* on move now.
13247 long lastTickLength;
13249 int flagged = FALSE;
13253 if (StopClockTimer() && appData.clockMode) {
13254 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13255 if (WhiteOnMove(forwardMostMove)) {
13256 if(blackNPS >= 0) lastTickLength = 0;
13257 blackTimeRemaining -= lastTickLength;
13258 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13259 // if(pvInfoList[forwardMostMove-1].time == -1)
13260 pvInfoList[forwardMostMove-1].time = // use GUI time
13261 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13263 if(whiteNPS >= 0) lastTickLength = 0;
13264 whiteTimeRemaining -= lastTickLength;
13265 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13266 // if(pvInfoList[forwardMostMove-1].time == -1)
13267 pvInfoList[forwardMostMove-1].time =
13268 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13270 flagged = CheckFlags();
13272 CheckTimeControl();
13274 if (flagged || !appData.clockMode) return;
13276 switch (gameMode) {
13277 case MachinePlaysBlack:
13278 case MachinePlaysWhite:
13279 case BeginningOfGame:
13280 if (pausing) return;
13284 case PlayFromGameFile:
13293 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13294 whiteTimeRemaining : blackTimeRemaining);
13295 StartClockTimer(intendedTickLength);
13299 /* Stop both clocks */
13303 long lastTickLength;
13306 if (!StopClockTimer()) return;
13307 if (!appData.clockMode) return;
13311 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13312 if (WhiteOnMove(forwardMostMove)) {
13313 if(whiteNPS >= 0) lastTickLength = 0;
13314 whiteTimeRemaining -= lastTickLength;
13315 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13317 if(blackNPS >= 0) lastTickLength = 0;
13318 blackTimeRemaining -= lastTickLength;
13319 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13324 /* Start clock of player on move. Time may have been reset, so
13325 if clock is already running, stop and restart it. */
13329 (void) StopClockTimer(); /* in case it was running already */
13330 DisplayBothClocks();
13331 if (CheckFlags()) return;
13333 if (!appData.clockMode) return;
13334 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13336 GetTimeMark(&tickStartTM);
13337 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13338 whiteTimeRemaining : blackTimeRemaining);
13340 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13341 whiteNPS = blackNPS = -1;
13342 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13343 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13344 whiteNPS = first.nps;
13345 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13346 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13347 blackNPS = first.nps;
13348 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13349 whiteNPS = second.nps;
13350 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13351 blackNPS = second.nps;
13352 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13354 StartClockTimer(intendedTickLength);
13361 long second, minute, hour, day;
13363 static char buf[32];
13365 if (ms > 0 && ms <= 9900) {
13366 /* convert milliseconds to tenths, rounding up */
13367 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13369 sprintf(buf, " %03.1f ", tenths/10.0);
13373 /* convert milliseconds to seconds, rounding up */
13374 /* use floating point to avoid strangeness of integer division
13375 with negative dividends on many machines */
13376 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13383 day = second / (60 * 60 * 24);
13384 second = second % (60 * 60 * 24);
13385 hour = second / (60 * 60);
13386 second = second % (60 * 60);
13387 minute = second / 60;
13388 second = second % 60;
13391 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13392 sign, day, hour, minute, second);
13394 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13396 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13403 * This is necessary because some C libraries aren't ANSI C compliant yet.
13406 StrStr(string, match)
13407 char *string, *match;
13411 length = strlen(match);
13413 for (i = strlen(string) - length; i >= 0; i--, string++)
13414 if (!strncmp(match, string, length))
13421 StrCaseStr(string, match)
13422 char *string, *match;
13426 length = strlen(match);
13428 for (i = strlen(string) - length; i >= 0; i--, string++) {
13429 for (j = 0; j < length; j++) {
13430 if (ToLower(match[j]) != ToLower(string[j]))
13433 if (j == length) return string;
13447 c1 = ToLower(*s1++);
13448 c2 = ToLower(*s2++);
13449 if (c1 > c2) return 1;
13450 if (c1 < c2) return -1;
13451 if (c1 == NULLCHAR) return 0;
13460 return isupper(c) ? tolower(c) : c;
13468 return islower(c) ? toupper(c) : c;
13470 #endif /* !_amigados */
13478 if ((ret = (char *) malloc(strlen(s) + 1))) {
13485 StrSavePtr(s, savePtr)
13486 char *s, **savePtr;
13491 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13492 strcpy(*savePtr, s);
13504 clock = time((time_t *)NULL);
13505 tm = localtime(&clock);
13506 sprintf(buf, "%04d.%02d.%02d",
13507 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13508 return StrSave(buf);
13513 PositionToFEN(move, overrideCastling)
13515 char *overrideCastling;
13517 int i, j, fromX, fromY, toX, toY;
13524 whiteToPlay = (gameMode == EditPosition) ?
13525 !blackPlaysFirst : (move % 2 == 0);
13528 /* Piece placement data */
13529 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13531 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13532 if (boards[move][i][j] == EmptySquare) {
13534 } else { ChessSquare piece = boards[move][i][j];
13535 if (emptycount > 0) {
13536 if(emptycount<10) /* [HGM] can be >= 10 */
13537 *p++ = '0' + emptycount;
13538 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13541 if(PieceToChar(piece) == '+') {
13542 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13544 piece = (ChessSquare)(DEMOTED piece);
13546 *p++ = PieceToChar(piece);
13548 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13549 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13554 if (emptycount > 0) {
13555 if(emptycount<10) /* [HGM] can be >= 10 */
13556 *p++ = '0' + emptycount;
13557 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13564 /* [HGM] print Crazyhouse or Shogi holdings */
13565 if( gameInfo.holdingsWidth ) {
13566 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13568 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13569 piece = boards[move][i][BOARD_WIDTH-1];
13570 if( piece != EmptySquare )
13571 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13572 *p++ = PieceToChar(piece);
13574 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13575 piece = boards[move][BOARD_HEIGHT-i-1][0];
13576 if( piece != EmptySquare )
13577 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13578 *p++ = PieceToChar(piece);
13581 if( q == p ) *p++ = '-';
13587 *p++ = whiteToPlay ? 'w' : 'b';
13590 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13591 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13593 if(nrCastlingRights) {
13595 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13596 /* [HGM] write directly from rights */
13597 if(castlingRights[move][2] >= 0 &&
13598 castlingRights[move][0] >= 0 )
13599 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13600 if(castlingRights[move][2] >= 0 &&
13601 castlingRights[move][1] >= 0 )
13602 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13603 if(castlingRights[move][5] >= 0 &&
13604 castlingRights[move][3] >= 0 )
13605 *p++ = castlingRights[move][3] + AAA;
13606 if(castlingRights[move][5] >= 0 &&
13607 castlingRights[move][4] >= 0 )
13608 *p++ = castlingRights[move][4] + AAA;
13611 /* [HGM] write true castling rights */
13612 if( nrCastlingRights == 6 ) {
13613 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13614 castlingRights[move][2] >= 0 ) *p++ = 'K';
13615 if(castlingRights[move][1] == BOARD_LEFT &&
13616 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13617 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13618 castlingRights[move][5] >= 0 ) *p++ = 'k';
13619 if(castlingRights[move][4] == BOARD_LEFT &&
13620 castlingRights[move][5] >= 0 ) *p++ = 'q';
13623 if (q == p) *p++ = '-'; /* No castling rights */
13627 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13628 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13629 /* En passant target square */
13630 if (move > backwardMostMove) {
13631 fromX = moveList[move - 1][0] - AAA;
13632 fromY = moveList[move - 1][1] - ONE;
13633 toX = moveList[move - 1][2] - AAA;
13634 toY = moveList[move - 1][3] - ONE;
13635 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13636 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13637 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13639 /* 2-square pawn move just happened */
13641 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13652 /* [HGM] find reversible plies */
13653 { int i = 0, j=move;
13655 if (appData.debugMode) { int k;
13656 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13657 for(k=backwardMostMove; k<=forwardMostMove; k++)
13658 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13662 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13663 if( j == backwardMostMove ) i += initialRulePlies;
13664 sprintf(p, "%d ", i);
13665 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13667 /* Fullmove number */
13668 sprintf(p, "%d", (move / 2) + 1);
13670 return StrSave(buf);
13674 ParseFEN(board, blackPlaysFirst, fen)
13676 int *blackPlaysFirst;
13686 /* [HGM] by default clear Crazyhouse holdings, if present */
13687 if(gameInfo.holdingsWidth) {
13688 for(i=0; i<BOARD_HEIGHT; i++) {
13689 board[i][0] = EmptySquare; /* black holdings */
13690 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13691 board[i][1] = (ChessSquare) 0; /* black counts */
13692 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13696 /* Piece placement data */
13697 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13700 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13701 if (*p == '/') p++;
13702 emptycount = gameInfo.boardWidth - j;
13703 while (emptycount--)
13704 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13706 #if(BOARD_SIZE >= 10)
13707 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13708 p++; emptycount=10;
13709 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13710 while (emptycount--)
13711 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13713 } else if (isdigit(*p)) {
13714 emptycount = *p++ - '0';
13715 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13716 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13717 while (emptycount--)
13718 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13719 } else if (*p == '+' || isalpha(*p)) {
13720 if (j >= gameInfo.boardWidth) return FALSE;
13722 piece = CharToPiece(*++p);
13723 if(piece == EmptySquare) return FALSE; /* unknown piece */
13724 piece = (ChessSquare) (PROMOTED piece ); p++;
13725 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13726 } else piece = CharToPiece(*p++);
13728 if(piece==EmptySquare) return FALSE; /* unknown piece */
13729 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13730 piece = (ChessSquare) (PROMOTED piece);
13731 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13734 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13740 while (*p == '/' || *p == ' ') p++;
13742 /* [HGM] look for Crazyhouse holdings here */
13743 while(*p==' ') p++;
13744 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13746 if(*p == '-' ) *p++; /* empty holdings */ else {
13747 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13748 /* if we would allow FEN reading to set board size, we would */
13749 /* have to add holdings and shift the board read so far here */
13750 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13752 if((int) piece >= (int) BlackPawn ) {
13753 i = (int)piece - (int)BlackPawn;
13754 i = PieceToNumber((ChessSquare)i);
13755 if( i >= gameInfo.holdingsSize ) return FALSE;
13756 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13757 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13759 i = (int)piece - (int)WhitePawn;
13760 i = PieceToNumber((ChessSquare)i);
13761 if( i >= gameInfo.holdingsSize ) return FALSE;
13762 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13763 board[i][BOARD_WIDTH-2]++; /* black holdings */
13767 if(*p == ']') *p++;
13770 while(*p == ' ') p++;
13775 *blackPlaysFirst = FALSE;
13778 *blackPlaysFirst = TRUE;
13784 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13785 /* return the extra info in global variiables */
13787 /* set defaults in case FEN is incomplete */
13788 FENepStatus = EP_UNKNOWN;
13789 for(i=0; i<nrCastlingRights; i++ ) {
13790 FENcastlingRights[i] =
13791 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13792 } /* assume possible unless obviously impossible */
13793 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13794 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13795 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13796 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13797 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13798 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13801 while(*p==' ') p++;
13802 if(nrCastlingRights) {
13803 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13804 /* castling indicator present, so default becomes no castlings */
13805 for(i=0; i<nrCastlingRights; i++ ) {
13806 FENcastlingRights[i] = -1;
13809 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13810 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13811 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13812 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13813 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13815 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13816 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13817 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13821 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13822 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13823 FENcastlingRights[2] = whiteKingFile;
13826 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13827 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13828 FENcastlingRights[2] = whiteKingFile;
13831 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13832 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13833 FENcastlingRights[5] = blackKingFile;
13836 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13837 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13838 FENcastlingRights[5] = blackKingFile;
13841 default: /* FRC castlings */
13842 if(c >= 'a') { /* black rights */
13843 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13844 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13845 if(i == BOARD_RGHT) break;
13846 FENcastlingRights[5] = i;
13848 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13849 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13851 FENcastlingRights[3] = c;
13853 FENcastlingRights[4] = c;
13854 } else { /* white rights */
13855 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13856 if(board[0][i] == WhiteKing) break;
13857 if(i == BOARD_RGHT) break;
13858 FENcastlingRights[2] = i;
13859 c -= AAA - 'a' + 'A';
13860 if(board[0][c] >= WhiteKing) break;
13862 FENcastlingRights[0] = c;
13864 FENcastlingRights[1] = c;
13868 if (appData.debugMode) {
13869 fprintf(debugFP, "FEN castling rights:");
13870 for(i=0; i<nrCastlingRights; i++)
13871 fprintf(debugFP, " %d", FENcastlingRights[i]);
13872 fprintf(debugFP, "\n");
13875 while(*p==' ') p++;
13878 /* read e.p. field in games that know e.p. capture */
13879 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13880 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13882 p++; FENepStatus = EP_NONE;
13884 char c = *p++ - AAA;
13886 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13887 if(*p >= '0' && *p <='9') *p++;
13893 if(sscanf(p, "%d", &i) == 1) {
13894 FENrulePlies = i; /* 50-move ply counter */
13895 /* (The move number is still ignored) */
13902 EditPositionPasteFEN(char *fen)
13905 Board initial_position;
13907 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13908 DisplayError(_("Bad FEN position in clipboard"), 0);
13911 int savedBlackPlaysFirst = blackPlaysFirst;
13912 EditPositionEvent();
13913 blackPlaysFirst = savedBlackPlaysFirst;
13914 CopyBoard(boards[0], initial_position);
13915 /* [HGM] copy FEN attributes as well */
13917 initialRulePlies = FENrulePlies;
13918 epStatus[0] = FENepStatus;
13919 for( i=0; i<nrCastlingRights; i++ )
13920 castlingRights[0][i] = FENcastlingRights[i];
13922 EditPositionDone();
13923 DisplayBothClocks();
13924 DrawPosition(FALSE, boards[currentMove]);