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 if(buf_len == 0 || buf[buf_len-1] != ' ')
2128 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2132 buf[buf_len] = NULLCHAR;
2133 next_out = leftover_len;
2137 while (i < buf_len) {
2138 /* Deal with part of the TELNET option negotiation
2139 protocol. We refuse to do anything beyond the
2140 defaults, except that we allow the WILL ECHO option,
2141 which ICS uses to turn off password echoing when we are
2142 directly connected to it. We reject this option
2143 if localLineEditing mode is on (always on in xboard)
2144 and we are talking to port 23, which might be a real
2145 telnet server that will try to keep WILL ECHO on permanently.
2147 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2148 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2149 unsigned char option;
2151 switch ((unsigned char) buf[++i]) {
2153 if (appData.debugMode)
2154 fprintf(debugFP, "\n<WILL ");
2155 switch (option = (unsigned char) buf[++i]) {
2157 if (appData.debugMode)
2158 fprintf(debugFP, "ECHO ");
2159 /* Reply only if this is a change, according
2160 to the protocol rules. */
2161 if (remoteEchoOption) break;
2162 if (appData.localLineEditing &&
2163 atoi(appData.icsPort) == TN_PORT) {
2164 TelnetRequest(TN_DONT, TN_ECHO);
2167 TelnetRequest(TN_DO, TN_ECHO);
2168 remoteEchoOption = TRUE;
2172 if (appData.debugMode)
2173 fprintf(debugFP, "%d ", option);
2174 /* Whatever this is, we don't want it. */
2175 TelnetRequest(TN_DONT, option);
2180 if (appData.debugMode)
2181 fprintf(debugFP, "\n<WONT ");
2182 switch (option = (unsigned char) buf[++i]) {
2184 if (appData.debugMode)
2185 fprintf(debugFP, "ECHO ");
2186 /* Reply only if this is a change, according
2187 to the protocol rules. */
2188 if (!remoteEchoOption) break;
2190 TelnetRequest(TN_DONT, TN_ECHO);
2191 remoteEchoOption = FALSE;
2194 if (appData.debugMode)
2195 fprintf(debugFP, "%d ", (unsigned char) option);
2196 /* Whatever this is, it must already be turned
2197 off, because we never agree to turn on
2198 anything non-default, so according to the
2199 protocol rules, we don't reply. */
2204 if (appData.debugMode)
2205 fprintf(debugFP, "\n<DO ");
2206 switch (option = (unsigned char) buf[++i]) {
2208 /* Whatever this is, we refuse to do it. */
2209 if (appData.debugMode)
2210 fprintf(debugFP, "%d ", option);
2211 TelnetRequest(TN_WONT, option);
2216 if (appData.debugMode)
2217 fprintf(debugFP, "\n<DONT ");
2218 switch (option = (unsigned char) buf[++i]) {
2220 if (appData.debugMode)
2221 fprintf(debugFP, "%d ", option);
2222 /* Whatever this is, we are already not doing
2223 it, because we never agree to do anything
2224 non-default, so according to the protocol
2225 rules, we don't reply. */
2230 if (appData.debugMode)
2231 fprintf(debugFP, "\n<IAC ");
2232 /* Doubled IAC; pass it through */
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2238 /* Drop all other telnet commands on the floor */
2241 if (oldi > next_out)
2242 SendToPlayer(&buf[next_out], oldi - next_out);
2248 /* OK, this at least will *usually* work */
2249 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2253 if (loggedOn && !intfSet) {
2254 if (ics_type == ICS_ICC) {
2256 "/set-quietly interface %s\n/set-quietly style 12\n",
2259 } else if (ics_type == ICS_CHESSNET) {
2260 sprintf(str, "/style 12\n");
2262 strcpy(str, "alias $ @\n$set interface ");
2263 strcat(str, programVersion);
2264 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2266 strcat(str, "$iset nohighlight 1\n");
2268 strcat(str, "$iset lock 1\n$style 12\n");
2274 if (started == STARTED_COMMENT) {
2275 /* Accumulate characters in comment */
2276 parse[parse_pos++] = buf[i];
2277 if (buf[i] == '\n') {
2278 parse[parse_pos] = NULLCHAR;
2279 if(chattingPartner>=0) {
2281 sprintf(mess, "%s%s", talker, parse);
2282 OutputChatMessage(chattingPartner, mess);
2283 chattingPartner = -1;
2285 if(!suppressKibitz) // [HGM] kibitz
2286 AppendComment(forwardMostMove, StripHighlight(parse));
2287 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2288 int nrDigit = 0, nrAlph = 0, i;
2289 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2290 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2291 parse[parse_pos] = NULLCHAR;
2292 // try to be smart: if it does not look like search info, it should go to
2293 // ICS interaction window after all, not to engine-output window.
2294 for(i=0; i<parse_pos; i++) { // count letters and digits
2295 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2296 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2297 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2299 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2300 int depth=0; float score;
2301 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2302 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2303 pvInfoList[forwardMostMove-1].depth = depth;
2304 pvInfoList[forwardMostMove-1].score = 100*score;
2306 OutputKibitz(suppressKibitz, parse);
2309 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2310 SendToPlayer(tmp, strlen(tmp));
2313 started = STARTED_NONE;
2315 /* Don't match patterns against characters in chatter */
2320 if (started == STARTED_CHATTER) {
2321 if (buf[i] != '\n') {
2322 /* Don't match patterns against characters in chatter */
2326 started = STARTED_NONE;
2329 /* Kludge to deal with rcmd protocol */
2330 if (firstTime && looking_at(buf, &i, "\001*")) {
2331 DisplayFatalError(&buf[1], 0, 1);
2337 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2340 if (appData.debugMode)
2341 fprintf(debugFP, "ics_type %d\n", ics_type);
2344 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2345 ics_type = ICS_FICS;
2347 if (appData.debugMode)
2348 fprintf(debugFP, "ics_type %d\n", ics_type);
2351 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2352 ics_type = ICS_CHESSNET;
2354 if (appData.debugMode)
2355 fprintf(debugFP, "ics_type %d\n", ics_type);
2360 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2361 looking_at(buf, &i, "Logging you in as \"*\"") ||
2362 looking_at(buf, &i, "will be \"*\""))) {
2363 strcpy(ics_handle, star_match[0]);
2367 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2369 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2370 DisplayIcsInteractionTitle(buf);
2371 have_set_title = TRUE;
2374 /* skip finger notes */
2375 if (started == STARTED_NONE &&
2376 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2377 (buf[i] == '1' && buf[i+1] == '0')) &&
2378 buf[i+2] == ':' && buf[i+3] == ' ') {
2379 started = STARTED_CHATTER;
2384 /* skip formula vars */
2385 if (started == STARTED_NONE &&
2386 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2387 started = STARTED_CHATTER;
2393 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2394 if (appData.autoKibitz && started == STARTED_NONE &&
2395 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2396 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2397 if(looking_at(buf, &i, "* kibitzes: ") &&
2398 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2399 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2400 suppressKibitz = TRUE;
2401 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2402 && (gameMode == IcsPlayingWhite)) ||
2403 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2404 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2405 started = STARTED_CHATTER; // own kibitz we simply discard
2407 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2408 parse_pos = 0; parse[0] = NULLCHAR;
2409 savingComment = TRUE;
2410 suppressKibitz = gameMode != IcsObserving ? 2 :
2411 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2415 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2416 started = STARTED_CHATTER;
2417 suppressKibitz = TRUE;
2419 } // [HGM] kibitz: end of patch
2421 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2423 // [HGM] chat: intercept tells by users for which we have an open chat window
2425 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2426 looking_at(buf, &i, "* whispers:") ||
2427 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2428 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2430 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2431 chattingPartner = -1;
2433 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2434 for(p=0; p<MAX_CHAT; p++) {
2435 if(channel == atoi(chatPartner[p])) {
2436 talker[0] = '['; strcat(talker, "]");
2437 chattingPartner = p; break;
2440 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2441 for(p=0; p<MAX_CHAT; p++) {
2442 if(!strcmp("WHISPER", chatPartner[p])) {
2443 talker[0] = '['; strcat(talker, "]");
2444 chattingPartner = p; break;
2447 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2448 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2450 chattingPartner = p; break;
2452 if(chattingPartner<0) i = oldi; else {
2453 started = STARTED_COMMENT;
2454 parse_pos = 0; parse[0] = NULLCHAR;
2455 savingComment = TRUE;
2456 suppressKibitz = TRUE;
2458 } // [HGM] chat: end of patch
2460 if (appData.zippyTalk || appData.zippyPlay) {
2461 /* [DM] Backup address for color zippy lines */
2465 if (loggedOn == TRUE)
2466 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2467 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2469 if (ZippyControl(buf, &i) ||
2470 ZippyConverse(buf, &i) ||
2471 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2473 if (!appData.colorize) continue;
2477 } // [DM] 'else { ' deleted
2479 /* Regular tells and says */
2480 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2481 looking_at(buf, &i, "* (your partner) tells you: ") ||
2482 looking_at(buf, &i, "* says: ") ||
2483 /* Don't color "message" or "messages" output */
2484 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2485 looking_at(buf, &i, "*. * at *:*: ") ||
2486 looking_at(buf, &i, "--* (*:*): ") ||
2487 /* Message notifications (same color as tells) */
2488 looking_at(buf, &i, "* has left a message ") ||
2489 looking_at(buf, &i, "* just sent you a message:\n") ||
2490 /* Whispers and kibitzes */
2491 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2492 looking_at(buf, &i, "* kibitzes: ") ||
2494 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2496 if (tkind == 1 && strchr(star_match[0], ':')) {
2497 /* Avoid "tells you:" spoofs in channels */
2500 if (star_match[0][0] == NULLCHAR ||
2501 strchr(star_match[0], ' ') ||
2502 (tkind == 3 && strchr(star_match[1], ' '))) {
2503 /* Reject bogus matches */
2506 if (appData.colorize) {
2507 if (oldi > next_out) {
2508 SendToPlayer(&buf[next_out], oldi - next_out);
2513 Colorize(ColorTell, FALSE);
2514 curColor = ColorTell;
2517 Colorize(ColorKibitz, FALSE);
2518 curColor = ColorKibitz;
2521 p = strrchr(star_match[1], '(');
2528 Colorize(ColorChannel1, FALSE);
2529 curColor = ColorChannel1;
2531 Colorize(ColorChannel, FALSE);
2532 curColor = ColorChannel;
2536 curColor = ColorNormal;
2540 if (started == STARTED_NONE && appData.autoComment &&
2541 (gameMode == IcsObserving ||
2542 gameMode == IcsPlayingWhite ||
2543 gameMode == IcsPlayingBlack)) {
2544 parse_pos = i - oldi;
2545 memcpy(parse, &buf[oldi], parse_pos);
2546 parse[parse_pos] = NULLCHAR;
2547 started = STARTED_COMMENT;
2548 savingComment = TRUE;
2550 started = STARTED_CHATTER;
2551 savingComment = FALSE;
2558 if (looking_at(buf, &i, "* s-shouts: ") ||
2559 looking_at(buf, &i, "* c-shouts: ")) {
2560 if (appData.colorize) {
2561 if (oldi > next_out) {
2562 SendToPlayer(&buf[next_out], oldi - next_out);
2565 Colorize(ColorSShout, FALSE);
2566 curColor = ColorSShout;
2569 started = STARTED_CHATTER;
2573 if (looking_at(buf, &i, "--->")) {
2578 if (looking_at(buf, &i, "* shouts: ") ||
2579 looking_at(buf, &i, "--> ")) {
2580 if (appData.colorize) {
2581 if (oldi > next_out) {
2582 SendToPlayer(&buf[next_out], oldi - next_out);
2585 Colorize(ColorShout, FALSE);
2586 curColor = ColorShout;
2589 started = STARTED_CHATTER;
2593 if (looking_at( buf, &i, "Challenge:")) {
2594 if (appData.colorize) {
2595 if (oldi > next_out) {
2596 SendToPlayer(&buf[next_out], oldi - next_out);
2599 Colorize(ColorChallenge, FALSE);
2600 curColor = ColorChallenge;
2606 if (looking_at(buf, &i, "* offers you") ||
2607 looking_at(buf, &i, "* offers to be") ||
2608 looking_at(buf, &i, "* would like to") ||
2609 looking_at(buf, &i, "* requests to") ||
2610 looking_at(buf, &i, "Your opponent offers") ||
2611 looking_at(buf, &i, "Your opponent requests")) {
2613 if (appData.colorize) {
2614 if (oldi > next_out) {
2615 SendToPlayer(&buf[next_out], oldi - next_out);
2618 Colorize(ColorRequest, FALSE);
2619 curColor = ColorRequest;
2624 if (looking_at(buf, &i, "* (*) seeking")) {
2625 if (appData.colorize) {
2626 if (oldi > next_out) {
2627 SendToPlayer(&buf[next_out], oldi - next_out);
2630 Colorize(ColorSeek, FALSE);
2631 curColor = ColorSeek;
2636 if (looking_at(buf, &i, "\\ ")) {
2637 if (prevColor != ColorNormal) {
2638 if (oldi > next_out) {
2639 SendToPlayer(&buf[next_out], oldi - next_out);
2642 Colorize(prevColor, TRUE);
2643 curColor = prevColor;
2645 if (savingComment) {
2646 parse_pos = i - oldi;
2647 memcpy(parse, &buf[oldi], parse_pos);
2648 parse[parse_pos] = NULLCHAR;
2649 started = STARTED_COMMENT;
2651 started = STARTED_CHATTER;
2656 if (looking_at(buf, &i, "Black Strength :") ||
2657 looking_at(buf, &i, "<<< style 10 board >>>") ||
2658 looking_at(buf, &i, "<10>") ||
2659 looking_at(buf, &i, "#@#")) {
2660 /* Wrong board style */
2662 SendToICS(ics_prefix);
2663 SendToICS("set style 12\n");
2664 SendToICS(ics_prefix);
2665 SendToICS("refresh\n");
2669 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2671 have_sent_ICS_logon = 1;
2675 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2676 (looking_at(buf, &i, "\n<12> ") ||
2677 looking_at(buf, &i, "<12> "))) {
2679 if (oldi > next_out) {
2680 SendToPlayer(&buf[next_out], oldi - next_out);
2683 started = STARTED_BOARD;
2688 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2689 looking_at(buf, &i, "<b1> ")) {
2690 if (oldi > next_out) {
2691 SendToPlayer(&buf[next_out], oldi - next_out);
2694 started = STARTED_HOLDINGS;
2699 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2701 /* Header for a move list -- first line */
2703 switch (ics_getting_history) {
2707 case BeginningOfGame:
2708 /* User typed "moves" or "oldmoves" while we
2709 were idle. Pretend we asked for these
2710 moves and soak them up so user can step
2711 through them and/or save them.
2714 gameMode = IcsObserving;
2717 ics_getting_history = H_GOT_UNREQ_HEADER;
2719 case EditGame: /*?*/
2720 case EditPosition: /*?*/
2721 /* Should above feature work in these modes too? */
2722 /* For now it doesn't */
2723 ics_getting_history = H_GOT_UNWANTED_HEADER;
2726 ics_getting_history = H_GOT_UNWANTED_HEADER;
2731 /* Is this the right one? */
2732 if (gameInfo.white && gameInfo.black &&
2733 strcmp(gameInfo.white, star_match[0]) == 0 &&
2734 strcmp(gameInfo.black, star_match[2]) == 0) {
2736 ics_getting_history = H_GOT_REQ_HEADER;
2739 case H_GOT_REQ_HEADER:
2740 case H_GOT_UNREQ_HEADER:
2741 case H_GOT_UNWANTED_HEADER:
2742 case H_GETTING_MOVES:
2743 /* Should not happen */
2744 DisplayError(_("Error gathering move list: two headers"), 0);
2745 ics_getting_history = H_FALSE;
2749 /* Save player ratings into gameInfo if needed */
2750 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2751 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2752 (gameInfo.whiteRating == -1 ||
2753 gameInfo.blackRating == -1)) {
2755 gameInfo.whiteRating = string_to_rating(star_match[1]);
2756 gameInfo.blackRating = string_to_rating(star_match[3]);
2757 if (appData.debugMode)
2758 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2759 gameInfo.whiteRating, gameInfo.blackRating);
2764 if (looking_at(buf, &i,
2765 "* * match, initial time: * minute*, increment: * second")) {
2766 /* Header for a move list -- second line */
2767 /* Initial board will follow if this is a wild game */
2768 if (gameInfo.event != NULL) free(gameInfo.event);
2769 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2770 gameInfo.event = StrSave(str);
2771 /* [HGM] we switched variant. Translate boards if needed. */
2772 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2776 if (looking_at(buf, &i, "Move ")) {
2777 /* Beginning of a move list */
2778 switch (ics_getting_history) {
2780 /* Normally should not happen */
2781 /* Maybe user hit reset while we were parsing */
2784 /* Happens if we are ignoring a move list that is not
2785 * the one we just requested. Common if the user
2786 * tries to observe two games without turning off
2789 case H_GETTING_MOVES:
2790 /* Should not happen */
2791 DisplayError(_("Error gathering move list: nested"), 0);
2792 ics_getting_history = H_FALSE;
2794 case H_GOT_REQ_HEADER:
2795 ics_getting_history = H_GETTING_MOVES;
2796 started = STARTED_MOVES;
2798 if (oldi > next_out) {
2799 SendToPlayer(&buf[next_out], oldi - next_out);
2802 case H_GOT_UNREQ_HEADER:
2803 ics_getting_history = H_GETTING_MOVES;
2804 started = STARTED_MOVES_NOHIDE;
2807 case H_GOT_UNWANTED_HEADER:
2808 ics_getting_history = H_FALSE;
2814 if (looking_at(buf, &i, "% ") ||
2815 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2816 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2817 savingComment = FALSE;
2820 case STARTED_MOVES_NOHIDE:
2821 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2822 parse[parse_pos + i - oldi] = NULLCHAR;
2823 ParseGameHistory(parse);
2825 if (appData.zippyPlay && first.initDone) {
2826 FeedMovesToProgram(&first, forwardMostMove);
2827 if (gameMode == IcsPlayingWhite) {
2828 if (WhiteOnMove(forwardMostMove)) {
2829 if (first.sendTime) {
2830 if (first.useColors) {
2831 SendToProgram("black\n", &first);
2833 SendTimeRemaining(&first, TRUE);
2836 if (first.useColors) {
2837 SendToProgram("white\ngo\n", &first);
2839 SendToProgram("go\n", &first);
2842 if (first.useColors) {
2843 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2845 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2847 first.maybeThinking = TRUE;
2849 if (first.usePlayother) {
2850 if (first.sendTime) {
2851 SendTimeRemaining(&first, TRUE);
2853 SendToProgram("playother\n", &first);
2859 } else if (gameMode == IcsPlayingBlack) {
2860 if (!WhiteOnMove(forwardMostMove)) {
2861 if (first.sendTime) {
2862 if (first.useColors) {
2863 SendToProgram("white\n", &first);
2865 SendTimeRemaining(&first, FALSE);
2868 if (first.useColors) {
2869 SendToProgram("black\ngo\n", &first);
2871 SendToProgram("go\n", &first);
2874 if (first.useColors) {
2875 SendToProgram("black\n", &first);
2877 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2879 first.maybeThinking = TRUE;
2881 if (first.usePlayother) {
2882 if (first.sendTime) {
2883 SendTimeRemaining(&first, FALSE);
2885 SendToProgram("playother\n", &first);
2894 if (gameMode == IcsObserving && ics_gamenum == -1) {
2895 /* Moves came from oldmoves or moves command
2896 while we weren't doing anything else.
2898 currentMove = forwardMostMove;
2899 ClearHighlights();/*!!could figure this out*/
2900 flipView = appData.flipView;
2901 DrawPosition(FALSE, boards[currentMove]);
2902 DisplayBothClocks();
2903 sprintf(str, "%s vs. %s",
2904 gameInfo.white, gameInfo.black);
2908 /* Moves were history of an active game */
2909 if (gameInfo.resultDetails != NULL) {
2910 free(gameInfo.resultDetails);
2911 gameInfo.resultDetails = NULL;
2914 HistorySet(parseList, backwardMostMove,
2915 forwardMostMove, currentMove-1);
2916 DisplayMove(currentMove - 1);
2917 if (started == STARTED_MOVES) next_out = i;
2918 started = STARTED_NONE;
2919 ics_getting_history = H_FALSE;
2922 case STARTED_OBSERVE:
2923 started = STARTED_NONE;
2924 SendToICS(ics_prefix);
2925 SendToICS("refresh\n");
2931 if(bookHit) { // [HGM] book: simulate book reply
2932 static char bookMove[MSG_SIZ]; // a bit generous?
2934 programStats.nodes = programStats.depth = programStats.time =
2935 programStats.score = programStats.got_only_move = 0;
2936 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2938 strcpy(bookMove, "move ");
2939 strcat(bookMove, bookHit);
2940 HandleMachineMove(bookMove, &first);
2945 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2946 started == STARTED_HOLDINGS ||
2947 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2948 /* Accumulate characters in move list or board */
2949 parse[parse_pos++] = buf[i];
2952 /* Start of game messages. Mostly we detect start of game
2953 when the first board image arrives. On some versions
2954 of the ICS, though, we need to do a "refresh" after starting
2955 to observe in order to get the current board right away. */
2956 if (looking_at(buf, &i, "Adding game * to observation list")) {
2957 started = STARTED_OBSERVE;
2961 /* Handle auto-observe */
2962 if (appData.autoObserve &&
2963 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2964 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2966 /* Choose the player that was highlighted, if any. */
2967 if (star_match[0][0] == '\033' ||
2968 star_match[1][0] != '\033') {
2969 player = star_match[0];
2971 player = star_match[2];
2973 sprintf(str, "%sobserve %s\n",
2974 ics_prefix, StripHighlightAndTitle(player));
2977 /* Save ratings from notify string */
2978 strcpy(player1Name, star_match[0]);
2979 player1Rating = string_to_rating(star_match[1]);
2980 strcpy(player2Name, star_match[2]);
2981 player2Rating = string_to_rating(star_match[3]);
2983 if (appData.debugMode)
2985 "Ratings from 'Game notification:' %s %d, %s %d\n",
2986 player1Name, player1Rating,
2987 player2Name, player2Rating);
2992 /* Deal with automatic examine mode after a game,
2993 and with IcsObserving -> IcsExamining transition */
2994 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2995 looking_at(buf, &i, "has made you an examiner of game *")) {
2997 int gamenum = atoi(star_match[0]);
2998 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2999 gamenum == ics_gamenum) {
3000 /* We were already playing or observing this game;
3001 no need to refetch history */
3002 gameMode = IcsExamining;
3004 pauseExamForwardMostMove = forwardMostMove;
3005 } else if (currentMove < forwardMostMove) {
3006 ForwardInner(forwardMostMove);
3009 /* I don't think this case really can happen */
3010 SendToICS(ics_prefix);
3011 SendToICS("refresh\n");
3016 /* Error messages */
3017 // if (ics_user_moved) {
3018 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3019 if (looking_at(buf, &i, "Illegal move") ||
3020 looking_at(buf, &i, "Not a legal move") ||
3021 looking_at(buf, &i, "Your king is in check") ||
3022 looking_at(buf, &i, "It isn't your turn") ||
3023 looking_at(buf, &i, "It is not your move")) {
3025 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3026 currentMove = --forwardMostMove;
3027 DisplayMove(currentMove - 1); /* before DMError */
3028 DrawPosition(FALSE, boards[currentMove]);
3030 DisplayBothClocks();
3032 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3038 if (looking_at(buf, &i, "still have time") ||
3039 looking_at(buf, &i, "not out of time") ||
3040 looking_at(buf, &i, "either player is out of time") ||
3041 looking_at(buf, &i, "has timeseal; checking")) {
3042 /* We must have called his flag a little too soon */
3043 whiteFlag = blackFlag = FALSE;
3047 if (looking_at(buf, &i, "added * seconds to") ||
3048 looking_at(buf, &i, "seconds were added to")) {
3049 /* Update the clocks */
3050 SendToICS(ics_prefix);
3051 SendToICS("refresh\n");
3055 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3056 ics_clock_paused = TRUE;
3061 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3062 ics_clock_paused = FALSE;
3067 /* Grab player ratings from the Creating: message.
3068 Note we have to check for the special case when
3069 the ICS inserts things like [white] or [black]. */
3070 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3071 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3073 0 player 1 name (not necessarily white)
3075 2 empty, white, or black (IGNORED)
3076 3 player 2 name (not necessarily black)
3079 The names/ratings are sorted out when the game
3080 actually starts (below).
3082 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3083 player1Rating = string_to_rating(star_match[1]);
3084 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3085 player2Rating = string_to_rating(star_match[4]);
3087 if (appData.debugMode)
3089 "Ratings from 'Creating:' %s %d, %s %d\n",
3090 player1Name, player1Rating,
3091 player2Name, player2Rating);
3096 /* Improved generic start/end-of-game messages */
3097 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3098 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3099 /* If tkind == 0: */
3100 /* star_match[0] is the game number */
3101 /* [1] is the white player's name */
3102 /* [2] is the black player's name */
3103 /* For end-of-game: */
3104 /* [3] is the reason for the game end */
3105 /* [4] is a PGN end game-token, preceded by " " */
3106 /* For start-of-game: */
3107 /* [3] begins with "Creating" or "Continuing" */
3108 /* [4] is " *" or empty (don't care). */
3109 int gamenum = atoi(star_match[0]);
3110 char *whitename, *blackname, *why, *endtoken;
3111 ChessMove endtype = (ChessMove) 0;
3114 whitename = star_match[1];
3115 blackname = star_match[2];
3116 why = star_match[3];
3117 endtoken = star_match[4];
3119 whitename = star_match[1];
3120 blackname = star_match[3];
3121 why = star_match[5];
3122 endtoken = star_match[6];
3125 /* Game start messages */
3126 if (strncmp(why, "Creating ", 9) == 0 ||
3127 strncmp(why, "Continuing ", 11) == 0) {
3128 gs_gamenum = gamenum;
3129 strcpy(gs_kind, strchr(why, ' ') + 1);
3131 if (appData.zippyPlay) {
3132 ZippyGameStart(whitename, blackname);
3138 /* Game end messages */
3139 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3140 ics_gamenum != gamenum) {
3143 while (endtoken[0] == ' ') endtoken++;
3144 switch (endtoken[0]) {
3147 endtype = GameUnfinished;
3150 endtype = BlackWins;
3153 if (endtoken[1] == '/')
3154 endtype = GameIsDrawn;
3156 endtype = WhiteWins;
3159 GameEnds(endtype, why, GE_ICS);
3161 if (appData.zippyPlay && first.initDone) {
3162 ZippyGameEnd(endtype, why);
3163 if (first.pr == NULL) {
3164 /* Start the next process early so that we'll
3165 be ready for the next challenge */
3166 StartChessProgram(&first);
3168 /* Send "new" early, in case this command takes
3169 a long time to finish, so that we'll be ready
3170 for the next challenge. */
3171 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3178 if (looking_at(buf, &i, "Removing game * from observation") ||
3179 looking_at(buf, &i, "no longer observing game *") ||
3180 looking_at(buf, &i, "Game * (*) has no examiners")) {
3181 if (gameMode == IcsObserving &&
3182 atoi(star_match[0]) == ics_gamenum)
3184 /* icsEngineAnalyze */
3185 if (appData.icsEngineAnalyze) {
3192 ics_user_moved = FALSE;
3197 if (looking_at(buf, &i, "no longer examining game *")) {
3198 if (gameMode == IcsExamining &&
3199 atoi(star_match[0]) == ics_gamenum)
3203 ics_user_moved = FALSE;
3208 /* Advance leftover_start past any newlines we find,
3209 so only partial lines can get reparsed */
3210 if (looking_at(buf, &i, "\n")) {
3211 prevColor = curColor;
3212 if (curColor != ColorNormal) {
3213 if (oldi > next_out) {
3214 SendToPlayer(&buf[next_out], oldi - next_out);
3217 Colorize(ColorNormal, FALSE);
3218 curColor = ColorNormal;
3220 if (started == STARTED_BOARD) {
3221 started = STARTED_NONE;
3222 parse[parse_pos] = NULLCHAR;
3223 ParseBoard12(parse);
3226 /* Send premove here */
3227 if (appData.premove) {
3229 if (currentMove == 0 &&
3230 gameMode == IcsPlayingWhite &&
3231 appData.premoveWhite) {
3232 sprintf(str, "%s%s\n", ics_prefix,
3233 appData.premoveWhiteText);
3234 if (appData.debugMode)
3235 fprintf(debugFP, "Sending premove:\n");
3237 } else if (currentMove == 1 &&
3238 gameMode == IcsPlayingBlack &&
3239 appData.premoveBlack) {
3240 sprintf(str, "%s%s\n", ics_prefix,
3241 appData.premoveBlackText);
3242 if (appData.debugMode)
3243 fprintf(debugFP, "Sending premove:\n");
3245 } else if (gotPremove) {
3247 ClearPremoveHighlights();
3248 if (appData.debugMode)
3249 fprintf(debugFP, "Sending premove:\n");
3250 UserMoveEvent(premoveFromX, premoveFromY,
3251 premoveToX, premoveToY,
3256 /* Usually suppress following prompt */
3257 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3258 if (looking_at(buf, &i, "*% ")) {
3259 savingComment = FALSE;
3263 } else if (started == STARTED_HOLDINGS) {
3265 char new_piece[MSG_SIZ];
3266 started = STARTED_NONE;
3267 parse[parse_pos] = NULLCHAR;
3268 if (appData.debugMode)
3269 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3270 parse, currentMove);
3271 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3272 gamenum == ics_gamenum) {
3273 if (gameInfo.variant == VariantNormal) {
3274 /* [HGM] We seem to switch variant during a game!
3275 * Presumably no holdings were displayed, so we have
3276 * to move the position two files to the right to
3277 * create room for them!
3279 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3280 /* Get a move list just to see the header, which
3281 will tell us whether this is really bug or zh */
3282 if (ics_getting_history == H_FALSE) {
3283 ics_getting_history = H_REQUESTED;
3284 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3288 new_piece[0] = NULLCHAR;
3289 sscanf(parse, "game %d white [%s black [%s <- %s",
3290 &gamenum, white_holding, black_holding,
3292 white_holding[strlen(white_holding)-1] = NULLCHAR;
3293 black_holding[strlen(black_holding)-1] = NULLCHAR;
3294 /* [HGM] copy holdings to board holdings area */
3295 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3296 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3298 if (appData.zippyPlay && first.initDone) {
3299 ZippyHoldings(white_holding, black_holding,
3303 if (tinyLayout || smallLayout) {
3304 char wh[16], bh[16];
3305 PackHolding(wh, white_holding);
3306 PackHolding(bh, black_holding);
3307 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3308 gameInfo.white, gameInfo.black);
3310 sprintf(str, "%s [%s] vs. %s [%s]",
3311 gameInfo.white, white_holding,
3312 gameInfo.black, black_holding);
3315 DrawPosition(FALSE, boards[currentMove]);
3318 /* Suppress following prompt */
3319 if (looking_at(buf, &i, "*% ")) {
3320 savingComment = FALSE;
3327 i++; /* skip unparsed character and loop back */
3330 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3331 started != STARTED_HOLDINGS && i > next_out) {
3332 SendToPlayer(&buf[next_out], i - next_out);
3335 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3337 leftover_len = buf_len - leftover_start;
3338 /* if buffer ends with something we couldn't parse,
3339 reparse it after appending the next read */
3341 } else if (count == 0) {
3342 RemoveInputSource(isr);
3343 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3345 DisplayFatalError(_("Error reading from ICS"), error, 1);
3350 /* Board style 12 looks like this:
3352 <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
3354 * The "<12> " is stripped before it gets to this routine. The two
3355 * trailing 0's (flip state and clock ticking) are later addition, and
3356 * some chess servers may not have them, or may have only the first.
3357 * Additional trailing fields may be added in the future.
3360 #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"
3362 #define RELATION_OBSERVING_PLAYED 0
3363 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3364 #define RELATION_PLAYING_MYMOVE 1
3365 #define RELATION_PLAYING_NOTMYMOVE -1
3366 #define RELATION_EXAMINING 2
3367 #define RELATION_ISOLATED_BOARD -3
3368 #define RELATION_STARTING_POSITION -4 /* FICS only */
3371 ParseBoard12(string)
3374 GameMode newGameMode;
3375 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3376 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3377 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3378 char to_play, board_chars[200];
3379 char move_str[500], str[500], elapsed_time[500];
3380 char black[32], white[32];
3382 int prevMove = currentMove;
3385 int fromX, fromY, toX, toY;
3387 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3388 char *bookHit = NULL; // [HGM] book
3390 fromX = fromY = toX = toY = -1;
3394 if (appData.debugMode)
3395 fprintf(debugFP, _("Parsing board: %s\n"), string);
3397 move_str[0] = NULLCHAR;
3398 elapsed_time[0] = NULLCHAR;
3399 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3401 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3402 if(string[i] == ' ') { ranks++; files = 0; }
3406 for(j = 0; j <i; j++) board_chars[j] = string[j];
3407 board_chars[i] = '\0';
3410 n = sscanf(string, PATTERN, &to_play, &double_push,
3411 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3412 &gamenum, white, black, &relation, &basetime, &increment,
3413 &white_stren, &black_stren, &white_time, &black_time,
3414 &moveNum, str, elapsed_time, move_str, &ics_flip,
3418 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3419 DisplayError(str, 0);
3423 /* Convert the move number to internal form */
3424 moveNum = (moveNum - 1) * 2;
3425 if (to_play == 'B') moveNum++;
3426 if (moveNum >= MAX_MOVES) {
3427 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3433 case RELATION_OBSERVING_PLAYED:
3434 case RELATION_OBSERVING_STATIC:
3435 if (gamenum == -1) {
3436 /* Old ICC buglet */
3437 relation = RELATION_OBSERVING_STATIC;
3439 newGameMode = IcsObserving;
3441 case RELATION_PLAYING_MYMOVE:
3442 case RELATION_PLAYING_NOTMYMOVE:
3444 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3445 IcsPlayingWhite : IcsPlayingBlack;
3447 case RELATION_EXAMINING:
3448 newGameMode = IcsExamining;
3450 case RELATION_ISOLATED_BOARD:
3452 /* Just display this board. If user was doing something else,
3453 we will forget about it until the next board comes. */
3454 newGameMode = IcsIdle;
3456 case RELATION_STARTING_POSITION:
3457 newGameMode = gameMode;
3461 /* Modify behavior for initial board display on move listing
3464 switch (ics_getting_history) {
3468 case H_GOT_REQ_HEADER:
3469 case H_GOT_UNREQ_HEADER:
3470 /* This is the initial position of the current game */
3471 gamenum = ics_gamenum;
3472 moveNum = 0; /* old ICS bug workaround */
3473 if (to_play == 'B') {
3474 startedFromSetupPosition = TRUE;
3475 blackPlaysFirst = TRUE;
3477 if (forwardMostMove == 0) forwardMostMove = 1;
3478 if (backwardMostMove == 0) backwardMostMove = 1;
3479 if (currentMove == 0) currentMove = 1;
3481 newGameMode = gameMode;
3482 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3484 case H_GOT_UNWANTED_HEADER:
3485 /* This is an initial board that we don't want */
3487 case H_GETTING_MOVES:
3488 /* Should not happen */
3489 DisplayError(_("Error gathering move list: extra board"), 0);
3490 ics_getting_history = H_FALSE;
3494 /* Take action if this is the first board of a new game, or of a
3495 different game than is currently being displayed. */
3496 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3497 relation == RELATION_ISOLATED_BOARD) {
3499 /* Forget the old game and get the history (if any) of the new one */
3500 if (gameMode != BeginningOfGame) {
3504 if (appData.autoRaiseBoard) BoardToTop();
3506 if (gamenum == -1) {
3507 newGameMode = IcsIdle;
3508 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3509 appData.getMoveList) {
3510 /* Need to get game history */
3511 ics_getting_history = H_REQUESTED;
3512 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3516 /* Initially flip the board to have black on the bottom if playing
3517 black or if the ICS flip flag is set, but let the user change
3518 it with the Flip View button. */
3519 flipView = appData.autoFlipView ?
3520 (newGameMode == IcsPlayingBlack) || ics_flip :
3523 /* Done with values from previous mode; copy in new ones */
3524 gameMode = newGameMode;
3526 ics_gamenum = gamenum;
3527 if (gamenum == gs_gamenum) {
3528 int klen = strlen(gs_kind);
3529 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3530 sprintf(str, "ICS %s", gs_kind);
3531 gameInfo.event = StrSave(str);
3533 gameInfo.event = StrSave("ICS game");
3535 gameInfo.site = StrSave(appData.icsHost);
3536 gameInfo.date = PGNDate();
3537 gameInfo.round = StrSave("-");
3538 gameInfo.white = StrSave(white);
3539 gameInfo.black = StrSave(black);
3540 timeControl = basetime * 60 * 1000;
3542 timeIncrement = increment * 1000;
3543 movesPerSession = 0;
3544 gameInfo.timeControl = TimeControlTagValue();
3545 VariantSwitch(board, StringToVariant(gameInfo.event) );
3546 if (appData.debugMode) {
3547 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3548 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3549 setbuf(debugFP, NULL);
3552 gameInfo.outOfBook = NULL;
3554 /* Do we have the ratings? */
3555 if (strcmp(player1Name, white) == 0 &&
3556 strcmp(player2Name, black) == 0) {
3557 if (appData.debugMode)
3558 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3559 player1Rating, player2Rating);
3560 gameInfo.whiteRating = player1Rating;
3561 gameInfo.blackRating = player2Rating;
3562 } else if (strcmp(player2Name, white) == 0 &&
3563 strcmp(player1Name, black) == 0) {
3564 if (appData.debugMode)
3565 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3566 player2Rating, player1Rating);
3567 gameInfo.whiteRating = player2Rating;
3568 gameInfo.blackRating = player1Rating;
3570 player1Name[0] = player2Name[0] = NULLCHAR;
3572 /* Silence shouts if requested */
3573 if (appData.quietPlay &&
3574 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3575 SendToICS(ics_prefix);
3576 SendToICS("set shout 0\n");
3580 /* Deal with midgame name changes */
3582 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3583 if (gameInfo.white) free(gameInfo.white);
3584 gameInfo.white = StrSave(white);
3586 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3587 if (gameInfo.black) free(gameInfo.black);
3588 gameInfo.black = StrSave(black);
3592 /* Throw away game result if anything actually changes in examine mode */
3593 if (gameMode == IcsExamining && !newGame) {
3594 gameInfo.result = GameUnfinished;
3595 if (gameInfo.resultDetails != NULL) {
3596 free(gameInfo.resultDetails);
3597 gameInfo.resultDetails = NULL;
3601 /* In pausing && IcsExamining mode, we ignore boards coming
3602 in if they are in a different variation than we are. */
3603 if (pauseExamInvalid) return;
3604 if (pausing && gameMode == IcsExamining) {
3605 if (moveNum <= pauseExamForwardMostMove) {
3606 pauseExamInvalid = TRUE;
3607 forwardMostMove = pauseExamForwardMostMove;
3612 if (appData.debugMode) {
3613 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3615 /* Parse the board */
3616 for (k = 0; k < ranks; k++) {
3617 for (j = 0; j < files; j++)
3618 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3619 if(gameInfo.holdingsWidth > 1) {
3620 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3621 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3624 CopyBoard(boards[moveNum], board);
3626 startedFromSetupPosition =
3627 !CompareBoards(board, initialPosition);
3628 if(startedFromSetupPosition)
3629 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3632 /* [HGM] Set castling rights. Take the outermost Rooks,
3633 to make it also work for FRC opening positions. Note that board12
3634 is really defective for later FRC positions, as it has no way to
3635 indicate which Rook can castle if they are on the same side of King.
3636 For the initial position we grant rights to the outermost Rooks,
3637 and remember thos rights, and we then copy them on positions
3638 later in an FRC game. This means WB might not recognize castlings with
3639 Rooks that have moved back to their original position as illegal,
3640 but in ICS mode that is not its job anyway.
3642 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3643 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3645 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3646 if(board[0][i] == WhiteRook) j = i;
3647 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3648 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3649 if(board[0][i] == WhiteRook) j = i;
3650 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3651 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3652 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3653 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3654 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3655 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3656 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3658 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3659 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3660 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3661 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3662 if(board[BOARD_HEIGHT-1][k] == bKing)
3663 initialRights[5] = castlingRights[moveNum][5] = k;
3665 r = castlingRights[moveNum][0] = initialRights[0];
3666 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3667 r = castlingRights[moveNum][1] = initialRights[1];
3668 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3669 r = castlingRights[moveNum][3] = initialRights[3];
3670 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3671 r = castlingRights[moveNum][4] = initialRights[4];
3672 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3673 /* wildcastle kludge: always assume King has rights */
3674 r = castlingRights[moveNum][2] = initialRights[2];
3675 r = castlingRights[moveNum][5] = initialRights[5];
3677 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3678 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3681 if (ics_getting_history == H_GOT_REQ_HEADER ||
3682 ics_getting_history == H_GOT_UNREQ_HEADER) {
3683 /* This was an initial position from a move list, not
3684 the current position */
3688 /* Update currentMove and known move number limits */
3689 newMove = newGame || moveNum > forwardMostMove;
3691 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3692 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3693 takeback = forwardMostMove - moveNum;
3694 for (i = 0; i < takeback; i++) {
3695 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3696 SendToProgram("undo\n", &first);
3701 forwardMostMove = backwardMostMove = currentMove = moveNum;
3702 if (gameMode == IcsExamining && moveNum == 0) {
3703 /* Workaround for ICS limitation: we are not told the wild
3704 type when starting to examine a game. But if we ask for
3705 the move list, the move list header will tell us */
3706 ics_getting_history = H_REQUESTED;
3707 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3710 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3711 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3712 forwardMostMove = moveNum;
3713 if (!pausing || currentMove > forwardMostMove)
3714 currentMove = forwardMostMove;
3716 /* New part of history that is not contiguous with old part */
3717 if (pausing && gameMode == IcsExamining) {
3718 pauseExamInvalid = TRUE;
3719 forwardMostMove = pauseExamForwardMostMove;
3722 forwardMostMove = backwardMostMove = currentMove = moveNum;
3723 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3724 ics_getting_history = H_REQUESTED;
3725 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3730 /* Update the clocks */
3731 if (strchr(elapsed_time, '.')) {
3733 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3734 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3736 /* Time is in seconds */
3737 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3738 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3743 if (appData.zippyPlay && newGame &&
3744 gameMode != IcsObserving && gameMode != IcsIdle &&
3745 gameMode != IcsExamining)
3746 ZippyFirstBoard(moveNum, basetime, increment);
3749 /* Put the move on the move list, first converting
3750 to canonical algebraic form. */
3752 if (appData.debugMode) {
3753 if (appData.debugMode) { int f = forwardMostMove;
3754 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3755 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3757 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3758 fprintf(debugFP, "moveNum = %d\n", moveNum);
3759 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3760 setbuf(debugFP, NULL);
3762 if (moveNum <= backwardMostMove) {
3763 /* We don't know what the board looked like before
3765 strcpy(parseList[moveNum - 1], move_str);
3766 strcat(parseList[moveNum - 1], " ");
3767 strcat(parseList[moveNum - 1], elapsed_time);
3768 moveList[moveNum - 1][0] = NULLCHAR;
3769 } else if (strcmp(move_str, "none") == 0) {
3770 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3771 /* Again, we don't know what the board looked like;
3772 this is really the start of the game. */
3773 parseList[moveNum - 1][0] = NULLCHAR;
3774 moveList[moveNum - 1][0] = NULLCHAR;
3775 backwardMostMove = moveNum;
3776 startedFromSetupPosition = TRUE;
3777 fromX = fromY = toX = toY = -1;
3779 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3780 // So we parse the long-algebraic move string in stead of the SAN move
3781 int valid; char buf[MSG_SIZ], *prom;
3783 // str looks something like "Q/a1-a2"; kill the slash
3785 sprintf(buf, "%c%s", str[0], str+2);
3786 else strcpy(buf, str); // might be castling
3787 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3788 strcat(buf, prom); // long move lacks promo specification!
3789 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3790 if(appData.debugMode)
3791 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3792 strcpy(move_str, buf);
3794 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3795 &fromX, &fromY, &toX, &toY, &promoChar)
3796 || ParseOneMove(buf, moveNum - 1, &moveType,
3797 &fromX, &fromY, &toX, &toY, &promoChar);
3798 // end of long SAN patch
3800 (void) CoordsToAlgebraic(boards[moveNum - 1],
3801 PosFlags(moveNum - 1), EP_UNKNOWN,
3802 fromY, fromX, toY, toX, promoChar,
3803 parseList[moveNum-1]);
3804 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3805 castlingRights[moveNum]) ) {
3811 if(gameInfo.variant != VariantShogi)
3812 strcat(parseList[moveNum - 1], "+");
3815 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3816 strcat(parseList[moveNum - 1], "#");
3819 strcat(parseList[moveNum - 1], " ");
3820 strcat(parseList[moveNum - 1], elapsed_time);
3821 /* currentMoveString is set as a side-effect of ParseOneMove */
3822 strcpy(moveList[moveNum - 1], currentMoveString);
3823 strcat(moveList[moveNum - 1], "\n");
3825 /* Move from ICS was illegal!? Punt. */
3826 if (appData.debugMode) {
3827 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3828 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3831 if (appData.testLegality && appData.debugMode) {
3832 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3833 DisplayError(str, 0);
3836 strcpy(parseList[moveNum - 1], move_str);
3837 strcat(parseList[moveNum - 1], " ");
3838 strcat(parseList[moveNum - 1], elapsed_time);
3839 moveList[moveNum - 1][0] = NULLCHAR;
3840 fromX = fromY = toX = toY = -1;
3843 if (appData.debugMode) {
3844 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3845 setbuf(debugFP, NULL);
3849 /* Send move to chess program (BEFORE animating it). */
3850 if (appData.zippyPlay && !newGame && newMove &&
3851 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3853 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3854 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3855 if (moveList[moveNum - 1][0] == NULLCHAR) {
3856 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3858 DisplayError(str, 0);
3860 if (first.sendTime) {
3861 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3863 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3864 if (firstMove && !bookHit) {
3866 if (first.useColors) {
3867 SendToProgram(gameMode == IcsPlayingWhite ?
3869 "black\ngo\n", &first);
3871 SendToProgram("go\n", &first);
3873 first.maybeThinking = TRUE;
3876 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3877 if (moveList[moveNum - 1][0] == NULLCHAR) {
3878 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3879 DisplayError(str, 0);
3881 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3882 SendMoveToProgram(moveNum - 1, &first);
3889 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3890 /* If move comes from a remote source, animate it. If it
3891 isn't remote, it will have already been animated. */
3892 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3893 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3895 if (!pausing && appData.highlightLastMove) {
3896 SetHighlights(fromX, fromY, toX, toY);
3900 /* Start the clocks */
3901 whiteFlag = blackFlag = FALSE;
3902 appData.clockMode = !(basetime == 0 && increment == 0);
3904 ics_clock_paused = TRUE;
3906 } else if (ticking == 1) {
3907 ics_clock_paused = FALSE;
3909 if (gameMode == IcsIdle ||
3910 relation == RELATION_OBSERVING_STATIC ||
3911 relation == RELATION_EXAMINING ||
3913 DisplayBothClocks();
3917 /* Display opponents and material strengths */
3918 if (gameInfo.variant != VariantBughouse &&
3919 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3920 if (tinyLayout || smallLayout) {
3921 if(gameInfo.variant == VariantNormal)
3922 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3923 gameInfo.white, white_stren, gameInfo.black, black_stren,
3924 basetime, increment);
3926 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3927 gameInfo.white, white_stren, gameInfo.black, black_stren,
3928 basetime, increment, (int) gameInfo.variant);
3930 if(gameInfo.variant == VariantNormal)
3931 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3932 gameInfo.white, white_stren, gameInfo.black, black_stren,
3933 basetime, increment);
3935 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3936 gameInfo.white, white_stren, gameInfo.black, black_stren,
3937 basetime, increment, VariantName(gameInfo.variant));
3940 if (appData.debugMode) {
3941 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3946 /* Display the board */
3947 if (!pausing && !appData.noGUI) {
3949 if (appData.premove)
3951 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3952 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3953 ClearPremoveHighlights();
3955 DrawPosition(FALSE, boards[currentMove]);
3956 DisplayMove(moveNum - 1);
3957 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3958 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3959 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3960 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3964 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3966 if(bookHit) { // [HGM] book: simulate book reply
3967 static char bookMove[MSG_SIZ]; // a bit generous?
3969 programStats.nodes = programStats.depth = programStats.time =
3970 programStats.score = programStats.got_only_move = 0;
3971 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3973 strcpy(bookMove, "move ");
3974 strcat(bookMove, bookHit);
3975 HandleMachineMove(bookMove, &first);
3984 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3985 ics_getting_history = H_REQUESTED;
3986 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3992 AnalysisPeriodicEvent(force)
3995 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3996 && !force) || !appData.periodicUpdates)
3999 /* Send . command to Crafty to collect stats */
4000 SendToProgram(".\n", &first);
4002 /* Don't send another until we get a response (this makes
4003 us stop sending to old Crafty's which don't understand
4004 the "." command (sending illegal cmds resets node count & time,
4005 which looks bad)) */
4006 programStats.ok_to_send = 0;
4010 SendMoveToProgram(moveNum, cps)
4012 ChessProgramState *cps;
4016 if (cps->useUsermove) {
4017 SendToProgram("usermove ", cps);
4021 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4022 int len = space - parseList[moveNum];
4023 memcpy(buf, parseList[moveNum], len);
4025 buf[len] = NULLCHAR;
4027 sprintf(buf, "%s\n", parseList[moveNum]);
4029 SendToProgram(buf, cps);
4031 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4032 AlphaRank(moveList[moveNum], 4);
4033 SendToProgram(moveList[moveNum], cps);
4034 AlphaRank(moveList[moveNum], 4); // and back
4036 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4037 * the engine. It would be nice to have a better way to identify castle
4039 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4040 && cps->useOOCastle) {
4041 int fromX = moveList[moveNum][0] - AAA;
4042 int fromY = moveList[moveNum][1] - ONE;
4043 int toX = moveList[moveNum][2] - AAA;
4044 int toY = moveList[moveNum][3] - ONE;
4045 if((boards[moveNum][fromY][fromX] == WhiteKing
4046 && boards[moveNum][toY][toX] == WhiteRook)
4047 || (boards[moveNum][fromY][fromX] == BlackKing
4048 && boards[moveNum][toY][toX] == BlackRook)) {
4049 if(toX > fromX) SendToProgram("O-O\n", cps);
4050 else SendToProgram("O-O-O\n", cps);
4052 else SendToProgram(moveList[moveNum], cps);
4054 else SendToProgram(moveList[moveNum], cps);
4055 /* End of additions by Tord */
4058 /* [HGM] setting up the opening has brought engine in force mode! */
4059 /* Send 'go' if we are in a mode where machine should play. */
4060 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4061 (gameMode == TwoMachinesPlay ||
4063 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4065 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4066 SendToProgram("go\n", cps);
4067 if (appData.debugMode) {
4068 fprintf(debugFP, "(extra)\n");
4071 setboardSpoiledMachineBlack = 0;
4075 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4077 int fromX, fromY, toX, toY;
4079 char user_move[MSG_SIZ];
4083 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4084 (int)moveType, fromX, fromY, toX, toY);
4085 DisplayError(user_move + strlen("say "), 0);
4087 case WhiteKingSideCastle:
4088 case BlackKingSideCastle:
4089 case WhiteQueenSideCastleWild:
4090 case BlackQueenSideCastleWild:
4092 case WhiteHSideCastleFR:
4093 case BlackHSideCastleFR:
4095 sprintf(user_move, "o-o\n");
4097 case WhiteQueenSideCastle:
4098 case BlackQueenSideCastle:
4099 case WhiteKingSideCastleWild:
4100 case BlackKingSideCastleWild:
4102 case WhiteASideCastleFR:
4103 case BlackASideCastleFR:
4105 sprintf(user_move, "o-o-o\n");
4107 case WhitePromotionQueen:
4108 case BlackPromotionQueen:
4109 case WhitePromotionRook:
4110 case BlackPromotionRook:
4111 case WhitePromotionBishop:
4112 case BlackPromotionBishop:
4113 case WhitePromotionKnight:
4114 case BlackPromotionKnight:
4115 case WhitePromotionKing:
4116 case BlackPromotionKing:
4117 case WhitePromotionChancellor:
4118 case BlackPromotionChancellor:
4119 case WhitePromotionArchbishop:
4120 case BlackPromotionArchbishop:
4121 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4122 sprintf(user_move, "%c%c%c%c=%c\n",
4123 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4124 PieceToChar(WhiteFerz));
4125 else if(gameInfo.variant == VariantGreat)
4126 sprintf(user_move, "%c%c%c%c=%c\n",
4127 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4128 PieceToChar(WhiteMan));
4130 sprintf(user_move, "%c%c%c%c=%c\n",
4131 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4132 PieceToChar(PromoPiece(moveType)));
4136 sprintf(user_move, "%c@%c%c\n",
4137 ToUpper(PieceToChar((ChessSquare) fromX)),
4138 AAA + toX, ONE + toY);
4141 case WhiteCapturesEnPassant:
4142 case BlackCapturesEnPassant:
4143 case IllegalMove: /* could be a variant we don't quite understand */
4144 sprintf(user_move, "%c%c%c%c\n",
4145 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4148 SendToICS(user_move);
4149 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4150 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4154 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4159 if (rf == DROP_RANK) {
4160 sprintf(move, "%c@%c%c\n",
4161 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4163 if (promoChar == 'x' || promoChar == NULLCHAR) {
4164 sprintf(move, "%c%c%c%c\n",
4165 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4167 sprintf(move, "%c%c%c%c%c\n",
4168 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4174 ProcessICSInitScript(f)
4179 while (fgets(buf, MSG_SIZ, f)) {
4180 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4187 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4189 AlphaRank(char *move, int n)
4191 // char *p = move, c; int x, y;
4193 if (appData.debugMode) {
4194 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4198 move[2]>='0' && move[2]<='9' &&
4199 move[3]>='a' && move[3]<='x' ) {
4201 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4202 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4204 if(move[0]>='0' && move[0]<='9' &&
4205 move[1]>='a' && move[1]<='x' &&
4206 move[2]>='0' && move[2]<='9' &&
4207 move[3]>='a' && move[3]<='x' ) {
4208 /* input move, Shogi -> normal */
4209 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4210 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4211 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4212 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4215 move[3]>='0' && move[3]<='9' &&
4216 move[2]>='a' && move[2]<='x' ) {
4218 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4219 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4222 move[0]>='a' && move[0]<='x' &&
4223 move[3]>='0' && move[3]<='9' &&
4224 move[2]>='a' && move[2]<='x' ) {
4225 /* output move, normal -> Shogi */
4226 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4227 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4228 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4229 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4230 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4232 if (appData.debugMode) {
4233 fprintf(debugFP, " out = '%s'\n", move);
4237 /* Parser for moves from gnuchess, ICS, or user typein box */
4239 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4242 ChessMove *moveType;
4243 int *fromX, *fromY, *toX, *toY;
4246 if (appData.debugMode) {
4247 fprintf(debugFP, "move to parse: %s\n", move);
4249 *moveType = yylexstr(moveNum, move);
4251 switch (*moveType) {
4252 case WhitePromotionChancellor:
4253 case BlackPromotionChancellor:
4254 case WhitePromotionArchbishop:
4255 case BlackPromotionArchbishop:
4256 case WhitePromotionQueen:
4257 case BlackPromotionQueen:
4258 case WhitePromotionRook:
4259 case BlackPromotionRook:
4260 case WhitePromotionBishop:
4261 case BlackPromotionBishop:
4262 case WhitePromotionKnight:
4263 case BlackPromotionKnight:
4264 case WhitePromotionKing:
4265 case BlackPromotionKing:
4267 case WhiteCapturesEnPassant:
4268 case BlackCapturesEnPassant:
4269 case WhiteKingSideCastle:
4270 case WhiteQueenSideCastle:
4271 case BlackKingSideCastle:
4272 case BlackQueenSideCastle:
4273 case WhiteKingSideCastleWild:
4274 case WhiteQueenSideCastleWild:
4275 case BlackKingSideCastleWild:
4276 case BlackQueenSideCastleWild:
4277 /* Code added by Tord: */
4278 case WhiteHSideCastleFR:
4279 case WhiteASideCastleFR:
4280 case BlackHSideCastleFR:
4281 case BlackASideCastleFR:
4282 /* End of code added by Tord */
4283 case IllegalMove: /* bug or odd chess variant */
4284 *fromX = currentMoveString[0] - AAA;
4285 *fromY = currentMoveString[1] - ONE;
4286 *toX = currentMoveString[2] - AAA;
4287 *toY = currentMoveString[3] - ONE;
4288 *promoChar = currentMoveString[4];
4289 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4290 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4291 if (appData.debugMode) {
4292 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4294 *fromX = *fromY = *toX = *toY = 0;
4297 if (appData.testLegality) {
4298 return (*moveType != IllegalMove);
4300 return !(fromX == fromY && toX == toY);
4305 *fromX = *moveType == WhiteDrop ?
4306 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4307 (int) CharToPiece(ToLower(currentMoveString[0]));
4309 *toX = currentMoveString[2] - AAA;
4310 *toY = currentMoveString[3] - ONE;
4311 *promoChar = NULLCHAR;
4315 case ImpossibleMove:
4316 case (ChessMove) 0: /* end of file */
4325 if (appData.debugMode) {
4326 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4329 *fromX = *fromY = *toX = *toY = 0;
4330 *promoChar = NULLCHAR;
4335 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4336 // All positions will have equal probability, but the current method will not provide a unique
4337 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4343 int piecesLeft[(int)BlackPawn];
4344 int seed, nrOfShuffles;
4346 void GetPositionNumber()
4347 { // sets global variable seed
4350 seed = appData.defaultFrcPosition;
4351 if(seed < 0) { // randomize based on time for negative FRC position numbers
4352 for(i=0; i<50; i++) seed += random();
4353 seed = random() ^ random() >> 8 ^ random() << 8;
4354 if(seed<0) seed = -seed;
4358 int put(Board board, int pieceType, int rank, int n, int shade)
4359 // put the piece on the (n-1)-th empty squares of the given shade
4363 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4364 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4365 board[rank][i] = (ChessSquare) pieceType;
4366 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4368 piecesLeft[pieceType]--;
4376 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4377 // calculate where the next piece goes, (any empty square), and put it there
4381 i = seed % squaresLeft[shade];
4382 nrOfShuffles *= squaresLeft[shade];
4383 seed /= squaresLeft[shade];
4384 put(board, pieceType, rank, i, shade);
4387 void AddTwoPieces(Board board, int pieceType, int rank)
4388 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4390 int i, n=squaresLeft[ANY], j=n-1, k;
4392 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4393 i = seed % k; // pick one
4396 while(i >= j) i -= j--;
4397 j = n - 1 - j; i += j;
4398 put(board, pieceType, rank, j, ANY);
4399 put(board, pieceType, rank, i, ANY);
4402 void SetUpShuffle(Board board, int number)
4406 GetPositionNumber(); nrOfShuffles = 1;
4408 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4409 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4410 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4412 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4414 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4415 p = (int) board[0][i];
4416 if(p < (int) BlackPawn) piecesLeft[p] ++;
4417 board[0][i] = EmptySquare;
4420 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4421 // shuffles restricted to allow normal castling put KRR first
4422 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4423 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4424 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4425 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4426 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4427 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4428 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4429 put(board, WhiteRook, 0, 0, ANY);
4430 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4433 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4434 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4435 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4436 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4437 while(piecesLeft[p] >= 2) {
4438 AddOnePiece(board, p, 0, LITE);
4439 AddOnePiece(board, p, 0, DARK);
4441 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4444 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4445 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4446 // but we leave King and Rooks for last, to possibly obey FRC restriction
4447 if(p == (int)WhiteRook) continue;
4448 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4449 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4452 // now everything is placed, except perhaps King (Unicorn) and Rooks
4454 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4455 // Last King gets castling rights
4456 while(piecesLeft[(int)WhiteUnicorn]) {
4457 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4458 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4461 while(piecesLeft[(int)WhiteKing]) {
4462 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4463 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4468 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4469 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4472 // Only Rooks can be left; simply place them all
4473 while(piecesLeft[(int)WhiteRook]) {
4474 i = put(board, WhiteRook, 0, 0, ANY);
4475 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4478 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4480 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4483 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4484 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4487 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4490 int SetCharTable( char *table, const char * map )
4491 /* [HGM] moved here from winboard.c because of its general usefulness */
4492 /* Basically a safe strcpy that uses the last character as King */
4494 int result = FALSE; int NrPieces;
4496 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4497 && NrPieces >= 12 && !(NrPieces&1)) {
4498 int i; /* [HGM] Accept even length from 12 to 34 */
4500 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4501 for( i=0; i<NrPieces/2-1; i++ ) {
4503 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4505 table[(int) WhiteKing] = map[NrPieces/2-1];
4506 table[(int) BlackKing] = map[NrPieces-1];
4514 void Prelude(Board board)
4515 { // [HGM] superchess: random selection of exo-pieces
4516 int i, j, k; ChessSquare p;
4517 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4519 GetPositionNumber(); // use FRC position number
4521 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4522 SetCharTable(pieceToChar, appData.pieceToCharTable);
4523 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4524 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4527 j = seed%4; seed /= 4;
4528 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4529 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4530 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4531 j = seed%3 + (seed%3 >= j); seed /= 3;
4532 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4533 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4534 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4535 j = seed%3; seed /= 3;
4536 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4537 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4538 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4539 j = seed%2 + (seed%2 >= j); seed /= 2;
4540 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4541 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4542 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4543 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4544 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4545 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4546 put(board, exoPieces[0], 0, 0, ANY);
4547 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4551 InitPosition(redraw)
4554 ChessSquare (* pieces)[BOARD_SIZE];
4555 int i, j, pawnRow, overrule,
4556 oldx = gameInfo.boardWidth,
4557 oldy = gameInfo.boardHeight,
4558 oldh = gameInfo.holdingsWidth,
4559 oldv = gameInfo.variant;
4561 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4563 /* [AS] Initialize pv info list [HGM] and game status */
4565 for( i=0; i<MAX_MOVES; i++ ) {
4566 pvInfoList[i].depth = 0;
4567 epStatus[i]=EP_NONE;
4568 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4571 initialRulePlies = 0; /* 50-move counter start */
4573 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4574 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4578 /* [HGM] logic here is completely changed. In stead of full positions */
4579 /* the initialized data only consist of the two backranks. The switch */
4580 /* selects which one we will use, which is than copied to the Board */
4581 /* initialPosition, which for the rest is initialized by Pawns and */
4582 /* empty squares. This initial position is then copied to boards[0], */
4583 /* possibly after shuffling, so that it remains available. */
4585 gameInfo.holdingsWidth = 0; /* default board sizes */
4586 gameInfo.boardWidth = 8;
4587 gameInfo.boardHeight = 8;
4588 gameInfo.holdingsSize = 0;
4589 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4590 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4591 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4593 switch (gameInfo.variant) {
4594 case VariantFischeRandom:
4595 shuffleOpenings = TRUE;
4599 case VariantShatranj:
4600 pieces = ShatranjArray;
4601 nrCastlingRights = 0;
4602 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4604 case VariantTwoKings:
4605 pieces = twoKingsArray;
4607 case VariantCapaRandom:
4608 shuffleOpenings = TRUE;
4609 case VariantCapablanca:
4610 pieces = CapablancaArray;
4611 gameInfo.boardWidth = 10;
4612 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4615 pieces = GothicArray;
4616 gameInfo.boardWidth = 10;
4617 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4620 pieces = JanusArray;
4621 gameInfo.boardWidth = 10;
4622 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4623 nrCastlingRights = 6;
4624 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4625 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4626 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4627 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4628 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4629 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4632 pieces = FalconArray;
4633 gameInfo.boardWidth = 10;
4634 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4636 case VariantXiangqi:
4637 pieces = XiangqiArray;
4638 gameInfo.boardWidth = 9;
4639 gameInfo.boardHeight = 10;
4640 nrCastlingRights = 0;
4641 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4644 pieces = ShogiArray;
4645 gameInfo.boardWidth = 9;
4646 gameInfo.boardHeight = 9;
4647 gameInfo.holdingsSize = 7;
4648 nrCastlingRights = 0;
4649 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4651 case VariantCourier:
4652 pieces = CourierArray;
4653 gameInfo.boardWidth = 12;
4654 nrCastlingRights = 0;
4655 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4656 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4658 case VariantKnightmate:
4659 pieces = KnightmateArray;
4660 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4663 pieces = fairyArray;
4664 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4667 pieces = GreatArray;
4668 gameInfo.boardWidth = 10;
4669 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4670 gameInfo.holdingsSize = 8;
4674 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4675 gameInfo.holdingsSize = 8;
4676 startedFromSetupPosition = TRUE;
4678 case VariantCrazyhouse:
4679 case VariantBughouse:
4681 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4682 gameInfo.holdingsSize = 5;
4684 case VariantWildCastle:
4686 /* !!?shuffle with kings guaranteed to be on d or e file */
4687 shuffleOpenings = 1;
4689 case VariantNoCastle:
4691 nrCastlingRights = 0;
4692 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4693 /* !!?unconstrained back-rank shuffle */
4694 shuffleOpenings = 1;
4699 if(appData.NrFiles >= 0) {
4700 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4701 gameInfo.boardWidth = appData.NrFiles;
4703 if(appData.NrRanks >= 0) {
4704 gameInfo.boardHeight = appData.NrRanks;
4706 if(appData.holdingsSize >= 0) {
4707 i = appData.holdingsSize;
4708 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4709 gameInfo.holdingsSize = i;
4711 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4712 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4713 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4715 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4716 if(pawnRow < 1) pawnRow = 1;
4718 /* User pieceToChar list overrules defaults */
4719 if(appData.pieceToCharTable != NULL)
4720 SetCharTable(pieceToChar, appData.pieceToCharTable);
4722 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4724 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4725 s = (ChessSquare) 0; /* account holding counts in guard band */
4726 for( i=0; i<BOARD_HEIGHT; i++ )
4727 initialPosition[i][j] = s;
4729 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4730 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4731 initialPosition[pawnRow][j] = WhitePawn;
4732 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4733 if(gameInfo.variant == VariantXiangqi) {
4735 initialPosition[pawnRow][j] =
4736 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4737 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4738 initialPosition[2][j] = WhiteCannon;
4739 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4743 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4745 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4748 initialPosition[1][j] = WhiteBishop;
4749 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4751 initialPosition[1][j] = WhiteRook;
4752 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4755 if( nrCastlingRights == -1) {
4756 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4757 /* This sets default castling rights from none to normal corners */
4758 /* Variants with other castling rights must set them themselves above */
4759 nrCastlingRights = 6;
4761 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4762 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4763 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4764 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4765 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4766 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4769 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4770 if(gameInfo.variant == VariantGreat) { // promotion commoners
4771 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4772 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4773 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4774 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4777 if(gameInfo.variant == VariantFischeRandom) {
4778 if( appData.defaultFrcPosition < 0 ) {
4779 ShuffleFRC( initialPosition );
4782 SetupFRC( initialPosition, appData.defaultFrcPosition );
4784 startedFromSetupPosition = TRUE;
4787 if (appData.debugMode) {
4788 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4790 if(shuffleOpenings) {
4791 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4792 startedFromSetupPosition = TRUE;
4795 if(startedFromPositionFile) {
4796 /* [HGM] loadPos: use PositionFile for every new game */
4797 CopyBoard(initialPosition, filePosition);
4798 for(i=0; i<nrCastlingRights; i++)
4799 castlingRights[0][i] = initialRights[i] = fileRights[i];
4800 startedFromSetupPosition = TRUE;
4803 CopyBoard(boards[0], initialPosition);
4805 if(oldx != gameInfo.boardWidth ||
4806 oldy != gameInfo.boardHeight ||
4807 oldh != gameInfo.holdingsWidth
4809 || oldv == VariantGothic || // For licensing popups
4810 gameInfo.variant == VariantGothic
4813 || oldv == VariantFalcon ||
4814 gameInfo.variant == VariantFalcon
4817 InitDrawingSizes(-2 ,0);
4820 DrawPosition(TRUE, boards[currentMove]);
4824 SendBoard(cps, moveNum)
4825 ChessProgramState *cps;
4828 char message[MSG_SIZ];
4830 if (cps->useSetboard) {
4831 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4832 sprintf(message, "setboard %s\n", fen);
4833 SendToProgram(message, cps);
4839 /* Kludge to set black to move, avoiding the troublesome and now
4840 * deprecated "black" command.
4842 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4844 SendToProgram("edit\n", cps);
4845 SendToProgram("#\n", cps);
4846 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4847 bp = &boards[moveNum][i][BOARD_LEFT];
4848 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4849 if ((int) *bp < (int) BlackPawn) {
4850 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4852 if(message[0] == '+' || message[0] == '~') {
4853 sprintf(message, "%c%c%c+\n",
4854 PieceToChar((ChessSquare)(DEMOTED *bp)),
4857 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4858 message[1] = BOARD_RGHT - 1 - j + '1';
4859 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4861 SendToProgram(message, cps);
4866 SendToProgram("c\n", cps);
4867 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4868 bp = &boards[moveNum][i][BOARD_LEFT];
4869 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4870 if (((int) *bp != (int) EmptySquare)
4871 && ((int) *bp >= (int) BlackPawn)) {
4872 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4874 if(message[0] == '+' || message[0] == '~') {
4875 sprintf(message, "%c%c%c+\n",
4876 PieceToChar((ChessSquare)(DEMOTED *bp)),
4879 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880 message[1] = BOARD_RGHT - 1 - j + '1';
4881 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4883 SendToProgram(message, cps);
4888 SendToProgram(".\n", cps);
4890 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4894 IsPromotion(fromX, fromY, toX, toY)
4895 int fromX, fromY, toX, toY;
4897 /* [HGM] add Shogi promotions */
4898 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4901 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4902 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4903 /* [HGM] Note to self: line above also weeds out drops */
4904 piece = boards[currentMove][fromY][fromX];
4905 if(gameInfo.variant == VariantShogi) {
4906 promotionZoneSize = 3;
4907 highestPromotingPiece = (int)WhiteKing;
4908 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4909 and if in normal chess we then allow promotion to King, why not
4910 allow promotion of other piece in Shogi? */
4912 if((int)piece >= BlackPawn) {
4913 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4915 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4917 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4918 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4920 return ( (int)piece <= highestPromotingPiece );
4924 InPalace(row, column)
4926 { /* [HGM] for Xiangqi */
4927 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4928 column < (BOARD_WIDTH + 4)/2 &&
4929 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4934 PieceForSquare (x, y)
4938 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4941 return boards[currentMove][y][x];
4945 OKToStartUserMove(x, y)
4948 ChessSquare from_piece;
4951 if (matchMode) return FALSE;
4952 if (gameMode == EditPosition) return TRUE;
4954 if (x >= 0 && y >= 0)
4955 from_piece = boards[currentMove][y][x];
4957 from_piece = EmptySquare;
4959 if (from_piece == EmptySquare) return FALSE;
4961 white_piece = (int)from_piece >= (int)WhitePawn &&
4962 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4965 case PlayFromGameFile:
4967 case TwoMachinesPlay:
4975 case MachinePlaysWhite:
4976 case IcsPlayingBlack:
4977 if (appData.zippyPlay) return FALSE;
4979 DisplayMoveError(_("You are playing Black"));
4984 case MachinePlaysBlack:
4985 case IcsPlayingWhite:
4986 if (appData.zippyPlay) return FALSE;
4988 DisplayMoveError(_("You are playing White"));
4994 if (!white_piece && WhiteOnMove(currentMove)) {
4995 DisplayMoveError(_("It is White's turn"));
4998 if (white_piece && !WhiteOnMove(currentMove)) {
4999 DisplayMoveError(_("It is Black's turn"));
5002 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5003 /* Editing correspondence game history */
5004 /* Could disallow this or prompt for confirmation */
5007 if (currentMove < forwardMostMove) {
5008 /* Discarding moves */
5009 /* Could prompt for confirmation here,
5010 but I don't think that's such a good idea */
5011 forwardMostMove = currentMove;
5015 case BeginningOfGame:
5016 if (appData.icsActive) return FALSE;
5017 if (!appData.noChessProgram) {
5019 DisplayMoveError(_("You are playing White"));
5026 if (!white_piece && WhiteOnMove(currentMove)) {
5027 DisplayMoveError(_("It is White's turn"));
5030 if (white_piece && !WhiteOnMove(currentMove)) {
5031 DisplayMoveError(_("It is Black's turn"));
5040 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5041 && gameMode != AnalyzeFile && gameMode != Training) {
5042 DisplayMoveError(_("Displayed position is not current"));
5048 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5049 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5050 int lastLoadGameUseList = FALSE;
5051 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5052 ChessMove lastLoadGameStart = (ChessMove) 0;
5055 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5056 int fromX, fromY, toX, toY;
5061 ChessSquare pdown, pup;
5063 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5065 /* [HGM] suppress all moves into holdings area and guard band */
5066 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5067 return ImpossibleMove;
5069 /* [HGM] <sameColor> moved to here from winboard.c */
5070 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5071 pdown = boards[currentMove][fromY][fromX];
5072 pup = boards[currentMove][toY][toX];
5073 if ( gameMode != EditPosition && !captureOwn &&
5074 (WhitePawn <= pdown && pdown < BlackPawn &&
5075 WhitePawn <= pup && pup < BlackPawn ||
5076 BlackPawn <= pdown && pdown < EmptySquare &&
5077 BlackPawn <= pup && pup < EmptySquare
5078 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5079 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5080 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5081 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5082 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5086 /* Check if the user is playing in turn. This is complicated because we
5087 let the user "pick up" a piece before it is his turn. So the piece he
5088 tried to pick up may have been captured by the time he puts it down!
5089 Therefore we use the color the user is supposed to be playing in this
5090 test, not the color of the piece that is currently on the starting
5091 square---except in EditGame mode, where the user is playing both
5092 sides; fortunately there the capture race can't happen. (It can
5093 now happen in IcsExamining mode, but that's just too bad. The user
5094 will get a somewhat confusing message in that case.)
5098 case PlayFromGameFile:
5100 case TwoMachinesPlay:
5104 /* We switched into a game mode where moves are not accepted,
5105 perhaps while the mouse button was down. */
5106 return ImpossibleMove;
5108 case MachinePlaysWhite:
5109 /* User is moving for Black */
5110 if (WhiteOnMove(currentMove)) {
5111 DisplayMoveError(_("It is White's turn"));
5112 return ImpossibleMove;
5116 case MachinePlaysBlack:
5117 /* User is moving for White */
5118 if (!WhiteOnMove(currentMove)) {
5119 DisplayMoveError(_("It is Black's turn"));
5120 return ImpossibleMove;
5126 case BeginningOfGame:
5129 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5130 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5131 /* User is moving for Black */
5132 if (WhiteOnMove(currentMove)) {
5133 DisplayMoveError(_("It is White's turn"));
5134 return ImpossibleMove;
5137 /* User is moving for White */
5138 if (!WhiteOnMove(currentMove)) {
5139 DisplayMoveError(_("It is Black's turn"));
5140 return ImpossibleMove;
5145 case IcsPlayingBlack:
5146 /* User is moving for Black */
5147 if (WhiteOnMove(currentMove)) {
5148 if (!appData.premove) {
5149 DisplayMoveError(_("It is White's turn"));
5150 } else if (toX >= 0 && toY >= 0) {
5153 premoveFromX = fromX;
5154 premoveFromY = fromY;
5155 premovePromoChar = promoChar;
5157 if (appData.debugMode)
5158 fprintf(debugFP, "Got premove: fromX %d,"
5159 "fromY %d, toX %d, toY %d\n",
5160 fromX, fromY, toX, toY);
5162 return ImpossibleMove;
5166 case IcsPlayingWhite:
5167 /* User is moving for White */
5168 if (!WhiteOnMove(currentMove)) {
5169 if (!appData.premove) {
5170 DisplayMoveError(_("It is Black's turn"));
5171 } else if (toX >= 0 && toY >= 0) {
5174 premoveFromX = fromX;
5175 premoveFromY = fromY;
5176 premovePromoChar = promoChar;
5178 if (appData.debugMode)
5179 fprintf(debugFP, "Got premove: fromX %d,"
5180 "fromY %d, toX %d, toY %d\n",
5181 fromX, fromY, toX, toY);
5183 return ImpossibleMove;
5191 /* EditPosition, empty square, or different color piece;
5192 click-click move is possible */
5193 if (toX == -2 || toY == -2) {
5194 boards[0][fromY][fromX] = EmptySquare;
5195 return AmbiguousMove;
5196 } else if (toX >= 0 && toY >= 0) {
5197 boards[0][toY][toX] = boards[0][fromY][fromX];
5198 boards[0][fromY][fromX] = EmptySquare;
5199 return AmbiguousMove;
5201 return ImpossibleMove;
5204 /* [HGM] If move started in holdings, it means a drop */
5205 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5206 if( pup != EmptySquare ) return ImpossibleMove;
5207 if(appData.testLegality) {
5208 /* it would be more logical if LegalityTest() also figured out
5209 * which drops are legal. For now we forbid pawns on back rank.
5210 * Shogi is on its own here...
5212 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5213 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5214 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5216 return WhiteDrop; /* Not needed to specify white or black yet */
5219 userOfferedDraw = FALSE;
5221 /* [HGM] always test for legality, to get promotion info */
5222 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5223 epStatus[currentMove], castlingRights[currentMove],
5224 fromY, fromX, toY, toX, promoChar);
5225 /* [HGM] but possibly ignore an IllegalMove result */
5226 if (appData.testLegality) {
5227 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5228 DisplayMoveError(_("Illegal move"));
5229 return ImpossibleMove;
5232 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5234 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5235 function is made into one that returns an OK move type if FinishMove
5236 should be called. This to give the calling driver routine the
5237 opportunity to finish the userMove input with a promotion popup,
5238 without bothering the user with this for invalid or illegal moves */
5240 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5243 /* Common tail of UserMoveEvent and DropMenuEvent */
5245 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5247 int fromX, fromY, toX, toY;
5248 /*char*/int promoChar;
5251 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5252 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5253 // [HGM] superchess: suppress promotions to non-available piece
5254 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5255 if(WhiteOnMove(currentMove)) {
5256 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5258 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5262 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5263 move type in caller when we know the move is a legal promotion */
5264 if(moveType == NormalMove && promoChar)
5265 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5266 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5267 /* [HGM] convert drag-and-drop piece drops to standard form */
5268 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5269 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5270 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5271 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5272 // fromX = boards[currentMove][fromY][fromX];
5273 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5274 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5275 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5276 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5280 /* [HGM] <popupFix> The following if has been moved here from
5281 UserMoveEvent(). Because it seemed to belon here (why not allow
5282 piece drops in training games?), and because it can only be
5283 performed after it is known to what we promote. */
5284 if (gameMode == Training) {
5285 /* compare the move played on the board to the next move in the
5286 * game. If they match, display the move and the opponent's response.
5287 * If they don't match, display an error message.
5290 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5291 CopyBoard(testBoard, boards[currentMove]);
5292 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5294 if (CompareBoards(testBoard, boards[currentMove+1])) {
5295 ForwardInner(currentMove+1);
5297 /* Autoplay the opponent's response.
5298 * if appData.animate was TRUE when Training mode was entered,
5299 * the response will be animated.
5301 saveAnimate = appData.animate;
5302 appData.animate = animateTraining;
5303 ForwardInner(currentMove+1);
5304 appData.animate = saveAnimate;
5306 /* check for the end of the game */
5307 if (currentMove >= forwardMostMove) {
5308 gameMode = PlayFromGameFile;
5310 SetTrainingModeOff();
5311 DisplayInformation(_("End of game"));
5314 DisplayError(_("Incorrect move"), 0);
5319 /* Ok, now we know that the move is good, so we can kill
5320 the previous line in Analysis Mode */
5321 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5322 forwardMostMove = currentMove;
5325 /* If we need the chess program but it's dead, restart it */
5326 ResurrectChessProgram();
5328 /* A user move restarts a paused game*/
5332 thinkOutput[0] = NULLCHAR;
5334 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5336 if (gameMode == BeginningOfGame) {
5337 if (appData.noChessProgram) {
5338 gameMode = EditGame;
5342 gameMode = MachinePlaysBlack;
5345 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5347 if (first.sendName) {
5348 sprintf(buf, "name %s\n", gameInfo.white);
5349 SendToProgram(buf, &first);
5355 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5356 /* Relay move to ICS or chess engine */
5357 if (appData.icsActive) {
5358 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5359 gameMode == IcsExamining) {
5360 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5364 if (first.sendTime && (gameMode == BeginningOfGame ||
5365 gameMode == MachinePlaysWhite ||
5366 gameMode == MachinePlaysBlack)) {
5367 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5369 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5370 // [HGM] book: if program might be playing, let it use book
5371 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5372 first.maybeThinking = TRUE;
5373 } else SendMoveToProgram(forwardMostMove-1, &first);
5374 if (currentMove == cmailOldMove + 1) {
5375 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5379 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5383 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5384 EP_UNKNOWN, castlingRights[currentMove]) ) {
5390 if (WhiteOnMove(currentMove)) {
5391 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5393 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5397 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5402 case MachinePlaysBlack:
5403 case MachinePlaysWhite:
5404 /* disable certain menu options while machine is thinking */
5405 SetMachineThinkingEnables();
5412 if(bookHit) { // [HGM] book: simulate book reply
5413 static char bookMove[MSG_SIZ]; // a bit generous?
5415 programStats.nodes = programStats.depth = programStats.time =
5416 programStats.score = programStats.got_only_move = 0;
5417 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5419 strcpy(bookMove, "move ");
5420 strcat(bookMove, bookHit);
5421 HandleMachineMove(bookMove, &first);
5427 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5428 int fromX, fromY, toX, toY;
5431 /* [HGM] This routine was added to allow calling of its two logical
5432 parts from other modules in the old way. Before, UserMoveEvent()
5433 automatically called FinishMove() if the move was OK, and returned
5434 otherwise. I separated the two, in order to make it possible to
5435 slip a promotion popup in between. But that it always needs two
5436 calls, to the first part, (now called UserMoveTest() ), and to
5437 FinishMove if the first part succeeded. Calls that do not need
5438 to do anything in between, can call this routine the old way.
5440 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5441 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5442 if(moveType == AmbiguousMove)
5443 DrawPosition(FALSE, boards[currentMove]);
5444 else if(moveType != ImpossibleMove && moveType != Comment)
5445 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5448 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5450 // char * hint = lastHint;
5451 FrontEndProgramStats stats;
5453 stats.which = cps == &first ? 0 : 1;
5454 stats.depth = cpstats->depth;
5455 stats.nodes = cpstats->nodes;
5456 stats.score = cpstats->score;
5457 stats.time = cpstats->time;
5458 stats.pv = cpstats->movelist;
5459 stats.hint = lastHint;
5460 stats.an_move_index = 0;
5461 stats.an_move_count = 0;
5463 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5464 stats.hint = cpstats->move_name;
5465 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5466 stats.an_move_count = cpstats->nr_moves;
5469 SetProgramStats( &stats );
5472 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5473 { // [HGM] book: this routine intercepts moves to simulate book replies
5474 char *bookHit = NULL;
5476 //first determine if the incoming move brings opponent into his book
5477 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5478 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5479 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5480 if(bookHit != NULL && !cps->bookSuspend) {
5481 // make sure opponent is not going to reply after receiving move to book position
5482 SendToProgram("force\n", cps);
5483 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5485 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5486 // now arrange restart after book miss
5488 // after a book hit we never send 'go', and the code after the call to this routine
5489 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5491 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5492 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5493 SendToProgram(buf, cps);
5494 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5495 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5496 SendToProgram("go\n", cps);
5497 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5498 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5499 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5500 SendToProgram("go\n", cps);
5501 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5503 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5507 ChessProgramState *savedState;
5508 void DeferredBookMove(void)
5510 if(savedState->lastPing != savedState->lastPong)
5511 ScheduleDelayedEvent(DeferredBookMove, 10);
5513 HandleMachineMove(savedMessage, savedState);
5517 HandleMachineMove(message, cps)
5519 ChessProgramState *cps;
5521 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5522 char realname[MSG_SIZ];
5523 int fromX, fromY, toX, toY;
5530 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5532 * Kludge to ignore BEL characters
5534 while (*message == '\007') message++;
5537 * [HGM] engine debug message: ignore lines starting with '#' character
5539 if(cps->debug && *message == '#') return;
5542 * Look for book output
5544 if (cps == &first && bookRequested) {
5545 if (message[0] == '\t' || message[0] == ' ') {
5546 /* Part of the book output is here; append it */
5547 strcat(bookOutput, message);
5548 strcat(bookOutput, " \n");
5550 } else if (bookOutput[0] != NULLCHAR) {
5551 /* All of book output has arrived; display it */
5552 char *p = bookOutput;
5553 while (*p != NULLCHAR) {
5554 if (*p == '\t') *p = ' ';
5557 DisplayInformation(bookOutput);
5558 bookRequested = FALSE;
5559 /* Fall through to parse the current output */
5564 * Look for machine move.
5566 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5567 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5569 /* This method is only useful on engines that support ping */
5570 if (cps->lastPing != cps->lastPong) {
5571 if (gameMode == BeginningOfGame) {
5572 /* Extra move from before last new; ignore */
5573 if (appData.debugMode) {
5574 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5577 if (appData.debugMode) {
5578 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5579 cps->which, gameMode);
5582 SendToProgram("undo\n", cps);
5588 case BeginningOfGame:
5589 /* Extra move from before last reset; ignore */
5590 if (appData.debugMode) {
5591 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5598 /* Extra move after we tried to stop. The mode test is
5599 not a reliable way of detecting this problem, but it's
5600 the best we can do on engines that don't support ping.
5602 if (appData.debugMode) {
5603 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5604 cps->which, gameMode);
5606 SendToProgram("undo\n", cps);
5609 case MachinePlaysWhite:
5610 case IcsPlayingWhite:
5611 machineWhite = TRUE;
5614 case MachinePlaysBlack:
5615 case IcsPlayingBlack:
5616 machineWhite = FALSE;
5619 case TwoMachinesPlay:
5620 machineWhite = (cps->twoMachinesColor[0] == 'w');
5623 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5624 if (appData.debugMode) {
5626 "Ignoring move out of turn by %s, gameMode %d"
5627 ", forwardMost %d\n",
5628 cps->which, gameMode, forwardMostMove);
5633 if (appData.debugMode) { int f = forwardMostMove;
5634 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5635 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5637 if(cps->alphaRank) AlphaRank(machineMove, 4);
5638 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5639 &fromX, &fromY, &toX, &toY, &promoChar)) {
5640 /* Machine move could not be parsed; ignore it. */
5641 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5642 machineMove, cps->which);
5643 DisplayError(buf1, 0);
5644 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5645 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5646 if (gameMode == TwoMachinesPlay) {
5647 GameEnds(machineWhite ? BlackWins : WhiteWins,
5653 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5654 /* So we have to redo legality test with true e.p. status here, */
5655 /* to make sure an illegal e.p. capture does not slip through, */
5656 /* to cause a forfeit on a justified illegal-move complaint */
5657 /* of the opponent. */
5658 if( gameMode==TwoMachinesPlay && appData.testLegality
5659 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5662 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5663 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5664 fromY, fromX, toY, toX, promoChar);
5665 if (appData.debugMode) {
5667 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5668 castlingRights[forwardMostMove][i], castlingRank[i]);
5669 fprintf(debugFP, "castling rights\n");
5671 if(moveType == IllegalMove) {
5672 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5673 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5674 GameEnds(machineWhite ? BlackWins : WhiteWins,
5677 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5678 /* [HGM] Kludge to handle engines that send FRC-style castling
5679 when they shouldn't (like TSCP-Gothic) */
5681 case WhiteASideCastleFR:
5682 case BlackASideCastleFR:
5684 currentMoveString[2]++;
5686 case WhiteHSideCastleFR:
5687 case BlackHSideCastleFR:
5689 currentMoveString[2]--;
5691 default: ; // nothing to do, but suppresses warning of pedantic compilers
5694 hintRequested = FALSE;
5695 lastHint[0] = NULLCHAR;
5696 bookRequested = FALSE;
5697 /* Program may be pondering now */
5698 cps->maybeThinking = TRUE;
5699 if (cps->sendTime == 2) cps->sendTime = 1;
5700 if (cps->offeredDraw) cps->offeredDraw--;
5703 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5705 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5707 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5708 char buf[3*MSG_SIZ];
5710 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5711 programStats.score / 100.,
5713 programStats.time / 100.,
5714 (unsigned int)programStats.nodes,
5715 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5716 programStats.movelist);
5718 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5722 /* currentMoveString is set as a side-effect of ParseOneMove */
5723 strcpy(machineMove, currentMoveString);
5724 strcat(machineMove, "\n");
5725 strcpy(moveList[forwardMostMove], machineMove);
5727 /* [AS] Save move info and clear stats for next move */
5728 pvInfoList[ forwardMostMove ].score = programStats.score;
5729 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5730 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5731 ClearProgramStats();
5732 thinkOutput[0] = NULLCHAR;
5733 hiddenThinkOutputState = 0;
5735 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5737 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5738 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5741 while( count < adjudicateLossPlies ) {
5742 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5745 score = -score; /* Flip score for winning side */
5748 if( score > adjudicateLossThreshold ) {
5755 if( count >= adjudicateLossPlies ) {
5756 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5758 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5759 "Xboard adjudication",
5766 if( gameMode == TwoMachinesPlay ) {
5767 // [HGM] some adjudications useful with buggy engines
5768 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5769 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5772 if( appData.testLegality )
5773 { /* [HGM] Some more adjudications for obstinate engines */
5774 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5775 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5776 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5777 static int moveCount = 6;
5779 char *reason = NULL;
5781 /* Count what is on board. */
5782 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5783 { ChessSquare p = boards[forwardMostMove][i][j];
5787 { /* count B,N,R and other of each side */
5790 NrK++; break; // [HGM] atomic: count Kings
5794 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5795 bishopsColor |= 1 << ((i^j)&1);
5800 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5801 bishopsColor |= 1 << ((i^j)&1);
5816 PawnAdvance += m; NrPawns++;
5818 NrPieces += (p != EmptySquare);
5819 NrW += ((int)p < (int)BlackPawn);
5820 if(gameInfo.variant == VariantXiangqi &&
5821 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5822 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5823 NrW -= ((int)p < (int)BlackPawn);
5827 /* Some material-based adjudications that have to be made before stalemate test */
5828 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5829 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5830 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5831 if(appData.checkMates) {
5832 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5833 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5834 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5835 "Xboard adjudication: King destroyed", GE_XBOARD );
5840 /* Bare King in Shatranj (loses) or Losers (wins) */
5841 if( NrW == 1 || NrPieces - NrW == 1) {
5842 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5843 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5844 if(appData.checkMates) {
5845 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5846 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5847 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5848 "Xboard adjudication: Bare king", GE_XBOARD );
5852 if( gameInfo.variant == VariantShatranj && --bare < 0)
5854 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5855 if(appData.checkMates) {
5856 /* but only adjudicate if adjudication enabled */
5857 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5858 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5860 "Xboard adjudication: Bare king", GE_XBOARD );
5867 // don't wait for engine to announce game end if we can judge ourselves
5868 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5869 castlingRights[forwardMostMove]) ) {
5871 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5872 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5873 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5874 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5877 reason = "Xboard adjudication: 3rd check";
5878 epStatus[forwardMostMove] = EP_CHECKMATE;
5888 reason = "Xboard adjudication: Stalemate";
5889 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5890 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5891 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5892 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5893 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5894 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5895 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5896 EP_CHECKMATE : EP_WINS);
5897 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5898 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5902 reason = "Xboard adjudication: Checkmate";
5903 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5907 switch(i = epStatus[forwardMostMove]) {
5909 result = GameIsDrawn; break;
5911 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5913 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5915 result = (ChessMove) 0;
5917 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5918 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5919 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5920 GameEnds( result, reason, GE_XBOARD );
5924 /* Next absolutely insufficient mating material. */
5925 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5926 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5927 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5928 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5929 { /* KBK, KNK, KK of KBKB with like Bishops */
5931 /* always flag draws, for judging claims */
5932 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5934 if(appData.materialDraws) {
5935 /* but only adjudicate them if adjudication enabled */
5936 SendToProgram("force\n", cps->other); // suppress reply
5937 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5938 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5939 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5944 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5946 ( NrWR == 1 && NrBR == 1 /* KRKR */
5947 || NrWQ==1 && NrBQ==1 /* KQKQ */
5948 || NrWN==2 || NrBN==2 /* KNNK */
5949 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5951 if(--moveCount < 0 && appData.trivialDraws)
5952 { /* if the first 3 moves do not show a tactical win, declare draw */
5953 SendToProgram("force\n", cps->other); // suppress reply
5954 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5955 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5956 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5959 } else moveCount = 6;
5963 if (appData.debugMode) { int i;
5964 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5965 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5966 appData.drawRepeats);
5967 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5968 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5972 /* Check for rep-draws */
5974 for(k = forwardMostMove-2;
5975 k>=backwardMostMove && k>=forwardMostMove-100 &&
5976 epStatus[k] < EP_UNKNOWN &&
5977 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5981 if (appData.debugMode) {
5982 fprintf(debugFP, " loop\n");
5985 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5987 if (appData.debugMode) {
5988 fprintf(debugFP, "match\n");
5991 /* compare castling rights */
5992 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5993 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5994 rights++; /* King lost rights, while rook still had them */
5995 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5996 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5997 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5998 rights++; /* but at least one rook lost them */
6000 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6001 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6003 if( castlingRights[forwardMostMove][5] >= 0 ) {
6004 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6005 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6009 if (appData.debugMode) {
6010 for(i=0; i<nrCastlingRights; i++)
6011 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6014 if (appData.debugMode) {
6015 fprintf(debugFP, " %d %d\n", rights, k);
6018 if( rights == 0 && ++count > appData.drawRepeats-2
6019 && appData.drawRepeats > 1) {
6020 /* adjudicate after user-specified nr of repeats */
6021 SendToProgram("force\n", cps->other); // suppress reply
6022 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6023 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6024 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6025 // [HGM] xiangqi: check for forbidden perpetuals
6026 int m, ourPerpetual = 1, hisPerpetual = 1;
6027 for(m=forwardMostMove; m>k; m-=2) {
6028 if(MateTest(boards[m], PosFlags(m),
6029 EP_NONE, castlingRights[m]) != MT_CHECK)
6030 ourPerpetual = 0; // the current mover did not always check
6031 if(MateTest(boards[m-1], PosFlags(m-1),
6032 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6033 hisPerpetual = 0; // the opponent did not always check
6035 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6036 ourPerpetual, hisPerpetual);
6037 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6038 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6039 "Xboard adjudication: perpetual checking", GE_XBOARD );
6042 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6043 break; // (or we would have caught him before). Abort repetition-checking loop.
6044 // Now check for perpetual chases
6045 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6046 hisPerpetual = PerpetualChase(k, forwardMostMove);
6047 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6048 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6049 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6050 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6053 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6054 break; // Abort repetition-checking loop.
6056 // if neither of us is checking or chasing all the time, or both are, it is draw
6058 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6061 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6062 epStatus[forwardMostMove] = EP_REP_DRAW;
6066 /* Now we test for 50-move draws. Determine ply count */
6067 count = forwardMostMove;
6068 /* look for last irreversble move */
6069 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6071 /* if we hit starting position, add initial plies */
6072 if( count == backwardMostMove )
6073 count -= initialRulePlies;
6074 count = forwardMostMove - count;
6076 epStatus[forwardMostMove] = EP_RULE_DRAW;
6077 /* this is used to judge if draw claims are legal */
6078 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6079 SendToProgram("force\n", cps->other); // suppress reply
6080 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6081 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6082 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6086 /* if draw offer is pending, treat it as a draw claim
6087 * when draw condition present, to allow engines a way to
6088 * claim draws before making their move to avoid a race
6089 * condition occurring after their move
6091 if( cps->other->offeredDraw || cps->offeredDraw ) {
6093 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6094 p = "Draw claim: 50-move rule";
6095 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6096 p = "Draw claim: 3-fold repetition";
6097 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6098 p = "Draw claim: insufficient mating material";
6100 SendToProgram("force\n", cps->other); // suppress reply
6101 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6102 GameEnds( GameIsDrawn, p, GE_XBOARD );
6103 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6109 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6110 SendToProgram("force\n", cps->other); // suppress reply
6111 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6112 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6114 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6121 if (gameMode == TwoMachinesPlay) {
6122 /* [HGM] relaying draw offers moved to after reception of move */
6123 /* and interpreting offer as claim if it brings draw condition */
6124 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6125 SendToProgram("draw\n", cps->other);
6127 if (cps->other->sendTime) {
6128 SendTimeRemaining(cps->other,
6129 cps->other->twoMachinesColor[0] == 'w');
6131 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6132 if (firstMove && !bookHit) {
6134 if (cps->other->useColors) {
6135 SendToProgram(cps->other->twoMachinesColor, cps->other);
6137 SendToProgram("go\n", cps->other);
6139 cps->other->maybeThinking = TRUE;
6142 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6144 if (!pausing && appData.ringBellAfterMoves) {
6149 * Reenable menu items that were disabled while
6150 * machine was thinking
6152 if (gameMode != TwoMachinesPlay)
6153 SetUserThinkingEnables();
6155 // [HGM] book: after book hit opponent has received move and is now in force mode
6156 // force the book reply into it, and then fake that it outputted this move by jumping
6157 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6159 static char bookMove[MSG_SIZ]; // a bit generous?
6161 strcpy(bookMove, "move ");
6162 strcat(bookMove, bookHit);
6165 programStats.nodes = programStats.depth = programStats.time =
6166 programStats.score = programStats.got_only_move = 0;
6167 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6169 if(cps->lastPing != cps->lastPong) {
6170 savedMessage = message; // args for deferred call
6172 ScheduleDelayedEvent(DeferredBookMove, 10);
6181 /* Set special modes for chess engines. Later something general
6182 * could be added here; for now there is just one kludge feature,
6183 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6184 * when "xboard" is given as an interactive command.
6186 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6187 cps->useSigint = FALSE;
6188 cps->useSigterm = FALSE;
6190 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6191 ParseFeatures(message+8, cps);
6192 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6195 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6196 * want this, I was asked to put it in, and obliged.
6198 if (!strncmp(message, "setboard ", 9)) {
6199 Board initial_position; int i;
6201 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6203 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6204 DisplayError(_("Bad FEN received from engine"), 0);
6207 Reset(FALSE, FALSE);
6208 CopyBoard(boards[0], initial_position);
6209 initialRulePlies = FENrulePlies;
6210 epStatus[0] = FENepStatus;
6211 for( i=0; i<nrCastlingRights; i++ )
6212 castlingRights[0][i] = FENcastlingRights[i];
6213 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6214 else gameMode = MachinePlaysBlack;
6215 DrawPosition(FALSE, boards[currentMove]);
6221 * Look for communication commands
6223 if (!strncmp(message, "telluser ", 9)) {
6224 DisplayNote(message + 9);
6227 if (!strncmp(message, "tellusererror ", 14)) {
6228 DisplayError(message + 14, 0);
6231 if (!strncmp(message, "tellopponent ", 13)) {
6232 if (appData.icsActive) {
6234 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6238 DisplayNote(message + 13);
6242 if (!strncmp(message, "tellothers ", 11)) {
6243 if (appData.icsActive) {
6245 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6251 if (!strncmp(message, "tellall ", 8)) {
6252 if (appData.icsActive) {
6254 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6258 DisplayNote(message + 8);
6262 if (strncmp(message, "warning", 7) == 0) {
6263 /* Undocumented feature, use tellusererror in new code */
6264 DisplayError(message, 0);
6267 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6268 strcpy(realname, cps->tidy);
6269 strcat(realname, " query");
6270 AskQuestion(realname, buf2, buf1, cps->pr);
6273 /* Commands from the engine directly to ICS. We don't allow these to be
6274 * sent until we are logged on. Crafty kibitzes have been known to
6275 * interfere with the login process.
6278 if (!strncmp(message, "tellics ", 8)) {
6279 SendToICS(message + 8);
6283 if (!strncmp(message, "tellicsnoalias ", 15)) {
6284 SendToICS(ics_prefix);
6285 SendToICS(message + 15);
6289 /* The following are for backward compatibility only */
6290 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6291 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6292 SendToICS(ics_prefix);
6298 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6302 * If the move is illegal, cancel it and redraw the board.
6303 * Also deal with other error cases. Matching is rather loose
6304 * here to accommodate engines written before the spec.
6306 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6307 strncmp(message, "Error", 5) == 0) {
6308 if (StrStr(message, "name") ||
6309 StrStr(message, "rating") || StrStr(message, "?") ||
6310 StrStr(message, "result") || StrStr(message, "board") ||
6311 StrStr(message, "bk") || StrStr(message, "computer") ||
6312 StrStr(message, "variant") || StrStr(message, "hint") ||
6313 StrStr(message, "random") || StrStr(message, "depth") ||
6314 StrStr(message, "accepted")) {
6317 if (StrStr(message, "protover")) {
6318 /* Program is responding to input, so it's apparently done
6319 initializing, and this error message indicates it is
6320 protocol version 1. So we don't need to wait any longer
6321 for it to initialize and send feature commands. */
6322 FeatureDone(cps, 1);
6323 cps->protocolVersion = 1;
6326 cps->maybeThinking = FALSE;
6328 if (StrStr(message, "draw")) {
6329 /* Program doesn't have "draw" command */
6330 cps->sendDrawOffers = 0;
6333 if (cps->sendTime != 1 &&
6334 (StrStr(message, "time") || StrStr(message, "otim"))) {
6335 /* Program apparently doesn't have "time" or "otim" command */
6339 if (StrStr(message, "analyze")) {
6340 cps->analysisSupport = FALSE;
6341 cps->analyzing = FALSE;
6343 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6344 DisplayError(buf2, 0);
6347 if (StrStr(message, "(no matching move)st")) {
6348 /* Special kludge for GNU Chess 4 only */
6349 cps->stKludge = TRUE;
6350 SendTimeControl(cps, movesPerSession, timeControl,
6351 timeIncrement, appData.searchDepth,
6355 if (StrStr(message, "(no matching move)sd")) {
6356 /* Special kludge for GNU Chess 4 only */
6357 cps->sdKludge = TRUE;
6358 SendTimeControl(cps, movesPerSession, timeControl,
6359 timeIncrement, appData.searchDepth,
6363 if (!StrStr(message, "llegal")) {
6366 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6367 gameMode == IcsIdle) return;
6368 if (forwardMostMove <= backwardMostMove) return;
6370 /* Following removed: it caused a bug where a real illegal move
6371 message in analyze mored would be ignored. */
6372 if (cps == &first && programStats.ok_to_send == 0) {
6373 /* Bogus message from Crafty responding to "." This filtering
6374 can miss some of the bad messages, but fortunately the bug
6375 is fixed in current Crafty versions, so it doesn't matter. */
6379 if (pausing) PauseEvent();
6380 if(appData.forceIllegal) {
6381 // [HGM] illegal: machine refused move; force position after move into it
6382 SendToProgram("force\n", cps);
6383 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6384 // we have a real problem now, as SendBoard will use the a2a3 kludge
6385 // when black is to move, while there might be nothing on a2 or black
6386 // might already have the move. So send the board as if white has the move.
6387 // But first we must change the stm of the engine, as it refused the last move
6388 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6389 if(WhiteOnMove(forwardMostMove)) {
6390 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6391 SendBoard(cps, forwardMostMove); // kludgeless board
6393 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6394 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6395 SendBoard(cps, forwardMostMove+1); // kludgeless board
6397 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6398 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6399 gameMode == TwoMachinesPlay)
6400 SendToProgram("go\n", cps);
6403 if (gameMode == PlayFromGameFile) {
6404 /* Stop reading this game file */
6405 gameMode = EditGame;
6408 currentMove = --forwardMostMove;
6409 DisplayMove(currentMove-1); /* before DisplayMoveError */
6411 DisplayBothClocks();
6412 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6413 parseList[currentMove], cps->which);
6414 DisplayMoveError(buf1);
6415 DrawPosition(FALSE, boards[currentMove]);
6417 /* [HGM] illegal-move claim should forfeit game when Xboard */
6418 /* only passes fully legal moves */
6419 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6420 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6421 "False illegal-move claim", GE_XBOARD );
6425 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6426 /* Program has a broken "time" command that
6427 outputs a string not ending in newline.
6433 * If chess program startup fails, exit with an error message.
6434 * Attempts to recover here are futile.
6436 if ((StrStr(message, "unknown host") != NULL)
6437 || (StrStr(message, "No remote directory") != NULL)
6438 || (StrStr(message, "not found") != NULL)
6439 || (StrStr(message, "No such file") != NULL)
6440 || (StrStr(message, "can't alloc") != NULL)
6441 || (StrStr(message, "Permission denied") != NULL)) {
6443 cps->maybeThinking = FALSE;
6444 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6445 cps->which, cps->program, cps->host, message);
6446 RemoveInputSource(cps->isr);
6447 DisplayFatalError(buf1, 0, 1);
6452 * Look for hint output
6454 if (sscanf(message, "Hint: %s", buf1) == 1) {
6455 if (cps == &first && hintRequested) {
6456 hintRequested = FALSE;
6457 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6458 &fromX, &fromY, &toX, &toY, &promoChar)) {
6459 (void) CoordsToAlgebraic(boards[forwardMostMove],
6460 PosFlags(forwardMostMove), EP_UNKNOWN,
6461 fromY, fromX, toY, toX, promoChar, buf1);
6462 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6463 DisplayInformation(buf2);
6465 /* Hint move could not be parsed!? */
6466 snprintf(buf2, sizeof(buf2),
6467 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6469 DisplayError(buf2, 0);
6472 strcpy(lastHint, buf1);
6478 * Ignore other messages if game is not in progress
6480 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6481 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6484 * look for win, lose, draw, or draw offer
6486 if (strncmp(message, "1-0", 3) == 0) {
6487 char *p, *q, *r = "";
6488 p = strchr(message, '{');
6496 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6498 } else if (strncmp(message, "0-1", 3) == 0) {
6499 char *p, *q, *r = "";
6500 p = strchr(message, '{');
6508 /* Kludge for Arasan 4.1 bug */
6509 if (strcmp(r, "Black resigns") == 0) {
6510 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6513 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6515 } else if (strncmp(message, "1/2", 3) == 0) {
6516 char *p, *q, *r = "";
6517 p = strchr(message, '{');
6526 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6529 } else if (strncmp(message, "White resign", 12) == 0) {
6530 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6532 } else if (strncmp(message, "Black resign", 12) == 0) {
6533 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6535 } else if (strncmp(message, "White matches", 13) == 0 ||
6536 strncmp(message, "Black matches", 13) == 0 ) {
6537 /* [HGM] ignore GNUShogi noises */
6539 } else if (strncmp(message, "White", 5) == 0 &&
6540 message[5] != '(' &&
6541 StrStr(message, "Black") == NULL) {
6542 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6544 } else if (strncmp(message, "Black", 5) == 0 &&
6545 message[5] != '(') {
6546 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6548 } else if (strcmp(message, "resign") == 0 ||
6549 strcmp(message, "computer resigns") == 0) {
6551 case MachinePlaysBlack:
6552 case IcsPlayingBlack:
6553 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6555 case MachinePlaysWhite:
6556 case IcsPlayingWhite:
6557 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6559 case TwoMachinesPlay:
6560 if (cps->twoMachinesColor[0] == 'w')
6561 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6563 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6570 } else if (strncmp(message, "opponent mates", 14) == 0) {
6572 case MachinePlaysBlack:
6573 case IcsPlayingBlack:
6574 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6576 case MachinePlaysWhite:
6577 case IcsPlayingWhite:
6578 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6580 case TwoMachinesPlay:
6581 if (cps->twoMachinesColor[0] == 'w')
6582 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6584 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6591 } else if (strncmp(message, "computer mates", 14) == 0) {
6593 case MachinePlaysBlack:
6594 case IcsPlayingBlack:
6595 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6597 case MachinePlaysWhite:
6598 case IcsPlayingWhite:
6599 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6601 case TwoMachinesPlay:
6602 if (cps->twoMachinesColor[0] == 'w')
6603 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6605 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6612 } else if (strncmp(message, "checkmate", 9) == 0) {
6613 if (WhiteOnMove(forwardMostMove)) {
6614 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6616 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6619 } else if (strstr(message, "Draw") != NULL ||
6620 strstr(message, "game is a draw") != NULL) {
6621 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6623 } else if (strstr(message, "offer") != NULL &&
6624 strstr(message, "draw") != NULL) {
6626 if (appData.zippyPlay && first.initDone) {
6627 /* Relay offer to ICS */
6628 SendToICS(ics_prefix);
6629 SendToICS("draw\n");
6632 cps->offeredDraw = 2; /* valid until this engine moves twice */
6633 if (gameMode == TwoMachinesPlay) {
6634 if (cps->other->offeredDraw) {
6635 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6636 /* [HGM] in two-machine mode we delay relaying draw offer */
6637 /* until after we also have move, to see if it is really claim */
6641 if (cps->other->sendDrawOffers) {
6642 SendToProgram("draw\n", cps->other);
6646 } else if (gameMode == MachinePlaysWhite ||
6647 gameMode == MachinePlaysBlack) {
6648 if (userOfferedDraw) {
6649 DisplayInformation(_("Machine accepts your draw offer"));
6650 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6652 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6659 * Look for thinking output
6661 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6662 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6664 int plylev, mvleft, mvtot, curscore, time;
6665 char mvname[MOVE_LEN];
6669 int prefixHint = FALSE;
6670 mvname[0] = NULLCHAR;
6673 case MachinePlaysBlack:
6674 case IcsPlayingBlack:
6675 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6677 case MachinePlaysWhite:
6678 case IcsPlayingWhite:
6679 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6684 case IcsObserving: /* [DM] icsEngineAnalyze */
6685 if (!appData.icsEngineAnalyze) ignore = TRUE;
6687 case TwoMachinesPlay:
6688 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6699 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6700 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6702 if (plyext != ' ' && plyext != '\t') {
6706 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6707 if( cps->scoreIsAbsolute &&
6708 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6710 curscore = -curscore;
6714 programStats.depth = plylev;
6715 programStats.nodes = nodes;
6716 programStats.time = time;
6717 programStats.score = curscore;
6718 programStats.got_only_move = 0;
6720 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6723 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6724 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6725 if(WhiteOnMove(forwardMostMove))
6726 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6727 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6730 /* Buffer overflow protection */
6731 if (buf1[0] != NULLCHAR) {
6732 if (strlen(buf1) >= sizeof(programStats.movelist)
6733 && appData.debugMode) {
6735 "PV is too long; using the first %d bytes.\n",
6736 sizeof(programStats.movelist) - 1);
6739 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6741 sprintf(programStats.movelist, " no PV\n");
6744 if (programStats.seen_stat) {
6745 programStats.ok_to_send = 1;
6748 if (strchr(programStats.movelist, '(') != NULL) {
6749 programStats.line_is_book = 1;
6750 programStats.nr_moves = 0;
6751 programStats.moves_left = 0;
6753 programStats.line_is_book = 0;
6756 SendProgramStatsToFrontend( cps, &programStats );
6759 [AS] Protect the thinkOutput buffer from overflow... this
6760 is only useful if buf1 hasn't overflowed first!
6762 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6764 (gameMode == TwoMachinesPlay ?
6765 ToUpper(cps->twoMachinesColor[0]) : ' '),
6766 ((double) curscore) / 100.0,
6767 prefixHint ? lastHint : "",
6768 prefixHint ? " " : "" );
6770 if( buf1[0] != NULLCHAR ) {
6771 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6773 if( strlen(buf1) > max_len ) {
6774 if( appData.debugMode) {
6775 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6777 buf1[max_len+1] = '\0';
6780 strcat( thinkOutput, buf1 );
6783 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6784 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6785 DisplayMove(currentMove - 1);
6790 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6791 /* crafty (9.25+) says "(only move) <move>"
6792 * if there is only 1 legal move
6794 sscanf(p, "(only move) %s", buf1);
6795 sprintf(thinkOutput, "%s (only move)", buf1);
6796 sprintf(programStats.movelist, "%s (only move)", buf1);
6797 programStats.depth = 1;
6798 programStats.nr_moves = 1;
6799 programStats.moves_left = 1;
6800 programStats.nodes = 1;
6801 programStats.time = 1;
6802 programStats.got_only_move = 1;
6804 /* Not really, but we also use this member to
6805 mean "line isn't going to change" (Crafty
6806 isn't searching, so stats won't change) */
6807 programStats.line_is_book = 1;
6809 SendProgramStatsToFrontend( cps, &programStats );
6811 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6812 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6813 DisplayMove(currentMove - 1);
6817 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6818 &time, &nodes, &plylev, &mvleft,
6819 &mvtot, mvname) >= 5) {
6820 /* The stat01: line is from Crafty (9.29+) in response
6821 to the "." command */
6822 programStats.seen_stat = 1;
6823 cps->maybeThinking = TRUE;
6825 if (programStats.got_only_move || !appData.periodicUpdates)
6828 programStats.depth = plylev;
6829 programStats.time = time;
6830 programStats.nodes = nodes;
6831 programStats.moves_left = mvleft;
6832 programStats.nr_moves = mvtot;
6833 strcpy(programStats.move_name, mvname);
6834 programStats.ok_to_send = 1;
6835 programStats.movelist[0] = '\0';
6837 SendProgramStatsToFrontend( cps, &programStats );
6842 } else if (strncmp(message,"++",2) == 0) {
6843 /* Crafty 9.29+ outputs this */
6844 programStats.got_fail = 2;
6847 } else if (strncmp(message,"--",2) == 0) {
6848 /* Crafty 9.29+ outputs this */
6849 programStats.got_fail = 1;
6852 } else if (thinkOutput[0] != NULLCHAR &&
6853 strncmp(message, " ", 4) == 0) {
6854 unsigned message_len;
6857 while (*p && *p == ' ') p++;
6859 message_len = strlen( p );
6861 /* [AS] Avoid buffer overflow */
6862 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6863 strcat(thinkOutput, " ");
6864 strcat(thinkOutput, p);
6867 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6868 strcat(programStats.movelist, " ");
6869 strcat(programStats.movelist, p);
6872 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6873 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6874 DisplayMove(currentMove - 1);
6883 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6884 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6886 ChessProgramStats cpstats;
6888 if (plyext != ' ' && plyext != '\t') {
6892 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6893 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6894 curscore = -curscore;
6897 cpstats.depth = plylev;
6898 cpstats.nodes = nodes;
6899 cpstats.time = time;
6900 cpstats.score = curscore;
6901 cpstats.got_only_move = 0;
6902 cpstats.movelist[0] = '\0';
6904 if (buf1[0] != NULLCHAR) {
6905 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6908 cpstats.ok_to_send = 0;
6909 cpstats.line_is_book = 0;
6910 cpstats.nr_moves = 0;
6911 cpstats.moves_left = 0;
6913 SendProgramStatsToFrontend( cps, &cpstats );
6920 /* Parse a game score from the character string "game", and
6921 record it as the history of the current game. The game
6922 score is NOT assumed to start from the standard position.
6923 The display is not updated in any way.
6926 ParseGameHistory(game)
6930 int fromX, fromY, toX, toY, boardIndex;
6935 if (appData.debugMode)
6936 fprintf(debugFP, "Parsing game history: %s\n", game);
6938 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6939 gameInfo.site = StrSave(appData.icsHost);
6940 gameInfo.date = PGNDate();
6941 gameInfo.round = StrSave("-");
6943 /* Parse out names of players */
6944 while (*game == ' ') game++;
6946 while (*game != ' ') *p++ = *game++;
6948 gameInfo.white = StrSave(buf);
6949 while (*game == ' ') game++;
6951 while (*game != ' ' && *game != '\n') *p++ = *game++;
6953 gameInfo.black = StrSave(buf);
6956 boardIndex = blackPlaysFirst ? 1 : 0;
6959 yyboardindex = boardIndex;
6960 moveType = (ChessMove) yylex();
6962 case IllegalMove: /* maybe suicide chess, etc. */
6963 if (appData.debugMode) {
6964 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6965 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6966 setbuf(debugFP, NULL);
6968 case WhitePromotionChancellor:
6969 case BlackPromotionChancellor:
6970 case WhitePromotionArchbishop:
6971 case BlackPromotionArchbishop:
6972 case WhitePromotionQueen:
6973 case BlackPromotionQueen:
6974 case WhitePromotionRook:
6975 case BlackPromotionRook:
6976 case WhitePromotionBishop:
6977 case BlackPromotionBishop:
6978 case WhitePromotionKnight:
6979 case BlackPromotionKnight:
6980 case WhitePromotionKing:
6981 case BlackPromotionKing:
6983 case WhiteCapturesEnPassant:
6984 case BlackCapturesEnPassant:
6985 case WhiteKingSideCastle:
6986 case WhiteQueenSideCastle:
6987 case BlackKingSideCastle:
6988 case BlackQueenSideCastle:
6989 case WhiteKingSideCastleWild:
6990 case WhiteQueenSideCastleWild:
6991 case BlackKingSideCastleWild:
6992 case BlackQueenSideCastleWild:
6994 case WhiteHSideCastleFR:
6995 case WhiteASideCastleFR:
6996 case BlackHSideCastleFR:
6997 case BlackASideCastleFR:
6999 fromX = currentMoveString[0] - AAA;
7000 fromY = currentMoveString[1] - ONE;
7001 toX = currentMoveString[2] - AAA;
7002 toY = currentMoveString[3] - ONE;
7003 promoChar = currentMoveString[4];
7007 fromX = moveType == WhiteDrop ?
7008 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7009 (int) CharToPiece(ToLower(currentMoveString[0]));
7011 toX = currentMoveString[2] - AAA;
7012 toY = currentMoveString[3] - ONE;
7013 promoChar = NULLCHAR;
7017 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7018 if (appData.debugMode) {
7019 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7020 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7021 setbuf(debugFP, NULL);
7023 DisplayError(buf, 0);
7025 case ImpossibleMove:
7027 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7028 if (appData.debugMode) {
7029 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7030 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7031 setbuf(debugFP, NULL);
7033 DisplayError(buf, 0);
7035 case (ChessMove) 0: /* end of file */
7036 if (boardIndex < backwardMostMove) {
7037 /* Oops, gap. How did that happen? */
7038 DisplayError(_("Gap in move list"), 0);
7041 backwardMostMove = blackPlaysFirst ? 1 : 0;
7042 if (boardIndex > forwardMostMove) {
7043 forwardMostMove = boardIndex;
7047 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7048 strcat(parseList[boardIndex-1], " ");
7049 strcat(parseList[boardIndex-1], yy_text);
7061 case GameUnfinished:
7062 if (gameMode == IcsExamining) {
7063 if (boardIndex < backwardMostMove) {
7064 /* Oops, gap. How did that happen? */
7067 backwardMostMove = blackPlaysFirst ? 1 : 0;
7070 gameInfo.result = moveType;
7071 p = strchr(yy_text, '{');
7072 if (p == NULL) p = strchr(yy_text, '(');
7075 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7077 q = strchr(p, *p == '{' ? '}' : ')');
7078 if (q != NULL) *q = NULLCHAR;
7081 gameInfo.resultDetails = StrSave(p);
7084 if (boardIndex >= forwardMostMove &&
7085 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7086 backwardMostMove = blackPlaysFirst ? 1 : 0;
7089 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7090 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7091 parseList[boardIndex]);
7092 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7093 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7094 /* currentMoveString is set as a side-effect of yylex */
7095 strcpy(moveList[boardIndex], currentMoveString);
7096 strcat(moveList[boardIndex], "\n");
7098 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7099 castlingRights[boardIndex], &epStatus[boardIndex]);
7100 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7101 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7107 if(gameInfo.variant != VariantShogi)
7108 strcat(parseList[boardIndex - 1], "+");
7112 strcat(parseList[boardIndex - 1], "#");
7119 /* Apply a move to the given board */
7121 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7122 int fromX, fromY, toX, toY;
7128 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7130 /* [HGM] compute & store e.p. status and castling rights for new position */
7131 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7134 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7138 if( board[toY][toX] != EmptySquare )
7141 if( board[fromY][fromX] == WhitePawn ) {
7142 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7145 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7146 gameInfo.variant != VariantBerolina || toX < fromX)
7147 *ep = toX | berolina;
7148 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7149 gameInfo.variant != VariantBerolina || toX > fromX)
7153 if( board[fromY][fromX] == BlackPawn ) {
7154 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7156 if( toY-fromY== -2) {
7157 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7158 gameInfo.variant != VariantBerolina || toX < fromX)
7159 *ep = toX | berolina;
7160 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7161 gameInfo.variant != VariantBerolina || toX > fromX)
7166 for(i=0; i<nrCastlingRights; i++) {
7167 if(castling[i] == fromX && castlingRank[i] == fromY ||
7168 castling[i] == toX && castlingRank[i] == toY
7169 ) castling[i] = -1; // revoke for moved or captured piece
7174 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7175 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7176 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7178 if (fromX == toX && fromY == toY) return;
7180 if (fromY == DROP_RANK) {
7182 piece = board[toY][toX] = (ChessSquare) fromX;
7184 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7185 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7186 if(gameInfo.variant == VariantKnightmate)
7187 king += (int) WhiteUnicorn - (int) WhiteKing;
7189 /* Code added by Tord: */
7190 /* FRC castling assumed when king captures friendly rook. */
7191 if (board[fromY][fromX] == WhiteKing &&
7192 board[toY][toX] == WhiteRook) {
7193 board[fromY][fromX] = EmptySquare;
7194 board[toY][toX] = EmptySquare;
7196 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7198 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7200 } else if (board[fromY][fromX] == BlackKing &&
7201 board[toY][toX] == BlackRook) {
7202 board[fromY][fromX] = EmptySquare;
7203 board[toY][toX] = EmptySquare;
7205 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7207 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7209 /* End of code added by Tord */
7211 } else if (board[fromY][fromX] == king
7212 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7213 && toY == fromY && toX > fromX+1) {
7214 board[fromY][fromX] = EmptySquare;
7215 board[toY][toX] = king;
7216 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7217 board[fromY][BOARD_RGHT-1] = EmptySquare;
7218 } else if (board[fromY][fromX] == king
7219 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7220 && toY == fromY && toX < fromX-1) {
7221 board[fromY][fromX] = EmptySquare;
7222 board[toY][toX] = king;
7223 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7224 board[fromY][BOARD_LEFT] = EmptySquare;
7225 } else if (board[fromY][fromX] == WhitePawn
7226 && toY == BOARD_HEIGHT-1
7227 && gameInfo.variant != VariantXiangqi
7229 /* white pawn promotion */
7230 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7231 if (board[toY][toX] == EmptySquare) {
7232 board[toY][toX] = WhiteQueen;
7234 if(gameInfo.variant==VariantBughouse ||
7235 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7236 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7237 board[fromY][fromX] = EmptySquare;
7238 } else if ((fromY == BOARD_HEIGHT-4)
7240 && gameInfo.variant != VariantXiangqi
7241 && gameInfo.variant != VariantBerolina
7242 && (board[fromY][fromX] == WhitePawn)
7243 && (board[toY][toX] == EmptySquare)) {
7244 board[fromY][fromX] = EmptySquare;
7245 board[toY][toX] = WhitePawn;
7246 captured = board[toY - 1][toX];
7247 board[toY - 1][toX] = EmptySquare;
7248 } else if ((fromY == BOARD_HEIGHT-4)
7250 && gameInfo.variant == VariantBerolina
7251 && (board[fromY][fromX] == WhitePawn)
7252 && (board[toY][toX] == EmptySquare)) {
7253 board[fromY][fromX] = EmptySquare;
7254 board[toY][toX] = WhitePawn;
7255 if(oldEP & EP_BEROLIN_A) {
7256 captured = board[fromY][fromX-1];
7257 board[fromY][fromX-1] = EmptySquare;
7258 }else{ captured = board[fromY][fromX+1];
7259 board[fromY][fromX+1] = EmptySquare;
7261 } else if (board[fromY][fromX] == king
7262 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7263 && toY == fromY && toX > fromX+1) {
7264 board[fromY][fromX] = EmptySquare;
7265 board[toY][toX] = king;
7266 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7267 board[fromY][BOARD_RGHT-1] = EmptySquare;
7268 } else if (board[fromY][fromX] == king
7269 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7270 && toY == fromY && toX < fromX-1) {
7271 board[fromY][fromX] = EmptySquare;
7272 board[toY][toX] = king;
7273 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7274 board[fromY][BOARD_LEFT] = EmptySquare;
7275 } else if (fromY == 7 && fromX == 3
7276 && board[fromY][fromX] == BlackKing
7277 && toY == 7 && toX == 5) {
7278 board[fromY][fromX] = EmptySquare;
7279 board[toY][toX] = BlackKing;
7280 board[fromY][7] = EmptySquare;
7281 board[toY][4] = BlackRook;
7282 } else if (fromY == 7 && fromX == 3
7283 && board[fromY][fromX] == BlackKing
7284 && toY == 7 && toX == 1) {
7285 board[fromY][fromX] = EmptySquare;
7286 board[toY][toX] = BlackKing;
7287 board[fromY][0] = EmptySquare;
7288 board[toY][2] = BlackRook;
7289 } else if (board[fromY][fromX] == BlackPawn
7291 && gameInfo.variant != VariantXiangqi
7293 /* black pawn promotion */
7294 board[0][toX] = CharToPiece(ToLower(promoChar));
7295 if (board[0][toX] == EmptySquare) {
7296 board[0][toX] = BlackQueen;
7298 if(gameInfo.variant==VariantBughouse ||
7299 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7300 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7301 board[fromY][fromX] = EmptySquare;
7302 } else if ((fromY == 3)
7304 && gameInfo.variant != VariantXiangqi
7305 && gameInfo.variant != VariantBerolina
7306 && (board[fromY][fromX] == BlackPawn)
7307 && (board[toY][toX] == EmptySquare)) {
7308 board[fromY][fromX] = EmptySquare;
7309 board[toY][toX] = BlackPawn;
7310 captured = board[toY + 1][toX];
7311 board[toY + 1][toX] = EmptySquare;
7312 } else if ((fromY == 3)
7314 && gameInfo.variant == VariantBerolina
7315 && (board[fromY][fromX] == BlackPawn)
7316 && (board[toY][toX] == EmptySquare)) {
7317 board[fromY][fromX] = EmptySquare;
7318 board[toY][toX] = BlackPawn;
7319 if(oldEP & EP_BEROLIN_A) {
7320 captured = board[fromY][fromX-1];
7321 board[fromY][fromX-1] = EmptySquare;
7322 }else{ captured = board[fromY][fromX+1];
7323 board[fromY][fromX+1] = EmptySquare;
7326 board[toY][toX] = board[fromY][fromX];
7327 board[fromY][fromX] = EmptySquare;
7330 /* [HGM] now we promote for Shogi, if needed */
7331 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7332 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7335 if (gameInfo.holdingsWidth != 0) {
7337 /* !!A lot more code needs to be written to support holdings */
7338 /* [HGM] OK, so I have written it. Holdings are stored in the */
7339 /* penultimate board files, so they are automaticlly stored */
7340 /* in the game history. */
7341 if (fromY == DROP_RANK) {
7342 /* Delete from holdings, by decreasing count */
7343 /* and erasing image if necessary */
7345 if(p < (int) BlackPawn) { /* white drop */
7346 p -= (int)WhitePawn;
7347 if(p >= gameInfo.holdingsSize) p = 0;
7348 if(--board[p][BOARD_WIDTH-2] == 0)
7349 board[p][BOARD_WIDTH-1] = EmptySquare;
7350 } else { /* black drop */
7351 p -= (int)BlackPawn;
7352 if(p >= gameInfo.holdingsSize) p = 0;
7353 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7354 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7357 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7358 && gameInfo.variant != VariantBughouse ) {
7359 /* [HGM] holdings: Add to holdings, if holdings exist */
7360 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7361 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7362 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7365 if (p >= (int) BlackPawn) {
7366 p -= (int)BlackPawn;
7367 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7368 /* in Shogi restore piece to its original first */
7369 captured = (ChessSquare) (DEMOTED captured);
7372 p = PieceToNumber((ChessSquare)p);
7373 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7374 board[p][BOARD_WIDTH-2]++;
7375 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7377 p -= (int)WhitePawn;
7378 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7379 captured = (ChessSquare) (DEMOTED captured);
7382 p = PieceToNumber((ChessSquare)p);
7383 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7384 board[BOARD_HEIGHT-1-p][1]++;
7385 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7389 } else if (gameInfo.variant == VariantAtomic) {
7390 if (captured != EmptySquare) {
7392 for (y = toY-1; y <= toY+1; y++) {
7393 for (x = toX-1; x <= toX+1; x++) {
7394 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7395 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7396 board[y][x] = EmptySquare;
7400 board[toY][toX] = EmptySquare;
7403 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7404 /* [HGM] Shogi promotions */
7405 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7408 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7409 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7410 // [HGM] superchess: take promotion piece out of holdings
7411 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7412 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7413 if(!--board[k][BOARD_WIDTH-2])
7414 board[k][BOARD_WIDTH-1] = EmptySquare;
7416 if(!--board[BOARD_HEIGHT-1-k][1])
7417 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7423 /* Updates forwardMostMove */
7425 MakeMove(fromX, fromY, toX, toY, promoChar)
7426 int fromX, fromY, toX, toY;
7429 // forwardMostMove++; // [HGM] bare: moved downstream
7431 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7432 int timeLeft; static int lastLoadFlag=0; int king, piece;
7433 piece = boards[forwardMostMove][fromY][fromX];
7434 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7435 if(gameInfo.variant == VariantKnightmate)
7436 king += (int) WhiteUnicorn - (int) WhiteKing;
7437 if(forwardMostMove == 0) {
7439 fprintf(serverMoves, "%s;", second.tidy);
7440 fprintf(serverMoves, "%s;", first.tidy);
7441 if(!blackPlaysFirst)
7442 fprintf(serverMoves, "%s;", second.tidy);
7443 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7444 lastLoadFlag = loadFlag;
7446 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7447 // print castling suffix
7448 if( toY == fromY && piece == king ) {
7450 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7452 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7455 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7456 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7457 boards[forwardMostMove][toY][toX] == EmptySquare
7459 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7461 if(promoChar != NULLCHAR)
7462 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7464 fprintf(serverMoves, "/%d/%d",
7465 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7466 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7467 else timeLeft = blackTimeRemaining/1000;
7468 fprintf(serverMoves, "/%d", timeLeft);
7470 fflush(serverMoves);
7473 if (forwardMostMove+1 >= MAX_MOVES) {
7474 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7478 if (commentList[forwardMostMove+1] != NULL) {
7479 free(commentList[forwardMostMove+1]);
7480 commentList[forwardMostMove+1] = NULL;
7482 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7483 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7484 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7485 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7486 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7487 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7488 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7489 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7490 gameInfo.result = GameUnfinished;
7491 if (gameInfo.resultDetails != NULL) {
7492 free(gameInfo.resultDetails);
7493 gameInfo.resultDetails = NULL;
7495 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7496 moveList[forwardMostMove - 1]);
7497 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7498 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7499 fromY, fromX, toY, toX, promoChar,
7500 parseList[forwardMostMove - 1]);
7501 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7502 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7503 castlingRights[forwardMostMove]) ) {
7509 if(gameInfo.variant != VariantShogi)
7510 strcat(parseList[forwardMostMove - 1], "+");
7514 strcat(parseList[forwardMostMove - 1], "#");
7517 if (appData.debugMode) {
7518 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7523 /* Updates currentMove if not pausing */
7525 ShowMove(fromX, fromY, toX, toY)
7527 int instant = (gameMode == PlayFromGameFile) ?
7528 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7529 if(appData.noGUI) return;
7530 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7532 if (forwardMostMove == currentMove + 1) {
7533 AnimateMove(boards[forwardMostMove - 1],
7534 fromX, fromY, toX, toY);
7536 if (appData.highlightLastMove) {
7537 SetHighlights(fromX, fromY, toX, toY);
7540 currentMove = forwardMostMove;
7543 if (instant) return;
7545 DisplayMove(currentMove - 1);
7546 DrawPosition(FALSE, boards[currentMove]);
7547 DisplayBothClocks();
7548 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7551 void SendEgtPath(ChessProgramState *cps)
7552 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7553 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7555 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7558 char c, *q = name+1, *r, *s;
7560 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7561 while(*p && *p != ',') *q++ = *p++;
7563 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7564 strcmp(name, ",nalimov:") == 0 ) {
7565 // take nalimov path from the menu-changeable option first, if it is defined
7566 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7567 SendToProgram(buf,cps); // send egtbpath command for nalimov
7569 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7570 (s = StrStr(appData.egtFormats, name)) != NULL) {
7571 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7572 s = r = StrStr(s, ":") + 1; // beginning of path info
7573 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7574 c = *r; *r = 0; // temporarily null-terminate path info
7575 *--q = 0; // strip of trailig ':' from name
7576 sprintf(buf, "egtpath %s %s\n", name+1, s);
7578 SendToProgram(buf,cps); // send egtbpath command for this format
7580 if(*p == ',') p++; // read away comma to position for next format name
7585 InitChessProgram(cps, setup)
7586 ChessProgramState *cps;
7587 int setup; /* [HGM] needed to setup FRC opening position */
7589 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7590 if (appData.noChessProgram) return;
7591 hintRequested = FALSE;
7592 bookRequested = FALSE;
7594 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7595 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7596 if(cps->memSize) { /* [HGM] memory */
7597 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7598 SendToProgram(buf, cps);
7600 SendEgtPath(cps); /* [HGM] EGT */
7601 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7602 sprintf(buf, "cores %d\n", appData.smpCores);
7603 SendToProgram(buf, cps);
7606 SendToProgram(cps->initString, cps);
7607 if (gameInfo.variant != VariantNormal &&
7608 gameInfo.variant != VariantLoadable
7609 /* [HGM] also send variant if board size non-standard */
7610 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7612 char *v = VariantName(gameInfo.variant);
7613 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7614 /* [HGM] in protocol 1 we have to assume all variants valid */
7615 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7616 DisplayFatalError(buf, 0, 1);
7620 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7621 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7622 if( gameInfo.variant == VariantXiangqi )
7623 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7624 if( gameInfo.variant == VariantShogi )
7625 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7626 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7627 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7628 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7629 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7630 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7631 if( gameInfo.variant == VariantCourier )
7632 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7633 if( gameInfo.variant == VariantSuper )
7634 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7635 if( gameInfo.variant == VariantGreat )
7636 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7639 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7640 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7641 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7642 if(StrStr(cps->variants, b) == NULL) {
7643 // specific sized variant not known, check if general sizing allowed
7644 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7645 if(StrStr(cps->variants, "boardsize") == NULL) {
7646 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7647 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7648 DisplayFatalError(buf, 0, 1);
7651 /* [HGM] here we really should compare with the maximum supported board size */
7654 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7655 sprintf(buf, "variant %s\n", b);
7656 SendToProgram(buf, cps);
7658 currentlyInitializedVariant = gameInfo.variant;
7660 /* [HGM] send opening position in FRC to first engine */
7662 SendToProgram("force\n", cps);
7664 /* engine is now in force mode! Set flag to wake it up after first move. */
7665 setboardSpoiledMachineBlack = 1;
7669 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7670 SendToProgram(buf, cps);
7672 cps->maybeThinking = FALSE;
7673 cps->offeredDraw = 0;
7674 if (!appData.icsActive) {
7675 SendTimeControl(cps, movesPerSession, timeControl,
7676 timeIncrement, appData.searchDepth,
7679 if (appData.showThinking
7680 // [HGM] thinking: four options require thinking output to be sent
7681 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7683 SendToProgram("post\n", cps);
7685 SendToProgram("hard\n", cps);
7686 if (!appData.ponderNextMove) {
7687 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7688 it without being sure what state we are in first. "hard"
7689 is not a toggle, so that one is OK.
7691 SendToProgram("easy\n", cps);
7694 sprintf(buf, "ping %d\n", ++cps->lastPing);
7695 SendToProgram(buf, cps);
7697 cps->initDone = TRUE;
7702 StartChessProgram(cps)
7703 ChessProgramState *cps;
7708 if (appData.noChessProgram) return;
7709 cps->initDone = FALSE;
7711 if (strcmp(cps->host, "localhost") == 0) {
7712 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7713 } else if (*appData.remoteShell == NULLCHAR) {
7714 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7716 if (*appData.remoteUser == NULLCHAR) {
7717 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7720 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7721 cps->host, appData.remoteUser, cps->program);
7723 err = StartChildProcess(buf, "", &cps->pr);
7727 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7728 DisplayFatalError(buf, err, 1);
7734 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7735 if (cps->protocolVersion > 1) {
7736 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7737 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7738 cps->comboCnt = 0; // and values of combo boxes
7739 SendToProgram(buf, cps);
7741 SendToProgram("xboard\n", cps);
7747 TwoMachinesEventIfReady P((void))
7749 if (first.lastPing != first.lastPong) {
7750 DisplayMessage("", _("Waiting for first chess program"));
7751 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7754 if (second.lastPing != second.lastPong) {
7755 DisplayMessage("", _("Waiting for second chess program"));
7756 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7764 NextMatchGame P((void))
7766 int index; /* [HGM] autoinc: step lod index during match */
7768 if (*appData.loadGameFile != NULLCHAR) {
7769 index = appData.loadGameIndex;
7770 if(index < 0) { // [HGM] autoinc
7771 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7772 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7774 LoadGameFromFile(appData.loadGameFile,
7776 appData.loadGameFile, FALSE);
7777 } else if (*appData.loadPositionFile != NULLCHAR) {
7778 index = appData.loadPositionIndex;
7779 if(index < 0) { // [HGM] autoinc
7780 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7781 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7783 LoadPositionFromFile(appData.loadPositionFile,
7785 appData.loadPositionFile);
7787 TwoMachinesEventIfReady();
7790 void UserAdjudicationEvent( int result )
7792 ChessMove gameResult = GameIsDrawn;
7795 gameResult = WhiteWins;
7797 else if( result < 0 ) {
7798 gameResult = BlackWins;
7801 if( gameMode == TwoMachinesPlay ) {
7802 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7807 // [HGM] save: calculate checksum of game to make games easily identifiable
7808 int StringCheckSum(char *s)
7811 if(s==NULL) return 0;
7812 while(*s) i = i*259 + *s++;
7819 for(i=backwardMostMove; i<forwardMostMove; i++) {
7820 sum += pvInfoList[i].depth;
7821 sum += StringCheckSum(parseList[i]);
7822 sum += StringCheckSum(commentList[i]);
7825 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7826 return sum + StringCheckSum(commentList[i]);
7827 } // end of save patch
7830 GameEnds(result, resultDetails, whosays)
7832 char *resultDetails;
7835 GameMode nextGameMode;
7839 if(endingGame) return; /* [HGM] crash: forbid recursion */
7842 if (appData.debugMode) {
7843 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7844 result, resultDetails ? resultDetails : "(null)", whosays);
7847 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7848 /* If we are playing on ICS, the server decides when the
7849 game is over, but the engine can offer to draw, claim
7853 if (appData.zippyPlay && first.initDone) {
7854 if (result == GameIsDrawn) {
7855 /* In case draw still needs to be claimed */
7856 SendToICS(ics_prefix);
7857 SendToICS("draw\n");
7858 } else if (StrCaseStr(resultDetails, "resign")) {
7859 SendToICS(ics_prefix);
7860 SendToICS("resign\n");
7864 endingGame = 0; /* [HGM] crash */
7868 /* If we're loading the game from a file, stop */
7869 if (whosays == GE_FILE) {
7870 (void) StopLoadGameTimer();
7874 /* Cancel draw offers */
7875 first.offeredDraw = second.offeredDraw = 0;
7877 /* If this is an ICS game, only ICS can really say it's done;
7878 if not, anyone can. */
7879 isIcsGame = (gameMode == IcsPlayingWhite ||
7880 gameMode == IcsPlayingBlack ||
7881 gameMode == IcsObserving ||
7882 gameMode == IcsExamining);
7884 if (!isIcsGame || whosays == GE_ICS) {
7885 /* OK -- not an ICS game, or ICS said it was done */
7887 if (!isIcsGame && !appData.noChessProgram)
7888 SetUserThinkingEnables();
7890 /* [HGM] if a machine claims the game end we verify this claim */
7891 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7892 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7894 ChessMove trueResult = (ChessMove) -1;
7896 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7897 first.twoMachinesColor[0] :
7898 second.twoMachinesColor[0] ;
7900 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7901 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7902 /* [HGM] verify: engine mate claims accepted if they were flagged */
7903 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7905 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7906 /* [HGM] verify: engine mate claims accepted if they were flagged */
7907 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7909 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7910 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7913 // now verify win claims, but not in drop games, as we don't understand those yet
7914 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7915 || gameInfo.variant == VariantGreat) &&
7916 (result == WhiteWins && claimer == 'w' ||
7917 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7918 if (appData.debugMode) {
7919 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7920 result, epStatus[forwardMostMove], forwardMostMove);
7922 if(result != trueResult) {
7923 sprintf(buf, "False win claim: '%s'", resultDetails);
7924 result = claimer == 'w' ? BlackWins : WhiteWins;
7925 resultDetails = buf;
7928 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7929 && (forwardMostMove <= backwardMostMove ||
7930 epStatus[forwardMostMove-1] > EP_DRAWS ||
7931 (claimer=='b')==(forwardMostMove&1))
7933 /* [HGM] verify: draws that were not flagged are false claims */
7934 sprintf(buf, "False draw claim: '%s'", resultDetails);
7935 result = claimer == 'w' ? BlackWins : WhiteWins;
7936 resultDetails = buf;
7938 /* (Claiming a loss is accepted no questions asked!) */
7940 /* [HGM] bare: don't allow bare King to win */
7941 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7942 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7943 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7944 && result != GameIsDrawn)
7945 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7946 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7947 int p = (int)boards[forwardMostMove][i][j] - color;
7948 if(p >= 0 && p <= (int)WhiteKing) k++;
7950 if (appData.debugMode) {
7951 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7952 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7955 result = GameIsDrawn;
7956 sprintf(buf, "%s but bare king", resultDetails);
7957 resultDetails = buf;
7963 if(serverMoves != NULL && !loadFlag) { char c = '=';
7964 if(result==WhiteWins) c = '+';
7965 if(result==BlackWins) c = '-';
7966 if(resultDetails != NULL)
7967 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7969 if (resultDetails != NULL) {
7970 gameInfo.result = result;
7971 gameInfo.resultDetails = StrSave(resultDetails);
7973 /* display last move only if game was not loaded from file */
7974 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7975 DisplayMove(currentMove - 1);
7977 if (forwardMostMove != 0) {
7978 if (gameMode != PlayFromGameFile && gameMode != EditGame
7979 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7981 if (*appData.saveGameFile != NULLCHAR) {
7982 SaveGameToFile(appData.saveGameFile, TRUE);
7983 } else if (appData.autoSaveGames) {
7986 if (*appData.savePositionFile != NULLCHAR) {
7987 SavePositionToFile(appData.savePositionFile);
7992 /* Tell program how game ended in case it is learning */
7993 /* [HGM] Moved this to after saving the PGN, just in case */
7994 /* engine died and we got here through time loss. In that */
7995 /* case we will get a fatal error writing the pipe, which */
7996 /* would otherwise lose us the PGN. */
7997 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7998 /* output during GameEnds should never be fatal anymore */
7999 if (gameMode == MachinePlaysWhite ||
8000 gameMode == MachinePlaysBlack ||
8001 gameMode == TwoMachinesPlay ||
8002 gameMode == IcsPlayingWhite ||
8003 gameMode == IcsPlayingBlack ||
8004 gameMode == BeginningOfGame) {
8006 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8008 if (first.pr != NoProc) {
8009 SendToProgram(buf, &first);
8011 if (second.pr != NoProc &&
8012 gameMode == TwoMachinesPlay) {
8013 SendToProgram(buf, &second);
8018 if (appData.icsActive) {
8019 if (appData.quietPlay &&
8020 (gameMode == IcsPlayingWhite ||
8021 gameMode == IcsPlayingBlack)) {
8022 SendToICS(ics_prefix);
8023 SendToICS("set shout 1\n");
8025 nextGameMode = IcsIdle;
8026 ics_user_moved = FALSE;
8027 /* clean up premove. It's ugly when the game has ended and the
8028 * premove highlights are still on the board.
8032 ClearPremoveHighlights();
8033 DrawPosition(FALSE, boards[currentMove]);
8035 if (whosays == GE_ICS) {
8038 if (gameMode == IcsPlayingWhite)
8040 else if(gameMode == IcsPlayingBlack)
8044 if (gameMode == IcsPlayingBlack)
8046 else if(gameMode == IcsPlayingWhite)
8053 PlayIcsUnfinishedSound();
8056 } else if (gameMode == EditGame ||
8057 gameMode == PlayFromGameFile ||
8058 gameMode == AnalyzeMode ||
8059 gameMode == AnalyzeFile) {
8060 nextGameMode = gameMode;
8062 nextGameMode = EndOfGame;
8067 nextGameMode = gameMode;
8070 if (appData.noChessProgram) {
8071 gameMode = nextGameMode;
8073 endingGame = 0; /* [HGM] crash */
8078 /* Put first chess program into idle state */
8079 if (first.pr != NoProc &&
8080 (gameMode == MachinePlaysWhite ||
8081 gameMode == MachinePlaysBlack ||
8082 gameMode == TwoMachinesPlay ||
8083 gameMode == IcsPlayingWhite ||
8084 gameMode == IcsPlayingBlack ||
8085 gameMode == BeginningOfGame)) {
8086 SendToProgram("force\n", &first);
8087 if (first.usePing) {
8089 sprintf(buf, "ping %d\n", ++first.lastPing);
8090 SendToProgram(buf, &first);
8093 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8094 /* Kill off first chess program */
8095 if (first.isr != NULL)
8096 RemoveInputSource(first.isr);
8099 if (first.pr != NoProc) {
8101 DoSleep( appData.delayBeforeQuit );
8102 SendToProgram("quit\n", &first);
8103 DoSleep( appData.delayAfterQuit );
8104 DestroyChildProcess(first.pr, first.useSigterm);
8109 /* Put second chess program into idle state */
8110 if (second.pr != NoProc &&
8111 gameMode == TwoMachinesPlay) {
8112 SendToProgram("force\n", &second);
8113 if (second.usePing) {
8115 sprintf(buf, "ping %d\n", ++second.lastPing);
8116 SendToProgram(buf, &second);
8119 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8120 /* Kill off second chess program */
8121 if (second.isr != NULL)
8122 RemoveInputSource(second.isr);
8125 if (second.pr != NoProc) {
8126 DoSleep( appData.delayBeforeQuit );
8127 SendToProgram("quit\n", &second);
8128 DoSleep( appData.delayAfterQuit );
8129 DestroyChildProcess(second.pr, second.useSigterm);
8134 if (matchMode && gameMode == TwoMachinesPlay) {
8137 if (first.twoMachinesColor[0] == 'w') {
8144 if (first.twoMachinesColor[0] == 'b') {
8153 if (matchGame < appData.matchGames) {
8155 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8156 tmp = first.twoMachinesColor;
8157 first.twoMachinesColor = second.twoMachinesColor;
8158 second.twoMachinesColor = tmp;
8160 gameMode = nextGameMode;
8162 if(appData.matchPause>10000 || appData.matchPause<10)
8163 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8164 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8165 endingGame = 0; /* [HGM] crash */
8169 gameMode = nextGameMode;
8170 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8171 first.tidy, second.tidy,
8172 first.matchWins, second.matchWins,
8173 appData.matchGames - (first.matchWins + second.matchWins));
8174 DisplayFatalError(buf, 0, 0);
8177 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8178 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8180 gameMode = nextGameMode;
8182 endingGame = 0; /* [HGM] crash */
8185 /* Assumes program was just initialized (initString sent).
8186 Leaves program in force mode. */
8188 FeedMovesToProgram(cps, upto)
8189 ChessProgramState *cps;
8194 if (appData.debugMode)
8195 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8196 startedFromSetupPosition ? "position and " : "",
8197 backwardMostMove, upto, cps->which);
8198 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8199 // [HGM] variantswitch: make engine aware of new variant
8200 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8201 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8202 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8203 SendToProgram(buf, cps);
8204 currentlyInitializedVariant = gameInfo.variant;
8206 SendToProgram("force\n", cps);
8207 if (startedFromSetupPosition) {
8208 SendBoard(cps, backwardMostMove);
8209 if (appData.debugMode) {
8210 fprintf(debugFP, "feedMoves\n");
8213 for (i = backwardMostMove; i < upto; i++) {
8214 SendMoveToProgram(i, cps);
8220 ResurrectChessProgram()
8222 /* The chess program may have exited.
8223 If so, restart it and feed it all the moves made so far. */
8225 if (appData.noChessProgram || first.pr != NoProc) return;
8227 StartChessProgram(&first);
8228 InitChessProgram(&first, FALSE);
8229 FeedMovesToProgram(&first, currentMove);
8231 if (!first.sendTime) {
8232 /* can't tell gnuchess what its clock should read,
8233 so we bow to its notion. */
8235 timeRemaining[0][currentMove] = whiteTimeRemaining;
8236 timeRemaining[1][currentMove] = blackTimeRemaining;
8239 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8240 appData.icsEngineAnalyze) && first.analysisSupport) {
8241 SendToProgram("analyze\n", &first);
8242 first.analyzing = TRUE;
8255 if (appData.debugMode) {
8256 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8257 redraw, init, gameMode);
8259 pausing = pauseExamInvalid = FALSE;
8260 startedFromSetupPosition = blackPlaysFirst = FALSE;
8262 whiteFlag = blackFlag = FALSE;
8263 userOfferedDraw = FALSE;
8264 hintRequested = bookRequested = FALSE;
8265 first.maybeThinking = FALSE;
8266 second.maybeThinking = FALSE;
8267 first.bookSuspend = FALSE; // [HGM] book
8268 second.bookSuspend = FALSE;
8269 thinkOutput[0] = NULLCHAR;
8270 lastHint[0] = NULLCHAR;
8271 ClearGameInfo(&gameInfo);
8272 gameInfo.variant = StringToVariant(appData.variant);
8273 ics_user_moved = ics_clock_paused = FALSE;
8274 ics_getting_history = H_FALSE;
8276 white_holding[0] = black_holding[0] = NULLCHAR;
8277 ClearProgramStats();
8278 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8282 flipView = appData.flipView;
8283 ClearPremoveHighlights();
8285 alarmSounded = FALSE;
8287 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8288 if(appData.serverMovesName != NULL) {
8289 /* [HGM] prepare to make moves file for broadcasting */
8290 clock_t t = clock();
8291 if(serverMoves != NULL) fclose(serverMoves);
8292 serverMoves = fopen(appData.serverMovesName, "r");
8293 if(serverMoves != NULL) {
8294 fclose(serverMoves);
8295 /* delay 15 sec before overwriting, so all clients can see end */
8296 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8298 serverMoves = fopen(appData.serverMovesName, "w");
8302 gameMode = BeginningOfGame;
8304 if(appData.icsActive) gameInfo.variant = VariantNormal;
8305 currentMove = forwardMostMove = backwardMostMove = 0;
8306 InitPosition(redraw);
8307 for (i = 0; i < MAX_MOVES; i++) {
8308 if (commentList[i] != NULL) {
8309 free(commentList[i]);
8310 commentList[i] = NULL;
8314 timeRemaining[0][0] = whiteTimeRemaining;
8315 timeRemaining[1][0] = blackTimeRemaining;
8316 if (first.pr == NULL) {
8317 StartChessProgram(&first);
8320 InitChessProgram(&first, startedFromSetupPosition);
8323 DisplayMessage("", "");
8324 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8325 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8332 if (!AutoPlayOneMove())
8334 if (matchMode || appData.timeDelay == 0)
8336 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8338 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8347 int fromX, fromY, toX, toY;
8349 if (appData.debugMode) {
8350 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8353 if (gameMode != PlayFromGameFile)
8356 if (currentMove >= forwardMostMove) {
8357 gameMode = EditGame;
8360 /* [AS] Clear current move marker at the end of a game */
8361 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8366 toX = moveList[currentMove][2] - AAA;
8367 toY = moveList[currentMove][3] - ONE;
8369 if (moveList[currentMove][1] == '@') {
8370 if (appData.highlightLastMove) {
8371 SetHighlights(-1, -1, toX, toY);
8374 fromX = moveList[currentMove][0] - AAA;
8375 fromY = moveList[currentMove][1] - ONE;
8377 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8379 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8381 if (appData.highlightLastMove) {
8382 SetHighlights(fromX, fromY, toX, toY);
8385 DisplayMove(currentMove);
8386 SendMoveToProgram(currentMove++, &first);
8387 DisplayBothClocks();
8388 DrawPosition(FALSE, boards[currentMove]);
8389 // [HGM] PV info: always display, routine tests if empty
8390 DisplayComment(currentMove - 1, commentList[currentMove]);
8396 LoadGameOneMove(readAhead)
8397 ChessMove readAhead;
8399 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8400 char promoChar = NULLCHAR;
8405 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8406 gameMode != AnalyzeMode && gameMode != Training) {
8411 yyboardindex = forwardMostMove;
8412 if (readAhead != (ChessMove)0) {
8413 moveType = readAhead;
8415 if (gameFileFP == NULL)
8417 moveType = (ChessMove) yylex();
8423 if (appData.debugMode)
8424 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8426 if (*p == '{' || *p == '[' || *p == '(') {
8427 p[strlen(p) - 1] = NULLCHAR;
8431 /* append the comment but don't display it */
8432 while (*p == '\n') p++;
8433 AppendComment(currentMove, p);
8436 case WhiteCapturesEnPassant:
8437 case BlackCapturesEnPassant:
8438 case WhitePromotionChancellor:
8439 case BlackPromotionChancellor:
8440 case WhitePromotionArchbishop:
8441 case BlackPromotionArchbishop:
8442 case WhitePromotionCentaur:
8443 case BlackPromotionCentaur:
8444 case WhitePromotionQueen:
8445 case BlackPromotionQueen:
8446 case WhitePromotionRook:
8447 case BlackPromotionRook:
8448 case WhitePromotionBishop:
8449 case BlackPromotionBishop:
8450 case WhitePromotionKnight:
8451 case BlackPromotionKnight:
8452 case WhitePromotionKing:
8453 case BlackPromotionKing:
8455 case WhiteKingSideCastle:
8456 case WhiteQueenSideCastle:
8457 case BlackKingSideCastle:
8458 case BlackQueenSideCastle:
8459 case WhiteKingSideCastleWild:
8460 case WhiteQueenSideCastleWild:
8461 case BlackKingSideCastleWild:
8462 case BlackQueenSideCastleWild:
8464 case WhiteHSideCastleFR:
8465 case WhiteASideCastleFR:
8466 case BlackHSideCastleFR:
8467 case BlackASideCastleFR:
8469 if (appData.debugMode)
8470 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8471 fromX = currentMoveString[0] - AAA;
8472 fromY = currentMoveString[1] - ONE;
8473 toX = currentMoveString[2] - AAA;
8474 toY = currentMoveString[3] - ONE;
8475 promoChar = currentMoveString[4];
8480 if (appData.debugMode)
8481 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8482 fromX = moveType == WhiteDrop ?
8483 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8484 (int) CharToPiece(ToLower(currentMoveString[0]));
8486 toX = currentMoveString[2] - AAA;
8487 toY = currentMoveString[3] - ONE;
8493 case GameUnfinished:
8494 if (appData.debugMode)
8495 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8496 p = strchr(yy_text, '{');
8497 if (p == NULL) p = strchr(yy_text, '(');
8500 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8502 q = strchr(p, *p == '{' ? '}' : ')');
8503 if (q != NULL) *q = NULLCHAR;
8506 GameEnds(moveType, p, GE_FILE);
8508 if (cmailMsgLoaded) {
8510 flipView = WhiteOnMove(currentMove);
8511 if (moveType == GameUnfinished) flipView = !flipView;
8512 if (appData.debugMode)
8513 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8517 case (ChessMove) 0: /* end of file */
8518 if (appData.debugMode)
8519 fprintf(debugFP, "Parser hit end of file\n");
8520 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8521 EP_UNKNOWN, castlingRights[currentMove]) ) {
8527 if (WhiteOnMove(currentMove)) {
8528 GameEnds(BlackWins, "Black mates", GE_FILE);
8530 GameEnds(WhiteWins, "White mates", GE_FILE);
8534 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8541 if (lastLoadGameStart == GNUChessGame) {
8542 /* GNUChessGames have numbers, but they aren't move numbers */
8543 if (appData.debugMode)
8544 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8545 yy_text, (int) moveType);
8546 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8548 /* else fall thru */
8553 /* Reached start of next game in file */
8554 if (appData.debugMode)
8555 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8556 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8557 EP_UNKNOWN, castlingRights[currentMove]) ) {
8563 if (WhiteOnMove(currentMove)) {
8564 GameEnds(BlackWins, "Black mates", GE_FILE);
8566 GameEnds(WhiteWins, "White mates", GE_FILE);
8570 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8576 case PositionDiagram: /* should not happen; ignore */
8577 case ElapsedTime: /* ignore */
8578 case NAG: /* ignore */
8579 if (appData.debugMode)
8580 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8581 yy_text, (int) moveType);
8582 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8585 if (appData.testLegality) {
8586 if (appData.debugMode)
8587 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8588 sprintf(move, _("Illegal move: %d.%s%s"),
8589 (forwardMostMove / 2) + 1,
8590 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8591 DisplayError(move, 0);
8594 if (appData.debugMode)
8595 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8596 yy_text, currentMoveString);
8597 fromX = currentMoveString[0] - AAA;
8598 fromY = currentMoveString[1] - ONE;
8599 toX = currentMoveString[2] - AAA;
8600 toY = currentMoveString[3] - ONE;
8601 promoChar = currentMoveString[4];
8606 if (appData.debugMode)
8607 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8608 sprintf(move, _("Ambiguous move: %d.%s%s"),
8609 (forwardMostMove / 2) + 1,
8610 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8611 DisplayError(move, 0);
8616 case ImpossibleMove:
8617 if (appData.debugMode)
8618 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8619 sprintf(move, _("Illegal move: %d.%s%s"),
8620 (forwardMostMove / 2) + 1,
8621 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8622 DisplayError(move, 0);
8628 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8629 DrawPosition(FALSE, boards[currentMove]);
8630 DisplayBothClocks();
8631 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8632 DisplayComment(currentMove - 1, commentList[currentMove]);
8634 (void) StopLoadGameTimer();
8636 cmailOldMove = forwardMostMove;
8639 /* currentMoveString is set as a side-effect of yylex */
8640 strcat(currentMoveString, "\n");
8641 strcpy(moveList[forwardMostMove], currentMoveString);
8643 thinkOutput[0] = NULLCHAR;
8644 MakeMove(fromX, fromY, toX, toY, promoChar);
8645 currentMove = forwardMostMove;
8650 /* Load the nth game from the given file */
8652 LoadGameFromFile(filename, n, title, useList)
8656 /*Boolean*/ int useList;
8661 if (strcmp(filename, "-") == 0) {
8665 f = fopen(filename, "rb");
8667 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8668 DisplayError(buf, errno);
8672 if (fseek(f, 0, 0) == -1) {
8673 /* f is not seekable; probably a pipe */
8676 if (useList && n == 0) {
8677 int error = GameListBuild(f);
8679 DisplayError(_("Cannot build game list"), error);
8680 } else if (!ListEmpty(&gameList) &&
8681 ((ListGame *) gameList.tailPred)->number > 1) {
8682 GameListPopUp(f, title);
8689 return LoadGame(f, n, title, FALSE);
8694 MakeRegisteredMove()
8696 int fromX, fromY, toX, toY;
8698 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8699 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8702 if (appData.debugMode)
8703 fprintf(debugFP, "Restoring %s for game %d\n",
8704 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8706 thinkOutput[0] = NULLCHAR;
8707 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8708 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8709 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8710 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8711 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8712 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8713 MakeMove(fromX, fromY, toX, toY, promoChar);
8714 ShowMove(fromX, fromY, toX, toY);
8716 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8717 EP_UNKNOWN, castlingRights[currentMove]) ) {
8724 if (WhiteOnMove(currentMove)) {
8725 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8727 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8732 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8739 if (WhiteOnMove(currentMove)) {
8740 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8742 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8747 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8758 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8760 CmailLoadGame(f, gameNumber, title, useList)
8768 if (gameNumber > nCmailGames) {
8769 DisplayError(_("No more games in this message"), 0);
8772 if (f == lastLoadGameFP) {
8773 int offset = gameNumber - lastLoadGameNumber;
8775 cmailMsg[0] = NULLCHAR;
8776 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8777 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8778 nCmailMovesRegistered--;
8780 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8781 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8782 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8785 if (! RegisterMove()) return FALSE;
8789 retVal = LoadGame(f, gameNumber, title, useList);
8791 /* Make move registered during previous look at this game, if any */
8792 MakeRegisteredMove();
8794 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8795 commentList[currentMove]
8796 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8797 DisplayComment(currentMove - 1, commentList[currentMove]);
8803 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8808 int gameNumber = lastLoadGameNumber + offset;
8809 if (lastLoadGameFP == NULL) {
8810 DisplayError(_("No game has been loaded yet"), 0);
8813 if (gameNumber <= 0) {
8814 DisplayError(_("Can't back up any further"), 0);
8817 if (cmailMsgLoaded) {
8818 return CmailLoadGame(lastLoadGameFP, gameNumber,
8819 lastLoadGameTitle, lastLoadGameUseList);
8821 return LoadGame(lastLoadGameFP, gameNumber,
8822 lastLoadGameTitle, lastLoadGameUseList);
8828 /* Load the nth game from open file f */
8830 LoadGame(f, gameNumber, title, useList)
8838 int gn = gameNumber;
8839 ListGame *lg = NULL;
8842 GameMode oldGameMode;
8843 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8845 if (appData.debugMode)
8846 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8848 if (gameMode == Training )
8849 SetTrainingModeOff();
8851 oldGameMode = gameMode;
8852 if (gameMode != BeginningOfGame) {
8857 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8858 fclose(lastLoadGameFP);
8862 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8865 fseek(f, lg->offset, 0);
8866 GameListHighlight(gameNumber);
8870 DisplayError(_("Game number out of range"), 0);
8875 if (fseek(f, 0, 0) == -1) {
8876 if (f == lastLoadGameFP ?
8877 gameNumber == lastLoadGameNumber + 1 :
8881 DisplayError(_("Can't seek on game file"), 0);
8887 lastLoadGameNumber = gameNumber;
8888 strcpy(lastLoadGameTitle, title);
8889 lastLoadGameUseList = useList;
8893 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8894 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8895 lg->gameInfo.black);
8897 } else if (*title != NULLCHAR) {
8898 if (gameNumber > 1) {
8899 sprintf(buf, "%s %d", title, gameNumber);
8902 DisplayTitle(title);
8906 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8907 gameMode = PlayFromGameFile;
8911 currentMove = forwardMostMove = backwardMostMove = 0;
8912 CopyBoard(boards[0], initialPosition);
8916 * Skip the first gn-1 games in the file.
8917 * Also skip over anything that precedes an identifiable
8918 * start of game marker, to avoid being confused by
8919 * garbage at the start of the file. Currently
8920 * recognized start of game markers are the move number "1",
8921 * the pattern "gnuchess .* game", the pattern
8922 * "^[#;%] [^ ]* game file", and a PGN tag block.
8923 * A game that starts with one of the latter two patterns
8924 * will also have a move number 1, possibly
8925 * following a position diagram.
8926 * 5-4-02: Let's try being more lenient and allowing a game to
8927 * start with an unnumbered move. Does that break anything?
8929 cm = lastLoadGameStart = (ChessMove) 0;
8931 yyboardindex = forwardMostMove;
8932 cm = (ChessMove) yylex();
8935 if (cmailMsgLoaded) {
8936 nCmailGames = CMAIL_MAX_GAMES - gn;
8939 DisplayError(_("Game not found in file"), 0);
8946 lastLoadGameStart = cm;
8950 switch (lastLoadGameStart) {
8957 gn--; /* count this game */
8958 lastLoadGameStart = cm;
8967 switch (lastLoadGameStart) {
8972 gn--; /* count this game */
8973 lastLoadGameStart = cm;
8976 lastLoadGameStart = cm; /* game counted already */
8984 yyboardindex = forwardMostMove;
8985 cm = (ChessMove) yylex();
8986 } while (cm == PGNTag || cm == Comment);
8993 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8994 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8995 != CMAIL_OLD_RESULT) {
8997 cmailResult[ CMAIL_MAX_GAMES
8998 - gn - 1] = CMAIL_OLD_RESULT;
9004 /* Only a NormalMove can be at the start of a game
9005 * without a position diagram. */
9006 if (lastLoadGameStart == (ChessMove) 0) {
9008 lastLoadGameStart = MoveNumberOne;
9017 if (appData.debugMode)
9018 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9020 if (cm == XBoardGame) {
9021 /* Skip any header junk before position diagram and/or move 1 */
9023 yyboardindex = forwardMostMove;
9024 cm = (ChessMove) yylex();
9026 if (cm == (ChessMove) 0 ||
9027 cm == GNUChessGame || cm == XBoardGame) {
9028 /* Empty game; pretend end-of-file and handle later */
9033 if (cm == MoveNumberOne || cm == PositionDiagram ||
9034 cm == PGNTag || cm == Comment)
9037 } else if (cm == GNUChessGame) {
9038 if (gameInfo.event != NULL) {
9039 free(gameInfo.event);
9041 gameInfo.event = StrSave(yy_text);
9044 startedFromSetupPosition = FALSE;
9045 while (cm == PGNTag) {
9046 if (appData.debugMode)
9047 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9048 err = ParsePGNTag(yy_text, &gameInfo);
9049 if (!err) numPGNTags++;
9051 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9052 if(gameInfo.variant != oldVariant) {
9053 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9055 oldVariant = gameInfo.variant;
9056 if (appData.debugMode)
9057 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9061 if (gameInfo.fen != NULL) {
9062 Board initial_position;
9063 startedFromSetupPosition = TRUE;
9064 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9066 DisplayError(_("Bad FEN position in file"), 0);
9069 CopyBoard(boards[0], initial_position);
9070 if (blackPlaysFirst) {
9071 currentMove = forwardMostMove = backwardMostMove = 1;
9072 CopyBoard(boards[1], initial_position);
9073 strcpy(moveList[0], "");
9074 strcpy(parseList[0], "");
9075 timeRemaining[0][1] = whiteTimeRemaining;
9076 timeRemaining[1][1] = blackTimeRemaining;
9077 if (commentList[0] != NULL) {
9078 commentList[1] = commentList[0];
9079 commentList[0] = NULL;
9082 currentMove = forwardMostMove = backwardMostMove = 0;
9084 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9086 initialRulePlies = FENrulePlies;
9087 epStatus[forwardMostMove] = FENepStatus;
9088 for( i=0; i< nrCastlingRights; i++ )
9089 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9091 yyboardindex = forwardMostMove;
9093 gameInfo.fen = NULL;
9096 yyboardindex = forwardMostMove;
9097 cm = (ChessMove) yylex();
9099 /* Handle comments interspersed among the tags */
9100 while (cm == Comment) {
9102 if (appData.debugMode)
9103 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9105 if (*p == '{' || *p == '[' || *p == '(') {
9106 p[strlen(p) - 1] = NULLCHAR;
9109 while (*p == '\n') p++;
9110 AppendComment(currentMove, p);
9111 yyboardindex = forwardMostMove;
9112 cm = (ChessMove) yylex();
9116 /* don't rely on existence of Event tag since if game was
9117 * pasted from clipboard the Event tag may not exist
9119 if (numPGNTags > 0){
9121 if (gameInfo.variant == VariantNormal) {
9122 gameInfo.variant = StringToVariant(gameInfo.event);
9125 if( appData.autoDisplayTags ) {
9126 tags = PGNTags(&gameInfo);
9127 TagsPopUp(tags, CmailMsg());
9132 /* Make something up, but don't display it now */
9137 if (cm == PositionDiagram) {
9140 Board initial_position;
9142 if (appData.debugMode)
9143 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9145 if (!startedFromSetupPosition) {
9147 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9148 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9158 initial_position[i][j++] = CharToPiece(*p);
9161 while (*p == ' ' || *p == '\t' ||
9162 *p == '\n' || *p == '\r') p++;
9164 if (strncmp(p, "black", strlen("black"))==0)
9165 blackPlaysFirst = TRUE;
9167 blackPlaysFirst = FALSE;
9168 startedFromSetupPosition = TRUE;
9170 CopyBoard(boards[0], initial_position);
9171 if (blackPlaysFirst) {
9172 currentMove = forwardMostMove = backwardMostMove = 1;
9173 CopyBoard(boards[1], initial_position);
9174 strcpy(moveList[0], "");
9175 strcpy(parseList[0], "");
9176 timeRemaining[0][1] = whiteTimeRemaining;
9177 timeRemaining[1][1] = blackTimeRemaining;
9178 if (commentList[0] != NULL) {
9179 commentList[1] = commentList[0];
9180 commentList[0] = NULL;
9183 currentMove = forwardMostMove = backwardMostMove = 0;
9186 yyboardindex = forwardMostMove;
9187 cm = (ChessMove) yylex();
9190 if (first.pr == NoProc) {
9191 StartChessProgram(&first);
9193 InitChessProgram(&first, FALSE);
9194 SendToProgram("force\n", &first);
9195 if (startedFromSetupPosition) {
9196 SendBoard(&first, forwardMostMove);
9197 if (appData.debugMode) {
9198 fprintf(debugFP, "Load Game\n");
9200 DisplayBothClocks();
9203 /* [HGM] server: flag to write setup moves in broadcast file as one */
9204 loadFlag = appData.suppressLoadMoves;
9206 while (cm == Comment) {
9208 if (appData.debugMode)
9209 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9211 if (*p == '{' || *p == '[' || *p == '(') {
9212 p[strlen(p) - 1] = NULLCHAR;
9215 while (*p == '\n') p++;
9216 AppendComment(currentMove, p);
9217 yyboardindex = forwardMostMove;
9218 cm = (ChessMove) yylex();
9221 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9222 cm == WhiteWins || cm == BlackWins ||
9223 cm == GameIsDrawn || cm == GameUnfinished) {
9224 DisplayMessage("", _("No moves in game"));
9225 if (cmailMsgLoaded) {
9226 if (appData.debugMode)
9227 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9231 DrawPosition(FALSE, boards[currentMove]);
9232 DisplayBothClocks();
9233 gameMode = EditGame;
9240 // [HGM] PV info: routine tests if comment empty
9241 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9242 DisplayComment(currentMove - 1, commentList[currentMove]);
9244 if (!matchMode && appData.timeDelay != 0)
9245 DrawPosition(FALSE, boards[currentMove]);
9247 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9248 programStats.ok_to_send = 1;
9251 /* if the first token after the PGN tags is a move
9252 * and not move number 1, retrieve it from the parser
9254 if (cm != MoveNumberOne)
9255 LoadGameOneMove(cm);
9257 /* load the remaining moves from the file */
9258 while (LoadGameOneMove((ChessMove)0)) {
9259 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9260 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9263 /* rewind to the start of the game */
9264 currentMove = backwardMostMove;
9266 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9268 if (oldGameMode == AnalyzeFile ||
9269 oldGameMode == AnalyzeMode) {
9273 if (matchMode || appData.timeDelay == 0) {
9275 gameMode = EditGame;
9277 } else if (appData.timeDelay > 0) {
9281 if (appData.debugMode)
9282 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9284 loadFlag = 0; /* [HGM] true game starts */
9288 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9290 ReloadPosition(offset)
9293 int positionNumber = lastLoadPositionNumber + offset;
9294 if (lastLoadPositionFP == NULL) {
9295 DisplayError(_("No position has been loaded yet"), 0);
9298 if (positionNumber <= 0) {
9299 DisplayError(_("Can't back up any further"), 0);
9302 return LoadPosition(lastLoadPositionFP, positionNumber,
9303 lastLoadPositionTitle);
9306 /* Load the nth position from the given file */
9308 LoadPositionFromFile(filename, n, title)
9316 if (strcmp(filename, "-") == 0) {
9317 return LoadPosition(stdin, n, "stdin");
9319 f = fopen(filename, "rb");
9321 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9322 DisplayError(buf, errno);
9325 return LoadPosition(f, n, title);
9330 /* Load the nth position from the given open file, and close it */
9332 LoadPosition(f, positionNumber, title)
9337 char *p, line[MSG_SIZ];
9338 Board initial_position;
9339 int i, j, fenMode, pn;
9341 if (gameMode == Training )
9342 SetTrainingModeOff();
9344 if (gameMode != BeginningOfGame) {
9347 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9348 fclose(lastLoadPositionFP);
9350 if (positionNumber == 0) positionNumber = 1;
9351 lastLoadPositionFP = f;
9352 lastLoadPositionNumber = positionNumber;
9353 strcpy(lastLoadPositionTitle, title);
9354 if (first.pr == NoProc) {
9355 StartChessProgram(&first);
9356 InitChessProgram(&first, FALSE);
9358 pn = positionNumber;
9359 if (positionNumber < 0) {
9360 /* Negative position number means to seek to that byte offset */
9361 if (fseek(f, -positionNumber, 0) == -1) {
9362 DisplayError(_("Can't seek on position file"), 0);
9367 if (fseek(f, 0, 0) == -1) {
9368 if (f == lastLoadPositionFP ?
9369 positionNumber == lastLoadPositionNumber + 1 :
9370 positionNumber == 1) {
9373 DisplayError(_("Can't seek on position file"), 0);
9378 /* See if this file is FEN or old-style xboard */
9379 if (fgets(line, MSG_SIZ, f) == NULL) {
9380 DisplayError(_("Position not found in file"), 0);
9389 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9390 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9391 case '1': case '2': case '3': case '4': case '5': case '6':
9392 case '7': case '8': case '9':
9393 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9394 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9395 case 'C': case 'W': case 'c': case 'w':
9400 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9401 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9405 if (fenMode || line[0] == '#') pn--;
9407 /* skip positions before number pn */
9408 if (fgets(line, MSG_SIZ, f) == NULL) {
9410 DisplayError(_("Position not found in file"), 0);
9413 if (fenMode || line[0] == '#') pn--;
9418 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9419 DisplayError(_("Bad FEN position in file"), 0);
9423 (void) fgets(line, MSG_SIZ, f);
9424 (void) fgets(line, MSG_SIZ, f);
9426 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9427 (void) fgets(line, MSG_SIZ, f);
9428 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9431 initial_position[i][j++] = CharToPiece(*p);
9435 blackPlaysFirst = FALSE;
9437 (void) fgets(line, MSG_SIZ, f);
9438 if (strncmp(line, "black", strlen("black"))==0)
9439 blackPlaysFirst = TRUE;
9442 startedFromSetupPosition = TRUE;
9444 SendToProgram("force\n", &first);
9445 CopyBoard(boards[0], initial_position);
9446 if (blackPlaysFirst) {
9447 currentMove = forwardMostMove = backwardMostMove = 1;
9448 strcpy(moveList[0], "");
9449 strcpy(parseList[0], "");
9450 CopyBoard(boards[1], initial_position);
9451 DisplayMessage("", _("Black to play"));
9453 currentMove = forwardMostMove = backwardMostMove = 0;
9454 DisplayMessage("", _("White to play"));
9456 /* [HGM] copy FEN attributes as well */
9458 initialRulePlies = FENrulePlies;
9459 epStatus[forwardMostMove] = FENepStatus;
9460 for( i=0; i< nrCastlingRights; i++ )
9461 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9463 SendBoard(&first, forwardMostMove);
9464 if (appData.debugMode) {
9466 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9467 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9468 fprintf(debugFP, "Load Position\n");
9471 if (positionNumber > 1) {
9472 sprintf(line, "%s %d", title, positionNumber);
9475 DisplayTitle(title);
9477 gameMode = EditGame;
9480 timeRemaining[0][1] = whiteTimeRemaining;
9481 timeRemaining[1][1] = blackTimeRemaining;
9482 DrawPosition(FALSE, boards[currentMove]);
9489 CopyPlayerNameIntoFileName(dest, src)
9492 while (*src != NULLCHAR && *src != ',') {
9497 *(*dest)++ = *src++;
9502 char *DefaultFileName(ext)
9505 static char def[MSG_SIZ];
9508 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9510 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9512 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9521 /* Save the current game to the given file */
9523 SaveGameToFile(filename, append)
9530 if (strcmp(filename, "-") == 0) {
9531 return SaveGame(stdout, 0, NULL);
9533 f = fopen(filename, append ? "a" : "w");
9535 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9536 DisplayError(buf, errno);
9539 return SaveGame(f, 0, NULL);
9548 static char buf[MSG_SIZ];
9551 p = strchr(str, ' ');
9552 if (p == NULL) return str;
9553 strncpy(buf, str, p - str);
9554 buf[p - str] = NULLCHAR;
9558 #define PGN_MAX_LINE 75
9560 #define PGN_SIDE_WHITE 0
9561 #define PGN_SIDE_BLACK 1
9564 static int FindFirstMoveOutOfBook( int side )
9568 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9569 int index = backwardMostMove;
9570 int has_book_hit = 0;
9572 if( (index % 2) != side ) {
9576 while( index < forwardMostMove ) {
9577 /* Check to see if engine is in book */
9578 int depth = pvInfoList[index].depth;
9579 int score = pvInfoList[index].score;
9585 else if( score == 0 && depth == 63 ) {
9586 in_book = 1; /* Zappa */
9588 else if( score == 2 && depth == 99 ) {
9589 in_book = 1; /* Abrok */
9592 has_book_hit += in_book;
9608 void GetOutOfBookInfo( char * buf )
9612 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9614 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9615 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9619 if( oob[0] >= 0 || oob[1] >= 0 ) {
9620 for( i=0; i<2; i++ ) {
9624 if( i > 0 && oob[0] >= 0 ) {
9628 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9629 sprintf( buf+strlen(buf), "%s%.2f",
9630 pvInfoList[idx].score >= 0 ? "+" : "",
9631 pvInfoList[idx].score / 100.0 );
9637 /* Save game in PGN style and close the file */
9642 int i, offset, linelen, newblock;
9646 int movelen, numlen, blank;
9647 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9649 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9651 tm = time((time_t *) NULL);
9653 PrintPGNTags(f, &gameInfo);
9655 if (backwardMostMove > 0 || startedFromSetupPosition) {
9656 char *fen = PositionToFEN(backwardMostMove, NULL);
9657 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9658 fprintf(f, "\n{--------------\n");
9659 PrintPosition(f, backwardMostMove);
9660 fprintf(f, "--------------}\n");
9664 /* [AS] Out of book annotation */
9665 if( appData.saveOutOfBookInfo ) {
9668 GetOutOfBookInfo( buf );
9670 if( buf[0] != '\0' ) {
9671 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9678 i = backwardMostMove;
9682 while (i < forwardMostMove) {
9683 /* Print comments preceding this move */
9684 if (commentList[i] != NULL) {
9685 if (linelen > 0) fprintf(f, "\n");
9686 fprintf(f, "{\n%s}\n", commentList[i]);
9691 /* Format move number */
9693 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9696 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9698 numtext[0] = NULLCHAR;
9701 numlen = strlen(numtext);
9704 /* Print move number */
9705 blank = linelen > 0 && numlen > 0;
9706 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9715 fprintf(f, numtext);
9719 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9720 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9722 // SavePart already does this!
9723 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9724 int p = movelen - 1;
9725 if(move_buffer[p] == ' ') p--;
9726 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9727 while(p && move_buffer[--p] != '(');
9728 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9733 blank = linelen > 0 && movelen > 0;
9734 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9743 fprintf(f, move_buffer);
9746 /* [AS] Add PV info if present */
9747 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9748 /* [HGM] add time */
9749 char buf[MSG_SIZ]; int seconds = 0;
9752 if(i >= backwardMostMove) {
9754 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9755 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9757 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9758 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9760 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9762 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9765 if( seconds <= 0) buf[0] = 0; else
9766 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9767 seconds = (seconds + 4)/10; // round to full seconds
9768 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9769 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9772 sprintf( move_buffer, "{%s%.2f/%d%s}",
9773 pvInfoList[i].score >= 0 ? "+" : "",
9774 pvInfoList[i].score / 100.0,
9775 pvInfoList[i].depth,
9778 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9780 /* Print score/depth */
9781 blank = linelen > 0 && movelen > 0;
9782 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9791 fprintf(f, move_buffer);
9798 /* Start a new line */
9799 if (linelen > 0) fprintf(f, "\n");
9801 /* Print comments after last move */
9802 if (commentList[i] != NULL) {
9803 fprintf(f, "{\n%s}\n", commentList[i]);
9807 if (gameInfo.resultDetails != NULL &&
9808 gameInfo.resultDetails[0] != NULLCHAR) {
9809 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9810 PGNResult(gameInfo.result));
9812 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9816 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9820 /* Save game in old style and close the file */
9828 tm = time((time_t *) NULL);
9830 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9833 if (backwardMostMove > 0 || startedFromSetupPosition) {
9834 fprintf(f, "\n[--------------\n");
9835 PrintPosition(f, backwardMostMove);
9836 fprintf(f, "--------------]\n");
9841 i = backwardMostMove;
9842 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9844 while (i < forwardMostMove) {
9845 if (commentList[i] != NULL) {
9846 fprintf(f, "[%s]\n", commentList[i]);
9850 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9853 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9855 if (commentList[i] != NULL) {
9859 if (i >= forwardMostMove) {
9863 fprintf(f, "%s\n", parseList[i]);
9868 if (commentList[i] != NULL) {
9869 fprintf(f, "[%s]\n", commentList[i]);
9872 /* This isn't really the old style, but it's close enough */
9873 if (gameInfo.resultDetails != NULL &&
9874 gameInfo.resultDetails[0] != NULLCHAR) {
9875 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9876 gameInfo.resultDetails);
9878 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9885 /* Save the current game to open file f and close the file */
9887 SaveGame(f, dummy, dummy2)
9892 if (gameMode == EditPosition) EditPositionDone();
9893 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9894 if (appData.oldSaveStyle)
9895 return SaveGameOldStyle(f);
9897 return SaveGamePGN(f);
9900 /* Save the current position to the given file */
9902 SavePositionToFile(filename)
9908 if (strcmp(filename, "-") == 0) {
9909 return SavePosition(stdout, 0, NULL);
9911 f = fopen(filename, "a");
9913 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9914 DisplayError(buf, errno);
9917 SavePosition(f, 0, NULL);
9923 /* Save the current position to the given open file and close the file */
9925 SavePosition(f, dummy, dummy2)
9933 if (appData.oldSaveStyle) {
9934 tm = time((time_t *) NULL);
9936 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9938 fprintf(f, "[--------------\n");
9939 PrintPosition(f, currentMove);
9940 fprintf(f, "--------------]\n");
9942 fen = PositionToFEN(currentMove, NULL);
9943 fprintf(f, "%s\n", fen);
9951 ReloadCmailMsgEvent(unregister)
9955 static char *inFilename = NULL;
9956 static char *outFilename;
9958 struct stat inbuf, outbuf;
9961 /* Any registered moves are unregistered if unregister is set, */
9962 /* i.e. invoked by the signal handler */
9964 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9965 cmailMoveRegistered[i] = FALSE;
9966 if (cmailCommentList[i] != NULL) {
9967 free(cmailCommentList[i]);
9968 cmailCommentList[i] = NULL;
9971 nCmailMovesRegistered = 0;
9974 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9975 cmailResult[i] = CMAIL_NOT_RESULT;
9979 if (inFilename == NULL) {
9980 /* Because the filenames are static they only get malloced once */
9981 /* and they never get freed */
9982 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9983 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9985 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9986 sprintf(outFilename, "%s.out", appData.cmailGameName);
9989 status = stat(outFilename, &outbuf);
9991 cmailMailedMove = FALSE;
9993 status = stat(inFilename, &inbuf);
9994 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9997 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9998 counts the games, notes how each one terminated, etc.
10000 It would be nice to remove this kludge and instead gather all
10001 the information while building the game list. (And to keep it
10002 in the game list nodes instead of having a bunch of fixed-size
10003 parallel arrays.) Note this will require getting each game's
10004 termination from the PGN tags, as the game list builder does
10005 not process the game moves. --mann
10007 cmailMsgLoaded = TRUE;
10008 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10010 /* Load first game in the file or popup game menu */
10011 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10013 #endif /* !WIN32 */
10021 char string[MSG_SIZ];
10023 if ( cmailMailedMove
10024 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10025 return TRUE; /* Allow free viewing */
10028 /* Unregister move to ensure that we don't leave RegisterMove */
10029 /* with the move registered when the conditions for registering no */
10031 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10032 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10033 nCmailMovesRegistered --;
10035 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10037 free(cmailCommentList[lastLoadGameNumber - 1]);
10038 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10042 if (cmailOldMove == -1) {
10043 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10047 if (currentMove > cmailOldMove + 1) {
10048 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10052 if (currentMove < cmailOldMove) {
10053 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10057 if (forwardMostMove > currentMove) {
10058 /* Silently truncate extra moves */
10062 if ( (currentMove == cmailOldMove + 1)
10063 || ( (currentMove == cmailOldMove)
10064 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10065 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10066 if (gameInfo.result != GameUnfinished) {
10067 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10070 if (commentList[currentMove] != NULL) {
10071 cmailCommentList[lastLoadGameNumber - 1]
10072 = StrSave(commentList[currentMove]);
10074 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10076 if (appData.debugMode)
10077 fprintf(debugFP, "Saving %s for game %d\n",
10078 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10081 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10083 f = fopen(string, "w");
10084 if (appData.oldSaveStyle) {
10085 SaveGameOldStyle(f); /* also closes the file */
10087 sprintf(string, "%s.pos.out", appData.cmailGameName);
10088 f = fopen(string, "w");
10089 SavePosition(f, 0, NULL); /* also closes the file */
10091 fprintf(f, "{--------------\n");
10092 PrintPosition(f, currentMove);
10093 fprintf(f, "--------------}\n\n");
10095 SaveGame(f, 0, NULL); /* also closes the file*/
10098 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10099 nCmailMovesRegistered ++;
10100 } else if (nCmailGames == 1) {
10101 DisplayError(_("You have not made a move yet"), 0);
10112 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10113 FILE *commandOutput;
10114 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10115 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10121 if (! cmailMsgLoaded) {
10122 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10126 if (nCmailGames == nCmailResults) {
10127 DisplayError(_("No unfinished games"), 0);
10131 #if CMAIL_PROHIBIT_REMAIL
10132 if (cmailMailedMove) {
10133 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);
10134 DisplayError(msg, 0);
10139 if (! (cmailMailedMove || RegisterMove())) return;
10141 if ( cmailMailedMove
10142 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10143 sprintf(string, partCommandString,
10144 appData.debugMode ? " -v" : "", appData.cmailGameName);
10145 commandOutput = popen(string, "r");
10147 if (commandOutput == NULL) {
10148 DisplayError(_("Failed to invoke cmail"), 0);
10150 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10151 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10153 if (nBuffers > 1) {
10154 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10155 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10156 nBytes = MSG_SIZ - 1;
10158 (void) memcpy(msg, buffer, nBytes);
10160 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10162 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10163 cmailMailedMove = TRUE; /* Prevent >1 moves */
10166 for (i = 0; i < nCmailGames; i ++) {
10167 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10172 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10174 sprintf(buffer, "%s/%s.%s.archive",
10176 appData.cmailGameName,
10178 LoadGameFromFile(buffer, 1, buffer, FALSE);
10179 cmailMsgLoaded = FALSE;
10183 DisplayInformation(msg);
10184 pclose(commandOutput);
10187 if ((*cmailMsg) != '\0') {
10188 DisplayInformation(cmailMsg);
10193 #endif /* !WIN32 */
10202 int prependComma = 0;
10204 char string[MSG_SIZ]; /* Space for game-list */
10207 if (!cmailMsgLoaded) return "";
10209 if (cmailMailedMove) {
10210 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10212 /* Create a list of games left */
10213 sprintf(string, "[");
10214 for (i = 0; i < nCmailGames; i ++) {
10215 if (! ( cmailMoveRegistered[i]
10216 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10217 if (prependComma) {
10218 sprintf(number, ",%d", i + 1);
10220 sprintf(number, "%d", i + 1);
10224 strcat(string, number);
10227 strcat(string, "]");
10229 if (nCmailMovesRegistered + nCmailResults == 0) {
10230 switch (nCmailGames) {
10233 _("Still need to make move for game\n"));
10238 _("Still need to make moves for both games\n"));
10243 _("Still need to make moves for all %d games\n"),
10248 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10251 _("Still need to make a move for game %s\n"),
10256 if (nCmailResults == nCmailGames) {
10257 sprintf(cmailMsg, _("No unfinished games\n"));
10259 sprintf(cmailMsg, _("Ready to send mail\n"));
10265 _("Still need to make moves for games %s\n"),
10277 if (gameMode == Training)
10278 SetTrainingModeOff();
10281 cmailMsgLoaded = FALSE;
10282 if (appData.icsActive) {
10283 SendToICS(ics_prefix);
10284 SendToICS("refresh\n");
10294 /* Give up on clean exit */
10298 /* Keep trying for clean exit */
10302 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10304 if (telnetISR != NULL) {
10305 RemoveInputSource(telnetISR);
10307 if (icsPR != NoProc) {
10308 DestroyChildProcess(icsPR, TRUE);
10311 /* Save game if resource set and not already saved by GameEnds() */
10312 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10313 && forwardMostMove > 0) {
10314 if (*appData.saveGameFile != NULLCHAR) {
10315 SaveGameToFile(appData.saveGameFile, TRUE);
10316 } else if (appData.autoSaveGames) {
10319 if (*appData.savePositionFile != NULLCHAR) {
10320 SavePositionToFile(appData.savePositionFile);
10323 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10325 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10326 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10328 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10329 /* make sure this other one finishes before killing it! */
10330 if(endingGame) { int count = 0;
10331 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10332 while(endingGame && count++ < 10) DoSleep(1);
10333 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10336 /* Kill off chess programs */
10337 if (first.pr != NoProc) {
10340 DoSleep( appData.delayBeforeQuit );
10341 SendToProgram("quit\n", &first);
10342 DoSleep( appData.delayAfterQuit );
10343 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10345 if (second.pr != NoProc) {
10346 DoSleep( appData.delayBeforeQuit );
10347 SendToProgram("quit\n", &second);
10348 DoSleep( appData.delayAfterQuit );
10349 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10351 if (first.isr != NULL) {
10352 RemoveInputSource(first.isr);
10354 if (second.isr != NULL) {
10355 RemoveInputSource(second.isr);
10358 ShutDownFrontEnd();
10365 if (appData.debugMode)
10366 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10370 if (gameMode == MachinePlaysWhite ||
10371 gameMode == MachinePlaysBlack) {
10374 DisplayBothClocks();
10376 if (gameMode == PlayFromGameFile) {
10377 if (appData.timeDelay >= 0)
10378 AutoPlayGameLoop();
10379 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10380 Reset(FALSE, TRUE);
10381 SendToICS(ics_prefix);
10382 SendToICS("refresh\n");
10383 } else if (currentMove < forwardMostMove) {
10384 ForwardInner(forwardMostMove);
10386 pauseExamInvalid = FALSE;
10388 switch (gameMode) {
10392 pauseExamForwardMostMove = forwardMostMove;
10393 pauseExamInvalid = FALSE;
10396 case IcsPlayingWhite:
10397 case IcsPlayingBlack:
10401 case PlayFromGameFile:
10402 (void) StopLoadGameTimer();
10406 case BeginningOfGame:
10407 if (appData.icsActive) return;
10408 /* else fall through */
10409 case MachinePlaysWhite:
10410 case MachinePlaysBlack:
10411 case TwoMachinesPlay:
10412 if (forwardMostMove == 0)
10413 return; /* don't pause if no one has moved */
10414 if ((gameMode == MachinePlaysWhite &&
10415 !WhiteOnMove(forwardMostMove)) ||
10416 (gameMode == MachinePlaysBlack &&
10417 WhiteOnMove(forwardMostMove))) {
10430 char title[MSG_SIZ];
10432 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10433 strcpy(title, _("Edit comment"));
10435 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10436 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10437 parseList[currentMove - 1]);
10440 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10447 char *tags = PGNTags(&gameInfo);
10448 EditTagsPopUp(tags);
10455 if (appData.noChessProgram || gameMode == AnalyzeMode)
10458 if (gameMode != AnalyzeFile) {
10459 if (!appData.icsEngineAnalyze) {
10461 if (gameMode != EditGame) return;
10463 ResurrectChessProgram();
10464 SendToProgram("analyze\n", &first);
10465 first.analyzing = TRUE;
10466 /*first.maybeThinking = TRUE;*/
10467 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10468 AnalysisPopUp(_("Analysis"),
10469 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10471 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10476 StartAnalysisClock();
10477 GetTimeMark(&lastNodeCountTime);
10484 if (appData.noChessProgram || gameMode == AnalyzeFile)
10487 if (gameMode != AnalyzeMode) {
10489 if (gameMode != EditGame) return;
10490 ResurrectChessProgram();
10491 SendToProgram("analyze\n", &first);
10492 first.analyzing = TRUE;
10493 /*first.maybeThinking = TRUE;*/
10494 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10495 AnalysisPopUp(_("Analysis"),
10496 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10498 gameMode = AnalyzeFile;
10503 StartAnalysisClock();
10504 GetTimeMark(&lastNodeCountTime);
10509 MachineWhiteEvent()
10512 char *bookHit = NULL;
10514 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10518 if (gameMode == PlayFromGameFile ||
10519 gameMode == TwoMachinesPlay ||
10520 gameMode == Training ||
10521 gameMode == AnalyzeMode ||
10522 gameMode == EndOfGame)
10525 if (gameMode == EditPosition)
10526 EditPositionDone();
10528 if (!WhiteOnMove(currentMove)) {
10529 DisplayError(_("It is not White's turn"), 0);
10533 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10536 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10537 gameMode == AnalyzeFile)
10540 ResurrectChessProgram(); /* in case it isn't running */
10541 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10542 gameMode = MachinePlaysWhite;
10545 gameMode = MachinePlaysWhite;
10549 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10551 if (first.sendName) {
10552 sprintf(buf, "name %s\n", gameInfo.black);
10553 SendToProgram(buf, &first);
10555 if (first.sendTime) {
10556 if (first.useColors) {
10557 SendToProgram("black\n", &first); /*gnu kludge*/
10559 SendTimeRemaining(&first, TRUE);
10561 if (first.useColors) {
10562 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10564 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10565 SetMachineThinkingEnables();
10566 first.maybeThinking = TRUE;
10570 if (appData.autoFlipView && !flipView) {
10571 flipView = !flipView;
10572 DrawPosition(FALSE, NULL);
10573 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10576 if(bookHit) { // [HGM] book: simulate book reply
10577 static char bookMove[MSG_SIZ]; // a bit generous?
10579 programStats.nodes = programStats.depth = programStats.time =
10580 programStats.score = programStats.got_only_move = 0;
10581 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10583 strcpy(bookMove, "move ");
10584 strcat(bookMove, bookHit);
10585 HandleMachineMove(bookMove, &first);
10590 MachineBlackEvent()
10593 char *bookHit = NULL;
10595 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10599 if (gameMode == PlayFromGameFile ||
10600 gameMode == TwoMachinesPlay ||
10601 gameMode == Training ||
10602 gameMode == AnalyzeMode ||
10603 gameMode == EndOfGame)
10606 if (gameMode == EditPosition)
10607 EditPositionDone();
10609 if (WhiteOnMove(currentMove)) {
10610 DisplayError(_("It is not Black's turn"), 0);
10614 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10617 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10618 gameMode == AnalyzeFile)
10621 ResurrectChessProgram(); /* in case it isn't running */
10622 gameMode = MachinePlaysBlack;
10626 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10628 if (first.sendName) {
10629 sprintf(buf, "name %s\n", gameInfo.white);
10630 SendToProgram(buf, &first);
10632 if (first.sendTime) {
10633 if (first.useColors) {
10634 SendToProgram("white\n", &first); /*gnu kludge*/
10636 SendTimeRemaining(&first, FALSE);
10638 if (first.useColors) {
10639 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10641 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10642 SetMachineThinkingEnables();
10643 first.maybeThinking = TRUE;
10646 if (appData.autoFlipView && flipView) {
10647 flipView = !flipView;
10648 DrawPosition(FALSE, NULL);
10649 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10651 if(bookHit) { // [HGM] book: simulate book reply
10652 static char bookMove[MSG_SIZ]; // a bit generous?
10654 programStats.nodes = programStats.depth = programStats.time =
10655 programStats.score = programStats.got_only_move = 0;
10656 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10658 strcpy(bookMove, "move ");
10659 strcat(bookMove, bookHit);
10660 HandleMachineMove(bookMove, &first);
10666 DisplayTwoMachinesTitle()
10669 if (appData.matchGames > 0) {
10670 if (first.twoMachinesColor[0] == 'w') {
10671 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10672 gameInfo.white, gameInfo.black,
10673 first.matchWins, second.matchWins,
10674 matchGame - 1 - (first.matchWins + second.matchWins));
10676 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10677 gameInfo.white, gameInfo.black,
10678 second.matchWins, first.matchWins,
10679 matchGame - 1 - (first.matchWins + second.matchWins));
10682 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10688 TwoMachinesEvent P((void))
10692 ChessProgramState *onmove;
10693 char *bookHit = NULL;
10695 if (appData.noChessProgram) return;
10697 switch (gameMode) {
10698 case TwoMachinesPlay:
10700 case MachinePlaysWhite:
10701 case MachinePlaysBlack:
10702 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10703 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10707 case BeginningOfGame:
10708 case PlayFromGameFile:
10711 if (gameMode != EditGame) return;
10714 EditPositionDone();
10725 forwardMostMove = currentMove;
10726 ResurrectChessProgram(); /* in case first program isn't running */
10728 if (second.pr == NULL) {
10729 StartChessProgram(&second);
10730 if (second.protocolVersion == 1) {
10731 TwoMachinesEventIfReady();
10733 /* kludge: allow timeout for initial "feature" command */
10735 DisplayMessage("", _("Starting second chess program"));
10736 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10740 DisplayMessage("", "");
10741 InitChessProgram(&second, FALSE);
10742 SendToProgram("force\n", &second);
10743 if (startedFromSetupPosition) {
10744 SendBoard(&second, backwardMostMove);
10745 if (appData.debugMode) {
10746 fprintf(debugFP, "Two Machines\n");
10749 for (i = backwardMostMove; i < forwardMostMove; i++) {
10750 SendMoveToProgram(i, &second);
10753 gameMode = TwoMachinesPlay;
10757 DisplayTwoMachinesTitle();
10759 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10765 SendToProgram(first.computerString, &first);
10766 if (first.sendName) {
10767 sprintf(buf, "name %s\n", second.tidy);
10768 SendToProgram(buf, &first);
10770 SendToProgram(second.computerString, &second);
10771 if (second.sendName) {
10772 sprintf(buf, "name %s\n", first.tidy);
10773 SendToProgram(buf, &second);
10777 if (!first.sendTime || !second.sendTime) {
10778 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10779 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10781 if (onmove->sendTime) {
10782 if (onmove->useColors) {
10783 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10785 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10787 if (onmove->useColors) {
10788 SendToProgram(onmove->twoMachinesColor, onmove);
10790 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10791 // SendToProgram("go\n", onmove);
10792 onmove->maybeThinking = TRUE;
10793 SetMachineThinkingEnables();
10797 if(bookHit) { // [HGM] book: simulate book reply
10798 static char bookMove[MSG_SIZ]; // a bit generous?
10800 programStats.nodes = programStats.depth = programStats.time =
10801 programStats.score = programStats.got_only_move = 0;
10802 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10804 strcpy(bookMove, "move ");
10805 strcat(bookMove, bookHit);
10806 HandleMachineMove(bookMove, &first);
10813 if (gameMode == Training) {
10814 SetTrainingModeOff();
10815 gameMode = PlayFromGameFile;
10816 DisplayMessage("", _("Training mode off"));
10818 gameMode = Training;
10819 animateTraining = appData.animate;
10821 /* make sure we are not already at the end of the game */
10822 if (currentMove < forwardMostMove) {
10823 SetTrainingModeOn();
10824 DisplayMessage("", _("Training mode on"));
10826 gameMode = PlayFromGameFile;
10827 DisplayError(_("Already at end of game"), 0);
10836 if (!appData.icsActive) return;
10837 switch (gameMode) {
10838 case IcsPlayingWhite:
10839 case IcsPlayingBlack:
10842 case BeginningOfGame:
10850 EditPositionDone();
10863 gameMode = IcsIdle;
10874 switch (gameMode) {
10876 SetTrainingModeOff();
10878 case MachinePlaysWhite:
10879 case MachinePlaysBlack:
10880 case BeginningOfGame:
10881 SendToProgram("force\n", &first);
10882 SetUserThinkingEnables();
10884 case PlayFromGameFile:
10885 (void) StopLoadGameTimer();
10886 if (gameFileFP != NULL) {
10891 EditPositionDone();
10896 SendToProgram("force\n", &first);
10898 case TwoMachinesPlay:
10899 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10900 ResurrectChessProgram();
10901 SetUserThinkingEnables();
10904 ResurrectChessProgram();
10906 case IcsPlayingBlack:
10907 case IcsPlayingWhite:
10908 DisplayError(_("Warning: You are still playing a game"), 0);
10911 DisplayError(_("Warning: You are still observing a game"), 0);
10914 DisplayError(_("Warning: You are still examining a game"), 0);
10925 first.offeredDraw = second.offeredDraw = 0;
10927 if (gameMode == PlayFromGameFile) {
10928 whiteTimeRemaining = timeRemaining[0][currentMove];
10929 blackTimeRemaining = timeRemaining[1][currentMove];
10933 if (gameMode == MachinePlaysWhite ||
10934 gameMode == MachinePlaysBlack ||
10935 gameMode == TwoMachinesPlay ||
10936 gameMode == EndOfGame) {
10937 i = forwardMostMove;
10938 while (i > currentMove) {
10939 SendToProgram("undo\n", &first);
10942 whiteTimeRemaining = timeRemaining[0][currentMove];
10943 blackTimeRemaining = timeRemaining[1][currentMove];
10944 DisplayBothClocks();
10945 if (whiteFlag || blackFlag) {
10946 whiteFlag = blackFlag = 0;
10951 gameMode = EditGame;
10958 EditPositionEvent()
10960 if (gameMode == EditPosition) {
10966 if (gameMode != EditGame) return;
10968 gameMode = EditPosition;
10971 if (currentMove > 0)
10972 CopyBoard(boards[0], boards[currentMove]);
10974 blackPlaysFirst = !WhiteOnMove(currentMove);
10976 currentMove = forwardMostMove = backwardMostMove = 0;
10977 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10984 /* [DM] icsEngineAnalyze - possible call from other functions */
10985 if (appData.icsEngineAnalyze) {
10986 appData.icsEngineAnalyze = FALSE;
10988 DisplayMessage("",_("Close ICS engine analyze..."));
10990 if (first.analysisSupport && first.analyzing) {
10991 SendToProgram("exit\n", &first);
10992 first.analyzing = FALSE;
10995 thinkOutput[0] = NULLCHAR;
11001 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11003 startedFromSetupPosition = TRUE;
11004 InitChessProgram(&first, FALSE);
11005 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11006 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11007 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11008 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11009 } else castlingRights[0][2] = -1;
11010 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11011 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11012 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11013 } else castlingRights[0][5] = -1;
11014 SendToProgram("force\n", &first);
11015 if (blackPlaysFirst) {
11016 strcpy(moveList[0], "");
11017 strcpy(parseList[0], "");
11018 currentMove = forwardMostMove = backwardMostMove = 1;
11019 CopyBoard(boards[1], boards[0]);
11020 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11022 epStatus[1] = epStatus[0];
11023 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11026 currentMove = forwardMostMove = backwardMostMove = 0;
11028 SendBoard(&first, forwardMostMove);
11029 if (appData.debugMode) {
11030 fprintf(debugFP, "EditPosDone\n");
11033 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11034 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11035 gameMode = EditGame;
11037 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11038 ClearHighlights(); /* [AS] */
11041 /* Pause for `ms' milliseconds */
11042 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11052 } while (SubtractTimeMarks(&m2, &m1) < ms);
11055 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11057 SendMultiLineToICS(buf)
11060 char temp[MSG_SIZ+1], *p;
11067 strncpy(temp, buf, len);
11072 if (*p == '\n' || *p == '\r')
11077 strcat(temp, "\n");
11079 SendToPlayer(temp, strlen(temp));
11083 SetWhiteToPlayEvent()
11085 if (gameMode == EditPosition) {
11086 blackPlaysFirst = FALSE;
11087 DisplayBothClocks(); /* works because currentMove is 0 */
11088 } else if (gameMode == IcsExamining) {
11089 SendToICS(ics_prefix);
11090 SendToICS("tomove white\n");
11095 SetBlackToPlayEvent()
11097 if (gameMode == EditPosition) {
11098 blackPlaysFirst = TRUE;
11099 currentMove = 1; /* kludge */
11100 DisplayBothClocks();
11102 } else if (gameMode == IcsExamining) {
11103 SendToICS(ics_prefix);
11104 SendToICS("tomove black\n");
11109 EditPositionMenuEvent(selection, x, y)
11110 ChessSquare selection;
11114 ChessSquare piece = boards[0][y][x];
11116 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11118 switch (selection) {
11120 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11121 SendToICS(ics_prefix);
11122 SendToICS("bsetup clear\n");
11123 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11124 SendToICS(ics_prefix);
11125 SendToICS("clearboard\n");
11127 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11128 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11129 for (y = 0; y < BOARD_HEIGHT; y++) {
11130 if (gameMode == IcsExamining) {
11131 if (boards[currentMove][y][x] != EmptySquare) {
11132 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11137 boards[0][y][x] = p;
11142 if (gameMode == EditPosition) {
11143 DrawPosition(FALSE, boards[0]);
11148 SetWhiteToPlayEvent();
11152 SetBlackToPlayEvent();
11156 if (gameMode == IcsExamining) {
11157 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11160 boards[0][y][x] = EmptySquare;
11161 DrawPosition(FALSE, boards[0]);
11166 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11167 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11168 selection = (ChessSquare) (PROMOTED piece);
11169 } else if(piece == EmptySquare) selection = WhiteSilver;
11170 else selection = (ChessSquare)((int)piece - 1);
11174 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11175 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11176 selection = (ChessSquare) (DEMOTED piece);
11177 } else if(piece == EmptySquare) selection = BlackSilver;
11178 else selection = (ChessSquare)((int)piece + 1);
11183 if(gameInfo.variant == VariantShatranj ||
11184 gameInfo.variant == VariantXiangqi ||
11185 gameInfo.variant == VariantCourier )
11186 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11191 if(gameInfo.variant == VariantXiangqi)
11192 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11193 if(gameInfo.variant == VariantKnightmate)
11194 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11197 if (gameMode == IcsExamining) {
11198 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11199 PieceToChar(selection), AAA + x, ONE + y);
11202 boards[0][y][x] = selection;
11203 DrawPosition(FALSE, boards[0]);
11211 DropMenuEvent(selection, x, y)
11212 ChessSquare selection;
11215 ChessMove moveType;
11217 switch (gameMode) {
11218 case IcsPlayingWhite:
11219 case MachinePlaysBlack:
11220 if (!WhiteOnMove(currentMove)) {
11221 DisplayMoveError(_("It is Black's turn"));
11224 moveType = WhiteDrop;
11226 case IcsPlayingBlack:
11227 case MachinePlaysWhite:
11228 if (WhiteOnMove(currentMove)) {
11229 DisplayMoveError(_("It is White's turn"));
11232 moveType = BlackDrop;
11235 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11241 if (moveType == BlackDrop && selection < BlackPawn) {
11242 selection = (ChessSquare) ((int) selection
11243 + (int) BlackPawn - (int) WhitePawn);
11245 if (boards[currentMove][y][x] != EmptySquare) {
11246 DisplayMoveError(_("That square is occupied"));
11250 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11256 /* Accept a pending offer of any kind from opponent */
11258 if (appData.icsActive) {
11259 SendToICS(ics_prefix);
11260 SendToICS("accept\n");
11261 } else if (cmailMsgLoaded) {
11262 if (currentMove == cmailOldMove &&
11263 commentList[cmailOldMove] != NULL &&
11264 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11265 "Black offers a draw" : "White offers a draw")) {
11267 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11268 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11270 DisplayError(_("There is no pending offer on this move"), 0);
11271 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11274 /* Not used for offers from chess program */
11281 /* Decline a pending offer of any kind from opponent */
11283 if (appData.icsActive) {
11284 SendToICS(ics_prefix);
11285 SendToICS("decline\n");
11286 } else if (cmailMsgLoaded) {
11287 if (currentMove == cmailOldMove &&
11288 commentList[cmailOldMove] != NULL &&
11289 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11290 "Black offers a draw" : "White offers a draw")) {
11292 AppendComment(cmailOldMove, "Draw declined");
11293 DisplayComment(cmailOldMove - 1, "Draw declined");
11296 DisplayError(_("There is no pending offer on this move"), 0);
11299 /* Not used for offers from chess program */
11306 /* Issue ICS rematch command */
11307 if (appData.icsActive) {
11308 SendToICS(ics_prefix);
11309 SendToICS("rematch\n");
11316 /* Call your opponent's flag (claim a win on time) */
11317 if (appData.icsActive) {
11318 SendToICS(ics_prefix);
11319 SendToICS("flag\n");
11321 switch (gameMode) {
11324 case MachinePlaysWhite:
11327 GameEnds(GameIsDrawn, "Both players ran out of time",
11330 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11332 DisplayError(_("Your opponent is not out of time"), 0);
11335 case MachinePlaysBlack:
11338 GameEnds(GameIsDrawn, "Both players ran out of time",
11341 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11343 DisplayError(_("Your opponent is not out of time"), 0);
11353 /* Offer draw or accept pending draw offer from opponent */
11355 if (appData.icsActive) {
11356 /* Note: tournament rules require draw offers to be
11357 made after you make your move but before you punch
11358 your clock. Currently ICS doesn't let you do that;
11359 instead, you immediately punch your clock after making
11360 a move, but you can offer a draw at any time. */
11362 SendToICS(ics_prefix);
11363 SendToICS("draw\n");
11364 } else if (cmailMsgLoaded) {
11365 if (currentMove == cmailOldMove &&
11366 commentList[cmailOldMove] != NULL &&
11367 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11368 "Black offers a draw" : "White offers a draw")) {
11369 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11370 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11371 } else if (currentMove == cmailOldMove + 1) {
11372 char *offer = WhiteOnMove(cmailOldMove) ?
11373 "White offers a draw" : "Black offers a draw";
11374 AppendComment(currentMove, offer);
11375 DisplayComment(currentMove - 1, offer);
11376 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11378 DisplayError(_("You must make your move before offering a draw"), 0);
11379 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11381 } else if (first.offeredDraw) {
11382 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11384 if (first.sendDrawOffers) {
11385 SendToProgram("draw\n", &first);
11386 userOfferedDraw = TRUE;
11394 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11396 if (appData.icsActive) {
11397 SendToICS(ics_prefix);
11398 SendToICS("adjourn\n");
11400 /* Currently GNU Chess doesn't offer or accept Adjourns */
11408 /* Offer Abort or accept pending Abort offer from opponent */
11410 if (appData.icsActive) {
11411 SendToICS(ics_prefix);
11412 SendToICS("abort\n");
11414 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11421 /* Resign. You can do this even if it's not your turn. */
11423 if (appData.icsActive) {
11424 SendToICS(ics_prefix);
11425 SendToICS("resign\n");
11427 switch (gameMode) {
11428 case MachinePlaysWhite:
11429 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11431 case MachinePlaysBlack:
11432 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11435 if (cmailMsgLoaded) {
11437 if (WhiteOnMove(cmailOldMove)) {
11438 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11440 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11442 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11453 StopObservingEvent()
11455 /* Stop observing current games */
11456 SendToICS(ics_prefix);
11457 SendToICS("unobserve\n");
11461 StopExaminingEvent()
11463 /* Stop observing current game */
11464 SendToICS(ics_prefix);
11465 SendToICS("unexamine\n");
11469 ForwardInner(target)
11474 if (appData.debugMode)
11475 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11476 target, currentMove, forwardMostMove);
11478 if (gameMode == EditPosition)
11481 if (gameMode == PlayFromGameFile && !pausing)
11484 if (gameMode == IcsExamining && pausing)
11485 limit = pauseExamForwardMostMove;
11487 limit = forwardMostMove;
11489 if (target > limit) target = limit;
11491 if (target > 0 && moveList[target - 1][0]) {
11492 int fromX, fromY, toX, toY;
11493 toX = moveList[target - 1][2] - AAA;
11494 toY = moveList[target - 1][3] - ONE;
11495 if (moveList[target - 1][1] == '@') {
11496 if (appData.highlightLastMove) {
11497 SetHighlights(-1, -1, toX, toY);
11500 fromX = moveList[target - 1][0] - AAA;
11501 fromY = moveList[target - 1][1] - ONE;
11502 if (target == currentMove + 1) {
11503 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11505 if (appData.highlightLastMove) {
11506 SetHighlights(fromX, fromY, toX, toY);
11510 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11511 gameMode == Training || gameMode == PlayFromGameFile ||
11512 gameMode == AnalyzeFile) {
11513 while (currentMove < target) {
11514 SendMoveToProgram(currentMove++, &first);
11517 currentMove = target;
11520 if (gameMode == EditGame || gameMode == EndOfGame) {
11521 whiteTimeRemaining = timeRemaining[0][currentMove];
11522 blackTimeRemaining = timeRemaining[1][currentMove];
11524 DisplayBothClocks();
11525 DisplayMove(currentMove - 1);
11526 DrawPosition(FALSE, boards[currentMove]);
11527 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11528 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11529 DisplayComment(currentMove - 1, commentList[currentMove]);
11537 if (gameMode == IcsExamining && !pausing) {
11538 SendToICS(ics_prefix);
11539 SendToICS("forward\n");
11541 ForwardInner(currentMove + 1);
11548 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11549 /* to optimze, we temporarily turn off analysis mode while we feed
11550 * the remaining moves to the engine. Otherwise we get analysis output
11553 if (first.analysisSupport) {
11554 SendToProgram("exit\nforce\n", &first);
11555 first.analyzing = FALSE;
11559 if (gameMode == IcsExamining && !pausing) {
11560 SendToICS(ics_prefix);
11561 SendToICS("forward 999999\n");
11563 ForwardInner(forwardMostMove);
11566 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11567 /* we have fed all the moves, so reactivate analysis mode */
11568 SendToProgram("analyze\n", &first);
11569 first.analyzing = TRUE;
11570 /*first.maybeThinking = TRUE;*/
11571 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11576 BackwardInner(target)
11579 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11581 if (appData.debugMode)
11582 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11583 target, currentMove, forwardMostMove);
11585 if (gameMode == EditPosition) return;
11586 if (currentMove <= backwardMostMove) {
11588 DrawPosition(full_redraw, boards[currentMove]);
11591 if (gameMode == PlayFromGameFile && !pausing)
11594 if (moveList[target][0]) {
11595 int fromX, fromY, toX, toY;
11596 toX = moveList[target][2] - AAA;
11597 toY = moveList[target][3] - ONE;
11598 if (moveList[target][1] == '@') {
11599 if (appData.highlightLastMove) {
11600 SetHighlights(-1, -1, toX, toY);
11603 fromX = moveList[target][0] - AAA;
11604 fromY = moveList[target][1] - ONE;
11605 if (target == currentMove - 1) {
11606 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11608 if (appData.highlightLastMove) {
11609 SetHighlights(fromX, fromY, toX, toY);
11613 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11614 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11615 while (currentMove > target) {
11616 SendToProgram("undo\n", &first);
11620 currentMove = target;
11623 if (gameMode == EditGame || gameMode == EndOfGame) {
11624 whiteTimeRemaining = timeRemaining[0][currentMove];
11625 blackTimeRemaining = timeRemaining[1][currentMove];
11627 DisplayBothClocks();
11628 DisplayMove(currentMove - 1);
11629 DrawPosition(full_redraw, boards[currentMove]);
11630 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11631 // [HGM] PV info: routine tests if comment empty
11632 DisplayComment(currentMove - 1, commentList[currentMove]);
11638 if (gameMode == IcsExamining && !pausing) {
11639 SendToICS(ics_prefix);
11640 SendToICS("backward\n");
11642 BackwardInner(currentMove - 1);
11649 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11650 /* to optimze, we temporarily turn off analysis mode while we undo
11651 * all the moves. Otherwise we get analysis output after each undo.
11653 if (first.analysisSupport) {
11654 SendToProgram("exit\nforce\n", &first);
11655 first.analyzing = FALSE;
11659 if (gameMode == IcsExamining && !pausing) {
11660 SendToICS(ics_prefix);
11661 SendToICS("backward 999999\n");
11663 BackwardInner(backwardMostMove);
11666 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11667 /* we have fed all the moves, so reactivate analysis mode */
11668 SendToProgram("analyze\n", &first);
11669 first.analyzing = TRUE;
11670 /*first.maybeThinking = TRUE;*/
11671 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11678 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11679 if (to >= forwardMostMove) to = forwardMostMove;
11680 if (to <= backwardMostMove) to = backwardMostMove;
11681 if (to < currentMove) {
11691 if (gameMode != IcsExamining) {
11692 DisplayError(_("You are not examining a game"), 0);
11696 DisplayError(_("You can't revert while pausing"), 0);
11699 SendToICS(ics_prefix);
11700 SendToICS("revert\n");
11706 switch (gameMode) {
11707 case MachinePlaysWhite:
11708 case MachinePlaysBlack:
11709 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11710 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11713 if (forwardMostMove < 2) return;
11714 currentMove = forwardMostMove = forwardMostMove - 2;
11715 whiteTimeRemaining = timeRemaining[0][currentMove];
11716 blackTimeRemaining = timeRemaining[1][currentMove];
11717 DisplayBothClocks();
11718 DisplayMove(currentMove - 1);
11719 ClearHighlights();/*!! could figure this out*/
11720 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11721 SendToProgram("remove\n", &first);
11722 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11725 case BeginningOfGame:
11729 case IcsPlayingWhite:
11730 case IcsPlayingBlack:
11731 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11732 SendToICS(ics_prefix);
11733 SendToICS("takeback 2\n");
11735 SendToICS(ics_prefix);
11736 SendToICS("takeback 1\n");
11745 ChessProgramState *cps;
11747 switch (gameMode) {
11748 case MachinePlaysWhite:
11749 if (!WhiteOnMove(forwardMostMove)) {
11750 DisplayError(_("It is your turn"), 0);
11755 case MachinePlaysBlack:
11756 if (WhiteOnMove(forwardMostMove)) {
11757 DisplayError(_("It is your turn"), 0);
11762 case TwoMachinesPlay:
11763 if (WhiteOnMove(forwardMostMove) ==
11764 (first.twoMachinesColor[0] == 'w')) {
11770 case BeginningOfGame:
11774 SendToProgram("?\n", cps);
11778 TruncateGameEvent()
11781 if (gameMode != EditGame) return;
11788 if (forwardMostMove > currentMove) {
11789 if (gameInfo.resultDetails != NULL) {
11790 free(gameInfo.resultDetails);
11791 gameInfo.resultDetails = NULL;
11792 gameInfo.result = GameUnfinished;
11794 forwardMostMove = currentMove;
11795 HistorySet(parseList, backwardMostMove, forwardMostMove,
11803 if (appData.noChessProgram) return;
11804 switch (gameMode) {
11805 case MachinePlaysWhite:
11806 if (WhiteOnMove(forwardMostMove)) {
11807 DisplayError(_("Wait until your turn"), 0);
11811 case BeginningOfGame:
11812 case MachinePlaysBlack:
11813 if (!WhiteOnMove(forwardMostMove)) {
11814 DisplayError(_("Wait until your turn"), 0);
11819 DisplayError(_("No hint available"), 0);
11822 SendToProgram("hint\n", &first);
11823 hintRequested = TRUE;
11829 if (appData.noChessProgram) return;
11830 switch (gameMode) {
11831 case MachinePlaysWhite:
11832 if (WhiteOnMove(forwardMostMove)) {
11833 DisplayError(_("Wait until your turn"), 0);
11837 case BeginningOfGame:
11838 case MachinePlaysBlack:
11839 if (!WhiteOnMove(forwardMostMove)) {
11840 DisplayError(_("Wait until your turn"), 0);
11845 EditPositionDone();
11847 case TwoMachinesPlay:
11852 SendToProgram("bk\n", &first);
11853 bookOutput[0] = NULLCHAR;
11854 bookRequested = TRUE;
11860 char *tags = PGNTags(&gameInfo);
11861 TagsPopUp(tags, CmailMsg());
11865 /* end button procedures */
11868 PrintPosition(fp, move)
11874 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11875 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11876 char c = PieceToChar(boards[move][i][j]);
11877 fputc(c == 'x' ? '.' : c, fp);
11878 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11881 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11882 fprintf(fp, "white to play\n");
11884 fprintf(fp, "black to play\n");
11891 if (gameInfo.white != NULL) {
11892 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11898 /* Find last component of program's own name, using some heuristics */
11900 TidyProgramName(prog, host, buf)
11901 char *prog, *host, buf[MSG_SIZ];
11904 int local = (strcmp(host, "localhost") == 0);
11905 while (!local && (p = strchr(prog, ';')) != NULL) {
11907 while (*p == ' ') p++;
11910 if (*prog == '"' || *prog == '\'') {
11911 q = strchr(prog + 1, *prog);
11913 q = strchr(prog, ' ');
11915 if (q == NULL) q = prog + strlen(prog);
11917 while (p >= prog && *p != '/' && *p != '\\') p--;
11919 if(p == prog && *p == '"') p++;
11920 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11921 memcpy(buf, p, q - p);
11922 buf[q - p] = NULLCHAR;
11930 TimeControlTagValue()
11933 if (!appData.clockMode) {
11935 } else if (movesPerSession > 0) {
11936 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11937 } else if (timeIncrement == 0) {
11938 sprintf(buf, "%ld", timeControl/1000);
11940 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11942 return StrSave(buf);
11948 /* This routine is used only for certain modes */
11949 VariantClass v = gameInfo.variant;
11950 ClearGameInfo(&gameInfo);
11951 gameInfo.variant = v;
11953 switch (gameMode) {
11954 case MachinePlaysWhite:
11955 gameInfo.event = StrSave( appData.pgnEventHeader );
11956 gameInfo.site = StrSave(HostName());
11957 gameInfo.date = PGNDate();
11958 gameInfo.round = StrSave("-");
11959 gameInfo.white = StrSave(first.tidy);
11960 gameInfo.black = StrSave(UserName());
11961 gameInfo.timeControl = TimeControlTagValue();
11964 case MachinePlaysBlack:
11965 gameInfo.event = StrSave( appData.pgnEventHeader );
11966 gameInfo.site = StrSave(HostName());
11967 gameInfo.date = PGNDate();
11968 gameInfo.round = StrSave("-");
11969 gameInfo.white = StrSave(UserName());
11970 gameInfo.black = StrSave(first.tidy);
11971 gameInfo.timeControl = TimeControlTagValue();
11974 case TwoMachinesPlay:
11975 gameInfo.event = StrSave( appData.pgnEventHeader );
11976 gameInfo.site = StrSave(HostName());
11977 gameInfo.date = PGNDate();
11978 if (matchGame > 0) {
11980 sprintf(buf, "%d", matchGame);
11981 gameInfo.round = StrSave(buf);
11983 gameInfo.round = StrSave("-");
11985 if (first.twoMachinesColor[0] == 'w') {
11986 gameInfo.white = StrSave(first.tidy);
11987 gameInfo.black = StrSave(second.tidy);
11989 gameInfo.white = StrSave(second.tidy);
11990 gameInfo.black = StrSave(first.tidy);
11992 gameInfo.timeControl = TimeControlTagValue();
11996 gameInfo.event = StrSave("Edited game");
11997 gameInfo.site = StrSave(HostName());
11998 gameInfo.date = PGNDate();
11999 gameInfo.round = StrSave("-");
12000 gameInfo.white = StrSave("-");
12001 gameInfo.black = StrSave("-");
12005 gameInfo.event = StrSave("Edited position");
12006 gameInfo.site = StrSave(HostName());
12007 gameInfo.date = PGNDate();
12008 gameInfo.round = StrSave("-");
12009 gameInfo.white = StrSave("-");
12010 gameInfo.black = StrSave("-");
12013 case IcsPlayingWhite:
12014 case IcsPlayingBlack:
12019 case PlayFromGameFile:
12020 gameInfo.event = StrSave("Game from non-PGN file");
12021 gameInfo.site = StrSave(HostName());
12022 gameInfo.date = PGNDate();
12023 gameInfo.round = StrSave("-");
12024 gameInfo.white = StrSave("?");
12025 gameInfo.black = StrSave("?");
12034 ReplaceComment(index, text)
12040 while (*text == '\n') text++;
12041 len = strlen(text);
12042 while (len > 0 && text[len - 1] == '\n') len--;
12044 if (commentList[index] != NULL)
12045 free(commentList[index]);
12048 commentList[index] = NULL;
12051 commentList[index] = (char *) malloc(len + 2);
12052 strncpy(commentList[index], text, len);
12053 commentList[index][len] = '\n';
12054 commentList[index][len + 1] = NULLCHAR;
12067 if (ch == '\r') continue;
12069 } while (ch != '\0');
12073 AppendComment(index, text)
12080 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12083 while (*text == '\n') text++;
12084 len = strlen(text);
12085 while (len > 0 && text[len - 1] == '\n') len--;
12087 if (len == 0) return;
12089 if (commentList[index] != NULL) {
12090 old = commentList[index];
12091 oldlen = strlen(old);
12092 commentList[index] = (char *) malloc(oldlen + len + 2);
12093 strcpy(commentList[index], old);
12095 strncpy(&commentList[index][oldlen], text, len);
12096 commentList[index][oldlen + len] = '\n';
12097 commentList[index][oldlen + len + 1] = NULLCHAR;
12099 commentList[index] = (char *) malloc(len + 2);
12100 strncpy(commentList[index], text, len);
12101 commentList[index][len] = '\n';
12102 commentList[index][len + 1] = NULLCHAR;
12106 static char * FindStr( char * text, char * sub_text )
12108 char * result = strstr( text, sub_text );
12110 if( result != NULL ) {
12111 result += strlen( sub_text );
12117 /* [AS] Try to extract PV info from PGN comment */
12118 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12119 char *GetInfoFromComment( int index, char * text )
12123 if( text != NULL && index > 0 ) {
12126 int time = -1, sec = 0, deci;
12127 char * s_eval = FindStr( text, "[%eval " );
12128 char * s_emt = FindStr( text, "[%emt " );
12130 if( s_eval != NULL || s_emt != NULL ) {
12134 if( s_eval != NULL ) {
12135 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12139 if( delim != ']' ) {
12144 if( s_emt != NULL ) {
12148 /* We expect something like: [+|-]nnn.nn/dd */
12151 sep = strchr( text, '/' );
12152 if( sep == NULL || sep < (text+4) ) {
12156 time = -1; sec = -1; deci = -1;
12157 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12158 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12159 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12160 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12164 if( score_lo < 0 || score_lo >= 100 ) {
12168 if(sec >= 0) time = 600*time + 10*sec; else
12169 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12171 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12173 /* [HGM] PV time: now locate end of PV info */
12174 while( *++sep >= '0' && *sep <= '9'); // strip depth
12176 while( *++sep >= '0' && *sep <= '9'); // strip time
12178 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12180 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12181 while(*sep == ' ') sep++;
12192 pvInfoList[index-1].depth = depth;
12193 pvInfoList[index-1].score = score;
12194 pvInfoList[index-1].time = 10*time; // centi-sec
12200 SendToProgram(message, cps)
12202 ChessProgramState *cps;
12204 int count, outCount, error;
12207 if (cps->pr == NULL) return;
12210 if (appData.debugMode) {
12213 fprintf(debugFP, "%ld >%-6s: %s",
12214 SubtractTimeMarks(&now, &programStartTime),
12215 cps->which, message);
12218 count = strlen(message);
12219 outCount = OutputToProcess(cps->pr, message, count, &error);
12220 if (outCount < count && !exiting
12221 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12222 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12223 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12224 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12225 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12226 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12228 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12230 gameInfo.resultDetails = buf;
12232 DisplayFatalError(buf, error, 1);
12237 ReceiveFromProgram(isr, closure, message, count, error)
12238 InputSourceRef isr;
12246 ChessProgramState *cps = (ChessProgramState *)closure;
12248 if (isr != cps->isr) return; /* Killed intentionally */
12252 _("Error: %s chess program (%s) exited unexpectedly"),
12253 cps->which, cps->program);
12254 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12255 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12256 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12257 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12259 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12261 gameInfo.resultDetails = buf;
12263 RemoveInputSource(cps->isr);
12264 DisplayFatalError(buf, 0, 1);
12267 _("Error reading from %s chess program (%s)"),
12268 cps->which, cps->program);
12269 RemoveInputSource(cps->isr);
12271 /* [AS] Program is misbehaving badly... kill it */
12272 if( count == -2 ) {
12273 DestroyChildProcess( cps->pr, 9 );
12277 DisplayFatalError(buf, error, 1);
12282 if ((end_str = strchr(message, '\r')) != NULL)
12283 *end_str = NULLCHAR;
12284 if ((end_str = strchr(message, '\n')) != NULL)
12285 *end_str = NULLCHAR;
12287 if (appData.debugMode) {
12288 TimeMark now; int print = 1;
12289 char *quote = ""; char c; int i;
12291 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12292 char start = message[0];
12293 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12294 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12295 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12296 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12297 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12298 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12299 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12300 sscanf(message, "pong %c", &c)!=1 && start != '#')
12301 { quote = "# "; print = (appData.engineComments == 2); }
12302 message[0] = start; // restore original message
12306 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12307 SubtractTimeMarks(&now, &programStartTime), cps->which,
12313 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12314 if (appData.icsEngineAnalyze) {
12315 if (strstr(message, "whisper") != NULL ||
12316 strstr(message, "kibitz") != NULL ||
12317 strstr(message, "tellics") != NULL) return;
12320 HandleMachineMove(message, cps);
12325 SendTimeControl(cps, mps, tc, inc, sd, st)
12326 ChessProgramState *cps;
12327 int mps, inc, sd, st;
12333 if( timeControl_2 > 0 ) {
12334 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12335 tc = timeControl_2;
12338 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12339 inc /= cps->timeOdds;
12340 st /= cps->timeOdds;
12342 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12345 /* Set exact time per move, normally using st command */
12346 if (cps->stKludge) {
12347 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12349 if (seconds == 0) {
12350 sprintf(buf, "level 1 %d\n", st/60);
12352 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12355 sprintf(buf, "st %d\n", st);
12358 /* Set conventional or incremental time control, using level command */
12359 if (seconds == 0) {
12360 /* Note old gnuchess bug -- minutes:seconds used to not work.
12361 Fixed in later versions, but still avoid :seconds
12362 when seconds is 0. */
12363 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12365 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12366 seconds, inc/1000);
12369 SendToProgram(buf, cps);
12371 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12372 /* Orthogonally, limit search to given depth */
12374 if (cps->sdKludge) {
12375 sprintf(buf, "depth\n%d\n", sd);
12377 sprintf(buf, "sd %d\n", sd);
12379 SendToProgram(buf, cps);
12382 if(cps->nps > 0) { /* [HGM] nps */
12383 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12385 sprintf(buf, "nps %d\n", cps->nps);
12386 SendToProgram(buf, cps);
12391 ChessProgramState *WhitePlayer()
12392 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12394 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12395 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12401 SendTimeRemaining(cps, machineWhite)
12402 ChessProgramState *cps;
12403 int /*boolean*/ machineWhite;
12405 char message[MSG_SIZ];
12408 /* Note: this routine must be called when the clocks are stopped
12409 or when they have *just* been set or switched; otherwise
12410 it will be off by the time since the current tick started.
12412 if (machineWhite) {
12413 time = whiteTimeRemaining / 10;
12414 otime = blackTimeRemaining / 10;
12416 time = blackTimeRemaining / 10;
12417 otime = whiteTimeRemaining / 10;
12419 /* [HGM] translate opponent's time by time-odds factor */
12420 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12421 if (appData.debugMode) {
12422 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12425 if (time <= 0) time = 1;
12426 if (otime <= 0) otime = 1;
12428 sprintf(message, "time %ld\n", time);
12429 SendToProgram(message, cps);
12431 sprintf(message, "otim %ld\n", otime);
12432 SendToProgram(message, cps);
12436 BoolFeature(p, name, loc, cps)
12440 ChessProgramState *cps;
12443 int len = strlen(name);
12445 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12447 sscanf(*p, "%d", &val);
12449 while (**p && **p != ' ') (*p)++;
12450 sprintf(buf, "accepted %s\n", name);
12451 SendToProgram(buf, cps);
12458 IntFeature(p, name, loc, cps)
12462 ChessProgramState *cps;
12465 int len = strlen(name);
12466 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12468 sscanf(*p, "%d", loc);
12469 while (**p && **p != ' ') (*p)++;
12470 sprintf(buf, "accepted %s\n", name);
12471 SendToProgram(buf, cps);
12478 StringFeature(p, name, loc, cps)
12482 ChessProgramState *cps;
12485 int len = strlen(name);
12486 if (strncmp((*p), name, len) == 0
12487 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12489 sscanf(*p, "%[^\"]", loc);
12490 while (**p && **p != '\"') (*p)++;
12491 if (**p == '\"') (*p)++;
12492 sprintf(buf, "accepted %s\n", name);
12493 SendToProgram(buf, cps);
12500 ParseOption(Option *opt, ChessProgramState *cps)
12501 // [HGM] options: process the string that defines an engine option, and determine
12502 // name, type, default value, and allowed value range
12504 char *p, *q, buf[MSG_SIZ];
12505 int n, min = (-1)<<31, max = 1<<31, def;
12507 if(p = strstr(opt->name, " -spin ")) {
12508 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12509 if(max < min) max = min; // enforce consistency
12510 if(def < min) def = min;
12511 if(def > max) def = max;
12516 } else if((p = strstr(opt->name, " -slider "))) {
12517 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12518 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12519 if(max < min) max = min; // enforce consistency
12520 if(def < min) def = min;
12521 if(def > max) def = max;
12525 opt->type = Spin; // Slider;
12526 } else if((p = strstr(opt->name, " -string "))) {
12527 opt->textValue = p+9;
12528 opt->type = TextBox;
12529 } else if((p = strstr(opt->name, " -file "))) {
12530 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12531 opt->textValue = p+7;
12532 opt->type = TextBox; // FileName;
12533 } else if((p = strstr(opt->name, " -path "))) {
12534 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12535 opt->textValue = p+7;
12536 opt->type = TextBox; // PathName;
12537 } else if(p = strstr(opt->name, " -check ")) {
12538 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12539 opt->value = (def != 0);
12540 opt->type = CheckBox;
12541 } else if(p = strstr(opt->name, " -combo ")) {
12542 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12543 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12544 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12545 opt->value = n = 0;
12546 while(q = StrStr(q, " /// ")) {
12547 n++; *q = 0; // count choices, and null-terminate each of them
12549 if(*q == '*') { // remember default, which is marked with * prefix
12553 cps->comboList[cps->comboCnt++] = q;
12555 cps->comboList[cps->comboCnt++] = NULL;
12557 opt->type = ComboBox;
12558 } else if(p = strstr(opt->name, " -button")) {
12559 opt->type = Button;
12560 } else if(p = strstr(opt->name, " -save")) {
12561 opt->type = SaveButton;
12562 } else return FALSE;
12563 *p = 0; // terminate option name
12564 // now look if the command-line options define a setting for this engine option.
12565 if(cps->optionSettings && cps->optionSettings[0])
12566 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12567 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12568 sprintf(buf, "option %s", p);
12569 if(p = strstr(buf, ",")) *p = 0;
12571 SendToProgram(buf, cps);
12577 FeatureDone(cps, val)
12578 ChessProgramState* cps;
12581 DelayedEventCallback cb = GetDelayedEvent();
12582 if ((cb == InitBackEnd3 && cps == &first) ||
12583 (cb == TwoMachinesEventIfReady && cps == &second)) {
12584 CancelDelayedEvent();
12585 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12587 cps->initDone = val;
12590 /* Parse feature command from engine */
12592 ParseFeatures(args, cps)
12594 ChessProgramState *cps;
12602 while (*p == ' ') p++;
12603 if (*p == NULLCHAR) return;
12605 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12606 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12607 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12608 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12609 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12610 if (BoolFeature(&p, "reuse", &val, cps)) {
12611 /* Engine can disable reuse, but can't enable it if user said no */
12612 if (!val) cps->reuse = FALSE;
12615 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12616 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12617 if (gameMode == TwoMachinesPlay) {
12618 DisplayTwoMachinesTitle();
12624 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12625 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12626 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12627 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12628 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12629 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12630 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12631 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12632 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12633 if (IntFeature(&p, "done", &val, cps)) {
12634 FeatureDone(cps, val);
12637 /* Added by Tord: */
12638 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12639 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12640 /* End of additions by Tord */
12642 /* [HGM] added features: */
12643 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12644 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12645 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12646 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12647 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12648 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12649 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12650 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12651 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12652 SendToProgram(buf, cps);
12655 if(cps->nrOptions >= MAX_OPTIONS) {
12657 sprintf(buf, "%s engine has too many options\n", cps->which);
12658 DisplayError(buf, 0);
12662 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12663 /* End of additions by HGM */
12665 /* unknown feature: complain and skip */
12667 while (*q && *q != '=') q++;
12668 sprintf(buf, "rejected %.*s\n", q-p, p);
12669 SendToProgram(buf, cps);
12675 while (*p && *p != '\"') p++;
12676 if (*p == '\"') p++;
12678 while (*p && *p != ' ') p++;
12686 PeriodicUpdatesEvent(newState)
12689 if (newState == appData.periodicUpdates)
12692 appData.periodicUpdates=newState;
12694 /* Display type changes, so update it now */
12697 /* Get the ball rolling again... */
12699 AnalysisPeriodicEvent(1);
12700 StartAnalysisClock();
12705 PonderNextMoveEvent(newState)
12708 if (newState == appData.ponderNextMove) return;
12709 if (gameMode == EditPosition) EditPositionDone();
12711 SendToProgram("hard\n", &first);
12712 if (gameMode == TwoMachinesPlay) {
12713 SendToProgram("hard\n", &second);
12716 SendToProgram("easy\n", &first);
12717 thinkOutput[0] = NULLCHAR;
12718 if (gameMode == TwoMachinesPlay) {
12719 SendToProgram("easy\n", &second);
12722 appData.ponderNextMove = newState;
12726 NewSettingEvent(option, command, value)
12732 if (gameMode == EditPosition) EditPositionDone();
12733 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12734 SendToProgram(buf, &first);
12735 if (gameMode == TwoMachinesPlay) {
12736 SendToProgram(buf, &second);
12741 ShowThinkingEvent()
12742 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12744 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12745 int newState = appData.showThinking
12746 // [HGM] thinking: other features now need thinking output as well
12747 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12749 if (oldState == newState) return;
12750 oldState = newState;
12751 if (gameMode == EditPosition) EditPositionDone();
12753 SendToProgram("post\n", &first);
12754 if (gameMode == TwoMachinesPlay) {
12755 SendToProgram("post\n", &second);
12758 SendToProgram("nopost\n", &first);
12759 thinkOutput[0] = NULLCHAR;
12760 if (gameMode == TwoMachinesPlay) {
12761 SendToProgram("nopost\n", &second);
12764 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12768 AskQuestionEvent(title, question, replyPrefix, which)
12769 char *title; char *question; char *replyPrefix; char *which;
12771 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12772 if (pr == NoProc) return;
12773 AskQuestion(title, question, replyPrefix, pr);
12777 DisplayMove(moveNumber)
12780 char message[MSG_SIZ];
12782 char cpThinkOutput[MSG_SIZ];
12784 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12786 if (moveNumber == forwardMostMove - 1 ||
12787 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12789 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12791 if (strchr(cpThinkOutput, '\n')) {
12792 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12795 *cpThinkOutput = NULLCHAR;
12798 /* [AS] Hide thinking from human user */
12799 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12800 *cpThinkOutput = NULLCHAR;
12801 if( thinkOutput[0] != NULLCHAR ) {
12804 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12805 cpThinkOutput[i] = '.';
12807 cpThinkOutput[i] = NULLCHAR;
12808 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12812 if (moveNumber == forwardMostMove - 1 &&
12813 gameInfo.resultDetails != NULL) {
12814 if (gameInfo.resultDetails[0] == NULLCHAR) {
12815 sprintf(res, " %s", PGNResult(gameInfo.result));
12817 sprintf(res, " {%s} %s",
12818 gameInfo.resultDetails, PGNResult(gameInfo.result));
12824 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12825 DisplayMessage(res, cpThinkOutput);
12827 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12828 WhiteOnMove(moveNumber) ? " " : ".. ",
12829 parseList[moveNumber], res);
12830 DisplayMessage(message, cpThinkOutput);
12835 DisplayAnalysisText(text)
12840 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12841 || appData.icsEngineAnalyze) {
12842 sprintf(buf, "Analysis (%s)", first.tidy);
12843 AnalysisPopUp(buf, text);
12851 while (*str && isspace(*str)) ++str;
12852 while (*str && !isspace(*str)) ++str;
12853 if (!*str) return 1;
12854 while (*str && isspace(*str)) ++str;
12855 if (!*str) return 1;
12863 char lst[MSG_SIZ / 2];
12865 static char *xtra[] = { "", " (--)", " (++)" };
12868 if (programStats.time == 0) {
12869 programStats.time = 1;
12872 if (programStats.got_only_move) {
12873 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12875 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12877 nps = (u64ToDouble(programStats.nodes) /
12878 ((double)programStats.time /100.0));
12880 cs = programStats.time % 100;
12881 s = programStats.time / 100;
12887 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12888 if (programStats.move_name[0] != NULLCHAR) {
12889 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12890 programStats.depth,
12891 programStats.nr_moves-programStats.moves_left,
12892 programStats.nr_moves, programStats.move_name,
12893 ((float)programStats.score)/100.0, lst,
12894 only_one_move(lst)?
12895 xtra[programStats.got_fail] : "",
12896 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12898 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12899 programStats.depth,
12900 programStats.nr_moves-programStats.moves_left,
12901 programStats.nr_moves, ((float)programStats.score)/100.0,
12903 only_one_move(lst)?
12904 xtra[programStats.got_fail] : "",
12905 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12908 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12909 programStats.depth,
12910 ((float)programStats.score)/100.0,
12912 only_one_move(lst)?
12913 xtra[programStats.got_fail] : "",
12914 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12917 DisplayAnalysisText(buf);
12921 DisplayComment(moveNumber, text)
12925 char title[MSG_SIZ];
12926 char buf[8000]; // comment can be long!
12929 if( appData.autoDisplayComment ) {
12930 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12931 strcpy(title, "Comment");
12933 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12934 WhiteOnMove(moveNumber) ? " " : ".. ",
12935 parseList[moveNumber]);
12937 // [HGM] PV info: display PV info together with (or as) comment
12938 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12939 if(text == NULL) text = "";
12940 score = pvInfoList[moveNumber].score;
12941 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12942 depth, (pvInfoList[moveNumber].time+50)/100, text);
12945 } else title[0] = 0;
12948 CommentPopUp(title, text);
12951 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12952 * might be busy thinking or pondering. It can be omitted if your
12953 * gnuchess is configured to stop thinking immediately on any user
12954 * input. However, that gnuchess feature depends on the FIONREAD
12955 * ioctl, which does not work properly on some flavors of Unix.
12959 ChessProgramState *cps;
12962 if (!cps->useSigint) return;
12963 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12964 switch (gameMode) {
12965 case MachinePlaysWhite:
12966 case MachinePlaysBlack:
12967 case TwoMachinesPlay:
12968 case IcsPlayingWhite:
12969 case IcsPlayingBlack:
12972 /* Skip if we know it isn't thinking */
12973 if (!cps->maybeThinking) return;
12974 if (appData.debugMode)
12975 fprintf(debugFP, "Interrupting %s\n", cps->which);
12976 InterruptChildProcess(cps->pr);
12977 cps->maybeThinking = FALSE;
12982 #endif /*ATTENTION*/
12988 if (whiteTimeRemaining <= 0) {
12991 if (appData.icsActive) {
12992 if (appData.autoCallFlag &&
12993 gameMode == IcsPlayingBlack && !blackFlag) {
12994 SendToICS(ics_prefix);
12995 SendToICS("flag\n");
12999 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13001 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13002 if (appData.autoCallFlag) {
13003 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13010 if (blackTimeRemaining <= 0) {
13013 if (appData.icsActive) {
13014 if (appData.autoCallFlag &&
13015 gameMode == IcsPlayingWhite && !whiteFlag) {
13016 SendToICS(ics_prefix);
13017 SendToICS("flag\n");
13021 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13023 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13024 if (appData.autoCallFlag) {
13025 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13038 if (!appData.clockMode || appData.icsActive ||
13039 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13042 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13044 if ( !WhiteOnMove(forwardMostMove) )
13045 /* White made time control */
13046 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13047 /* [HGM] time odds: correct new time quota for time odds! */
13048 / WhitePlayer()->timeOdds;
13050 /* Black made time control */
13051 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13052 / WhitePlayer()->other->timeOdds;
13056 DisplayBothClocks()
13058 int wom = gameMode == EditPosition ?
13059 !blackPlaysFirst : WhiteOnMove(currentMove);
13060 DisplayWhiteClock(whiteTimeRemaining, wom);
13061 DisplayBlackClock(blackTimeRemaining, !wom);
13065 /* Timekeeping seems to be a portability nightmare. I think everyone
13066 has ftime(), but I'm really not sure, so I'm including some ifdefs
13067 to use other calls if you don't. Clocks will be less accurate if
13068 you have neither ftime nor gettimeofday.
13071 /* VS 2008 requires the #include outside of the function */
13072 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13073 #include <sys/timeb.h>
13076 /* Get the current time as a TimeMark */
13081 #if HAVE_GETTIMEOFDAY
13083 struct timeval timeVal;
13084 struct timezone timeZone;
13086 gettimeofday(&timeVal, &timeZone);
13087 tm->sec = (long) timeVal.tv_sec;
13088 tm->ms = (int) (timeVal.tv_usec / 1000L);
13090 #else /*!HAVE_GETTIMEOFDAY*/
13093 // include <sys/timeb.h> / moved to just above start of function
13094 struct timeb timeB;
13097 tm->sec = (long) timeB.time;
13098 tm->ms = (int) timeB.millitm;
13100 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13101 tm->sec = (long) time(NULL);
13107 /* Return the difference in milliseconds between two
13108 time marks. We assume the difference will fit in a long!
13111 SubtractTimeMarks(tm2, tm1)
13112 TimeMark *tm2, *tm1;
13114 return 1000L*(tm2->sec - tm1->sec) +
13115 (long) (tm2->ms - tm1->ms);
13120 * Code to manage the game clocks.
13122 * In tournament play, black starts the clock and then white makes a move.
13123 * We give the human user a slight advantage if he is playing white---the
13124 * clocks don't run until he makes his first move, so it takes zero time.
13125 * Also, we don't account for network lag, so we could get out of sync
13126 * with GNU Chess's clock -- but then, referees are always right.
13129 static TimeMark tickStartTM;
13130 static long intendedTickLength;
13133 NextTickLength(timeRemaining)
13134 long timeRemaining;
13136 long nominalTickLength, nextTickLength;
13138 if (timeRemaining > 0L && timeRemaining <= 10000L)
13139 nominalTickLength = 100L;
13141 nominalTickLength = 1000L;
13142 nextTickLength = timeRemaining % nominalTickLength;
13143 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13145 return nextTickLength;
13148 /* Adjust clock one minute up or down */
13150 AdjustClock(Boolean which, int dir)
13152 if(which) blackTimeRemaining += 60000*dir;
13153 else whiteTimeRemaining += 60000*dir;
13154 DisplayBothClocks();
13157 /* Stop clocks and reset to a fresh time control */
13161 (void) StopClockTimer();
13162 if (appData.icsActive) {
13163 whiteTimeRemaining = blackTimeRemaining = 0;
13164 } else { /* [HGM] correct new time quote for time odds */
13165 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13166 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13168 if (whiteFlag || blackFlag) {
13170 whiteFlag = blackFlag = FALSE;
13172 DisplayBothClocks();
13175 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13177 /* Decrement running clock by amount of time that has passed */
13181 long timeRemaining;
13182 long lastTickLength, fudge;
13185 if (!appData.clockMode) return;
13186 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13190 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13192 /* Fudge if we woke up a little too soon */
13193 fudge = intendedTickLength - lastTickLength;
13194 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13196 if (WhiteOnMove(forwardMostMove)) {
13197 if(whiteNPS >= 0) lastTickLength = 0;
13198 timeRemaining = whiteTimeRemaining -= lastTickLength;
13199 DisplayWhiteClock(whiteTimeRemaining - fudge,
13200 WhiteOnMove(currentMove));
13202 if(blackNPS >= 0) lastTickLength = 0;
13203 timeRemaining = blackTimeRemaining -= lastTickLength;
13204 DisplayBlackClock(blackTimeRemaining - fudge,
13205 !WhiteOnMove(currentMove));
13208 if (CheckFlags()) return;
13211 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13212 StartClockTimer(intendedTickLength);
13214 /* if the time remaining has fallen below the alarm threshold, sound the
13215 * alarm. if the alarm has sounded and (due to a takeback or time control
13216 * with increment) the time remaining has increased to a level above the
13217 * threshold, reset the alarm so it can sound again.
13220 if (appData.icsActive && appData.icsAlarm) {
13222 /* make sure we are dealing with the user's clock */
13223 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13224 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13227 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13228 alarmSounded = FALSE;
13229 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13231 alarmSounded = TRUE;
13237 /* A player has just moved, so stop the previously running
13238 clock and (if in clock mode) start the other one.
13239 We redisplay both clocks in case we're in ICS mode, because
13240 ICS gives us an update to both clocks after every move.
13241 Note that this routine is called *after* forwardMostMove
13242 is updated, so the last fractional tick must be subtracted
13243 from the color that is *not* on move now.
13248 long lastTickLength;
13250 int flagged = FALSE;
13254 if (StopClockTimer() && appData.clockMode) {
13255 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13256 if (WhiteOnMove(forwardMostMove)) {
13257 if(blackNPS >= 0) lastTickLength = 0;
13258 blackTimeRemaining -= lastTickLength;
13259 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13260 // if(pvInfoList[forwardMostMove-1].time == -1)
13261 pvInfoList[forwardMostMove-1].time = // use GUI time
13262 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13264 if(whiteNPS >= 0) lastTickLength = 0;
13265 whiteTimeRemaining -= lastTickLength;
13266 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13267 // if(pvInfoList[forwardMostMove-1].time == -1)
13268 pvInfoList[forwardMostMove-1].time =
13269 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13271 flagged = CheckFlags();
13273 CheckTimeControl();
13275 if (flagged || !appData.clockMode) return;
13277 switch (gameMode) {
13278 case MachinePlaysBlack:
13279 case MachinePlaysWhite:
13280 case BeginningOfGame:
13281 if (pausing) return;
13285 case PlayFromGameFile:
13294 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13295 whiteTimeRemaining : blackTimeRemaining);
13296 StartClockTimer(intendedTickLength);
13300 /* Stop both clocks */
13304 long lastTickLength;
13307 if (!StopClockTimer()) return;
13308 if (!appData.clockMode) return;
13312 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13313 if (WhiteOnMove(forwardMostMove)) {
13314 if(whiteNPS >= 0) lastTickLength = 0;
13315 whiteTimeRemaining -= lastTickLength;
13316 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13318 if(blackNPS >= 0) lastTickLength = 0;
13319 blackTimeRemaining -= lastTickLength;
13320 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13325 /* Start clock of player on move. Time may have been reset, so
13326 if clock is already running, stop and restart it. */
13330 (void) StopClockTimer(); /* in case it was running already */
13331 DisplayBothClocks();
13332 if (CheckFlags()) return;
13334 if (!appData.clockMode) return;
13335 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13337 GetTimeMark(&tickStartTM);
13338 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13339 whiteTimeRemaining : blackTimeRemaining);
13341 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13342 whiteNPS = blackNPS = -1;
13343 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13344 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13345 whiteNPS = first.nps;
13346 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13347 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13348 blackNPS = first.nps;
13349 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13350 whiteNPS = second.nps;
13351 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13352 blackNPS = second.nps;
13353 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13355 StartClockTimer(intendedTickLength);
13362 long second, minute, hour, day;
13364 static char buf[32];
13366 if (ms > 0 && ms <= 9900) {
13367 /* convert milliseconds to tenths, rounding up */
13368 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13370 sprintf(buf, " %03.1f ", tenths/10.0);
13374 /* convert milliseconds to seconds, rounding up */
13375 /* use floating point to avoid strangeness of integer division
13376 with negative dividends on many machines */
13377 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13384 day = second / (60 * 60 * 24);
13385 second = second % (60 * 60 * 24);
13386 hour = second / (60 * 60);
13387 second = second % (60 * 60);
13388 minute = second / 60;
13389 second = second % 60;
13392 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13393 sign, day, hour, minute, second);
13395 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13397 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13404 * This is necessary because some C libraries aren't ANSI C compliant yet.
13407 StrStr(string, match)
13408 char *string, *match;
13412 length = strlen(match);
13414 for (i = strlen(string) - length; i >= 0; i--, string++)
13415 if (!strncmp(match, string, length))
13422 StrCaseStr(string, match)
13423 char *string, *match;
13427 length = strlen(match);
13429 for (i = strlen(string) - length; i >= 0; i--, string++) {
13430 for (j = 0; j < length; j++) {
13431 if (ToLower(match[j]) != ToLower(string[j]))
13434 if (j == length) return string;
13448 c1 = ToLower(*s1++);
13449 c2 = ToLower(*s2++);
13450 if (c1 > c2) return 1;
13451 if (c1 < c2) return -1;
13452 if (c1 == NULLCHAR) return 0;
13461 return isupper(c) ? tolower(c) : c;
13469 return islower(c) ? toupper(c) : c;
13471 #endif /* !_amigados */
13479 if ((ret = (char *) malloc(strlen(s) + 1))) {
13486 StrSavePtr(s, savePtr)
13487 char *s, **savePtr;
13492 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13493 strcpy(*savePtr, s);
13505 clock = time((time_t *)NULL);
13506 tm = localtime(&clock);
13507 sprintf(buf, "%04d.%02d.%02d",
13508 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13509 return StrSave(buf);
13514 PositionToFEN(move, overrideCastling)
13516 char *overrideCastling;
13518 int i, j, fromX, fromY, toX, toY;
13525 whiteToPlay = (gameMode == EditPosition) ?
13526 !blackPlaysFirst : (move % 2 == 0);
13529 /* Piece placement data */
13530 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13532 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13533 if (boards[move][i][j] == EmptySquare) {
13535 } else { ChessSquare piece = boards[move][i][j];
13536 if (emptycount > 0) {
13537 if(emptycount<10) /* [HGM] can be >= 10 */
13538 *p++ = '0' + emptycount;
13539 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13542 if(PieceToChar(piece) == '+') {
13543 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13545 piece = (ChessSquare)(DEMOTED piece);
13547 *p++ = PieceToChar(piece);
13549 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13550 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13555 if (emptycount > 0) {
13556 if(emptycount<10) /* [HGM] can be >= 10 */
13557 *p++ = '0' + emptycount;
13558 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13565 /* [HGM] print Crazyhouse or Shogi holdings */
13566 if( gameInfo.holdingsWidth ) {
13567 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13569 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13570 piece = boards[move][i][BOARD_WIDTH-1];
13571 if( piece != EmptySquare )
13572 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13573 *p++ = PieceToChar(piece);
13575 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13576 piece = boards[move][BOARD_HEIGHT-i-1][0];
13577 if( piece != EmptySquare )
13578 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13579 *p++ = PieceToChar(piece);
13582 if( q == p ) *p++ = '-';
13588 *p++ = whiteToPlay ? 'w' : 'b';
13591 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13592 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13594 if(nrCastlingRights) {
13596 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13597 /* [HGM] write directly from rights */
13598 if(castlingRights[move][2] >= 0 &&
13599 castlingRights[move][0] >= 0 )
13600 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13601 if(castlingRights[move][2] >= 0 &&
13602 castlingRights[move][1] >= 0 )
13603 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13604 if(castlingRights[move][5] >= 0 &&
13605 castlingRights[move][3] >= 0 )
13606 *p++ = castlingRights[move][3] + AAA;
13607 if(castlingRights[move][5] >= 0 &&
13608 castlingRights[move][4] >= 0 )
13609 *p++ = castlingRights[move][4] + AAA;
13612 /* [HGM] write true castling rights */
13613 if( nrCastlingRights == 6 ) {
13614 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13615 castlingRights[move][2] >= 0 ) *p++ = 'K';
13616 if(castlingRights[move][1] == BOARD_LEFT &&
13617 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13618 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13619 castlingRights[move][5] >= 0 ) *p++ = 'k';
13620 if(castlingRights[move][4] == BOARD_LEFT &&
13621 castlingRights[move][5] >= 0 ) *p++ = 'q';
13624 if (q == p) *p++ = '-'; /* No castling rights */
13628 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13629 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13630 /* En passant target square */
13631 if (move > backwardMostMove) {
13632 fromX = moveList[move - 1][0] - AAA;
13633 fromY = moveList[move - 1][1] - ONE;
13634 toX = moveList[move - 1][2] - AAA;
13635 toY = moveList[move - 1][3] - ONE;
13636 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13637 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13638 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13640 /* 2-square pawn move just happened */
13642 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13653 /* [HGM] find reversible plies */
13654 { int i = 0, j=move;
13656 if (appData.debugMode) { int k;
13657 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13658 for(k=backwardMostMove; k<=forwardMostMove; k++)
13659 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13663 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13664 if( j == backwardMostMove ) i += initialRulePlies;
13665 sprintf(p, "%d ", i);
13666 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13668 /* Fullmove number */
13669 sprintf(p, "%d", (move / 2) + 1);
13671 return StrSave(buf);
13675 ParseFEN(board, blackPlaysFirst, fen)
13677 int *blackPlaysFirst;
13687 /* [HGM] by default clear Crazyhouse holdings, if present */
13688 if(gameInfo.holdingsWidth) {
13689 for(i=0; i<BOARD_HEIGHT; i++) {
13690 board[i][0] = EmptySquare; /* black holdings */
13691 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13692 board[i][1] = (ChessSquare) 0; /* black counts */
13693 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13697 /* Piece placement data */
13698 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13701 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13702 if (*p == '/') p++;
13703 emptycount = gameInfo.boardWidth - j;
13704 while (emptycount--)
13705 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13707 #if(BOARD_SIZE >= 10)
13708 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13709 p++; emptycount=10;
13710 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13711 while (emptycount--)
13712 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13714 } else if (isdigit(*p)) {
13715 emptycount = *p++ - '0';
13716 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13717 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13718 while (emptycount--)
13719 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13720 } else if (*p == '+' || isalpha(*p)) {
13721 if (j >= gameInfo.boardWidth) return FALSE;
13723 piece = CharToPiece(*++p);
13724 if(piece == EmptySquare) return FALSE; /* unknown piece */
13725 piece = (ChessSquare) (PROMOTED piece ); p++;
13726 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13727 } else piece = CharToPiece(*p++);
13729 if(piece==EmptySquare) return FALSE; /* unknown piece */
13730 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13731 piece = (ChessSquare) (PROMOTED piece);
13732 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13735 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13741 while (*p == '/' || *p == ' ') p++;
13743 /* [HGM] look for Crazyhouse holdings here */
13744 while(*p==' ') p++;
13745 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13747 if(*p == '-' ) *p++; /* empty holdings */ else {
13748 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13749 /* if we would allow FEN reading to set board size, we would */
13750 /* have to add holdings and shift the board read so far here */
13751 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13753 if((int) piece >= (int) BlackPawn ) {
13754 i = (int)piece - (int)BlackPawn;
13755 i = PieceToNumber((ChessSquare)i);
13756 if( i >= gameInfo.holdingsSize ) return FALSE;
13757 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13758 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13760 i = (int)piece - (int)WhitePawn;
13761 i = PieceToNumber((ChessSquare)i);
13762 if( i >= gameInfo.holdingsSize ) return FALSE;
13763 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13764 board[i][BOARD_WIDTH-2]++; /* black holdings */
13768 if(*p == ']') *p++;
13771 while(*p == ' ') p++;
13776 *blackPlaysFirst = FALSE;
13779 *blackPlaysFirst = TRUE;
13785 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13786 /* return the extra info in global variiables */
13788 /* set defaults in case FEN is incomplete */
13789 FENepStatus = EP_UNKNOWN;
13790 for(i=0; i<nrCastlingRights; i++ ) {
13791 FENcastlingRights[i] =
13792 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13793 } /* assume possible unless obviously impossible */
13794 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13795 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13796 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13797 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13798 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13799 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13802 while(*p==' ') p++;
13803 if(nrCastlingRights) {
13804 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13805 /* castling indicator present, so default becomes no castlings */
13806 for(i=0; i<nrCastlingRights; i++ ) {
13807 FENcastlingRights[i] = -1;
13810 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13811 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13812 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13813 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13814 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13816 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13817 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13818 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13822 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13823 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13824 FENcastlingRights[2] = whiteKingFile;
13827 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13828 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13829 FENcastlingRights[2] = whiteKingFile;
13832 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13833 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13834 FENcastlingRights[5] = blackKingFile;
13837 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13838 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13839 FENcastlingRights[5] = blackKingFile;
13842 default: /* FRC castlings */
13843 if(c >= 'a') { /* black rights */
13844 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13845 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13846 if(i == BOARD_RGHT) break;
13847 FENcastlingRights[5] = i;
13849 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13850 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13852 FENcastlingRights[3] = c;
13854 FENcastlingRights[4] = c;
13855 } else { /* white rights */
13856 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13857 if(board[0][i] == WhiteKing) break;
13858 if(i == BOARD_RGHT) break;
13859 FENcastlingRights[2] = i;
13860 c -= AAA - 'a' + 'A';
13861 if(board[0][c] >= WhiteKing) break;
13863 FENcastlingRights[0] = c;
13865 FENcastlingRights[1] = c;
13869 if (appData.debugMode) {
13870 fprintf(debugFP, "FEN castling rights:");
13871 for(i=0; i<nrCastlingRights; i++)
13872 fprintf(debugFP, " %d", FENcastlingRights[i]);
13873 fprintf(debugFP, "\n");
13876 while(*p==' ') p++;
13879 /* read e.p. field in games that know e.p. capture */
13880 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13881 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13883 p++; FENepStatus = EP_NONE;
13885 char c = *p++ - AAA;
13887 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13888 if(*p >= '0' && *p <='9') *p++;
13894 if(sscanf(p, "%d", &i) == 1) {
13895 FENrulePlies = i; /* 50-move ply counter */
13896 /* (The move number is still ignored) */
13903 EditPositionPasteFEN(char *fen)
13906 Board initial_position;
13908 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13909 DisplayError(_("Bad FEN position in clipboard"), 0);
13912 int savedBlackPlaysFirst = blackPlaysFirst;
13913 EditPositionEvent();
13914 blackPlaysFirst = savedBlackPlaysFirst;
13915 CopyBoard(boards[0], initial_position);
13916 /* [HGM] copy FEN attributes as well */
13918 initialRulePlies = FENrulePlies;
13919 epStatus[0] = FENepStatus;
13920 for( i=0; i<nrCastlingRights; i++ )
13921 castlingRights[0][i] = FENcastlingRights[i];
13923 EditPositionDone();
13924 DisplayBothClocks();
13925 DrawPosition(FALSE, boards[currentMove]);