2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
472 int mute; // mute all sounds
474 ChessSquare FIDEArray[2][BOARD_SIZE] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
496 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499 BlackKing, BlackBishop, BlackKnight, BlackRook }
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
529 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
534 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
536 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
541 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
543 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
549 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
551 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
554 #define GothicArray CapablancaArray
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
560 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
562 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
565 #define FalconArray CapablancaArray
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
587 Board initialPosition;
590 /* Convert str to a rating. Checks for special cases of "----",
592 "++++", etc. Also strips ()'s */
594 string_to_rating(str)
597 while(*str && !isdigit(*str)) ++str;
599 return 0; /* One of the special "no rating" cases */
607 /* Init programStats */
608 programStats.movelist[0] = 0;
609 programStats.depth = 0;
610 programStats.nr_moves = 0;
611 programStats.moves_left = 0;
612 programStats.nodes = 0;
613 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
614 programStats.score = 0;
615 programStats.got_only_move = 0;
616 programStats.got_fail = 0;
617 programStats.line_is_book = 0;
623 int matched, min, sec;
625 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
627 GetTimeMark(&programStartTime);
628 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
631 programStats.ok_to_send = 1;
632 programStats.seen_stat = 0;
635 * Initialize game list
641 * Internet chess server status
643 if (appData.icsActive) {
644 appData.matchMode = FALSE;
645 appData.matchGames = 0;
647 appData.noChessProgram = !appData.zippyPlay;
649 appData.zippyPlay = FALSE;
650 appData.zippyTalk = FALSE;
651 appData.noChessProgram = TRUE;
653 if (*appData.icsHelper != NULLCHAR) {
654 appData.useTelnet = TRUE;
655 appData.telnetProgram = appData.icsHelper;
658 appData.zippyTalk = appData.zippyPlay = FALSE;
661 /* [AS] Initialize pv info list [HGM] and game state */
665 for( i=0; i<MAX_MOVES; i++ ) {
666 pvInfoList[i].depth = -1;
668 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
673 * Parse timeControl resource
675 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676 appData.movesPerSession)) {
678 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679 DisplayFatalError(buf, 0, 2);
683 * Parse searchTime resource
685 if (*appData.searchTime != NULLCHAR) {
686 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
688 searchTime = min * 60;
689 } else if (matched == 2) {
690 searchTime = min * 60 + sec;
693 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694 DisplayFatalError(buf, 0, 2);
698 /* [AS] Adjudication threshold */
699 adjudicateLossThreshold = appData.adjudicateLossThreshold;
701 first.which = "first";
702 second.which = "second";
703 first.maybeThinking = second.maybeThinking = FALSE;
704 first.pr = second.pr = NoProc;
705 first.isr = second.isr = NULL;
706 first.sendTime = second.sendTime = 2;
707 first.sendDrawOffers = 1;
708 if (appData.firstPlaysBlack) {
709 first.twoMachinesColor = "black\n";
710 second.twoMachinesColor = "white\n";
712 first.twoMachinesColor = "white\n";
713 second.twoMachinesColor = "black\n";
715 first.program = appData.firstChessProgram;
716 second.program = appData.secondChessProgram;
717 first.host = appData.firstHost;
718 second.host = appData.secondHost;
719 first.dir = appData.firstDirectory;
720 second.dir = appData.secondDirectory;
721 first.other = &second;
722 second.other = &first;
723 first.initString = appData.initString;
724 second.initString = appData.secondInitString;
725 first.computerString = appData.firstComputerString;
726 second.computerString = appData.secondComputerString;
727 first.useSigint = second.useSigint = TRUE;
728 first.useSigterm = second.useSigterm = TRUE;
729 first.reuse = appData.reuseFirst;
730 second.reuse = appData.reuseSecond;
731 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
732 second.nps = appData.secondNPS;
733 first.useSetboard = second.useSetboard = FALSE;
734 first.useSAN = second.useSAN = FALSE;
735 first.usePing = second.usePing = FALSE;
736 first.lastPing = second.lastPing = 0;
737 first.lastPong = second.lastPong = 0;
738 first.usePlayother = second.usePlayother = FALSE;
739 first.useColors = second.useColors = TRUE;
740 first.useUsermove = second.useUsermove = FALSE;
741 first.sendICS = second.sendICS = FALSE;
742 first.sendName = second.sendName = appData.icsActive;
743 first.sdKludge = second.sdKludge = FALSE;
744 first.stKludge = second.stKludge = FALSE;
745 TidyProgramName(first.program, first.host, first.tidy);
746 TidyProgramName(second.program, second.host, second.tidy);
747 first.matchWins = second.matchWins = 0;
748 strcpy(first.variants, appData.variant);
749 strcpy(second.variants, appData.variant);
750 first.analysisSupport = second.analysisSupport = 2; /* detect */
751 first.analyzing = second.analyzing = FALSE;
752 first.initDone = second.initDone = FALSE;
754 /* New features added by Tord: */
755 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757 /* End of new features added by Tord. */
758 first.fenOverride = appData.fenOverride1;
759 second.fenOverride = appData.fenOverride2;
761 /* [HGM] time odds: set factor for each machine */
762 first.timeOdds = appData.firstTimeOdds;
763 second.timeOdds = appData.secondTimeOdds;
765 if(appData.timeOddsMode) {
766 norm = first.timeOdds;
767 if(norm > second.timeOdds) norm = second.timeOdds;
769 first.timeOdds /= norm;
770 second.timeOdds /= norm;
773 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774 first.accumulateTC = appData.firstAccumulateTC;
775 second.accumulateTC = appData.secondAccumulateTC;
776 first.maxNrOfSessions = second.maxNrOfSessions = 1;
779 first.debug = second.debug = FALSE;
780 first.supportsNPS = second.supportsNPS = UNKNOWN;
783 first.optionSettings = appData.firstOptions;
784 second.optionSettings = appData.secondOptions;
786 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788 first.isUCI = appData.firstIsUCI; /* [AS] */
789 second.isUCI = appData.secondIsUCI; /* [AS] */
790 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
793 if (appData.firstProtocolVersion > PROTOVER ||
794 appData.firstProtocolVersion < 1) {
796 sprintf(buf, _("protocol version %d not supported"),
797 appData.firstProtocolVersion);
798 DisplayFatalError(buf, 0, 2);
800 first.protocolVersion = appData.firstProtocolVersion;
803 if (appData.secondProtocolVersion > PROTOVER ||
804 appData.secondProtocolVersion < 1) {
806 sprintf(buf, _("protocol version %d not supported"),
807 appData.secondProtocolVersion);
808 DisplayFatalError(buf, 0, 2);
810 second.protocolVersion = appData.secondProtocolVersion;
813 if (appData.icsActive) {
814 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
815 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816 appData.clockMode = FALSE;
817 first.sendTime = second.sendTime = 0;
821 /* Override some settings from environment variables, for backward
822 compatibility. Unfortunately it's not feasible to have the env
823 vars just set defaults, at least in xboard. Ugh.
825 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
830 if (appData.noChessProgram) {
831 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832 sprintf(programVersion, "%s", PACKAGE_STRING);
837 while (*q != ' ' && *q != NULLCHAR) q++;
839 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841 sprintf(programVersion, "%s + ", PACKAGE_STRING);
842 strncat(programVersion, p, q - p);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
850 if (!appData.icsActive) {
852 /* Check for variants that are supported only in ICS mode,
853 or not at all. Some that are accepted here nevertheless
854 have bugs; see comments below.
856 VariantClass variant = StringToVariant(appData.variant);
858 case VariantBughouse: /* need four players and two boards */
859 case VariantKriegspiel: /* need to hide pieces and move details */
860 /* case VariantFischeRandom: (Fabien: moved below) */
861 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862 DisplayFatalError(buf, 0, 2);
866 case VariantLoadable:
876 sprintf(buf, _("Unknown variant name %s"), appData.variant);
877 DisplayFatalError(buf, 0, 2);
880 case VariantXiangqi: /* [HGM] repetition rules not implemented */
881 case VariantFairy: /* [HGM] TestLegality definitely off! */
882 case VariantGothic: /* [HGM] should work */
883 case VariantCapablanca: /* [HGM] should work */
884 case VariantCourier: /* [HGM] initial forced moves not implemented */
885 case VariantShogi: /* [HGM] drops not tested for legality */
886 case VariantKnightmate: /* [HGM] should work */
887 case VariantCylinder: /* [HGM] untested */
888 case VariantFalcon: /* [HGM] untested */
889 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890 offboard interposition not understood */
891 case VariantNormal: /* definitely works! */
892 case VariantWildCastle: /* pieces not automatically shuffled */
893 case VariantNoCastle: /* pieces not automatically shuffled */
894 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895 case VariantLosers: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantSuicide: /* should work except for win condition,
898 and doesn't know captures are mandatory */
899 case VariantGiveaway: /* should work except for win condition,
900 and doesn't know captures are mandatory */
901 case VariantTwoKings: /* should work */
902 case VariantAtomic: /* should work except for win condition */
903 case Variant3Check: /* should work except for win condition */
904 case VariantShatranj: /* should work except for all win conditions */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1014 int matched, min, sec;
1016 matched = sscanf(tc, "%d:%d", &min, &sec);
1018 timeControl = min * 60 * 1000;
1019 } else if (matched == 2) {
1020 timeControl = (min * 60 + sec) * 1000;
1029 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033 else sprintf(buf, "+%s+%d", tc, ti);
1036 sprintf(buf, "+%d/%s", mps, tc);
1037 else sprintf(buf, "+%s", tc);
1039 fullTimeControlString = StrSave(buf);
1041 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1046 /* Parse second time control */
1049 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1057 timeControl_2 = tc2 * 1000;
1067 timeControl = tc1 * 1000;
1071 timeIncrement = ti * 1000; /* convert to ms */
1072 movesPerSession = 0;
1075 movesPerSession = mps;
1083 if (appData.debugMode) {
1084 fprintf(debugFP, "%s\n", programVersion);
1087 if (appData.matchGames > 0) {
1088 appData.matchMode = TRUE;
1089 } else if (appData.matchMode) {
1090 appData.matchGames = 1;
1092 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093 appData.matchGames = appData.sameColorGames;
1094 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1102 /* kludge: allow timeout for initial "feature" commands */
1104 DisplayMessage("", _("Starting chess program"));
1105 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 InitBackEnd3 P((void))
1112 GameMode initialMode;
1116 InitChessProgram(&first, startedFromSetupPosition);
1119 if (appData.icsActive) {
1121 /* [DM] Make a console window if needed [HGM] merged ifs */
1126 if (*appData.icsCommPort != NULLCHAR) {
1127 sprintf(buf, _("Could not open comm port %s"),
1128 appData.icsCommPort);
1130 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131 appData.icsHost, appData.icsPort);
1133 DisplayFatalError(buf, err, 1);
1138 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1140 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141 } else if (appData.noChessProgram) {
1147 if (*appData.cmailGameName != NULLCHAR) {
1149 OpenLoopback(&cmailPR);
1151 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1155 DisplayMessage("", "");
1156 if (StrCaseCmp(appData.initialMode, "") == 0) {
1157 initialMode = BeginningOfGame;
1158 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159 initialMode = TwoMachinesPlay;
1160 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161 initialMode = AnalyzeFile;
1162 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163 initialMode = AnalyzeMode;
1164 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165 initialMode = MachinePlaysWhite;
1166 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167 initialMode = MachinePlaysBlack;
1168 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169 initialMode = EditGame;
1170 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171 initialMode = EditPosition;
1172 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173 initialMode = Training;
1175 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176 DisplayFatalError(buf, 0, 2);
1180 if (appData.matchMode) {
1181 /* Set up machine vs. machine match */
1182 if (appData.noChessProgram) {
1183 DisplayFatalError(_("Can't have a match with no chess programs"),
1189 if (*appData.loadGameFile != NULLCHAR) {
1190 int index = appData.loadGameIndex; // [HGM] autoinc
1191 if(index<0) lastIndex = index = 1;
1192 if (!LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameFile, FALSE)) {
1195 DisplayFatalError(_("Bad game file"), 0, 1);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 int index = appData.loadPositionIndex; // [HGM] autoinc
1200 if(index<0) lastIndex = index = 1;
1201 if (!LoadPositionFromFile(appData.loadPositionFile,
1203 appData.loadPositionFile)) {
1204 DisplayFatalError(_("Bad position file"), 0, 1);
1209 } else if (*appData.cmailGameName != NULLCHAR) {
1210 /* Set up cmail mode */
1211 ReloadCmailMsgEvent(TRUE);
1213 /* Set up other modes */
1214 if (initialMode == AnalyzeFile) {
1215 if (*appData.loadGameFile == NULLCHAR) {
1216 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1220 if (*appData.loadGameFile != NULLCHAR) {
1221 (void) LoadGameFromFile(appData.loadGameFile,
1222 appData.loadGameIndex,
1223 appData.loadGameFile, TRUE);
1224 } else if (*appData.loadPositionFile != NULLCHAR) {
1225 (void) LoadPositionFromFile(appData.loadPositionFile,
1226 appData.loadPositionIndex,
1227 appData.loadPositionFile);
1228 /* [HGM] try to make self-starting even after FEN load */
1229 /* to allow automatic setup of fairy variants with wtm */
1230 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231 gameMode = BeginningOfGame;
1232 setboardSpoiledMachineBlack = 1;
1234 /* [HGM] loadPos: make that every new game uses the setup */
1235 /* from file as long as we do not switch variant */
1236 if(!blackPlaysFirst) { int i;
1237 startedFromPositionFile = TRUE;
1238 CopyBoard(filePosition, boards[0]);
1239 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1242 if (initialMode == AnalyzeMode) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1252 } else if (initialMode == AnalyzeFile) {
1253 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254 ShowThinkingEvent();
1256 AnalysisPeriodicEvent(1);
1257 } else if (initialMode == MachinePlaysWhite) {
1258 if (appData.noChessProgram) {
1259 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1263 if (appData.icsActive) {
1264 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1268 MachineWhiteEvent();
1269 } else if (initialMode == MachinePlaysBlack) {
1270 if (appData.noChessProgram) {
1271 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1275 if (appData.icsActive) {
1276 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1280 MachineBlackEvent();
1281 } else if (initialMode == TwoMachinesPlay) {
1282 if (appData.noChessProgram) {
1283 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1287 if (appData.icsActive) {
1288 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1293 } else if (initialMode == EditGame) {
1295 } else if (initialMode == EditPosition) {
1296 EditPositionEvent();
1297 } else if (initialMode == Training) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308 * Establish will establish a contact to a remote host.port.
1309 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310 * used to talk to the host.
1311 * Returns 0 if okay, error code if not.
1318 if (*appData.icsCommPort != NULLCHAR) {
1319 /* Talk to the host through a serial comm port */
1320 return OpenCommPort(appData.icsCommPort, &icsPR);
1322 } else if (*appData.gateway != NULLCHAR) {
1323 if (*appData.remoteShell == NULLCHAR) {
1324 /* Use the rcmd protocol to run telnet program on a gateway host */
1325 snprintf(buf, sizeof(buf), "%s %s %s",
1326 appData.telnetProgram, appData.icsHost, appData.icsPort);
1327 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1330 /* Use the rsh program to run telnet program on a gateway host */
1331 if (*appData.remoteUser == NULLCHAR) {
1332 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333 appData.gateway, appData.telnetProgram,
1334 appData.icsHost, appData.icsPort);
1336 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337 appData.remoteShell, appData.gateway,
1338 appData.remoteUser, appData.telnetProgram,
1339 appData.icsHost, appData.icsPort);
1341 return StartChildProcess(buf, "", &icsPR);
1344 } else if (appData.useTelnet) {
1345 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1348 /* TCP socket interface differs somewhat between
1349 Unix and NT; handle details in the front end.
1351 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1356 show_bytes(fp, buf, count)
1362 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363 fprintf(fp, "\\%03o", *buf & 0xff);
1372 /* Returns an errno value */
1374 OutputMaybeTelnet(pr, message, count, outError)
1380 char buf[8192], *p, *q, *buflim;
1381 int left, newcount, outcount;
1383 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384 *appData.gateway != NULLCHAR) {
1385 if (appData.debugMode) {
1386 fprintf(debugFP, ">ICS: ");
1387 show_bytes(debugFP, message, count);
1388 fprintf(debugFP, "\n");
1390 return OutputToProcess(pr, message, count, outError);
1393 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1400 if (appData.debugMode) {
1401 fprintf(debugFP, ">ICS: ");
1402 show_bytes(debugFP, buf, newcount);
1403 fprintf(debugFP, "\n");
1405 outcount = OutputToProcess(pr, buf, newcount, outError);
1406 if (outcount < newcount) return -1; /* to be sure */
1413 } else if (((unsigned char) *p) == TN_IAC) {
1414 *q++ = (char) TN_IAC;
1421 if (appData.debugMode) {
1422 fprintf(debugFP, ">ICS: ");
1423 show_bytes(debugFP, buf, newcount);
1424 fprintf(debugFP, "\n");
1426 outcount = OutputToProcess(pr, buf, newcount, outError);
1427 if (outcount < newcount) return -1; /* to be sure */
1432 read_from_player(isr, closure, message, count, error)
1439 int outError, outCount;
1440 static int gotEof = 0;
1442 /* Pass data read from player on to ICS */
1445 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 } else if (count < 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452 } else if (gotEof++ > 0) {
1453 RemoveInputSource(isr);
1454 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1460 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461 SendToICS("date\n");
1462 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469 int count, outCount, outError;
1471 if (icsPR == NULL) return;
1474 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475 if (outCount < count) {
1476 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480 /* This is used for sending logon scripts to the ICS. Sending
1481 without a delay causes problems when using timestamp on ICC
1482 (at least on my machine). */
1484 SendToICSDelayed(s,msdelay)
1488 int count, outCount, outError;
1490 if (icsPR == NULL) return;
1493 if (appData.debugMode) {
1494 fprintf(debugFP, ">ICS: ");
1495 show_bytes(debugFP, s, count);
1496 fprintf(debugFP, "\n");
1498 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1500 if (outCount < count) {
1501 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 /* Remove all highlighting escape sequences in s
1507 Also deletes any suffix starting with '('
1510 StripHighlightAndTitle(s)
1513 static char retbuf[MSG_SIZ];
1516 while (*s != NULLCHAR) {
1517 while (*s == '\033') {
1518 while (*s != NULLCHAR && !isalpha(*s)) s++;
1519 if (*s != NULLCHAR) s++;
1521 while (*s != NULLCHAR && *s != '\033') {
1522 if (*s == '(' || *s == '[') {
1533 /* Remove all highlighting escape sequences in s */
1538 static char retbuf[MSG_SIZ];
1541 while (*s != NULLCHAR) {
1542 while (*s == '\033') {
1543 while (*s != NULLCHAR && !isalpha(*s)) s++;
1544 if (*s != NULLCHAR) s++;
1546 while (*s != NULLCHAR && *s != '\033') {
1554 char *variantNames[] = VARIANT_NAMES;
1559 return variantNames[v];
1563 /* Identify a variant from the strings the chess servers use or the
1564 PGN Variant tag names we use. */
1571 VariantClass v = VariantNormal;
1572 int i, found = FALSE;
1577 /* [HGM] skip over optional board-size prefixes */
1578 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580 while( *e++ != '_');
1583 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584 if (StrCaseStr(e, variantNames[i])) {
1585 v = (VariantClass) i;
1592 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593 || StrCaseStr(e, "wild/fr")
1594 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595 v = VariantFischeRandom;
1596 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597 (i = 1, p = StrCaseStr(e, "w"))) {
1599 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606 case 0: /* FICS only, actually */
1608 /* Castling legal even if K starts on d-file */
1609 v = VariantWildCastle;
1614 /* Castling illegal even if K & R happen to start in
1615 normal positions. */
1616 v = VariantNoCastle;
1629 /* Castling legal iff K & R start in normal positions */
1635 /* Special wilds for position setup; unclear what to do here */
1636 v = VariantLoadable;
1639 /* Bizarre ICC game */
1640 v = VariantTwoKings;
1643 v = VariantKriegspiel;
1649 v = VariantFischeRandom;
1652 v = VariantCrazyhouse;
1655 v = VariantBughouse;
1661 /* Not quite the same as FICS suicide! */
1662 v = VariantGiveaway;
1668 v = VariantShatranj;
1671 /* Temporary names for future ICC types. The name *will* change in
1672 the next xboard/WinBoard release after ICC defines it. */
1710 v = VariantCapablanca;
1713 v = VariantKnightmate;
1719 v = VariantCylinder;
1725 v = VariantCapaRandom;
1728 v = VariantBerolina;
1740 /* Found "wild" or "w" in the string but no number;
1741 must assume it's normal chess. */
1745 sprintf(buf, _("Unknown wild type %d"), wnum);
1746 DisplayError(buf, 0);
1752 if (appData.debugMode) {
1753 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754 e, wnum, VariantName(v));
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763 advance *index beyond it, and set leftover_start to the new value of
1764 *index; else return FALSE. If pattern contains the character '*', it
1765 matches any sequence of characters not containing '\r', '\n', or the
1766 character following the '*' (if any), and the matched sequence(s) are
1767 copied into star_match.
1770 looking_at(buf, index, pattern)
1775 char *bufp = &buf[*index], *patternp = pattern;
1777 char *matchp = star_match[0];
1780 if (*patternp == NULLCHAR) {
1781 *index = leftover_start = bufp - buf;
1785 if (*bufp == NULLCHAR) return FALSE;
1786 if (*patternp == '*') {
1787 if (*bufp == *(patternp + 1)) {
1789 matchp = star_match[++star_count];
1793 } else if (*bufp == '\n' || *bufp == '\r') {
1795 if (*patternp == NULLCHAR)
1800 *matchp++ = *bufp++;
1804 if (*patternp != *bufp) return FALSE;
1811 SendToPlayer(data, length)
1815 int error, outCount;
1816 outCount = OutputToProcess(NoProc, data, length, &error);
1817 if (outCount < length) {
1818 DisplayFatalError(_("Error writing to display"), error, 1);
1823 PackHolding(packed, holding)
1835 switch (runlength) {
1846 sprintf(q, "%d", runlength);
1858 /* Telnet protocol requests from the front end */
1860 TelnetRequest(ddww, option)
1861 unsigned char ddww, option;
1863 unsigned char msg[3];
1864 int outCount, outError;
1866 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1868 if (appData.debugMode) {
1869 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1885 sprintf(buf1, "%d", ddww);
1894 sprintf(buf2, "%d", option);
1897 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1902 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1904 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911 if (!appData.icsActive) return;
1912 TelnetRequest(TN_DO, TN_ECHO);
1918 if (!appData.icsActive) return;
1919 TelnetRequest(TN_DONT, TN_ECHO);
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1925 /* put the holdings sent to us by the server on the board holdings area */
1926 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1930 if(gameInfo.holdingsWidth < 2) return;
1932 if( (int)lowestPiece >= BlackPawn ) {
1935 holdingsStartRow = BOARD_HEIGHT-1;
1938 holdingsColumn = BOARD_WIDTH-1;
1939 countsColumn = BOARD_WIDTH-2;
1940 holdingsStartRow = 0;
1944 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945 board[i][holdingsColumn] = EmptySquare;
1946 board[i][countsColumn] = (ChessSquare) 0;
1948 while( (p=*holdings++) != NULLCHAR ) {
1949 piece = CharToPiece( ToUpper(p) );
1950 if(piece == EmptySquare) continue;
1951 /*j = (int) piece - (int) WhitePawn;*/
1952 j = PieceToNumber(piece);
1953 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954 if(j < 0) continue; /* should not happen */
1955 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957 board[holdingsStartRow+j*direction][countsColumn]++;
1964 VariantSwitch(Board board, VariantClass newVariant)
1966 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1970 startedFromPositionFile = FALSE;
1971 if(gameInfo.variant == newVariant) return;
1973 /* [HGM] This routine is called each time an assignment is made to
1974 * gameInfo.variant during a game, to make sure the board sizes
1975 * are set to match the new variant. If that means adding or deleting
1976 * holdings, we shift the playing board accordingly
1977 * This kludge is needed because in ICS observe mode, we get boards
1978 * of an ongoing game without knowing the variant, and learn about the
1979 * latter only later. This can be because of the move list we requested,
1980 * in which case the game history is refilled from the beginning anyway,
1981 * but also when receiving holdings of a crazyhouse game. In the latter
1982 * case we want to add those holdings to the already received position.
1986 if (appData.debugMode) {
1987 fprintf(debugFP, "Switch board from %s to %s\n",
1988 VariantName(gameInfo.variant), VariantName(newVariant));
1989 setbuf(debugFP, NULL);
1991 shuffleOpenings = 0; /* [HGM] shuffle */
1992 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993 switch(newVariant) {
1995 newWidth = 9; newHeight = 9;
1996 gameInfo.holdingsSize = 7;
1997 case VariantBughouse:
1998 case VariantCrazyhouse:
1999 newHoldingsWidth = 2; break;
2001 newHoldingsWidth = gameInfo.holdingsSize = 0;
2004 if(newWidth != gameInfo.boardWidth ||
2005 newHeight != gameInfo.boardHeight ||
2006 newHoldingsWidth != gameInfo.holdingsWidth ) {
2008 /* shift position to new playing area, if needed */
2009 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010 for(i=0; i<BOARD_HEIGHT; i++)
2011 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 for(i=0; i<newHeight; i++) {
2015 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019 for(i=0; i<BOARD_HEIGHT; i++)
2020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2031 /* [HGM] The following should definitely be solved in a better way */
2033 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035 saveEP = epStatus[0];
2037 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2039 epStatus[0] = saveEP;
2040 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041 CopyBoard(tempBoard, board); /* restore position received from ICS */
2043 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2045 forwardMostMove = oldForwardMostMove;
2046 backwardMostMove = oldBackwardMostMove;
2047 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2050 static int loggedOn = FALSE;
2052 /*-- Game start info cache: --*/
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2065 read_from_ics(isr, closure, data, count, error)
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2082 static int started = STARTED_NONE;
2083 static char parse[20000];
2084 static int parse_pos = 0;
2085 static char buf[BUF_SIZE + 1];
2086 static int firstTime = TRUE, intfSet = FALSE;
2087 static ColorClass prevColor = ColorNormal;
2088 static int savingComment = FALSE;
2094 int backup; /* [DM] For zippy color lines */
2096 char talker[MSG_SIZ]; // [HGM] chat
2099 if (appData.debugMode) {
2101 fprintf(debugFP, "<ICS: ");
2102 show_bytes(debugFP, data, count);
2103 fprintf(debugFP, "\n");
2107 if (appData.debugMode) { int f = forwardMostMove;
2108 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2112 /* If last read ended with a partial line that we couldn't parse,
2113 prepend it to the new read and try again. */
2114 if (leftover_len > 0) {
2115 for (i=0; i<leftover_len; i++)
2116 buf[i] = buf[leftover_start + i];
2119 /* Copy in new characters, removing nulls and \r's */
2120 buf_len = leftover_len;
2121 for (i = 0; i < count; i++) {
2122 if (data[i] != NULLCHAR && data[i] != '\r')
2123 buf[buf_len++] = data[i];
2124 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2125 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2126 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2131 buf[buf_len] = NULLCHAR;
2132 next_out = leftover_len;
2136 while (i < buf_len) {
2137 /* Deal with part of the TELNET option negotiation
2138 protocol. We refuse to do anything beyond the
2139 defaults, except that we allow the WILL ECHO option,
2140 which ICS uses to turn off password echoing when we are
2141 directly connected to it. We reject this option
2142 if localLineEditing mode is on (always on in xboard)
2143 and we are talking to port 23, which might be a real
2144 telnet server that will try to keep WILL ECHO on permanently.
2146 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148 unsigned char option;
2150 switch ((unsigned char) buf[++i]) {
2152 if (appData.debugMode)
2153 fprintf(debugFP, "\n<WILL ");
2154 switch (option = (unsigned char) buf[++i]) {
2156 if (appData.debugMode)
2157 fprintf(debugFP, "ECHO ");
2158 /* Reply only if this is a change, according
2159 to the protocol rules. */
2160 if (remoteEchoOption) break;
2161 if (appData.localLineEditing &&
2162 atoi(appData.icsPort) == TN_PORT) {
2163 TelnetRequest(TN_DONT, TN_ECHO);
2166 TelnetRequest(TN_DO, TN_ECHO);
2167 remoteEchoOption = TRUE;
2171 if (appData.debugMode)
2172 fprintf(debugFP, "%d ", option);
2173 /* Whatever this is, we don't want it. */
2174 TelnetRequest(TN_DONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<WONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "ECHO ");
2185 /* Reply only if this is a change, according
2186 to the protocol rules. */
2187 if (!remoteEchoOption) break;
2189 TelnetRequest(TN_DONT, TN_ECHO);
2190 remoteEchoOption = FALSE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", (unsigned char) option);
2195 /* Whatever this is, it must already be turned
2196 off, because we never agree to turn on
2197 anything non-default, so according to the
2198 protocol rules, we don't reply. */
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DO ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 /* Whatever this is, we refuse to do it. */
2208 if (appData.debugMode)
2209 fprintf(debugFP, "%d ", option);
2210 TelnetRequest(TN_WONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<DONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "%d ", option);
2221 /* Whatever this is, we are already not doing
2222 it, because we never agree to do anything
2223 non-default, so according to the protocol
2224 rules, we don't reply. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "\n<IAC ");
2231 /* Doubled IAC; pass it through */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237 /* Drop all other telnet commands on the floor */
2240 if (oldi > next_out)
2241 SendToPlayer(&buf[next_out], oldi - next_out);
2247 /* OK, this at least will *usually* work */
2248 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2252 if (loggedOn && !intfSet) {
2253 if (ics_type == ICS_ICC) {
2255 "/set-quietly interface %s\n/set-quietly style 12\n",
2258 } else if (ics_type == ICS_CHESSNET) {
2259 sprintf(str, "/style 12\n");
2261 strcpy(str, "alias $ @\n$set interface ");
2262 strcat(str, programVersion);
2263 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2265 strcat(str, "$iset nohighlight 1\n");
2267 strcat(str, "$iset lock 1\n$style 12\n");
2273 if (started == STARTED_COMMENT) {
2274 /* Accumulate characters in comment */
2275 parse[parse_pos++] = buf[i];
2276 if (buf[i] == '\n') {
2277 parse[parse_pos] = NULLCHAR;
2278 if(chattingPartner>=0) {
2280 sprintf(mess, "%s%s", talker, parse);
2281 OutputChatMessage(chattingPartner, mess);
2282 chattingPartner = -1;
2284 if(!suppressKibitz) // [HGM] kibitz
2285 AppendComment(forwardMostMove, StripHighlight(parse));
2286 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287 int nrDigit = 0, nrAlph = 0, i;
2288 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290 parse[parse_pos] = NULLCHAR;
2291 // try to be smart: if it does not look like search info, it should go to
2292 // ICS interaction window after all, not to engine-output window.
2293 for(i=0; i<parse_pos; i++) { // count letters and digits
2294 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2296 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2298 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299 int depth=0; float score;
2300 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302 pvInfoList[forwardMostMove-1].depth = depth;
2303 pvInfoList[forwardMostMove-1].score = 100*score;
2305 OutputKibitz(suppressKibitz, parse);
2308 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309 SendToPlayer(tmp, strlen(tmp));
2312 started = STARTED_NONE;
2314 /* Don't match patterns against characters in chatter */
2319 if (started == STARTED_CHATTER) {
2320 if (buf[i] != '\n') {
2321 /* Don't match patterns against characters in chatter */
2325 started = STARTED_NONE;
2328 /* Kludge to deal with rcmd protocol */
2329 if (firstTime && looking_at(buf, &i, "\001*")) {
2330 DisplayFatalError(&buf[1], 0, 1);
2336 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2339 if (appData.debugMode)
2340 fprintf(debugFP, "ics_type %d\n", ics_type);
2343 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344 ics_type = ICS_FICS;
2346 if (appData.debugMode)
2347 fprintf(debugFP, "ics_type %d\n", ics_type);
2350 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351 ics_type = ICS_CHESSNET;
2353 if (appData.debugMode)
2354 fprintf(debugFP, "ics_type %d\n", ics_type);
2359 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360 looking_at(buf, &i, "Logging you in as \"*\"") ||
2361 looking_at(buf, &i, "will be \"*\""))) {
2362 strcpy(ics_handle, star_match[0]);
2366 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2368 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369 DisplayIcsInteractionTitle(buf);
2370 have_set_title = TRUE;
2373 /* skip finger notes */
2374 if (started == STARTED_NONE &&
2375 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376 (buf[i] == '1' && buf[i+1] == '0')) &&
2377 buf[i+2] == ':' && buf[i+3] == ' ') {
2378 started = STARTED_CHATTER;
2383 /* skip formula vars */
2384 if (started == STARTED_NONE &&
2385 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386 started = STARTED_CHATTER;
2392 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393 if (appData.autoKibitz && started == STARTED_NONE &&
2394 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2395 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396 if(looking_at(buf, &i, "* kibitzes: ") &&
2397 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2398 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2399 suppressKibitz = TRUE;
2400 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401 && (gameMode == IcsPlayingWhite)) ||
2402 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2404 started = STARTED_CHATTER; // own kibitz we simply discard
2406 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407 parse_pos = 0; parse[0] = NULLCHAR;
2408 savingComment = TRUE;
2409 suppressKibitz = gameMode != IcsObserving ? 2 :
2410 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2414 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415 started = STARTED_CHATTER;
2416 suppressKibitz = TRUE;
2418 } // [HGM] kibitz: end of patch
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2422 // [HGM] chat: intercept tells by users for which we have an open chat window
2424 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2425 looking_at(buf, &i, "* whispers:") ||
2426 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2429 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430 chattingPartner = -1;
2432 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433 for(p=0; p<MAX_CHAT; p++) {
2434 if(channel == atoi(chatPartner[p])) {
2435 talker[0] = '['; strcat(talker, "]");
2436 chattingPartner = p; break;
2439 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440 for(p=0; p<MAX_CHAT; p++) {
2441 if(!strcmp("WHISPER", chatPartner[p])) {
2442 talker[0] = '['; strcat(talker, "]");
2443 chattingPartner = p; break;
2446 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2449 chattingPartner = p; break;
2451 if(chattingPartner<0) i = oldi; else {
2452 started = STARTED_COMMENT;
2453 parse_pos = 0; parse[0] = NULLCHAR;
2454 savingComment = TRUE;
2455 suppressKibitz = TRUE;
2457 } // [HGM] chat: end of patch
2459 if (appData.zippyTalk || appData.zippyPlay) {
2460 /* [DM] Backup address for color zippy lines */
2464 if (loggedOn == TRUE)
2465 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2468 if (ZippyControl(buf, &i) ||
2469 ZippyConverse(buf, &i) ||
2470 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2472 if (!appData.colorize) continue;
2476 } // [DM] 'else { ' deleted
2478 /* Regular tells and says */
2479 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480 looking_at(buf, &i, "* (your partner) tells you: ") ||
2481 looking_at(buf, &i, "* says: ") ||
2482 /* Don't color "message" or "messages" output */
2483 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484 looking_at(buf, &i, "*. * at *:*: ") ||
2485 looking_at(buf, &i, "--* (*:*): ") ||
2486 /* Message notifications (same color as tells) */
2487 looking_at(buf, &i, "* has left a message ") ||
2488 looking_at(buf, &i, "* just sent you a message:\n") ||
2489 /* Whispers and kibitzes */
2490 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491 looking_at(buf, &i, "* kibitzes: ") ||
2493 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2495 if (tkind == 1 && strchr(star_match[0], ':')) {
2496 /* Avoid "tells you:" spoofs in channels */
2499 if (star_match[0][0] == NULLCHAR ||
2500 strchr(star_match[0], ' ') ||
2501 (tkind == 3 && strchr(star_match[1], ' '))) {
2502 /* Reject bogus matches */
2505 if (appData.colorize) {
2506 if (oldi > next_out) {
2507 SendToPlayer(&buf[next_out], oldi - next_out);
2512 Colorize(ColorTell, FALSE);
2513 curColor = ColorTell;
2516 Colorize(ColorKibitz, FALSE);
2517 curColor = ColorKibitz;
2520 p = strrchr(star_match[1], '(');
2527 Colorize(ColorChannel1, FALSE);
2528 curColor = ColorChannel1;
2530 Colorize(ColorChannel, FALSE);
2531 curColor = ColorChannel;
2535 curColor = ColorNormal;
2539 if (started == STARTED_NONE && appData.autoComment &&
2540 (gameMode == IcsObserving ||
2541 gameMode == IcsPlayingWhite ||
2542 gameMode == IcsPlayingBlack)) {
2543 parse_pos = i - oldi;
2544 memcpy(parse, &buf[oldi], parse_pos);
2545 parse[parse_pos] = NULLCHAR;
2546 started = STARTED_COMMENT;
2547 savingComment = TRUE;
2549 started = STARTED_CHATTER;
2550 savingComment = FALSE;
2557 if (looking_at(buf, &i, "* s-shouts: ") ||
2558 looking_at(buf, &i, "* c-shouts: ")) {
2559 if (appData.colorize) {
2560 if (oldi > next_out) {
2561 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorSShout, FALSE);
2565 curColor = ColorSShout;
2568 started = STARTED_CHATTER;
2572 if (looking_at(buf, &i, "--->")) {
2577 if (looking_at(buf, &i, "* shouts: ") ||
2578 looking_at(buf, &i, "--> ")) {
2579 if (appData.colorize) {
2580 if (oldi > next_out) {
2581 SendToPlayer(&buf[next_out], oldi - next_out);
2584 Colorize(ColorShout, FALSE);
2585 curColor = ColorShout;
2588 started = STARTED_CHATTER;
2592 if (looking_at( buf, &i, "Challenge:")) {
2593 if (appData.colorize) {
2594 if (oldi > next_out) {
2595 SendToPlayer(&buf[next_out], oldi - next_out);
2598 Colorize(ColorChallenge, FALSE);
2599 curColor = ColorChallenge;
2605 if (looking_at(buf, &i, "* offers you") ||
2606 looking_at(buf, &i, "* offers to be") ||
2607 looking_at(buf, &i, "* would like to") ||
2608 looking_at(buf, &i, "* requests to") ||
2609 looking_at(buf, &i, "Your opponent offers") ||
2610 looking_at(buf, &i, "Your opponent requests")) {
2612 if (appData.colorize) {
2613 if (oldi > next_out) {
2614 SendToPlayer(&buf[next_out], oldi - next_out);
2617 Colorize(ColorRequest, FALSE);
2618 curColor = ColorRequest;
2623 if (looking_at(buf, &i, "* (*) seeking")) {
2624 if (appData.colorize) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 Colorize(ColorSeek, FALSE);
2630 curColor = ColorSeek;
2635 if (looking_at(buf, &i, "\\ ")) {
2636 if (prevColor != ColorNormal) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(prevColor, TRUE);
2642 curColor = prevColor;
2644 if (savingComment) {
2645 parse_pos = i - oldi;
2646 memcpy(parse, &buf[oldi], parse_pos);
2647 parse[parse_pos] = NULLCHAR;
2648 started = STARTED_COMMENT;
2650 started = STARTED_CHATTER;
2655 if (looking_at(buf, &i, "Black Strength :") ||
2656 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657 looking_at(buf, &i, "<10>") ||
2658 looking_at(buf, &i, "#@#")) {
2659 /* Wrong board style */
2661 SendToICS(ics_prefix);
2662 SendToICS("set style 12\n");
2663 SendToICS(ics_prefix);
2664 SendToICS("refresh\n");
2668 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2670 have_sent_ICS_logon = 1;
2674 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2675 (looking_at(buf, &i, "\n<12> ") ||
2676 looking_at(buf, &i, "<12> "))) {
2678 if (oldi > next_out) {
2679 SendToPlayer(&buf[next_out], oldi - next_out);
2682 started = STARTED_BOARD;
2687 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688 looking_at(buf, &i, "<b1> ")) {
2689 if (oldi > next_out) {
2690 SendToPlayer(&buf[next_out], oldi - next_out);
2693 started = STARTED_HOLDINGS;
2698 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2700 /* Header for a move list -- first line */
2702 switch (ics_getting_history) {
2706 case BeginningOfGame:
2707 /* User typed "moves" or "oldmoves" while we
2708 were idle. Pretend we asked for these
2709 moves and soak them up so user can step
2710 through them and/or save them.
2713 gameMode = IcsObserving;
2716 ics_getting_history = H_GOT_UNREQ_HEADER;
2718 case EditGame: /*?*/
2719 case EditPosition: /*?*/
2720 /* Should above feature work in these modes too? */
2721 /* For now it doesn't */
2722 ics_getting_history = H_GOT_UNWANTED_HEADER;
2725 ics_getting_history = H_GOT_UNWANTED_HEADER;
2730 /* Is this the right one? */
2731 if (gameInfo.white && gameInfo.black &&
2732 strcmp(gameInfo.white, star_match[0]) == 0 &&
2733 strcmp(gameInfo.black, star_match[2]) == 0) {
2735 ics_getting_history = H_GOT_REQ_HEADER;
2738 case H_GOT_REQ_HEADER:
2739 case H_GOT_UNREQ_HEADER:
2740 case H_GOT_UNWANTED_HEADER:
2741 case H_GETTING_MOVES:
2742 /* Should not happen */
2743 DisplayError(_("Error gathering move list: two headers"), 0);
2744 ics_getting_history = H_FALSE;
2748 /* Save player ratings into gameInfo if needed */
2749 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751 (gameInfo.whiteRating == -1 ||
2752 gameInfo.blackRating == -1)) {
2754 gameInfo.whiteRating = string_to_rating(star_match[1]);
2755 gameInfo.blackRating = string_to_rating(star_match[3]);
2756 if (appData.debugMode)
2757 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2758 gameInfo.whiteRating, gameInfo.blackRating);
2763 if (looking_at(buf, &i,
2764 "* * match, initial time: * minute*, increment: * second")) {
2765 /* Header for a move list -- second line */
2766 /* Initial board will follow if this is a wild game */
2767 if (gameInfo.event != NULL) free(gameInfo.event);
2768 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769 gameInfo.event = StrSave(str);
2770 /* [HGM] we switched variant. Translate boards if needed. */
2771 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2775 if (looking_at(buf, &i, "Move ")) {
2776 /* Beginning of a move list */
2777 switch (ics_getting_history) {
2779 /* Normally should not happen */
2780 /* Maybe user hit reset while we were parsing */
2783 /* Happens if we are ignoring a move list that is not
2784 * the one we just requested. Common if the user
2785 * tries to observe two games without turning off
2788 case H_GETTING_MOVES:
2789 /* Should not happen */
2790 DisplayError(_("Error gathering move list: nested"), 0);
2791 ics_getting_history = H_FALSE;
2793 case H_GOT_REQ_HEADER:
2794 ics_getting_history = H_GETTING_MOVES;
2795 started = STARTED_MOVES;
2797 if (oldi > next_out) {
2798 SendToPlayer(&buf[next_out], oldi - next_out);
2801 case H_GOT_UNREQ_HEADER:
2802 ics_getting_history = H_GETTING_MOVES;
2803 started = STARTED_MOVES_NOHIDE;
2806 case H_GOT_UNWANTED_HEADER:
2807 ics_getting_history = H_FALSE;
2813 if (looking_at(buf, &i, "% ") ||
2814 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816 savingComment = FALSE;
2819 case STARTED_MOVES_NOHIDE:
2820 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821 parse[parse_pos + i - oldi] = NULLCHAR;
2822 ParseGameHistory(parse);
2824 if (appData.zippyPlay && first.initDone) {
2825 FeedMovesToProgram(&first, forwardMostMove);
2826 if (gameMode == IcsPlayingWhite) {
2827 if (WhiteOnMove(forwardMostMove)) {
2828 if (first.sendTime) {
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 SendTimeRemaining(&first, TRUE);
2835 if (first.useColors) {
2836 SendToProgram("white\ngo\n", &first);
2838 SendToProgram("go\n", &first);
2841 if (first.useColors) {
2842 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2844 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2846 first.maybeThinking = TRUE;
2848 if (first.usePlayother) {
2849 if (first.sendTime) {
2850 SendTimeRemaining(&first, TRUE);
2852 SendToProgram("playother\n", &first);
2858 } else if (gameMode == IcsPlayingBlack) {
2859 if (!WhiteOnMove(forwardMostMove)) {
2860 if (first.sendTime) {
2861 if (first.useColors) {
2862 SendToProgram("white\n", &first);
2864 SendTimeRemaining(&first, FALSE);
2867 if (first.useColors) {
2868 SendToProgram("black\ngo\n", &first);
2870 SendToProgram("go\n", &first);
2873 if (first.useColors) {
2874 SendToProgram("black\n", &first);
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2878 first.maybeThinking = TRUE;
2880 if (first.usePlayother) {
2881 if (first.sendTime) {
2882 SendTimeRemaining(&first, FALSE);
2884 SendToProgram("playother\n", &first);
2893 if (gameMode == IcsObserving && ics_gamenum == -1) {
2894 /* Moves came from oldmoves or moves command
2895 while we weren't doing anything else.
2897 currentMove = forwardMostMove;
2898 ClearHighlights();/*!!could figure this out*/
2899 flipView = appData.flipView;
2900 DrawPosition(FALSE, boards[currentMove]);
2901 DisplayBothClocks();
2902 sprintf(str, "%s vs. %s",
2903 gameInfo.white, gameInfo.black);
2907 /* Moves were history of an active game */
2908 if (gameInfo.resultDetails != NULL) {
2909 free(gameInfo.resultDetails);
2910 gameInfo.resultDetails = NULL;
2913 HistorySet(parseList, backwardMostMove,
2914 forwardMostMove, currentMove-1);
2915 DisplayMove(currentMove - 1);
2916 if (started == STARTED_MOVES) next_out = i;
2917 started = STARTED_NONE;
2918 ics_getting_history = H_FALSE;
2921 case STARTED_OBSERVE:
2922 started = STARTED_NONE;
2923 SendToICS(ics_prefix);
2924 SendToICS("refresh\n");
2930 if(bookHit) { // [HGM] book: simulate book reply
2931 static char bookMove[MSG_SIZ]; // a bit generous?
2933 programStats.nodes = programStats.depth = programStats.time =
2934 programStats.score = programStats.got_only_move = 0;
2935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2937 strcpy(bookMove, "move ");
2938 strcat(bookMove, bookHit);
2939 HandleMachineMove(bookMove, &first);
2944 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945 started == STARTED_HOLDINGS ||
2946 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947 /* Accumulate characters in move list or board */
2948 parse[parse_pos++] = buf[i];
2951 /* Start of game messages. Mostly we detect start of game
2952 when the first board image arrives. On some versions
2953 of the ICS, though, we need to do a "refresh" after starting
2954 to observe in order to get the current board right away. */
2955 if (looking_at(buf, &i, "Adding game * to observation list")) {
2956 started = STARTED_OBSERVE;
2960 /* Handle auto-observe */
2961 if (appData.autoObserve &&
2962 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2965 /* Choose the player that was highlighted, if any. */
2966 if (star_match[0][0] == '\033' ||
2967 star_match[1][0] != '\033') {
2968 player = star_match[0];
2970 player = star_match[2];
2972 sprintf(str, "%sobserve %s\n",
2973 ics_prefix, StripHighlightAndTitle(player));
2976 /* Save ratings from notify string */
2977 strcpy(player1Name, star_match[0]);
2978 player1Rating = string_to_rating(star_match[1]);
2979 strcpy(player2Name, star_match[2]);
2980 player2Rating = string_to_rating(star_match[3]);
2982 if (appData.debugMode)
2984 "Ratings from 'Game notification:' %s %d, %s %d\n",
2985 player1Name, player1Rating,
2986 player2Name, player2Rating);
2991 /* Deal with automatic examine mode after a game,
2992 and with IcsObserving -> IcsExamining transition */
2993 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994 looking_at(buf, &i, "has made you an examiner of game *")) {
2996 int gamenum = atoi(star_match[0]);
2997 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998 gamenum == ics_gamenum) {
2999 /* We were already playing or observing this game;
3000 no need to refetch history */
3001 gameMode = IcsExamining;
3003 pauseExamForwardMostMove = forwardMostMove;
3004 } else if (currentMove < forwardMostMove) {
3005 ForwardInner(forwardMostMove);
3008 /* I don't think this case really can happen */
3009 SendToICS(ics_prefix);
3010 SendToICS("refresh\n");
3015 /* Error messages */
3016 // if (ics_user_moved) {
3017 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018 if (looking_at(buf, &i, "Illegal move") ||
3019 looking_at(buf, &i, "Not a legal move") ||
3020 looking_at(buf, &i, "Your king is in check") ||
3021 looking_at(buf, &i, "It isn't your turn") ||
3022 looking_at(buf, &i, "It is not your move")) {
3024 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025 currentMove = --forwardMostMove;
3026 DisplayMove(currentMove - 1); /* before DMError */
3027 DrawPosition(FALSE, boards[currentMove]);
3029 DisplayBothClocks();
3031 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037 if (looking_at(buf, &i, "still have time") ||
3038 looking_at(buf, &i, "not out of time") ||
3039 looking_at(buf, &i, "either player is out of time") ||
3040 looking_at(buf, &i, "has timeseal; checking")) {
3041 /* We must have called his flag a little too soon */
3042 whiteFlag = blackFlag = FALSE;
3046 if (looking_at(buf, &i, "added * seconds to") ||
3047 looking_at(buf, &i, "seconds were added to")) {
3048 /* Update the clocks */
3049 SendToICS(ics_prefix);
3050 SendToICS("refresh\n");
3054 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055 ics_clock_paused = TRUE;
3060 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061 ics_clock_paused = FALSE;
3066 /* Grab player ratings from the Creating: message.
3067 Note we have to check for the special case when
3068 the ICS inserts things like [white] or [black]. */
3069 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3072 0 player 1 name (not necessarily white)
3074 2 empty, white, or black (IGNORED)
3075 3 player 2 name (not necessarily black)
3078 The names/ratings are sorted out when the game
3079 actually starts (below).
3081 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082 player1Rating = string_to_rating(star_match[1]);
3083 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084 player2Rating = string_to_rating(star_match[4]);
3086 if (appData.debugMode)
3088 "Ratings from 'Creating:' %s %d, %s %d\n",
3089 player1Name, player1Rating,
3090 player2Name, player2Rating);
3095 /* Improved generic start/end-of-game messages */
3096 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098 /* If tkind == 0: */
3099 /* star_match[0] is the game number */
3100 /* [1] is the white player's name */
3101 /* [2] is the black player's name */
3102 /* For end-of-game: */
3103 /* [3] is the reason for the game end */
3104 /* [4] is a PGN end game-token, preceded by " " */
3105 /* For start-of-game: */
3106 /* [3] begins with "Creating" or "Continuing" */
3107 /* [4] is " *" or empty (don't care). */
3108 int gamenum = atoi(star_match[0]);
3109 char *whitename, *blackname, *why, *endtoken;
3110 ChessMove endtype = (ChessMove) 0;
3113 whitename = star_match[1];
3114 blackname = star_match[2];
3115 why = star_match[3];
3116 endtoken = star_match[4];
3118 whitename = star_match[1];
3119 blackname = star_match[3];
3120 why = star_match[5];
3121 endtoken = star_match[6];
3124 /* Game start messages */
3125 if (strncmp(why, "Creating ", 9) == 0 ||
3126 strncmp(why, "Continuing ", 11) == 0) {
3127 gs_gamenum = gamenum;
3128 strcpy(gs_kind, strchr(why, ' ') + 1);
3130 if (appData.zippyPlay) {
3131 ZippyGameStart(whitename, blackname);
3137 /* Game end messages */
3138 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139 ics_gamenum != gamenum) {
3142 while (endtoken[0] == ' ') endtoken++;
3143 switch (endtoken[0]) {
3146 endtype = GameUnfinished;
3149 endtype = BlackWins;
3152 if (endtoken[1] == '/')
3153 endtype = GameIsDrawn;
3155 endtype = WhiteWins;
3158 GameEnds(endtype, why, GE_ICS);
3160 if (appData.zippyPlay && first.initDone) {
3161 ZippyGameEnd(endtype, why);
3162 if (first.pr == NULL) {
3163 /* Start the next process early so that we'll
3164 be ready for the next challenge */
3165 StartChessProgram(&first);
3167 /* Send "new" early, in case this command takes
3168 a long time to finish, so that we'll be ready
3169 for the next challenge. */
3170 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177 if (looking_at(buf, &i, "Removing game * from observation") ||
3178 looking_at(buf, &i, "no longer observing game *") ||
3179 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180 if (gameMode == IcsObserving &&
3181 atoi(star_match[0]) == ics_gamenum)
3183 /* icsEngineAnalyze */
3184 if (appData.icsEngineAnalyze) {
3191 ics_user_moved = FALSE;
3196 if (looking_at(buf, &i, "no longer examining game *")) {
3197 if (gameMode == IcsExamining &&
3198 atoi(star_match[0]) == ics_gamenum)
3202 ics_user_moved = FALSE;
3207 /* Advance leftover_start past any newlines we find,
3208 so only partial lines can get reparsed */
3209 if (looking_at(buf, &i, "\n")) {
3210 prevColor = curColor;
3211 if (curColor != ColorNormal) {
3212 if (oldi > next_out) {
3213 SendToPlayer(&buf[next_out], oldi - next_out);
3216 Colorize(ColorNormal, FALSE);
3217 curColor = ColorNormal;
3219 if (started == STARTED_BOARD) {
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 ParseBoard12(parse);
3225 /* Send premove here */
3226 if (appData.premove) {
3228 if (currentMove == 0 &&
3229 gameMode == IcsPlayingWhite &&
3230 appData.premoveWhite) {
3231 sprintf(str, "%s%s\n", ics_prefix,
3232 appData.premoveWhiteText);
3233 if (appData.debugMode)
3234 fprintf(debugFP, "Sending premove:\n");
3236 } else if (currentMove == 1 &&
3237 gameMode == IcsPlayingBlack &&
3238 appData.premoveBlack) {
3239 sprintf(str, "%s%s\n", ics_prefix,
3240 appData.premoveBlackText);
3241 if (appData.debugMode)
3242 fprintf(debugFP, "Sending premove:\n");
3244 } else if (gotPremove) {
3246 ClearPremoveHighlights();
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3249 UserMoveEvent(premoveFromX, premoveFromY,
3250 premoveToX, premoveToY,
3255 /* Usually suppress following prompt */
3256 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3262 } else if (started == STARTED_HOLDINGS) {
3264 char new_piece[MSG_SIZ];
3265 started = STARTED_NONE;
3266 parse[parse_pos] = NULLCHAR;
3267 if (appData.debugMode)
3268 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269 parse, currentMove);
3270 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271 gamenum == ics_gamenum) {
3272 if (gameInfo.variant == VariantNormal) {
3273 /* [HGM] We seem to switch variant during a game!
3274 * Presumably no holdings were displayed, so we have
3275 * to move the position two files to the right to
3276 * create room for them!
3278 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279 /* Get a move list just to see the header, which
3280 will tell us whether this is really bug or zh */
3281 if (ics_getting_history == H_FALSE) {
3282 ics_getting_history = H_REQUESTED;
3283 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3287 new_piece[0] = NULLCHAR;
3288 sscanf(parse, "game %d white [%s black [%s <- %s",
3289 &gamenum, white_holding, black_holding,
3291 white_holding[strlen(white_holding)-1] = NULLCHAR;
3292 black_holding[strlen(black_holding)-1] = NULLCHAR;
3293 /* [HGM] copy holdings to board holdings area */
3294 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3297 if (appData.zippyPlay && first.initDone) {
3298 ZippyHoldings(white_holding, black_holding,
3302 if (tinyLayout || smallLayout) {
3303 char wh[16], bh[16];
3304 PackHolding(wh, white_holding);
3305 PackHolding(bh, black_holding);
3306 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307 gameInfo.white, gameInfo.black);
3309 sprintf(str, "%s [%s] vs. %s [%s]",
3310 gameInfo.white, white_holding,
3311 gameInfo.black, black_holding);
3314 DrawPosition(FALSE, boards[currentMove]);
3317 /* Suppress following prompt */
3318 if (looking_at(buf, &i, "*% ")) {
3319 savingComment = FALSE;
3326 i++; /* skip unparsed character and loop back */
3329 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3330 started != STARTED_HOLDINGS && i > next_out) {
3331 SendToPlayer(&buf[next_out], i - next_out);
3334 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3336 leftover_len = buf_len - leftover_start;
3337 /* if buffer ends with something we couldn't parse,
3338 reparse it after appending the next read */
3340 } else if (count == 0) {
3341 RemoveInputSource(isr);
3342 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3344 DisplayFatalError(_("Error reading from ICS"), error, 1);
3349 /* Board style 12 looks like this:
3351 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3353 * The "<12> " is stripped before it gets to this routine. The two
3354 * trailing 0's (flip state and clock ticking) are later addition, and
3355 * some chess servers may not have them, or may have only the first.
3356 * Additional trailing fields may be added in the future.
3359 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3361 #define RELATION_OBSERVING_PLAYED 0
3362 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3363 #define RELATION_PLAYING_MYMOVE 1
3364 #define RELATION_PLAYING_NOTMYMOVE -1
3365 #define RELATION_EXAMINING 2
3366 #define RELATION_ISOLATED_BOARD -3
3367 #define RELATION_STARTING_POSITION -4 /* FICS only */
3370 ParseBoard12(string)
3373 GameMode newGameMode;
3374 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3375 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3376 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3377 char to_play, board_chars[200];
3378 char move_str[500], str[500], elapsed_time[500];
3379 char black[32], white[32];
3381 int prevMove = currentMove;
3384 int fromX, fromY, toX, toY;
3386 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3387 char *bookHit = NULL; // [HGM] book
3389 fromX = fromY = toX = toY = -1;
3393 if (appData.debugMode)
3394 fprintf(debugFP, _("Parsing board: %s\n"), string);
3396 move_str[0] = NULLCHAR;
3397 elapsed_time[0] = NULLCHAR;
3398 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3400 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3401 if(string[i] == ' ') { ranks++; files = 0; }
3405 for(j = 0; j <i; j++) board_chars[j] = string[j];
3406 board_chars[i] = '\0';
3409 n = sscanf(string, PATTERN, &to_play, &double_push,
3410 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3411 &gamenum, white, black, &relation, &basetime, &increment,
3412 &white_stren, &black_stren, &white_time, &black_time,
3413 &moveNum, str, elapsed_time, move_str, &ics_flip,
3417 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3418 DisplayError(str, 0);
3422 /* Convert the move number to internal form */
3423 moveNum = (moveNum - 1) * 2;
3424 if (to_play == 'B') moveNum++;
3425 if (moveNum >= MAX_MOVES) {
3426 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3432 case RELATION_OBSERVING_PLAYED:
3433 case RELATION_OBSERVING_STATIC:
3434 if (gamenum == -1) {
3435 /* Old ICC buglet */
3436 relation = RELATION_OBSERVING_STATIC;
3438 newGameMode = IcsObserving;
3440 case RELATION_PLAYING_MYMOVE:
3441 case RELATION_PLAYING_NOTMYMOVE:
3443 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3444 IcsPlayingWhite : IcsPlayingBlack;
3446 case RELATION_EXAMINING:
3447 newGameMode = IcsExamining;
3449 case RELATION_ISOLATED_BOARD:
3451 /* Just display this board. If user was doing something else,
3452 we will forget about it until the next board comes. */
3453 newGameMode = IcsIdle;
3455 case RELATION_STARTING_POSITION:
3456 newGameMode = gameMode;
3460 /* Modify behavior for initial board display on move listing
3463 switch (ics_getting_history) {
3467 case H_GOT_REQ_HEADER:
3468 case H_GOT_UNREQ_HEADER:
3469 /* This is the initial position of the current game */
3470 gamenum = ics_gamenum;
3471 moveNum = 0; /* old ICS bug workaround */
3472 if (to_play == 'B') {
3473 startedFromSetupPosition = TRUE;
3474 blackPlaysFirst = TRUE;
3476 if (forwardMostMove == 0) forwardMostMove = 1;
3477 if (backwardMostMove == 0) backwardMostMove = 1;
3478 if (currentMove == 0) currentMove = 1;
3480 newGameMode = gameMode;
3481 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3483 case H_GOT_UNWANTED_HEADER:
3484 /* This is an initial board that we don't want */
3486 case H_GETTING_MOVES:
3487 /* Should not happen */
3488 DisplayError(_("Error gathering move list: extra board"), 0);
3489 ics_getting_history = H_FALSE;
3493 /* Take action if this is the first board of a new game, or of a
3494 different game than is currently being displayed. */
3495 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3496 relation == RELATION_ISOLATED_BOARD) {
3498 /* Forget the old game and get the history (if any) of the new one */
3499 if (gameMode != BeginningOfGame) {
3503 if (appData.autoRaiseBoard) BoardToTop();
3505 if (gamenum == -1) {
3506 newGameMode = IcsIdle;
3507 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3508 appData.getMoveList) {
3509 /* Need to get game history */
3510 ics_getting_history = H_REQUESTED;
3511 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3515 /* Initially flip the board to have black on the bottom if playing
3516 black or if the ICS flip flag is set, but let the user change
3517 it with the Flip View button. */
3518 flipView = appData.autoFlipView ?
3519 (newGameMode == IcsPlayingBlack) || ics_flip :
3522 /* Done with values from previous mode; copy in new ones */
3523 gameMode = newGameMode;
3525 ics_gamenum = gamenum;
3526 if (gamenum == gs_gamenum) {
3527 int klen = strlen(gs_kind);
3528 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3529 sprintf(str, "ICS %s", gs_kind);
3530 gameInfo.event = StrSave(str);
3532 gameInfo.event = StrSave("ICS game");
3534 gameInfo.site = StrSave(appData.icsHost);
3535 gameInfo.date = PGNDate();
3536 gameInfo.round = StrSave("-");
3537 gameInfo.white = StrSave(white);
3538 gameInfo.black = StrSave(black);
3539 timeControl = basetime * 60 * 1000;
3541 timeIncrement = increment * 1000;
3542 movesPerSession = 0;
3543 gameInfo.timeControl = TimeControlTagValue();
3544 VariantSwitch(board, StringToVariant(gameInfo.event) );
3545 if (appData.debugMode) {
3546 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3547 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3548 setbuf(debugFP, NULL);
3551 gameInfo.outOfBook = NULL;
3553 /* Do we have the ratings? */
3554 if (strcmp(player1Name, white) == 0 &&
3555 strcmp(player2Name, black) == 0) {
3556 if (appData.debugMode)
3557 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3558 player1Rating, player2Rating);
3559 gameInfo.whiteRating = player1Rating;
3560 gameInfo.blackRating = player2Rating;
3561 } else if (strcmp(player2Name, white) == 0 &&
3562 strcmp(player1Name, black) == 0) {
3563 if (appData.debugMode)
3564 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3565 player2Rating, player1Rating);
3566 gameInfo.whiteRating = player2Rating;
3567 gameInfo.blackRating = player1Rating;
3569 player1Name[0] = player2Name[0] = NULLCHAR;
3571 /* Silence shouts if requested */
3572 if (appData.quietPlay &&
3573 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3574 SendToICS(ics_prefix);
3575 SendToICS("set shout 0\n");
3579 /* Deal with midgame name changes */
3581 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3582 if (gameInfo.white) free(gameInfo.white);
3583 gameInfo.white = StrSave(white);
3585 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3586 if (gameInfo.black) free(gameInfo.black);
3587 gameInfo.black = StrSave(black);
3591 /* Throw away game result if anything actually changes in examine mode */
3592 if (gameMode == IcsExamining && !newGame) {
3593 gameInfo.result = GameUnfinished;
3594 if (gameInfo.resultDetails != NULL) {
3595 free(gameInfo.resultDetails);
3596 gameInfo.resultDetails = NULL;
3600 /* In pausing && IcsExamining mode, we ignore boards coming
3601 in if they are in a different variation than we are. */
3602 if (pauseExamInvalid) return;
3603 if (pausing && gameMode == IcsExamining) {
3604 if (moveNum <= pauseExamForwardMostMove) {
3605 pauseExamInvalid = TRUE;
3606 forwardMostMove = pauseExamForwardMostMove;
3611 if (appData.debugMode) {
3612 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3614 /* Parse the board */
3615 for (k = 0; k < ranks; k++) {
3616 for (j = 0; j < files; j++)
3617 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3618 if(gameInfo.holdingsWidth > 1) {
3619 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3620 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3623 CopyBoard(boards[moveNum], board);
3625 startedFromSetupPosition =
3626 !CompareBoards(board, initialPosition);
3627 if(startedFromSetupPosition)
3628 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3631 /* [HGM] Set castling rights. Take the outermost Rooks,
3632 to make it also work for FRC opening positions. Note that board12
3633 is really defective for later FRC positions, as it has no way to
3634 indicate which Rook can castle if they are on the same side of King.
3635 For the initial position we grant rights to the outermost Rooks,
3636 and remember thos rights, and we then copy them on positions
3637 later in an FRC game. This means WB might not recognize castlings with
3638 Rooks that have moved back to their original position as illegal,
3639 but in ICS mode that is not its job anyway.
3641 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3642 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3644 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3645 if(board[0][i] == WhiteRook) j = i;
3646 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3647 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3648 if(board[0][i] == WhiteRook) j = i;
3649 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3650 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3651 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3652 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3653 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3654 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3655 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3657 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3658 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3659 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3660 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3661 if(board[BOARD_HEIGHT-1][k] == bKing)
3662 initialRights[5] = castlingRights[moveNum][5] = k;
3664 r = castlingRights[moveNum][0] = initialRights[0];
3665 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3666 r = castlingRights[moveNum][1] = initialRights[1];
3667 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3668 r = castlingRights[moveNum][3] = initialRights[3];
3669 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3670 r = castlingRights[moveNum][4] = initialRights[4];
3671 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3672 /* wildcastle kludge: always assume King has rights */
3673 r = castlingRights[moveNum][2] = initialRights[2];
3674 r = castlingRights[moveNum][5] = initialRights[5];
3676 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3677 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3680 if (ics_getting_history == H_GOT_REQ_HEADER ||
3681 ics_getting_history == H_GOT_UNREQ_HEADER) {
3682 /* This was an initial position from a move list, not
3683 the current position */
3687 /* Update currentMove and known move number limits */
3688 newMove = newGame || moveNum > forwardMostMove;
3690 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3691 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3692 takeback = forwardMostMove - moveNum;
3693 for (i = 0; i < takeback; i++) {
3694 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3695 SendToProgram("undo\n", &first);
3700 forwardMostMove = backwardMostMove = currentMove = moveNum;
3701 if (gameMode == IcsExamining && moveNum == 0) {
3702 /* Workaround for ICS limitation: we are not told the wild
3703 type when starting to examine a game. But if we ask for
3704 the move list, the move list header will tell us */
3705 ics_getting_history = H_REQUESTED;
3706 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3709 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3710 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3711 forwardMostMove = moveNum;
3712 if (!pausing || currentMove > forwardMostMove)
3713 currentMove = forwardMostMove;
3715 /* New part of history that is not contiguous with old part */
3716 if (pausing && gameMode == IcsExamining) {
3717 pauseExamInvalid = TRUE;
3718 forwardMostMove = pauseExamForwardMostMove;
3721 forwardMostMove = backwardMostMove = currentMove = moveNum;
3722 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3723 ics_getting_history = H_REQUESTED;
3724 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3729 /* Update the clocks */
3730 if (strchr(elapsed_time, '.')) {
3732 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3733 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3735 /* Time is in seconds */
3736 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3737 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3742 if (appData.zippyPlay && newGame &&
3743 gameMode != IcsObserving && gameMode != IcsIdle &&
3744 gameMode != IcsExamining)
3745 ZippyFirstBoard(moveNum, basetime, increment);
3748 /* Put the move on the move list, first converting
3749 to canonical algebraic form. */
3751 if (appData.debugMode) {
3752 if (appData.debugMode) { int f = forwardMostMove;
3753 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3754 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3756 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3757 fprintf(debugFP, "moveNum = %d\n", moveNum);
3758 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3759 setbuf(debugFP, NULL);
3761 if (moveNum <= backwardMostMove) {
3762 /* We don't know what the board looked like before
3764 strcpy(parseList[moveNum - 1], move_str);
3765 strcat(parseList[moveNum - 1], " ");
3766 strcat(parseList[moveNum - 1], elapsed_time);
3767 moveList[moveNum - 1][0] = NULLCHAR;
3768 } else if (strcmp(move_str, "none") == 0) {
3769 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3770 /* Again, we don't know what the board looked like;
3771 this is really the start of the game. */
3772 parseList[moveNum - 1][0] = NULLCHAR;
3773 moveList[moveNum - 1][0] = NULLCHAR;
3774 backwardMostMove = moveNum;
3775 startedFromSetupPosition = TRUE;
3776 fromX = fromY = toX = toY = -1;
3778 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3779 // So we parse the long-algebraic move string in stead of the SAN move
3780 int valid; char buf[MSG_SIZ], *prom;
3782 // str looks something like "Q/a1-a2"; kill the slash
3784 sprintf(buf, "%c%s", str[0], str+2);
3785 else strcpy(buf, str); // might be castling
3786 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3787 strcat(buf, prom); // long move lacks promo specification!
3788 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3789 if(appData.debugMode)
3790 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3791 strcpy(move_str, buf);
3793 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3794 &fromX, &fromY, &toX, &toY, &promoChar)
3795 || ParseOneMove(buf, moveNum - 1, &moveType,
3796 &fromX, &fromY, &toX, &toY, &promoChar);
3797 // end of long SAN patch
3799 (void) CoordsToAlgebraic(boards[moveNum - 1],
3800 PosFlags(moveNum - 1), EP_UNKNOWN,
3801 fromY, fromX, toY, toX, promoChar,
3802 parseList[moveNum-1]);
3803 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3804 castlingRights[moveNum]) ) {
3810 if(gameInfo.variant != VariantShogi)
3811 strcat(parseList[moveNum - 1], "+");
3814 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3815 strcat(parseList[moveNum - 1], "#");
3818 strcat(parseList[moveNum - 1], " ");
3819 strcat(parseList[moveNum - 1], elapsed_time);
3820 /* currentMoveString is set as a side-effect of ParseOneMove */
3821 strcpy(moveList[moveNum - 1], currentMoveString);
3822 strcat(moveList[moveNum - 1], "\n");
3824 /* Move from ICS was illegal!? Punt. */
3825 if (appData.debugMode) {
3826 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3827 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3830 if (appData.testLegality && appData.debugMode) {
3831 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3832 DisplayError(str, 0);
3835 strcpy(parseList[moveNum - 1], move_str);
3836 strcat(parseList[moveNum - 1], " ");
3837 strcat(parseList[moveNum - 1], elapsed_time);
3838 moveList[moveNum - 1][0] = NULLCHAR;
3839 fromX = fromY = toX = toY = -1;
3842 if (appData.debugMode) {
3843 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3844 setbuf(debugFP, NULL);
3848 /* Send move to chess program (BEFORE animating it). */
3849 if (appData.zippyPlay && !newGame && newMove &&
3850 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3852 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3853 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3854 if (moveList[moveNum - 1][0] == NULLCHAR) {
3855 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3857 DisplayError(str, 0);
3859 if (first.sendTime) {
3860 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3862 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3863 if (firstMove && !bookHit) {
3865 if (first.useColors) {
3866 SendToProgram(gameMode == IcsPlayingWhite ?
3868 "black\ngo\n", &first);
3870 SendToProgram("go\n", &first);
3872 first.maybeThinking = TRUE;
3875 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3876 if (moveList[moveNum - 1][0] == NULLCHAR) {
3877 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3878 DisplayError(str, 0);
3880 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3881 SendMoveToProgram(moveNum - 1, &first);
3888 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3889 /* If move comes from a remote source, animate it. If it
3890 isn't remote, it will have already been animated. */
3891 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3892 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3894 if (!pausing && appData.highlightLastMove) {
3895 SetHighlights(fromX, fromY, toX, toY);
3899 /* Start the clocks */
3900 whiteFlag = blackFlag = FALSE;
3901 appData.clockMode = !(basetime == 0 && increment == 0);
3903 ics_clock_paused = TRUE;
3905 } else if (ticking == 1) {
3906 ics_clock_paused = FALSE;
3908 if (gameMode == IcsIdle ||
3909 relation == RELATION_OBSERVING_STATIC ||
3910 relation == RELATION_EXAMINING ||
3912 DisplayBothClocks();
3916 /* Display opponents and material strengths */
3917 if (gameInfo.variant != VariantBughouse &&
3918 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3919 if (tinyLayout || smallLayout) {
3920 if(gameInfo.variant == VariantNormal)
3921 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3922 gameInfo.white, white_stren, gameInfo.black, black_stren,
3923 basetime, increment);
3925 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3926 gameInfo.white, white_stren, gameInfo.black, black_stren,
3927 basetime, increment, (int) gameInfo.variant);
3929 if(gameInfo.variant == VariantNormal)
3930 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3931 gameInfo.white, white_stren, gameInfo.black, black_stren,
3932 basetime, increment);
3934 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3935 gameInfo.white, white_stren, gameInfo.black, black_stren,
3936 basetime, increment, VariantName(gameInfo.variant));
3939 if (appData.debugMode) {
3940 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3945 /* Display the board */
3946 if (!pausing && !appData.noGUI) {
3948 if (appData.premove)
3950 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3951 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3952 ClearPremoveHighlights();
3954 DrawPosition(FALSE, boards[currentMove]);
3955 DisplayMove(moveNum - 1);
3956 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3957 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3958 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3959 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3963 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3965 if(bookHit) { // [HGM] book: simulate book reply
3966 static char bookMove[MSG_SIZ]; // a bit generous?
3968 programStats.nodes = programStats.depth = programStats.time =
3969 programStats.score = programStats.got_only_move = 0;
3970 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3972 strcpy(bookMove, "move ");
3973 strcat(bookMove, bookHit);
3974 HandleMachineMove(bookMove, &first);
3983 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3984 ics_getting_history = H_REQUESTED;
3985 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3991 AnalysisPeriodicEvent(force)
3994 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3995 && !force) || !appData.periodicUpdates)
3998 /* Send . command to Crafty to collect stats */
3999 SendToProgram(".\n", &first);
4001 /* Don't send another until we get a response (this makes
4002 us stop sending to old Crafty's which don't understand
4003 the "." command (sending illegal cmds resets node count & time,
4004 which looks bad)) */
4005 programStats.ok_to_send = 0;
4009 SendMoveToProgram(moveNum, cps)
4011 ChessProgramState *cps;
4015 if (cps->useUsermove) {
4016 SendToProgram("usermove ", cps);
4020 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4021 int len = space - parseList[moveNum];
4022 memcpy(buf, parseList[moveNum], len);
4024 buf[len] = NULLCHAR;
4026 sprintf(buf, "%s\n", parseList[moveNum]);
4028 SendToProgram(buf, cps);
4030 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4031 AlphaRank(moveList[moveNum], 4);
4032 SendToProgram(moveList[moveNum], cps);
4033 AlphaRank(moveList[moveNum], 4); // and back
4035 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4036 * the engine. It would be nice to have a better way to identify castle
4038 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4039 && cps->useOOCastle) {
4040 int fromX = moveList[moveNum][0] - AAA;
4041 int fromY = moveList[moveNum][1] - ONE;
4042 int toX = moveList[moveNum][2] - AAA;
4043 int toY = moveList[moveNum][3] - ONE;
4044 if((boards[moveNum][fromY][fromX] == WhiteKing
4045 && boards[moveNum][toY][toX] == WhiteRook)
4046 || (boards[moveNum][fromY][fromX] == BlackKing
4047 && boards[moveNum][toY][toX] == BlackRook)) {
4048 if(toX > fromX) SendToProgram("O-O\n", cps);
4049 else SendToProgram("O-O-O\n", cps);
4051 else SendToProgram(moveList[moveNum], cps);
4053 else SendToProgram(moveList[moveNum], cps);
4054 /* End of additions by Tord */
4057 /* [HGM] setting up the opening has brought engine in force mode! */
4058 /* Send 'go' if we are in a mode where machine should play. */
4059 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4060 (gameMode == TwoMachinesPlay ||
4062 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4064 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4065 SendToProgram("go\n", cps);
4066 if (appData.debugMode) {
4067 fprintf(debugFP, "(extra)\n");
4070 setboardSpoiledMachineBlack = 0;
4074 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4076 int fromX, fromY, toX, toY;
4078 char user_move[MSG_SIZ];
4082 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4083 (int)moveType, fromX, fromY, toX, toY);
4084 DisplayError(user_move + strlen("say "), 0);
4086 case WhiteKingSideCastle:
4087 case BlackKingSideCastle:
4088 case WhiteQueenSideCastleWild:
4089 case BlackQueenSideCastleWild:
4091 case WhiteHSideCastleFR:
4092 case BlackHSideCastleFR:
4094 sprintf(user_move, "o-o\n");
4096 case WhiteQueenSideCastle:
4097 case BlackQueenSideCastle:
4098 case WhiteKingSideCastleWild:
4099 case BlackKingSideCastleWild:
4101 case WhiteASideCastleFR:
4102 case BlackASideCastleFR:
4104 sprintf(user_move, "o-o-o\n");
4106 case WhitePromotionQueen:
4107 case BlackPromotionQueen:
4108 case WhitePromotionRook:
4109 case BlackPromotionRook:
4110 case WhitePromotionBishop:
4111 case BlackPromotionBishop:
4112 case WhitePromotionKnight:
4113 case BlackPromotionKnight:
4114 case WhitePromotionKing:
4115 case BlackPromotionKing:
4116 case WhitePromotionChancellor:
4117 case BlackPromotionChancellor:
4118 case WhitePromotionArchbishop:
4119 case BlackPromotionArchbishop:
4120 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4121 sprintf(user_move, "%c%c%c%c=%c\n",
4122 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4123 PieceToChar(WhiteFerz));
4124 else if(gameInfo.variant == VariantGreat)
4125 sprintf(user_move, "%c%c%c%c=%c\n",
4126 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4127 PieceToChar(WhiteMan));
4129 sprintf(user_move, "%c%c%c%c=%c\n",
4130 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4131 PieceToChar(PromoPiece(moveType)));
4135 sprintf(user_move, "%c@%c%c\n",
4136 ToUpper(PieceToChar((ChessSquare) fromX)),
4137 AAA + toX, ONE + toY);
4140 case WhiteCapturesEnPassant:
4141 case BlackCapturesEnPassant:
4142 case IllegalMove: /* could be a variant we don't quite understand */
4143 sprintf(user_move, "%c%c%c%c\n",
4144 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4147 SendToICS(user_move);
4148 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4149 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4153 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4158 if (rf == DROP_RANK) {
4159 sprintf(move, "%c@%c%c\n",
4160 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4162 if (promoChar == 'x' || promoChar == NULLCHAR) {
4163 sprintf(move, "%c%c%c%c\n",
4164 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4166 sprintf(move, "%c%c%c%c%c\n",
4167 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4173 ProcessICSInitScript(f)
4178 while (fgets(buf, MSG_SIZ, f)) {
4179 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4186 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4188 AlphaRank(char *move, int n)
4190 // char *p = move, c; int x, y;
4192 if (appData.debugMode) {
4193 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4197 move[2]>='0' && move[2]<='9' &&
4198 move[3]>='a' && move[3]<='x' ) {
4200 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4201 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4203 if(move[0]>='0' && move[0]<='9' &&
4204 move[1]>='a' && move[1]<='x' &&
4205 move[2]>='0' && move[2]<='9' &&
4206 move[3]>='a' && move[3]<='x' ) {
4207 /* input move, Shogi -> normal */
4208 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4209 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4210 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4211 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4214 move[3]>='0' && move[3]<='9' &&
4215 move[2]>='a' && move[2]<='x' ) {
4217 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4218 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4221 move[0]>='a' && move[0]<='x' &&
4222 move[3]>='0' && move[3]<='9' &&
4223 move[2]>='a' && move[2]<='x' ) {
4224 /* output move, normal -> Shogi */
4225 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4226 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4227 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4228 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4229 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4231 if (appData.debugMode) {
4232 fprintf(debugFP, " out = '%s'\n", move);
4236 /* Parser for moves from gnuchess, ICS, or user typein box */
4238 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4241 ChessMove *moveType;
4242 int *fromX, *fromY, *toX, *toY;
4245 if (appData.debugMode) {
4246 fprintf(debugFP, "move to parse: %s\n", move);
4248 *moveType = yylexstr(moveNum, move);
4250 switch (*moveType) {
4251 case WhitePromotionChancellor:
4252 case BlackPromotionChancellor:
4253 case WhitePromotionArchbishop:
4254 case BlackPromotionArchbishop:
4255 case WhitePromotionQueen:
4256 case BlackPromotionQueen:
4257 case WhitePromotionRook:
4258 case BlackPromotionRook:
4259 case WhitePromotionBishop:
4260 case BlackPromotionBishop:
4261 case WhitePromotionKnight:
4262 case BlackPromotionKnight:
4263 case WhitePromotionKing:
4264 case BlackPromotionKing:
4266 case WhiteCapturesEnPassant:
4267 case BlackCapturesEnPassant:
4268 case WhiteKingSideCastle:
4269 case WhiteQueenSideCastle:
4270 case BlackKingSideCastle:
4271 case BlackQueenSideCastle:
4272 case WhiteKingSideCastleWild:
4273 case WhiteQueenSideCastleWild:
4274 case BlackKingSideCastleWild:
4275 case BlackQueenSideCastleWild:
4276 /* Code added by Tord: */
4277 case WhiteHSideCastleFR:
4278 case WhiteASideCastleFR:
4279 case BlackHSideCastleFR:
4280 case BlackASideCastleFR:
4281 /* End of code added by Tord */
4282 case IllegalMove: /* bug or odd chess variant */
4283 *fromX = currentMoveString[0] - AAA;
4284 *fromY = currentMoveString[1] - ONE;
4285 *toX = currentMoveString[2] - AAA;
4286 *toY = currentMoveString[3] - ONE;
4287 *promoChar = currentMoveString[4];
4288 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4289 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4290 if (appData.debugMode) {
4291 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4293 *fromX = *fromY = *toX = *toY = 0;
4296 if (appData.testLegality) {
4297 return (*moveType != IllegalMove);
4299 return !(fromX == fromY && toX == toY);
4304 *fromX = *moveType == WhiteDrop ?
4305 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4306 (int) CharToPiece(ToLower(currentMoveString[0]));
4308 *toX = currentMoveString[2] - AAA;
4309 *toY = currentMoveString[3] - ONE;
4310 *promoChar = NULLCHAR;
4314 case ImpossibleMove:
4315 case (ChessMove) 0: /* end of file */
4324 if (appData.debugMode) {
4325 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4328 *fromX = *fromY = *toX = *toY = 0;
4329 *promoChar = NULLCHAR;
4334 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4335 // All positions will have equal probability, but the current method will not provide a unique
4336 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4342 int piecesLeft[(int)BlackPawn];
4343 int seed, nrOfShuffles;
4345 void GetPositionNumber()
4346 { // sets global variable seed
4349 seed = appData.defaultFrcPosition;
4350 if(seed < 0) { // randomize based on time for negative FRC position numbers
4351 for(i=0; i<50; i++) seed += random();
4352 seed = random() ^ random() >> 8 ^ random() << 8;
4353 if(seed<0) seed = -seed;
4357 int put(Board board, int pieceType, int rank, int n, int shade)
4358 // put the piece on the (n-1)-th empty squares of the given shade
4362 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4363 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4364 board[rank][i] = (ChessSquare) pieceType;
4365 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4367 piecesLeft[pieceType]--;
4375 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4376 // calculate where the next piece goes, (any empty square), and put it there
4380 i = seed % squaresLeft[shade];
4381 nrOfShuffles *= squaresLeft[shade];
4382 seed /= squaresLeft[shade];
4383 put(board, pieceType, rank, i, shade);
4386 void AddTwoPieces(Board board, int pieceType, int rank)
4387 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4389 int i, n=squaresLeft[ANY], j=n-1, k;
4391 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4392 i = seed % k; // pick one
4395 while(i >= j) i -= j--;
4396 j = n - 1 - j; i += j;
4397 put(board, pieceType, rank, j, ANY);
4398 put(board, pieceType, rank, i, ANY);
4401 void SetUpShuffle(Board board, int number)
4405 GetPositionNumber(); nrOfShuffles = 1;
4407 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4408 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4409 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4411 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4413 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4414 p = (int) board[0][i];
4415 if(p < (int) BlackPawn) piecesLeft[p] ++;
4416 board[0][i] = EmptySquare;
4419 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4420 // shuffles restricted to allow normal castling put KRR first
4421 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4422 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4423 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4424 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4425 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4426 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4427 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4428 put(board, WhiteRook, 0, 0, ANY);
4429 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4432 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4433 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4434 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4435 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4436 while(piecesLeft[p] >= 2) {
4437 AddOnePiece(board, p, 0, LITE);
4438 AddOnePiece(board, p, 0, DARK);
4440 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4443 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4444 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4445 // but we leave King and Rooks for last, to possibly obey FRC restriction
4446 if(p == (int)WhiteRook) continue;
4447 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4448 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4451 // now everything is placed, except perhaps King (Unicorn) and Rooks
4453 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4454 // Last King gets castling rights
4455 while(piecesLeft[(int)WhiteUnicorn]) {
4456 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4457 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4460 while(piecesLeft[(int)WhiteKing]) {
4461 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4462 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4467 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4468 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4471 // Only Rooks can be left; simply place them all
4472 while(piecesLeft[(int)WhiteRook]) {
4473 i = put(board, WhiteRook, 0, 0, ANY);
4474 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4477 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4479 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4482 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4483 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4486 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4489 int SetCharTable( char *table, const char * map )
4490 /* [HGM] moved here from winboard.c because of its general usefulness */
4491 /* Basically a safe strcpy that uses the last character as King */
4493 int result = FALSE; int NrPieces;
4495 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4496 && NrPieces >= 12 && !(NrPieces&1)) {
4497 int i; /* [HGM] Accept even length from 12 to 34 */
4499 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4500 for( i=0; i<NrPieces/2-1; i++ ) {
4502 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4504 table[(int) WhiteKing] = map[NrPieces/2-1];
4505 table[(int) BlackKing] = map[NrPieces-1];
4513 void Prelude(Board board)
4514 { // [HGM] superchess: random selection of exo-pieces
4515 int i, j, k; ChessSquare p;
4516 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4518 GetPositionNumber(); // use FRC position number
4520 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4521 SetCharTable(pieceToChar, appData.pieceToCharTable);
4522 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4523 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4526 j = seed%4; seed /= 4;
4527 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4528 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4529 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4530 j = seed%3 + (seed%3 >= j); seed /= 3;
4531 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4532 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4533 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4534 j = seed%3; seed /= 3;
4535 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4536 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4537 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4538 j = seed%2 + (seed%2 >= j); seed /= 2;
4539 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4540 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4541 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4542 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4543 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4544 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4545 put(board, exoPieces[0], 0, 0, ANY);
4546 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4550 InitPosition(redraw)
4553 ChessSquare (* pieces)[BOARD_SIZE];
4554 int i, j, pawnRow, overrule,
4555 oldx = gameInfo.boardWidth,
4556 oldy = gameInfo.boardHeight,
4557 oldh = gameInfo.holdingsWidth,
4558 oldv = gameInfo.variant;
4560 currentMove = forwardMostMove = backwardMostMove = 0;
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;
5056 UserMoveTest(fromX, fromY, toX, toY, promoChar)
5057 int fromX, fromY, toX, toY;
5061 ChessSquare pdown, pup;
5063 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5064 if ((fromX == toX) && (fromY == toY)) {
5065 return ImpossibleMove;
5068 /* [HGM] suppress all moves into holdings area and guard band */
5069 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5070 return ImpossibleMove;
5072 /* [HGM] <sameColor> moved to here from winboard.c */
5073 /* note: this code seems to exist for filtering out some obviously illegal premoves */
5074 pdown = boards[currentMove][fromY][fromX];
5075 pup = boards[currentMove][toY][toX];
5076 if ( gameMode != EditPosition &&
5077 (WhitePawn <= pdown && pdown < BlackPawn &&
5078 WhitePawn <= pup && pup < BlackPawn ||
5079 BlackPawn <= pdown && pdown < EmptySquare &&
5080 BlackPawn <= pup && pup < EmptySquare
5081 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5082 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5083 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5085 return ImpossibleMove;
5087 /* Check if the user is playing in turn. This is complicated because we
5088 let the user "pick up" a piece before it is his turn. So the piece he
5089 tried to pick up may have been captured by the time he puts it down!
5090 Therefore we use the color the user is supposed to be playing in this
5091 test, not the color of the piece that is currently on the starting
5092 square---except in EditGame mode, where the user is playing both
5093 sides; fortunately there the capture race can't happen. (It can
5094 now happen in IcsExamining mode, but that's just too bad. The user
5095 will get a somewhat confusing message in that case.)
5099 case PlayFromGameFile:
5101 case TwoMachinesPlay:
5105 /* We switched into a game mode where moves are not accepted,
5106 perhaps while the mouse button was down. */
5107 return ImpossibleMove;
5109 case MachinePlaysWhite:
5110 /* User is moving for Black */
5111 if (WhiteOnMove(currentMove)) {
5112 DisplayMoveError(_("It is White's turn"));
5113 return ImpossibleMove;
5117 case MachinePlaysBlack:
5118 /* User is moving for White */
5119 if (!WhiteOnMove(currentMove)) {
5120 DisplayMoveError(_("It is Black's turn"));
5121 return ImpossibleMove;
5127 case BeginningOfGame:
5130 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5131 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5132 /* User is moving for Black */
5133 if (WhiteOnMove(currentMove)) {
5134 DisplayMoveError(_("It is White's turn"));
5135 return ImpossibleMove;
5138 /* User is moving for White */
5139 if (!WhiteOnMove(currentMove)) {
5140 DisplayMoveError(_("It is Black's turn"));
5141 return ImpossibleMove;
5146 case IcsPlayingBlack:
5147 /* User is moving for Black */
5148 if (WhiteOnMove(currentMove)) {
5149 if (!appData.premove) {
5150 DisplayMoveError(_("It is White's turn"));
5151 } else if (toX >= 0 && toY >= 0) {
5154 premoveFromX = fromX;
5155 premoveFromY = fromY;
5156 premovePromoChar = promoChar;
5158 if (appData.debugMode)
5159 fprintf(debugFP, "Got premove: fromX %d,"
5160 "fromY %d, toX %d, toY %d\n",
5161 fromX, fromY, toX, toY);
5163 return ImpossibleMove;
5167 case IcsPlayingWhite:
5168 /* User is moving for White */
5169 if (!WhiteOnMove(currentMove)) {
5170 if (!appData.premove) {
5171 DisplayMoveError(_("It is Black's turn"));
5172 } else if (toX >= 0 && toY >= 0) {
5175 premoveFromX = fromX;
5176 premoveFromY = fromY;
5177 premovePromoChar = promoChar;
5179 if (appData.debugMode)
5180 fprintf(debugFP, "Got premove: fromX %d,"
5181 "fromY %d, toX %d, toY %d\n",
5182 fromX, fromY, toX, toY);
5184 return ImpossibleMove;
5192 /* EditPosition, empty square, or different color piece;
5193 click-click move is possible */
5194 if (toX == -2 || toY == -2) {
5195 boards[0][fromY][fromX] = EmptySquare;
5196 return AmbiguousMove;
5197 } else if (toX >= 0 && toY >= 0) {
5198 boards[0][toY][toX] = boards[0][fromY][fromX];
5199 boards[0][fromY][fromX] = EmptySquare;
5200 return AmbiguousMove;
5202 return ImpossibleMove;
5205 /* [HGM] If move started in holdings, it means a drop */
5206 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5207 if( pup != EmptySquare ) return ImpossibleMove;
5208 if(appData.testLegality) {
5209 /* it would be more logical if LegalityTest() also figured out
5210 * which drops are legal. For now we forbid pawns on back rank.
5211 * Shogi is on its own here...
5213 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5214 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5215 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5217 return WhiteDrop; /* Not needed to specify white or black yet */
5220 userOfferedDraw = FALSE;
5222 /* [HGM] always test for legality, to get promotion info */
5223 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5224 epStatus[currentMove], castlingRights[currentMove],
5225 fromY, fromX, toY, toX, promoChar);
5227 /* [HGM] but possibly ignore an IllegalMove result */
5228 if (appData.testLegality) {
5229 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5230 DisplayMoveError(_("Illegal move"));
5231 return ImpossibleMove;
5234 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5236 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5237 function is made into one that returns an OK move type if FinishMove
5238 should be called. This to give the calling driver routine the
5239 opportunity to finish the userMove input with a promotion popup,
5240 without bothering the user with this for invalid or illegal moves */
5242 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5245 /* Common tail of UserMoveEvent and DropMenuEvent */
5247 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5249 int fromX, fromY, toX, toY;
5250 /*char*/int promoChar;
5253 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5254 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5255 // [HGM] superchess: suppress promotions to non-available piece
5256 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5257 if(WhiteOnMove(currentMove)) {
5258 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5260 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5264 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5265 move type in caller when we know the move is a legal promotion */
5266 if(moveType == NormalMove && promoChar)
5267 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5268 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5269 /* [HGM] convert drag-and-drop piece drops to standard form */
5270 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5271 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5272 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5273 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5274 // fromX = boards[currentMove][fromY][fromX];
5275 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5276 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5277 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5278 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5282 /* [HGM] <popupFix> The following if has been moved here from
5283 UserMoveEvent(). Because it seemed to belon here (why not allow
5284 piece drops in training games?), and because it can only be
5285 performed after it is known to what we promote. */
5286 if (gameMode == Training) {
5287 /* compare the move played on the board to the next move in the
5288 * game. If they match, display the move and the opponent's response.
5289 * If they don't match, display an error message.
5292 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5293 CopyBoard(testBoard, boards[currentMove]);
5294 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5296 if (CompareBoards(testBoard, boards[currentMove+1])) {
5297 ForwardInner(currentMove+1);
5299 /* Autoplay the opponent's response.
5300 * if appData.animate was TRUE when Training mode was entered,
5301 * the response will be animated.
5303 saveAnimate = appData.animate;
5304 appData.animate = animateTraining;
5305 ForwardInner(currentMove+1);
5306 appData.animate = saveAnimate;
5308 /* check for the end of the game */
5309 if (currentMove >= forwardMostMove) {
5310 gameMode = PlayFromGameFile;
5312 SetTrainingModeOff();
5313 DisplayInformation(_("End of game"));
5316 DisplayError(_("Incorrect move"), 0);
5321 /* Ok, now we know that the move is good, so we can kill
5322 the previous line in Analysis Mode */
5323 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5324 forwardMostMove = currentMove;
5327 /* If we need the chess program but it's dead, restart it */
5328 ResurrectChessProgram();
5330 /* A user move restarts a paused game*/
5334 thinkOutput[0] = NULLCHAR;
5336 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5338 if (gameMode == BeginningOfGame) {
5339 if (appData.noChessProgram) {
5340 gameMode = EditGame;
5344 gameMode = MachinePlaysBlack;
5347 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5349 if (first.sendName) {
5350 sprintf(buf, "name %s\n", gameInfo.white);
5351 SendToProgram(buf, &first);
5357 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5358 /* Relay move to ICS or chess engine */
5359 if (appData.icsActive) {
5360 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5361 gameMode == IcsExamining) {
5362 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5366 if (first.sendTime && (gameMode == BeginningOfGame ||
5367 gameMode == MachinePlaysWhite ||
5368 gameMode == MachinePlaysBlack)) {
5369 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5371 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5372 // [HGM] book: if program might be playing, let it use book
5373 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5374 first.maybeThinking = TRUE;
5375 } else SendMoveToProgram(forwardMostMove-1, &first);
5376 if (currentMove == cmailOldMove + 1) {
5377 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5381 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5385 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5386 EP_UNKNOWN, castlingRights[currentMove]) ) {
5392 if (WhiteOnMove(currentMove)) {
5393 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5395 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5399 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5404 case MachinePlaysBlack:
5405 case MachinePlaysWhite:
5406 /* disable certain menu options while machine is thinking */
5407 SetMachineThinkingEnables();
5414 if(bookHit) { // [HGM] book: simulate book reply
5415 static char bookMove[MSG_SIZ]; // a bit generous?
5417 programStats.nodes = programStats.depth = programStats.time =
5418 programStats.score = programStats.got_only_move = 0;
5419 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5421 strcpy(bookMove, "move ");
5422 strcat(bookMove, bookHit);
5423 HandleMachineMove(bookMove, &first);
5429 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5430 int fromX, fromY, toX, toY;
5433 /* [HGM] This routine was added to allow calling of its two logical
5434 parts from other modules in the old way. Before, UserMoveEvent()
5435 automatically called FinishMove() if the move was OK, and returned
5436 otherwise. I separated the two, in order to make it possible to
5437 slip a promotion popup in between. But that it always needs two
5438 calls, to the first part, (now called UserMoveTest() ), and to
5439 FinishMove if the first part succeeded. Calls that do not need
5440 to do anything in between, can call this routine the old way.
5442 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
5443 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5444 if(moveType == AmbiguousMove)
5445 DrawPosition(FALSE, boards[currentMove]);
5446 else if(moveType != ImpossibleMove)
5447 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5450 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5452 // char * hint = lastHint;
5453 FrontEndProgramStats stats;
5455 stats.which = cps == &first ? 0 : 1;
5456 stats.depth = cpstats->depth;
5457 stats.nodes = cpstats->nodes;
5458 stats.score = cpstats->score;
5459 stats.time = cpstats->time;
5460 stats.pv = cpstats->movelist;
5461 stats.hint = lastHint;
5462 stats.an_move_index = 0;
5463 stats.an_move_count = 0;
5465 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5466 stats.hint = cpstats->move_name;
5467 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5468 stats.an_move_count = cpstats->nr_moves;
5471 SetProgramStats( &stats );
5474 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5475 { // [HGM] book: this routine intercepts moves to simulate book replies
5476 char *bookHit = NULL;
5478 //first determine if the incoming move brings opponent into his book
5479 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5480 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5481 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5482 if(bookHit != NULL && !cps->bookSuspend) {
5483 // make sure opponent is not going to reply after receiving move to book position
5484 SendToProgram("force\n", cps);
5485 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5487 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5488 // now arrange restart after book miss
5490 // after a book hit we never send 'go', and the code after the call to this routine
5491 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5493 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5494 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5495 SendToProgram(buf, cps);
5496 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5497 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5498 SendToProgram("go\n", cps);
5499 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5500 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5501 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5502 SendToProgram("go\n", cps);
5503 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5505 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5509 ChessProgramState *savedState;
5510 void DeferredBookMove(void)
5512 if(savedState->lastPing != savedState->lastPong)
5513 ScheduleDelayedEvent(DeferredBookMove, 10);
5515 HandleMachineMove(savedMessage, savedState);
5519 HandleMachineMove(message, cps)
5521 ChessProgramState *cps;
5523 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5524 char realname[MSG_SIZ];
5525 int fromX, fromY, toX, toY;
5532 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5534 * Kludge to ignore BEL characters
5536 while (*message == '\007') message++;
5539 * [HGM] engine debug message: ignore lines starting with '#' character
5541 if(cps->debug && *message == '#') return;
5544 * Look for book output
5546 if (cps == &first && bookRequested) {
5547 if (message[0] == '\t' || message[0] == ' ') {
5548 /* Part of the book output is here; append it */
5549 strcat(bookOutput, message);
5550 strcat(bookOutput, " \n");
5552 } else if (bookOutput[0] != NULLCHAR) {
5553 /* All of book output has arrived; display it */
5554 char *p = bookOutput;
5555 while (*p != NULLCHAR) {
5556 if (*p == '\t') *p = ' ';
5559 DisplayInformation(bookOutput);
5560 bookRequested = FALSE;
5561 /* Fall through to parse the current output */
5566 * Look for machine move.
5568 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5569 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5571 /* This method is only useful on engines that support ping */
5572 if (cps->lastPing != cps->lastPong) {
5573 if (gameMode == BeginningOfGame) {
5574 /* Extra move from before last new; ignore */
5575 if (appData.debugMode) {
5576 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5579 if (appData.debugMode) {
5580 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5581 cps->which, gameMode);
5584 SendToProgram("undo\n", cps);
5590 case BeginningOfGame:
5591 /* Extra move from before last reset; ignore */
5592 if (appData.debugMode) {
5593 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5600 /* Extra move after we tried to stop. The mode test is
5601 not a reliable way of detecting this problem, but it's
5602 the best we can do on engines that don't support ping.
5604 if (appData.debugMode) {
5605 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5606 cps->which, gameMode);
5608 SendToProgram("undo\n", cps);
5611 case MachinePlaysWhite:
5612 case IcsPlayingWhite:
5613 machineWhite = TRUE;
5616 case MachinePlaysBlack:
5617 case IcsPlayingBlack:
5618 machineWhite = FALSE;
5621 case TwoMachinesPlay:
5622 machineWhite = (cps->twoMachinesColor[0] == 'w');
5625 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5626 if (appData.debugMode) {
5628 "Ignoring move out of turn by %s, gameMode %d"
5629 ", forwardMost %d\n",
5630 cps->which, gameMode, forwardMostMove);
5635 if (appData.debugMode) { int f = forwardMostMove;
5636 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5637 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5639 if(cps->alphaRank) AlphaRank(machineMove, 4);
5640 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5641 &fromX, &fromY, &toX, &toY, &promoChar)) {
5642 /* Machine move could not be parsed; ignore it. */
5643 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5644 machineMove, cps->which);
5645 DisplayError(buf1, 0);
5646 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5647 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5648 if (gameMode == TwoMachinesPlay) {
5649 GameEnds(machineWhite ? BlackWins : WhiteWins,
5655 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5656 /* So we have to redo legality test with true e.p. status here, */
5657 /* to make sure an illegal e.p. capture does not slip through, */
5658 /* to cause a forfeit on a justified illegal-move complaint */
5659 /* of the opponent. */
5660 if( gameMode==TwoMachinesPlay && appData.testLegality
5661 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5664 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5665 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5666 fromY, fromX, toY, toX, promoChar);
5667 if (appData.debugMode) {
5669 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5670 castlingRights[forwardMostMove][i], castlingRank[i]);
5671 fprintf(debugFP, "castling rights\n");
5673 if(moveType == IllegalMove) {
5674 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5675 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5676 GameEnds(machineWhite ? BlackWins : WhiteWins,
5679 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5680 /* [HGM] Kludge to handle engines that send FRC-style castling
5681 when they shouldn't (like TSCP-Gothic) */
5683 case WhiteASideCastleFR:
5684 case BlackASideCastleFR:
5686 currentMoveString[2]++;
5688 case WhiteHSideCastleFR:
5689 case BlackHSideCastleFR:
5691 currentMoveString[2]--;
5693 default: ; // nothing to do, but suppresses warning of pedantic compilers
5696 hintRequested = FALSE;
5697 lastHint[0] = NULLCHAR;
5698 bookRequested = FALSE;
5699 /* Program may be pondering now */
5700 cps->maybeThinking = TRUE;
5701 if (cps->sendTime == 2) cps->sendTime = 1;
5702 if (cps->offeredDraw) cps->offeredDraw--;
5705 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5707 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5709 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5710 char buf[3*MSG_SIZ];
5712 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5713 programStats.score / 100.,
5715 programStats.time / 100.,
5716 (unsigned int)programStats.nodes,
5717 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5718 programStats.movelist);
5720 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5724 /* currentMoveString is set as a side-effect of ParseOneMove */
5725 strcpy(machineMove, currentMoveString);
5726 strcat(machineMove, "\n");
5727 strcpy(moveList[forwardMostMove], machineMove);
5729 /* [AS] Save move info and clear stats for next move */
5730 pvInfoList[ forwardMostMove ].score = programStats.score;
5731 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5732 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5733 ClearProgramStats();
5734 thinkOutput[0] = NULLCHAR;
5735 hiddenThinkOutputState = 0;
5737 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5739 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5740 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5743 while( count < adjudicateLossPlies ) {
5744 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5747 score = -score; /* Flip score for winning side */
5750 if( score > adjudicateLossThreshold ) {
5757 if( count >= adjudicateLossPlies ) {
5758 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5760 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5761 "Xboard adjudication",
5768 if( gameMode == TwoMachinesPlay ) {
5769 // [HGM] some adjudications useful with buggy engines
5770 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5771 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5774 if( appData.testLegality )
5775 { /* [HGM] Some more adjudications for obstinate engines */
5776 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5777 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5778 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5779 static int moveCount = 6;
5781 char *reason = NULL;
5783 /* Count what is on board. */
5784 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5785 { ChessSquare p = boards[forwardMostMove][i][j];
5789 { /* count B,N,R and other of each side */
5792 NrK++; break; // [HGM] atomic: count Kings
5796 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5797 bishopsColor |= 1 << ((i^j)&1);
5802 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5803 bishopsColor |= 1 << ((i^j)&1);
5818 PawnAdvance += m; NrPawns++;
5820 NrPieces += (p != EmptySquare);
5821 NrW += ((int)p < (int)BlackPawn);
5822 if(gameInfo.variant == VariantXiangqi &&
5823 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5824 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5825 NrW -= ((int)p < (int)BlackPawn);
5829 /* Some material-based adjudications that have to be made before stalemate test */
5830 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5831 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5832 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5833 if(appData.checkMates) {
5834 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5835 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5836 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5837 "Xboard adjudication: King destroyed", GE_XBOARD );
5842 /* Bare King in Shatranj (loses) or Losers (wins) */
5843 if( NrW == 1 || NrPieces - NrW == 1) {
5844 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5845 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5846 if(appData.checkMates) {
5847 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5848 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5849 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5850 "Xboard adjudication: Bare king", GE_XBOARD );
5854 if( gameInfo.variant == VariantShatranj && --bare < 0)
5856 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5857 if(appData.checkMates) {
5858 /* but only adjudicate if adjudication enabled */
5859 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5860 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5861 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5862 "Xboard adjudication: Bare king", GE_XBOARD );
5869 // don't wait for engine to announce game end if we can judge ourselves
5870 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5871 castlingRights[forwardMostMove]) ) {
5873 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5874 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5875 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5876 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5879 reason = "Xboard adjudication: 3rd check";
5880 epStatus[forwardMostMove] = EP_CHECKMATE;
5890 reason = "Xboard adjudication: Stalemate";
5891 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5892 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5893 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5894 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5895 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5896 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5897 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5898 EP_CHECKMATE : EP_WINS);
5899 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5900 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5904 reason = "Xboard adjudication: Checkmate";
5905 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5909 switch(i = epStatus[forwardMostMove]) {
5911 result = GameIsDrawn; break;
5913 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5915 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5917 result = (ChessMove) 0;
5919 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5920 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5921 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5922 GameEnds( result, reason, GE_XBOARD );
5926 /* Next absolutely insufficient mating material. */
5927 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5928 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5929 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5930 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5931 { /* KBK, KNK, KK of KBKB with like Bishops */
5933 /* always flag draws, for judging claims */
5934 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5936 if(appData.materialDraws) {
5937 /* but only adjudicate them if adjudication enabled */
5938 SendToProgram("force\n", cps->other); // suppress reply
5939 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5940 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5941 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5946 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5948 ( NrWR == 1 && NrBR == 1 /* KRKR */
5949 || NrWQ==1 && NrBQ==1 /* KQKQ */
5950 || NrWN==2 || NrBN==2 /* KNNK */
5951 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5953 if(--moveCount < 0 && appData.trivialDraws)
5954 { /* if the first 3 moves do not show a tactical win, declare draw */
5955 SendToProgram("force\n", cps->other); // suppress reply
5956 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5957 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5958 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5961 } else moveCount = 6;
5965 if (appData.debugMode) { int i;
5966 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5967 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5968 appData.drawRepeats);
5969 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5970 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5974 /* Check for rep-draws */
5976 for(k = forwardMostMove-2;
5977 k>=backwardMostMove && k>=forwardMostMove-100 &&
5978 epStatus[k] < EP_UNKNOWN &&
5979 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5983 if (appData.debugMode) {
5984 fprintf(debugFP, " loop\n");
5987 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5989 if (appData.debugMode) {
5990 fprintf(debugFP, "match\n");
5993 /* compare castling rights */
5994 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5995 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5996 rights++; /* King lost rights, while rook still had them */
5997 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5998 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5999 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6000 rights++; /* but at least one rook lost them */
6002 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6003 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6005 if( castlingRights[forwardMostMove][5] >= 0 ) {
6006 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6007 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6011 if (appData.debugMode) {
6012 for(i=0; i<nrCastlingRights; i++)
6013 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
6016 if (appData.debugMode) {
6017 fprintf(debugFP, " %d %d\n", rights, k);
6020 if( rights == 0 && ++count > appData.drawRepeats-2
6021 && appData.drawRepeats > 1) {
6022 /* adjudicate after user-specified nr of repeats */
6023 SendToProgram("force\n", cps->other); // suppress reply
6024 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6025 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6027 // [HGM] xiangqi: check for forbidden perpetuals
6028 int m, ourPerpetual = 1, hisPerpetual = 1;
6029 for(m=forwardMostMove; m>k; m-=2) {
6030 if(MateTest(boards[m], PosFlags(m),
6031 EP_NONE, castlingRights[m]) != MT_CHECK)
6032 ourPerpetual = 0; // the current mover did not always check
6033 if(MateTest(boards[m-1], PosFlags(m-1),
6034 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6035 hisPerpetual = 0; // the opponent did not always check
6037 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6038 ourPerpetual, hisPerpetual);
6039 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6040 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6041 "Xboard adjudication: perpetual checking", GE_XBOARD );
6044 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6045 break; // (or we would have caught him before). Abort repetition-checking loop.
6046 // Now check for perpetual chases
6047 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6048 hisPerpetual = PerpetualChase(k, forwardMostMove);
6049 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6050 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6051 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6052 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6055 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6056 break; // Abort repetition-checking loop.
6058 // if neither of us is checking or chasing all the time, or both are, it is draw
6060 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6063 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6064 epStatus[forwardMostMove] = EP_REP_DRAW;
6068 /* Now we test for 50-move draws. Determine ply count */
6069 count = forwardMostMove;
6070 /* look for last irreversble move */
6071 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6073 /* if we hit starting position, add initial plies */
6074 if( count == backwardMostMove )
6075 count -= initialRulePlies;
6076 count = forwardMostMove - count;
6078 epStatus[forwardMostMove] = EP_RULE_DRAW;
6079 /* this is used to judge if draw claims are legal */
6080 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6081 SendToProgram("force\n", cps->other); // suppress reply
6082 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6083 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6084 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6088 /* if draw offer is pending, treat it as a draw claim
6089 * when draw condition present, to allow engines a way to
6090 * claim draws before making their move to avoid a race
6091 * condition occurring after their move
6093 if( cps->other->offeredDraw || cps->offeredDraw ) {
6095 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6096 p = "Draw claim: 50-move rule";
6097 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6098 p = "Draw claim: 3-fold repetition";
6099 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6100 p = "Draw claim: insufficient mating material";
6102 SendToProgram("force\n", cps->other); // suppress reply
6103 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6104 GameEnds( GameIsDrawn, p, GE_XBOARD );
6105 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6111 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6112 SendToProgram("force\n", cps->other); // suppress reply
6113 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6114 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6116 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6123 if (gameMode == TwoMachinesPlay) {
6124 /* [HGM] relaying draw offers moved to after reception of move */
6125 /* and interpreting offer as claim if it brings draw condition */
6126 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6127 SendToProgram("draw\n", cps->other);
6129 if (cps->other->sendTime) {
6130 SendTimeRemaining(cps->other,
6131 cps->other->twoMachinesColor[0] == 'w');
6133 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6134 if (firstMove && !bookHit) {
6136 if (cps->other->useColors) {
6137 SendToProgram(cps->other->twoMachinesColor, cps->other);
6139 SendToProgram("go\n", cps->other);
6141 cps->other->maybeThinking = TRUE;
6144 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146 if (!pausing && appData.ringBellAfterMoves) {
6151 * Reenable menu items that were disabled while
6152 * machine was thinking
6154 if (gameMode != TwoMachinesPlay)
6155 SetUserThinkingEnables();
6157 // [HGM] book: after book hit opponent has received move and is now in force mode
6158 // force the book reply into it, and then fake that it outputted this move by jumping
6159 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6161 static char bookMove[MSG_SIZ]; // a bit generous?
6163 strcpy(bookMove, "move ");
6164 strcat(bookMove, bookHit);
6167 programStats.nodes = programStats.depth = programStats.time =
6168 programStats.score = programStats.got_only_move = 0;
6169 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6171 if(cps->lastPing != cps->lastPong) {
6172 savedMessage = message; // args for deferred call
6174 ScheduleDelayedEvent(DeferredBookMove, 10);
6183 /* Set special modes for chess engines. Later something general
6184 * could be added here; for now there is just one kludge feature,
6185 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6186 * when "xboard" is given as an interactive command.
6188 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6189 cps->useSigint = FALSE;
6190 cps->useSigterm = FALSE;
6192 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6193 ParseFeatures(message+8, cps);
6194 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6197 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6198 * want this, I was asked to put it in, and obliged.
6200 if (!strncmp(message, "setboard ", 9)) {
6201 Board initial_position; int i;
6203 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6205 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6206 DisplayError(_("Bad FEN received from engine"), 0);
6209 Reset(FALSE, FALSE);
6210 CopyBoard(boards[0], initial_position);
6211 initialRulePlies = FENrulePlies;
6212 epStatus[0] = FENepStatus;
6213 for( i=0; i<nrCastlingRights; i++ )
6214 castlingRights[0][i] = FENcastlingRights[i];
6215 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6216 else gameMode = MachinePlaysBlack;
6217 DrawPosition(FALSE, boards[currentMove]);
6223 * Look for communication commands
6225 if (!strncmp(message, "telluser ", 9)) {
6226 DisplayNote(message + 9);
6229 if (!strncmp(message, "tellusererror ", 14)) {
6230 DisplayError(message + 14, 0);
6233 if (!strncmp(message, "tellopponent ", 13)) {
6234 if (appData.icsActive) {
6236 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6240 DisplayNote(message + 13);
6244 if (!strncmp(message, "tellothers ", 11)) {
6245 if (appData.icsActive) {
6247 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6253 if (!strncmp(message, "tellall ", 8)) {
6254 if (appData.icsActive) {
6256 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6260 DisplayNote(message + 8);
6264 if (strncmp(message, "warning", 7) == 0) {
6265 /* Undocumented feature, use tellusererror in new code */
6266 DisplayError(message, 0);
6269 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6270 strcpy(realname, cps->tidy);
6271 strcat(realname, " query");
6272 AskQuestion(realname, buf2, buf1, cps->pr);
6275 /* Commands from the engine directly to ICS. We don't allow these to be
6276 * sent until we are logged on. Crafty kibitzes have been known to
6277 * interfere with the login process.
6280 if (!strncmp(message, "tellics ", 8)) {
6281 SendToICS(message + 8);
6285 if (!strncmp(message, "tellicsnoalias ", 15)) {
6286 SendToICS(ics_prefix);
6287 SendToICS(message + 15);
6291 /* The following are for backward compatibility only */
6292 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6293 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6294 SendToICS(ics_prefix);
6300 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6304 * If the move is illegal, cancel it and redraw the board.
6305 * Also deal with other error cases. Matching is rather loose
6306 * here to accommodate engines written before the spec.
6308 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6309 strncmp(message, "Error", 5) == 0) {
6310 if (StrStr(message, "name") ||
6311 StrStr(message, "rating") || StrStr(message, "?") ||
6312 StrStr(message, "result") || StrStr(message, "board") ||
6313 StrStr(message, "bk") || StrStr(message, "computer") ||
6314 StrStr(message, "variant") || StrStr(message, "hint") ||
6315 StrStr(message, "random") || StrStr(message, "depth") ||
6316 StrStr(message, "accepted")) {
6319 if (StrStr(message, "protover")) {
6320 /* Program is responding to input, so it's apparently done
6321 initializing, and this error message indicates it is
6322 protocol version 1. So we don't need to wait any longer
6323 for it to initialize and send feature commands. */
6324 FeatureDone(cps, 1);
6325 cps->protocolVersion = 1;
6328 cps->maybeThinking = FALSE;
6330 if (StrStr(message, "draw")) {
6331 /* Program doesn't have "draw" command */
6332 cps->sendDrawOffers = 0;
6335 if (cps->sendTime != 1 &&
6336 (StrStr(message, "time") || StrStr(message, "otim"))) {
6337 /* Program apparently doesn't have "time" or "otim" command */
6341 if (StrStr(message, "analyze")) {
6342 cps->analysisSupport = FALSE;
6343 cps->analyzing = FALSE;
6345 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6346 DisplayError(buf2, 0);
6349 if (StrStr(message, "(no matching move)st")) {
6350 /* Special kludge for GNU Chess 4 only */
6351 cps->stKludge = TRUE;
6352 SendTimeControl(cps, movesPerSession, timeControl,
6353 timeIncrement, appData.searchDepth,
6357 if (StrStr(message, "(no matching move)sd")) {
6358 /* Special kludge for GNU Chess 4 only */
6359 cps->sdKludge = TRUE;
6360 SendTimeControl(cps, movesPerSession, timeControl,
6361 timeIncrement, appData.searchDepth,
6365 if (!StrStr(message, "llegal")) {
6368 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6369 gameMode == IcsIdle) return;
6370 if (forwardMostMove <= backwardMostMove) return;
6372 /* Following removed: it caused a bug where a real illegal move
6373 message in analyze mored would be ignored. */
6374 if (cps == &first && programStats.ok_to_send == 0) {
6375 /* Bogus message from Crafty responding to "." This filtering
6376 can miss some of the bad messages, but fortunately the bug
6377 is fixed in current Crafty versions, so it doesn't matter. */
6381 if (pausing) PauseEvent();
6382 if (gameMode == PlayFromGameFile) {
6383 /* Stop reading this game file */
6384 gameMode = EditGame;
6387 currentMove = --forwardMostMove;
6388 DisplayMove(currentMove-1); /* before DisplayMoveError */
6390 DisplayBothClocks();
6391 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6392 parseList[currentMove], cps->which);
6393 DisplayMoveError(buf1);
6394 DrawPosition(FALSE, boards[currentMove]);
6396 /* [HGM] illegal-move claim should forfeit game when Xboard */
6397 /* only passes fully legal moves */
6398 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6399 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6400 "False illegal-move claim", GE_XBOARD );
6404 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6405 /* Program has a broken "time" command that
6406 outputs a string not ending in newline.
6412 * If chess program startup fails, exit with an error message.
6413 * Attempts to recover here are futile.
6415 if ((StrStr(message, "unknown host") != NULL)
6416 || (StrStr(message, "No remote directory") != NULL)
6417 || (StrStr(message, "not found") != NULL)
6418 || (StrStr(message, "No such file") != NULL)
6419 || (StrStr(message, "can't alloc") != NULL)
6420 || (StrStr(message, "Permission denied") != NULL)) {
6422 cps->maybeThinking = FALSE;
6423 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6424 cps->which, cps->program, cps->host, message);
6425 RemoveInputSource(cps->isr);
6426 DisplayFatalError(buf1, 0, 1);
6431 * Look for hint output
6433 if (sscanf(message, "Hint: %s", buf1) == 1) {
6434 if (cps == &first && hintRequested) {
6435 hintRequested = FALSE;
6436 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6437 &fromX, &fromY, &toX, &toY, &promoChar)) {
6438 (void) CoordsToAlgebraic(boards[forwardMostMove],
6439 PosFlags(forwardMostMove), EP_UNKNOWN,
6440 fromY, fromX, toY, toX, promoChar, buf1);
6441 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6442 DisplayInformation(buf2);
6444 /* Hint move could not be parsed!? */
6445 snprintf(buf2, sizeof(buf2),
6446 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6448 DisplayError(buf2, 0);
6451 strcpy(lastHint, buf1);
6457 * Ignore other messages if game is not in progress
6459 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6460 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6463 * look for win, lose, draw, or draw offer
6465 if (strncmp(message, "1-0", 3) == 0) {
6466 char *p, *q, *r = "";
6467 p = strchr(message, '{');
6475 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6477 } else if (strncmp(message, "0-1", 3) == 0) {
6478 char *p, *q, *r = "";
6479 p = strchr(message, '{');
6487 /* Kludge for Arasan 4.1 bug */
6488 if (strcmp(r, "Black resigns") == 0) {
6489 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6492 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6494 } else if (strncmp(message, "1/2", 3) == 0) {
6495 char *p, *q, *r = "";
6496 p = strchr(message, '{');
6505 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6508 } else if (strncmp(message, "White resign", 12) == 0) {
6509 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6511 } else if (strncmp(message, "Black resign", 12) == 0) {
6512 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6514 } else if (strncmp(message, "White matches", 13) == 0 ||
6515 strncmp(message, "Black matches", 13) == 0 ) {
6516 /* [HGM] ignore GNUShogi noises */
6518 } else if (strncmp(message, "White", 5) == 0 &&
6519 message[5] != '(' &&
6520 StrStr(message, "Black") == NULL) {
6521 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6523 } else if (strncmp(message, "Black", 5) == 0 &&
6524 message[5] != '(') {
6525 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6527 } else if (strcmp(message, "resign") == 0 ||
6528 strcmp(message, "computer resigns") == 0) {
6530 case MachinePlaysBlack:
6531 case IcsPlayingBlack:
6532 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6534 case MachinePlaysWhite:
6535 case IcsPlayingWhite:
6536 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6538 case TwoMachinesPlay:
6539 if (cps->twoMachinesColor[0] == 'w')
6540 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6542 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6549 } else if (strncmp(message, "opponent mates", 14) == 0) {
6551 case MachinePlaysBlack:
6552 case IcsPlayingBlack:
6553 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6555 case MachinePlaysWhite:
6556 case IcsPlayingWhite:
6557 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6559 case TwoMachinesPlay:
6560 if (cps->twoMachinesColor[0] == 'w')
6561 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6563 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6570 } else if (strncmp(message, "computer mates", 14) == 0) {
6572 case MachinePlaysBlack:
6573 case IcsPlayingBlack:
6574 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6576 case MachinePlaysWhite:
6577 case IcsPlayingWhite:
6578 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6580 case TwoMachinesPlay:
6581 if (cps->twoMachinesColor[0] == 'w')
6582 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6584 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6591 } else if (strncmp(message, "checkmate", 9) == 0) {
6592 if (WhiteOnMove(forwardMostMove)) {
6593 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6595 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6598 } else if (strstr(message, "Draw") != NULL ||
6599 strstr(message, "game is a draw") != NULL) {
6600 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6602 } else if (strstr(message, "offer") != NULL &&
6603 strstr(message, "draw") != NULL) {
6605 if (appData.zippyPlay && first.initDone) {
6606 /* Relay offer to ICS */
6607 SendToICS(ics_prefix);
6608 SendToICS("draw\n");
6611 cps->offeredDraw = 2; /* valid until this engine moves twice */
6612 if (gameMode == TwoMachinesPlay) {
6613 if (cps->other->offeredDraw) {
6614 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6615 /* [HGM] in two-machine mode we delay relaying draw offer */
6616 /* until after we also have move, to see if it is really claim */
6620 if (cps->other->sendDrawOffers) {
6621 SendToProgram("draw\n", cps->other);
6625 } else if (gameMode == MachinePlaysWhite ||
6626 gameMode == MachinePlaysBlack) {
6627 if (userOfferedDraw) {
6628 DisplayInformation(_("Machine accepts your draw offer"));
6629 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6631 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6638 * Look for thinking output
6640 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6641 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6643 int plylev, mvleft, mvtot, curscore, time;
6644 char mvname[MOVE_LEN];
6648 int prefixHint = FALSE;
6649 mvname[0] = NULLCHAR;
6652 case MachinePlaysBlack:
6653 case IcsPlayingBlack:
6654 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6656 case MachinePlaysWhite:
6657 case IcsPlayingWhite:
6658 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6663 case IcsObserving: /* [DM] icsEngineAnalyze */
6664 if (!appData.icsEngineAnalyze) ignore = TRUE;
6666 case TwoMachinesPlay:
6667 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6678 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6679 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6681 if (plyext != ' ' && plyext != '\t') {
6685 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6686 if( cps->scoreIsAbsolute &&
6687 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6689 curscore = -curscore;
6693 programStats.depth = plylev;
6694 programStats.nodes = nodes;
6695 programStats.time = time;
6696 programStats.score = curscore;
6697 programStats.got_only_move = 0;
6699 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6702 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6703 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6704 if(WhiteOnMove(forwardMostMove))
6705 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6706 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6709 /* Buffer overflow protection */
6710 if (buf1[0] != NULLCHAR) {
6711 if (strlen(buf1) >= sizeof(programStats.movelist)
6712 && appData.debugMode) {
6714 "PV is too long; using the first %d bytes.\n",
6715 sizeof(programStats.movelist) - 1);
6718 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6720 sprintf(programStats.movelist, " no PV\n");
6723 if (programStats.seen_stat) {
6724 programStats.ok_to_send = 1;
6727 if (strchr(programStats.movelist, '(') != NULL) {
6728 programStats.line_is_book = 1;
6729 programStats.nr_moves = 0;
6730 programStats.moves_left = 0;
6732 programStats.line_is_book = 0;
6735 SendProgramStatsToFrontend( cps, &programStats );
6738 [AS] Protect the thinkOutput buffer from overflow... this
6739 is only useful if buf1 hasn't overflowed first!
6741 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6743 (gameMode == TwoMachinesPlay ?
6744 ToUpper(cps->twoMachinesColor[0]) : ' '),
6745 ((double) curscore) / 100.0,
6746 prefixHint ? lastHint : "",
6747 prefixHint ? " " : "" );
6749 if( buf1[0] != NULLCHAR ) {
6750 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6752 if( strlen(buf1) > max_len ) {
6753 if( appData.debugMode) {
6754 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6756 buf1[max_len+1] = '\0';
6759 strcat( thinkOutput, buf1 );
6762 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6763 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6764 DisplayMove(currentMove - 1);
6769 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6770 /* crafty (9.25+) says "(only move) <move>"
6771 * if there is only 1 legal move
6773 sscanf(p, "(only move) %s", buf1);
6774 sprintf(thinkOutput, "%s (only move)", buf1);
6775 sprintf(programStats.movelist, "%s (only move)", buf1);
6776 programStats.depth = 1;
6777 programStats.nr_moves = 1;
6778 programStats.moves_left = 1;
6779 programStats.nodes = 1;
6780 programStats.time = 1;
6781 programStats.got_only_move = 1;
6783 /* Not really, but we also use this member to
6784 mean "line isn't going to change" (Crafty
6785 isn't searching, so stats won't change) */
6786 programStats.line_is_book = 1;
6788 SendProgramStatsToFrontend( cps, &programStats );
6790 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6791 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6792 DisplayMove(currentMove - 1);
6796 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6797 &time, &nodes, &plylev, &mvleft,
6798 &mvtot, mvname) >= 5) {
6799 /* The stat01: line is from Crafty (9.29+) in response
6800 to the "." command */
6801 programStats.seen_stat = 1;
6802 cps->maybeThinking = TRUE;
6804 if (programStats.got_only_move || !appData.periodicUpdates)
6807 programStats.depth = plylev;
6808 programStats.time = time;
6809 programStats.nodes = nodes;
6810 programStats.moves_left = mvleft;
6811 programStats.nr_moves = mvtot;
6812 strcpy(programStats.move_name, mvname);
6813 programStats.ok_to_send = 1;
6814 programStats.movelist[0] = '\0';
6816 SendProgramStatsToFrontend( cps, &programStats );
6821 } else if (strncmp(message,"++",2) == 0) {
6822 /* Crafty 9.29+ outputs this */
6823 programStats.got_fail = 2;
6826 } else if (strncmp(message,"--",2) == 0) {
6827 /* Crafty 9.29+ outputs this */
6828 programStats.got_fail = 1;
6831 } else if (thinkOutput[0] != NULLCHAR &&
6832 strncmp(message, " ", 4) == 0) {
6833 unsigned message_len;
6836 while (*p && *p == ' ') p++;
6838 message_len = strlen( p );
6840 /* [AS] Avoid buffer overflow */
6841 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6842 strcat(thinkOutput, " ");
6843 strcat(thinkOutput, p);
6846 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6847 strcat(programStats.movelist, " ");
6848 strcat(programStats.movelist, p);
6851 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6852 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6853 DisplayMove(currentMove - 1);
6862 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6863 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6865 ChessProgramStats cpstats;
6867 if (plyext != ' ' && plyext != '\t') {
6871 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6872 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6873 curscore = -curscore;
6876 cpstats.depth = plylev;
6877 cpstats.nodes = nodes;
6878 cpstats.time = time;
6879 cpstats.score = curscore;
6880 cpstats.got_only_move = 0;
6881 cpstats.movelist[0] = '\0';
6883 if (buf1[0] != NULLCHAR) {
6884 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6887 cpstats.ok_to_send = 0;
6888 cpstats.line_is_book = 0;
6889 cpstats.nr_moves = 0;
6890 cpstats.moves_left = 0;
6892 SendProgramStatsToFrontend( cps, &cpstats );
6899 /* Parse a game score from the character string "game", and
6900 record it as the history of the current game. The game
6901 score is NOT assumed to start from the standard position.
6902 The display is not updated in any way.
6905 ParseGameHistory(game)
6909 int fromX, fromY, toX, toY, boardIndex;
6914 if (appData.debugMode)
6915 fprintf(debugFP, "Parsing game history: %s\n", game);
6917 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6918 gameInfo.site = StrSave(appData.icsHost);
6919 gameInfo.date = PGNDate();
6920 gameInfo.round = StrSave("-");
6922 /* Parse out names of players */
6923 while (*game == ' ') game++;
6925 while (*game != ' ') *p++ = *game++;
6927 gameInfo.white = StrSave(buf);
6928 while (*game == ' ') game++;
6930 while (*game != ' ' && *game != '\n') *p++ = *game++;
6932 gameInfo.black = StrSave(buf);
6935 boardIndex = blackPlaysFirst ? 1 : 0;
6938 yyboardindex = boardIndex;
6939 moveType = (ChessMove) yylex();
6941 case IllegalMove: /* maybe suicide chess, etc. */
6942 if (appData.debugMode) {
6943 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6944 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6945 setbuf(debugFP, NULL);
6947 case WhitePromotionChancellor:
6948 case BlackPromotionChancellor:
6949 case WhitePromotionArchbishop:
6950 case BlackPromotionArchbishop:
6951 case WhitePromotionQueen:
6952 case BlackPromotionQueen:
6953 case WhitePromotionRook:
6954 case BlackPromotionRook:
6955 case WhitePromotionBishop:
6956 case BlackPromotionBishop:
6957 case WhitePromotionKnight:
6958 case BlackPromotionKnight:
6959 case WhitePromotionKing:
6960 case BlackPromotionKing:
6962 case WhiteCapturesEnPassant:
6963 case BlackCapturesEnPassant:
6964 case WhiteKingSideCastle:
6965 case WhiteQueenSideCastle:
6966 case BlackKingSideCastle:
6967 case BlackQueenSideCastle:
6968 case WhiteKingSideCastleWild:
6969 case WhiteQueenSideCastleWild:
6970 case BlackKingSideCastleWild:
6971 case BlackQueenSideCastleWild:
6973 case WhiteHSideCastleFR:
6974 case WhiteASideCastleFR:
6975 case BlackHSideCastleFR:
6976 case BlackASideCastleFR:
6978 fromX = currentMoveString[0] - AAA;
6979 fromY = currentMoveString[1] - ONE;
6980 toX = currentMoveString[2] - AAA;
6981 toY = currentMoveString[3] - ONE;
6982 promoChar = currentMoveString[4];
6986 fromX = moveType == WhiteDrop ?
6987 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6988 (int) CharToPiece(ToLower(currentMoveString[0]));
6990 toX = currentMoveString[2] - AAA;
6991 toY = currentMoveString[3] - ONE;
6992 promoChar = NULLCHAR;
6996 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6997 if (appData.debugMode) {
6998 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6999 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7000 setbuf(debugFP, NULL);
7002 DisplayError(buf, 0);
7004 case ImpossibleMove:
7006 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7007 if (appData.debugMode) {
7008 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7009 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7010 setbuf(debugFP, NULL);
7012 DisplayError(buf, 0);
7014 case (ChessMove) 0: /* end of file */
7015 if (boardIndex < backwardMostMove) {
7016 /* Oops, gap. How did that happen? */
7017 DisplayError(_("Gap in move list"), 0);
7020 backwardMostMove = blackPlaysFirst ? 1 : 0;
7021 if (boardIndex > forwardMostMove) {
7022 forwardMostMove = boardIndex;
7026 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7027 strcat(parseList[boardIndex-1], " ");
7028 strcat(parseList[boardIndex-1], yy_text);
7040 case GameUnfinished:
7041 if (gameMode == IcsExamining) {
7042 if (boardIndex < backwardMostMove) {
7043 /* Oops, gap. How did that happen? */
7046 backwardMostMove = blackPlaysFirst ? 1 : 0;
7049 gameInfo.result = moveType;
7050 p = strchr(yy_text, '{');
7051 if (p == NULL) p = strchr(yy_text, '(');
7054 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7056 q = strchr(p, *p == '{' ? '}' : ')');
7057 if (q != NULL) *q = NULLCHAR;
7060 gameInfo.resultDetails = StrSave(p);
7063 if (boardIndex >= forwardMostMove &&
7064 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7065 backwardMostMove = blackPlaysFirst ? 1 : 0;
7068 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7069 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7070 parseList[boardIndex]);
7071 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7072 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7073 /* currentMoveString is set as a side-effect of yylex */
7074 strcpy(moveList[boardIndex], currentMoveString);
7075 strcat(moveList[boardIndex], "\n");
7077 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7078 castlingRights[boardIndex], &epStatus[boardIndex]);
7079 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7080 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7086 if(gameInfo.variant != VariantShogi)
7087 strcat(parseList[boardIndex - 1], "+");
7091 strcat(parseList[boardIndex - 1], "#");
7098 /* Apply a move to the given board */
7100 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7101 int fromX, fromY, toX, toY;
7107 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7109 /* [HGM] compute & store e.p. status and castling rights for new position */
7110 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7113 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7117 if( board[toY][toX] != EmptySquare )
7120 if( board[fromY][fromX] == WhitePawn ) {
7121 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7124 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7125 gameInfo.variant != VariantBerolina || toX < fromX)
7126 *ep = toX | berolina;
7127 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7128 gameInfo.variant != VariantBerolina || toX > fromX)
7132 if( board[fromY][fromX] == BlackPawn ) {
7133 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7135 if( toY-fromY== -2) {
7136 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7137 gameInfo.variant != VariantBerolina || toX < fromX)
7138 *ep = toX | berolina;
7139 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7140 gameInfo.variant != VariantBerolina || toX > fromX)
7145 for(i=0; i<nrCastlingRights; i++) {
7146 if(castling[i] == fromX && castlingRank[i] == fromY ||
7147 castling[i] == toX && castlingRank[i] == toY
7148 ) castling[i] = -1; // revoke for moved or captured piece
7153 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7154 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7155 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7157 if (fromX == toX && fromY == toY) return;
7159 if (fromY == DROP_RANK) {
7161 piece = board[toY][toX] = (ChessSquare) fromX;
7163 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7164 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7165 if(gameInfo.variant == VariantKnightmate)
7166 king += (int) WhiteUnicorn - (int) WhiteKing;
7168 /* Code added by Tord: */
7169 /* FRC castling assumed when king captures friendly rook. */
7170 if (board[fromY][fromX] == WhiteKing &&
7171 board[toY][toX] == WhiteRook) {
7172 board[fromY][fromX] = EmptySquare;
7173 board[toY][toX] = EmptySquare;
7175 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7177 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7179 } else if (board[fromY][fromX] == BlackKing &&
7180 board[toY][toX] == BlackRook) {
7181 board[fromY][fromX] = EmptySquare;
7182 board[toY][toX] = EmptySquare;
7184 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7186 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7188 /* End of code added by Tord */
7190 } else if (board[fromY][fromX] == king
7191 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7192 && toY == fromY && toX > fromX+1) {
7193 board[fromY][fromX] = EmptySquare;
7194 board[toY][toX] = king;
7195 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7196 board[fromY][BOARD_RGHT-1] = EmptySquare;
7197 } else if (board[fromY][fromX] == king
7198 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7199 && toY == fromY && toX < fromX-1) {
7200 board[fromY][fromX] = EmptySquare;
7201 board[toY][toX] = king;
7202 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7203 board[fromY][BOARD_LEFT] = EmptySquare;
7204 } else if (board[fromY][fromX] == WhitePawn
7205 && toY == BOARD_HEIGHT-1
7206 && gameInfo.variant != VariantXiangqi
7208 /* white pawn promotion */
7209 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7210 if (board[toY][toX] == EmptySquare) {
7211 board[toY][toX] = WhiteQueen;
7213 if(gameInfo.variant==VariantBughouse ||
7214 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7215 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7216 board[fromY][fromX] = EmptySquare;
7217 } else if ((fromY == BOARD_HEIGHT-4)
7219 && gameInfo.variant != VariantXiangqi
7220 && gameInfo.variant != VariantBerolina
7221 && (board[fromY][fromX] == WhitePawn)
7222 && (board[toY][toX] == EmptySquare)) {
7223 board[fromY][fromX] = EmptySquare;
7224 board[toY][toX] = WhitePawn;
7225 captured = board[toY - 1][toX];
7226 board[toY - 1][toX] = EmptySquare;
7227 } else if ((fromY == BOARD_HEIGHT-4)
7229 && gameInfo.variant == VariantBerolina
7230 && (board[fromY][fromX] == WhitePawn)
7231 && (board[toY][toX] == EmptySquare)) {
7232 board[fromY][fromX] = EmptySquare;
7233 board[toY][toX] = WhitePawn;
7234 if(oldEP & EP_BEROLIN_A) {
7235 captured = board[fromY][fromX-1];
7236 board[fromY][fromX-1] = EmptySquare;
7237 }else{ captured = board[fromY][fromX+1];
7238 board[fromY][fromX+1] = EmptySquare;
7240 } else if (board[fromY][fromX] == king
7241 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7242 && toY == fromY && toX > fromX+1) {
7243 board[fromY][fromX] = EmptySquare;
7244 board[toY][toX] = king;
7245 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7246 board[fromY][BOARD_RGHT-1] = EmptySquare;
7247 } else if (board[fromY][fromX] == king
7248 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7249 && toY == fromY && toX < fromX-1) {
7250 board[fromY][fromX] = EmptySquare;
7251 board[toY][toX] = king;
7252 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7253 board[fromY][BOARD_LEFT] = EmptySquare;
7254 } else if (fromY == 7 && fromX == 3
7255 && board[fromY][fromX] == BlackKing
7256 && toY == 7 && toX == 5) {
7257 board[fromY][fromX] = EmptySquare;
7258 board[toY][toX] = BlackKing;
7259 board[fromY][7] = EmptySquare;
7260 board[toY][4] = BlackRook;
7261 } else if (fromY == 7 && fromX == 3
7262 && board[fromY][fromX] == BlackKing
7263 && toY == 7 && toX == 1) {
7264 board[fromY][fromX] = EmptySquare;
7265 board[toY][toX] = BlackKing;
7266 board[fromY][0] = EmptySquare;
7267 board[toY][2] = BlackRook;
7268 } else if (board[fromY][fromX] == BlackPawn
7270 && gameInfo.variant != VariantXiangqi
7272 /* black pawn promotion */
7273 board[0][toX] = CharToPiece(ToLower(promoChar));
7274 if (board[0][toX] == EmptySquare) {
7275 board[0][toX] = BlackQueen;
7277 if(gameInfo.variant==VariantBughouse ||
7278 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7279 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7280 board[fromY][fromX] = EmptySquare;
7281 } else if ((fromY == 3)
7283 && gameInfo.variant != VariantXiangqi
7284 && gameInfo.variant != VariantBerolina
7285 && (board[fromY][fromX] == BlackPawn)
7286 && (board[toY][toX] == EmptySquare)) {
7287 board[fromY][fromX] = EmptySquare;
7288 board[toY][toX] = BlackPawn;
7289 captured = board[toY + 1][toX];
7290 board[toY + 1][toX] = EmptySquare;
7291 } else if ((fromY == 3)
7293 && gameInfo.variant == VariantBerolina
7294 && (board[fromY][fromX] == BlackPawn)
7295 && (board[toY][toX] == EmptySquare)) {
7296 board[fromY][fromX] = EmptySquare;
7297 board[toY][toX] = BlackPawn;
7298 if(oldEP & EP_BEROLIN_A) {
7299 captured = board[fromY][fromX-1];
7300 board[fromY][fromX-1] = EmptySquare;
7301 }else{ captured = board[fromY][fromX+1];
7302 board[fromY][fromX+1] = EmptySquare;
7305 board[toY][toX] = board[fromY][fromX];
7306 board[fromY][fromX] = EmptySquare;
7309 /* [HGM] now we promote for Shogi, if needed */
7310 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7311 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7314 if (gameInfo.holdingsWidth != 0) {
7316 /* !!A lot more code needs to be written to support holdings */
7317 /* [HGM] OK, so I have written it. Holdings are stored in the */
7318 /* penultimate board files, so they are automaticlly stored */
7319 /* in the game history. */
7320 if (fromY == DROP_RANK) {
7321 /* Delete from holdings, by decreasing count */
7322 /* and erasing image if necessary */
7324 if(p < (int) BlackPawn) { /* white drop */
7325 p -= (int)WhitePawn;
7326 if(p >= gameInfo.holdingsSize) p = 0;
7327 if(--board[p][BOARD_WIDTH-2] == 0)
7328 board[p][BOARD_WIDTH-1] = EmptySquare;
7329 } else { /* black drop */
7330 p -= (int)BlackPawn;
7331 if(p >= gameInfo.holdingsSize) p = 0;
7332 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7333 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7336 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7337 && gameInfo.variant != VariantBughouse ) {
7338 /* [HGM] holdings: Add to holdings, if holdings exist */
7339 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7340 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7341 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7344 if (p >= (int) BlackPawn) {
7345 p -= (int)BlackPawn;
7346 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7347 /* in Shogi restore piece to its original first */
7348 captured = (ChessSquare) (DEMOTED captured);
7351 p = PieceToNumber((ChessSquare)p);
7352 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7353 board[p][BOARD_WIDTH-2]++;
7354 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7356 p -= (int)WhitePawn;
7357 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7358 captured = (ChessSquare) (DEMOTED captured);
7361 p = PieceToNumber((ChessSquare)p);
7362 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7363 board[BOARD_HEIGHT-1-p][1]++;
7364 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7368 } else if (gameInfo.variant == VariantAtomic) {
7369 if (captured != EmptySquare) {
7371 for (y = toY-1; y <= toY+1; y++) {
7372 for (x = toX-1; x <= toX+1; x++) {
7373 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7374 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7375 board[y][x] = EmptySquare;
7379 board[toY][toX] = EmptySquare;
7382 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7383 /* [HGM] Shogi promotions */
7384 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7387 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7388 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7389 // [HGM] superchess: take promotion piece out of holdings
7390 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7391 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7392 if(!--board[k][BOARD_WIDTH-2])
7393 board[k][BOARD_WIDTH-1] = EmptySquare;
7395 if(!--board[BOARD_HEIGHT-1-k][1])
7396 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7402 /* Updates forwardMostMove */
7404 MakeMove(fromX, fromY, toX, toY, promoChar)
7405 int fromX, fromY, toX, toY;
7408 // forwardMostMove++; // [HGM] bare: moved downstream
7410 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7411 int timeLeft; static int lastLoadFlag=0; int king, piece;
7412 piece = boards[forwardMostMove][fromY][fromX];
7413 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7414 if(gameInfo.variant == VariantKnightmate)
7415 king += (int) WhiteUnicorn - (int) WhiteKing;
7416 if(forwardMostMove == 0) {
7418 fprintf(serverMoves, "%s;", second.tidy);
7419 fprintf(serverMoves, "%s;", first.tidy);
7420 if(!blackPlaysFirst)
7421 fprintf(serverMoves, "%s;", second.tidy);
7422 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7423 lastLoadFlag = loadFlag;
7425 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7426 // print castling suffix
7427 if( toY == fromY && piece == king ) {
7429 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7431 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7434 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7435 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7436 boards[forwardMostMove][toY][toX] == EmptySquare
7438 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7440 if(promoChar != NULLCHAR)
7441 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7443 fprintf(serverMoves, "/%d/%d",
7444 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7445 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7446 else timeLeft = blackTimeRemaining/1000;
7447 fprintf(serverMoves, "/%d", timeLeft);
7449 fflush(serverMoves);
7452 if (forwardMostMove+1 >= MAX_MOVES) {
7453 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7458 timeRemaining[0][forwardMostMove+1] = whiteTimeRemaining;
7459 timeRemaining[1][forwardMostMove+1] = blackTimeRemaining;
7460 if (commentList[forwardMostMove+1] != NULL) {
7461 free(commentList[forwardMostMove+1]);
7462 commentList[forwardMostMove+1] = NULL;
7464 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7465 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7466 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7467 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7468 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7469 gameInfo.result = GameUnfinished;
7470 if (gameInfo.resultDetails != NULL) {
7471 free(gameInfo.resultDetails);
7472 gameInfo.resultDetails = NULL;
7474 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7475 moveList[forwardMostMove - 1]);
7476 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7477 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7478 fromY, fromX, toY, toX, promoChar,
7479 parseList[forwardMostMove - 1]);
7480 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7481 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7482 castlingRights[forwardMostMove]) ) {
7488 if(gameInfo.variant != VariantShogi)
7489 strcat(parseList[forwardMostMove - 1], "+");
7493 strcat(parseList[forwardMostMove - 1], "#");
7496 if (appData.debugMode) {
7497 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7502 /* Updates currentMove if not pausing */
7504 ShowMove(fromX, fromY, toX, toY)
7506 int instant = (gameMode == PlayFromGameFile) ?
7507 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7508 if(appData.noGUI) return;
7509 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7511 if (forwardMostMove == currentMove + 1) {
7512 AnimateMove(boards[forwardMostMove - 1],
7513 fromX, fromY, toX, toY);
7515 if (appData.highlightLastMove) {
7516 SetHighlights(fromX, fromY, toX, toY);
7519 currentMove = forwardMostMove;
7522 if (instant) return;
7524 DisplayMove(currentMove - 1);
7525 DrawPosition(FALSE, boards[currentMove]);
7526 DisplayBothClocks();
7527 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7530 void SendEgtPath(ChessProgramState *cps)
7531 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7532 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7534 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7537 char c, *q = name+1, *r, *s;
7539 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7540 while(*p && *p != ',') *q++ = *p++;
7542 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7543 strcmp(name, ",nalimov:") == 0 ) {
7544 // take nalimov path from the menu-changeable option first, if it is defined
7545 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7546 SendToProgram(buf,cps); // send egtbpath command for nalimov
7548 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7549 (s = StrStr(appData.egtFormats, name)) != NULL) {
7550 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7551 s = r = StrStr(s, ":") + 1; // beginning of path info
7552 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7553 c = *r; *r = 0; // temporarily null-terminate path info
7554 *--q = 0; // strip of trailig ':' from name
7555 sprintf(buf, "egtpath %s %s\n", name+1, s);
7557 SendToProgram(buf,cps); // send egtbpath command for this format
7559 if(*p == ',') p++; // read away comma to position for next format name
7564 InitChessProgram(cps, setup)
7565 ChessProgramState *cps;
7566 int setup; /* [HGM] needed to setup FRC opening position */
7568 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7569 if (appData.noChessProgram) return;
7570 hintRequested = FALSE;
7571 bookRequested = FALSE;
7573 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7574 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7575 if(cps->memSize) { /* [HGM] memory */
7576 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7577 SendToProgram(buf, cps);
7579 SendEgtPath(cps); /* [HGM] EGT */
7580 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7581 sprintf(buf, "cores %d\n", appData.smpCores);
7582 SendToProgram(buf, cps);
7585 SendToProgram(cps->initString, cps);
7586 if (gameInfo.variant != VariantNormal &&
7587 gameInfo.variant != VariantLoadable
7588 /* [HGM] also send variant if board size non-standard */
7589 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7591 char *v = VariantName(gameInfo.variant);
7592 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7593 /* [HGM] in protocol 1 we have to assume all variants valid */
7594 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7595 DisplayFatalError(buf, 0, 1);
7599 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7600 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7601 if( gameInfo.variant == VariantXiangqi )
7602 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7603 if( gameInfo.variant == VariantShogi )
7604 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7605 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7606 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7607 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7608 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7609 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7610 if( gameInfo.variant == VariantCourier )
7611 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7612 if( gameInfo.variant == VariantSuper )
7613 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7614 if( gameInfo.variant == VariantGreat )
7615 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7618 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7619 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7620 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7621 if(StrStr(cps->variants, b) == NULL) {
7622 // specific sized variant not known, check if general sizing allowed
7623 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7624 if(StrStr(cps->variants, "boardsize") == NULL) {
7625 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7626 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7627 DisplayFatalError(buf, 0, 1);
7630 /* [HGM] here we really should compare with the maximum supported board size */
7633 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7634 sprintf(buf, "variant %s\n", b);
7635 SendToProgram(buf, cps);
7637 currentlyInitializedVariant = gameInfo.variant;
7639 /* [HGM] send opening position in FRC to first engine */
7641 SendToProgram("force\n", cps);
7643 /* engine is now in force mode! Set flag to wake it up after first move. */
7644 setboardSpoiledMachineBlack = 1;
7648 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7649 SendToProgram(buf, cps);
7651 cps->maybeThinking = FALSE;
7652 cps->offeredDraw = 0;
7653 if (!appData.icsActive) {
7654 SendTimeControl(cps, movesPerSession, timeControl,
7655 timeIncrement, appData.searchDepth,
7658 if (appData.showThinking
7659 // [HGM] thinking: four options require thinking output to be sent
7660 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7662 SendToProgram("post\n", cps);
7664 SendToProgram("hard\n", cps);
7665 if (!appData.ponderNextMove) {
7666 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7667 it without being sure what state we are in first. "hard"
7668 is not a toggle, so that one is OK.
7670 SendToProgram("easy\n", cps);
7673 sprintf(buf, "ping %d\n", ++cps->lastPing);
7674 SendToProgram(buf, cps);
7676 cps->initDone = TRUE;
7681 StartChessProgram(cps)
7682 ChessProgramState *cps;
7687 if (appData.noChessProgram) return;
7688 cps->initDone = FALSE;
7690 if (strcmp(cps->host, "localhost") == 0) {
7691 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7692 } else if (*appData.remoteShell == NULLCHAR) {
7693 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7695 if (*appData.remoteUser == NULLCHAR) {
7696 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7699 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7700 cps->host, appData.remoteUser, cps->program);
7702 err = StartChildProcess(buf, "", &cps->pr);
7706 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7707 DisplayFatalError(buf, err, 1);
7713 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7714 if (cps->protocolVersion > 1) {
7715 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7716 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7717 cps->comboCnt = 0; // and values of combo boxes
7718 SendToProgram(buf, cps);
7720 SendToProgram("xboard\n", cps);
7726 TwoMachinesEventIfReady P((void))
7728 if (first.lastPing != first.lastPong) {
7729 DisplayMessage("", _("Waiting for first chess program"));
7730 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7733 if (second.lastPing != second.lastPong) {
7734 DisplayMessage("", _("Waiting for second chess program"));
7735 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7743 NextMatchGame P((void))
7745 int index; /* [HGM] autoinc: step lod index during match */
7747 if (*appData.loadGameFile != NULLCHAR) {
7748 index = appData.loadGameIndex;
7749 if(index < 0) { // [HGM] autoinc
7750 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7751 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7753 LoadGameFromFile(appData.loadGameFile,
7755 appData.loadGameFile, FALSE);
7756 } else if (*appData.loadPositionFile != NULLCHAR) {
7757 index = appData.loadPositionIndex;
7758 if(index < 0) { // [HGM] autoinc
7759 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7760 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7762 LoadPositionFromFile(appData.loadPositionFile,
7764 appData.loadPositionFile);
7766 TwoMachinesEventIfReady();
7769 void UserAdjudicationEvent( int result )
7771 ChessMove gameResult = GameIsDrawn;
7774 gameResult = WhiteWins;
7776 else if( result < 0 ) {
7777 gameResult = BlackWins;
7780 if( gameMode == TwoMachinesPlay ) {
7781 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7786 // [HGM] save: calculate checksum of game to make games easily identifiable
7787 int StringCheckSum(char *s)
7790 if(s==NULL) return 0;
7791 while(*s) i = i*259 + *s++;
7798 for(i=backwardMostMove; i<forwardMostMove; i++) {
7799 sum += pvInfoList[i].depth;
7800 sum += StringCheckSum(parseList[i]);
7801 sum += StringCheckSum(commentList[i]);
7804 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7805 return sum + StringCheckSum(commentList[i]);
7806 } // end of save patch
7809 GameEnds(result, resultDetails, whosays)
7811 char *resultDetails;
7814 GameMode nextGameMode;
7818 if(endingGame) return; /* [HGM] crash: forbid recursion */
7821 if (appData.debugMode) {
7822 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7823 result, resultDetails ? resultDetails : "(null)", whosays);
7826 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7827 /* If we are playing on ICS, the server decides when the
7828 game is over, but the engine can offer to draw, claim
7832 if (appData.zippyPlay && first.initDone) {
7833 if (result == GameIsDrawn) {
7834 /* In case draw still needs to be claimed */
7835 SendToICS(ics_prefix);
7836 SendToICS("draw\n");
7837 } else if (StrCaseStr(resultDetails, "resign")) {
7838 SendToICS(ics_prefix);
7839 SendToICS("resign\n");
7843 endingGame = 0; /* [HGM] crash */
7847 /* If we're loading the game from a file, stop */
7848 if (whosays == GE_FILE) {
7849 (void) StopLoadGameTimer();
7853 /* Cancel draw offers */
7854 first.offeredDraw = second.offeredDraw = 0;
7856 /* If this is an ICS game, only ICS can really say it's done;
7857 if not, anyone can. */
7858 isIcsGame = (gameMode == IcsPlayingWhite ||
7859 gameMode == IcsPlayingBlack ||
7860 gameMode == IcsObserving ||
7861 gameMode == IcsExamining);
7863 if (!isIcsGame || whosays == GE_ICS) {
7864 /* OK -- not an ICS game, or ICS said it was done */
7866 if (!isIcsGame && !appData.noChessProgram)
7867 SetUserThinkingEnables();
7869 /* [HGM] if a machine claims the game end we verify this claim */
7870 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7871 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7873 ChessMove trueResult = (ChessMove) -1;
7875 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7876 first.twoMachinesColor[0] :
7877 second.twoMachinesColor[0] ;
7879 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7880 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7881 /* [HGM] verify: engine mate claims accepted if they were flagged */
7882 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7884 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7885 /* [HGM] verify: engine mate claims accepted if they were flagged */
7886 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7888 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7889 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7892 // now verify win claims, but not in drop games, as we don't understand those yet
7893 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7894 || gameInfo.variant == VariantGreat) &&
7895 (result == WhiteWins && claimer == 'w' ||
7896 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7897 if (appData.debugMode) {
7898 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7899 result, epStatus[forwardMostMove], forwardMostMove);
7901 if(result != trueResult) {
7902 sprintf(buf, "False win claim: '%s'", resultDetails);
7903 result = claimer == 'w' ? BlackWins : WhiteWins;
7904 resultDetails = buf;
7907 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7908 && (forwardMostMove <= backwardMostMove ||
7909 epStatus[forwardMostMove-1] > EP_DRAWS ||
7910 (claimer=='b')==(forwardMostMove&1))
7912 /* [HGM] verify: draws that were not flagged are false claims */
7913 sprintf(buf, "False draw claim: '%s'", resultDetails);
7914 result = claimer == 'w' ? BlackWins : WhiteWins;
7915 resultDetails = buf;
7917 /* (Claiming a loss is accepted no questions asked!) */
7919 /* [HGM] bare: don't allow bare King to win */
7920 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7921 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7922 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7923 && result != GameIsDrawn)
7924 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7925 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7926 int p = (int)boards[forwardMostMove][i][j] - color;
7927 if(p >= 0 && p <= (int)WhiteKing) k++;
7929 if (appData.debugMode) {
7930 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7931 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7934 result = GameIsDrawn;
7935 sprintf(buf, "%s but bare king", resultDetails);
7936 resultDetails = buf;
7942 if(serverMoves != NULL && !loadFlag) { char c = '=';
7943 if(result==WhiteWins) c = '+';
7944 if(result==BlackWins) c = '-';
7945 if(resultDetails != NULL)
7946 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7948 if (resultDetails != NULL) {
7949 gameInfo.result = result;
7950 gameInfo.resultDetails = StrSave(resultDetails);
7952 /* display last move only if game was not loaded from file */
7953 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7954 DisplayMove(currentMove - 1);
7956 if (forwardMostMove != 0) {
7957 if (gameMode != PlayFromGameFile && gameMode != EditGame
7958 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7960 if (*appData.saveGameFile != NULLCHAR) {
7961 SaveGameToFile(appData.saveGameFile, TRUE);
7962 } else if (appData.autoSaveGames) {
7965 if (*appData.savePositionFile != NULLCHAR) {
7966 SavePositionToFile(appData.savePositionFile);
7971 /* Tell program how game ended in case it is learning */
7972 /* [HGM] Moved this to after saving the PGN, just in case */
7973 /* engine died and we got here through time loss. In that */
7974 /* case we will get a fatal error writing the pipe, which */
7975 /* would otherwise lose us the PGN. */
7976 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7977 /* output during GameEnds should never be fatal anymore */
7978 if (gameMode == MachinePlaysWhite ||
7979 gameMode == MachinePlaysBlack ||
7980 gameMode == TwoMachinesPlay ||
7981 gameMode == IcsPlayingWhite ||
7982 gameMode == IcsPlayingBlack ||
7983 gameMode == BeginningOfGame) {
7985 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7987 if (first.pr != NoProc) {
7988 SendToProgram(buf, &first);
7990 if (second.pr != NoProc &&
7991 gameMode == TwoMachinesPlay) {
7992 SendToProgram(buf, &second);
7997 if (appData.icsActive) {
7998 if (appData.quietPlay &&
7999 (gameMode == IcsPlayingWhite ||
8000 gameMode == IcsPlayingBlack)) {
8001 SendToICS(ics_prefix);
8002 SendToICS("set shout 1\n");
8004 nextGameMode = IcsIdle;
8005 ics_user_moved = FALSE;
8006 /* clean up premove. It's ugly when the game has ended and the
8007 * premove highlights are still on the board.
8011 ClearPremoveHighlights();
8012 DrawPosition(FALSE, boards[currentMove]);
8014 if (whosays == GE_ICS) {
8017 if (gameMode == IcsPlayingWhite)
8019 else if(gameMode == IcsPlayingBlack)
8023 if (gameMode == IcsPlayingBlack)
8025 else if(gameMode == IcsPlayingWhite)
8032 PlayIcsUnfinishedSound();
8035 } else if (gameMode == EditGame ||
8036 gameMode == PlayFromGameFile ||
8037 gameMode == AnalyzeMode ||
8038 gameMode == AnalyzeFile) {
8039 nextGameMode = gameMode;
8041 nextGameMode = EndOfGame;
8046 nextGameMode = gameMode;
8049 if (appData.noChessProgram) {
8050 gameMode = nextGameMode;
8052 endingGame = 0; /* [HGM] crash */
8057 /* Put first chess program into idle state */
8058 if (first.pr != NoProc &&
8059 (gameMode == MachinePlaysWhite ||
8060 gameMode == MachinePlaysBlack ||
8061 gameMode == TwoMachinesPlay ||
8062 gameMode == IcsPlayingWhite ||
8063 gameMode == IcsPlayingBlack ||
8064 gameMode == BeginningOfGame)) {
8065 SendToProgram("force\n", &first);
8066 if (first.usePing) {
8068 sprintf(buf, "ping %d\n", ++first.lastPing);
8069 SendToProgram(buf, &first);
8072 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8073 /* Kill off first chess program */
8074 if (first.isr != NULL)
8075 RemoveInputSource(first.isr);
8078 if (first.pr != NoProc) {
8080 DoSleep( appData.delayBeforeQuit );
8081 SendToProgram("quit\n", &first);
8082 DoSleep( appData.delayAfterQuit );
8083 DestroyChildProcess(first.pr, first.useSigterm);
8088 /* Put second chess program into idle state */
8089 if (second.pr != NoProc &&
8090 gameMode == TwoMachinesPlay) {
8091 SendToProgram("force\n", &second);
8092 if (second.usePing) {
8094 sprintf(buf, "ping %d\n", ++second.lastPing);
8095 SendToProgram(buf, &second);
8098 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8099 /* Kill off second chess program */
8100 if (second.isr != NULL)
8101 RemoveInputSource(second.isr);
8104 if (second.pr != NoProc) {
8105 DoSleep( appData.delayBeforeQuit );
8106 SendToProgram("quit\n", &second);
8107 DoSleep( appData.delayAfterQuit );
8108 DestroyChildProcess(second.pr, second.useSigterm);
8113 if (matchMode && gameMode == TwoMachinesPlay) {
8116 if (first.twoMachinesColor[0] == 'w') {
8123 if (first.twoMachinesColor[0] == 'b') {
8132 if (matchGame < appData.matchGames) {
8134 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8135 tmp = first.twoMachinesColor;
8136 first.twoMachinesColor = second.twoMachinesColor;
8137 second.twoMachinesColor = tmp;
8139 gameMode = nextGameMode;
8141 if(appData.matchPause>10000 || appData.matchPause<10)
8142 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8143 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8144 endingGame = 0; /* [HGM] crash */
8148 gameMode = nextGameMode;
8149 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8150 first.tidy, second.tidy,
8151 first.matchWins, second.matchWins,
8152 appData.matchGames - (first.matchWins + second.matchWins));
8153 DisplayFatalError(buf, 0, 0);
8156 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8157 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8159 gameMode = nextGameMode;
8161 endingGame = 0; /* [HGM] crash */
8164 /* Assumes program was just initialized (initString sent).
8165 Leaves program in force mode. */
8167 FeedMovesToProgram(cps, upto)
8168 ChessProgramState *cps;
8173 if (appData.debugMode)
8174 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8175 startedFromSetupPosition ? "position and " : "",
8176 backwardMostMove, upto, cps->which);
8177 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8178 // [HGM] variantswitch: make engine aware of new variant
8179 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8180 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8181 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8182 SendToProgram(buf, cps);
8183 currentlyInitializedVariant = gameInfo.variant;
8185 SendToProgram("force\n", cps);
8186 if (startedFromSetupPosition) {
8187 SendBoard(cps, backwardMostMove);
8188 if (appData.debugMode) {
8189 fprintf(debugFP, "feedMoves\n");
8192 for (i = backwardMostMove; i < upto; i++) {
8193 SendMoveToProgram(i, cps);
8199 ResurrectChessProgram()
8201 /* The chess program may have exited.
8202 If so, restart it and feed it all the moves made so far. */
8204 if (appData.noChessProgram || first.pr != NoProc) return;
8206 StartChessProgram(&first);
8207 InitChessProgram(&first, FALSE);
8208 FeedMovesToProgram(&first, currentMove);
8210 if (!first.sendTime) {
8211 /* can't tell gnuchess what its clock should read,
8212 so we bow to its notion. */
8214 timeRemaining[0][currentMove] = whiteTimeRemaining;
8215 timeRemaining[1][currentMove] = blackTimeRemaining;
8218 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8219 appData.icsEngineAnalyze) && first.analysisSupport) {
8220 SendToProgram("analyze\n", &first);
8221 first.analyzing = TRUE;
8234 if (appData.debugMode) {
8235 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8236 redraw, init, gameMode);
8238 pausing = pauseExamInvalid = FALSE;
8239 startedFromSetupPosition = blackPlaysFirst = FALSE;
8241 whiteFlag = blackFlag = FALSE;
8242 userOfferedDraw = FALSE;
8243 hintRequested = bookRequested = FALSE;
8244 first.maybeThinking = FALSE;
8245 second.maybeThinking = FALSE;
8246 first.bookSuspend = FALSE; // [HGM] book
8247 second.bookSuspend = FALSE;
8248 thinkOutput[0] = NULLCHAR;
8249 lastHint[0] = NULLCHAR;
8250 ClearGameInfo(&gameInfo);
8251 gameInfo.variant = StringToVariant(appData.variant);
8252 ics_user_moved = ics_clock_paused = FALSE;
8253 ics_getting_history = H_FALSE;
8255 white_holding[0] = black_holding[0] = NULLCHAR;
8256 ClearProgramStats();
8257 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8261 flipView = appData.flipView;
8262 ClearPremoveHighlights();
8264 alarmSounded = FALSE;
8266 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8267 if(appData.serverMovesName != NULL) {
8268 /* [HGM] prepare to make moves file for broadcasting */
8269 clock_t t = clock();
8270 if(serverMoves != NULL) fclose(serverMoves);
8271 serverMoves = fopen(appData.serverMovesName, "r");
8272 if(serverMoves != NULL) {
8273 fclose(serverMoves);
8274 /* delay 15 sec before overwriting, so all clients can see end */
8275 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8277 serverMoves = fopen(appData.serverMovesName, "w");
8281 gameMode = BeginningOfGame;
8283 if(appData.icsActive) gameInfo.variant = VariantNormal;
8284 InitPosition(redraw);
8285 for (i = 0; i < MAX_MOVES; i++) {
8286 if (commentList[i] != NULL) {
8287 free(commentList[i]);
8288 commentList[i] = NULL;
8292 timeRemaining[0][0] = whiteTimeRemaining;
8293 timeRemaining[1][0] = blackTimeRemaining;
8294 if (first.pr == NULL) {
8295 StartChessProgram(&first);
8298 InitChessProgram(&first, startedFromSetupPosition);
8301 DisplayMessage("", "");
8302 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8303 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8310 if (!AutoPlayOneMove())
8312 if (matchMode || appData.timeDelay == 0)
8314 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8316 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8325 int fromX, fromY, toX, toY;
8327 if (appData.debugMode) {
8328 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8331 if (gameMode != PlayFromGameFile)
8334 if (currentMove >= forwardMostMove) {
8335 gameMode = EditGame;
8338 /* [AS] Clear current move marker at the end of a game */
8339 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8344 toX = moveList[currentMove][2] - AAA;
8345 toY = moveList[currentMove][3] - ONE;
8347 if (moveList[currentMove][1] == '@') {
8348 if (appData.highlightLastMove) {
8349 SetHighlights(-1, -1, toX, toY);
8352 fromX = moveList[currentMove][0] - AAA;
8353 fromY = moveList[currentMove][1] - ONE;
8355 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8357 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8359 if (appData.highlightLastMove) {
8360 SetHighlights(fromX, fromY, toX, toY);
8363 DisplayMove(currentMove);
8364 SendMoveToProgram(currentMove++, &first);
8365 DisplayBothClocks();
8366 DrawPosition(FALSE, boards[currentMove]);
8367 // [HGM] PV info: always display, routine tests if empty
8368 DisplayComment(currentMove - 1, commentList[currentMove]);
8374 LoadGameOneMove(readAhead)
8375 ChessMove readAhead;
8377 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8378 char promoChar = NULLCHAR;
8383 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8384 gameMode != AnalyzeMode && gameMode != Training) {
8389 yyboardindex = forwardMostMove;
8390 if (readAhead != (ChessMove)0) {
8391 moveType = readAhead;
8393 if (gameFileFP == NULL)
8395 moveType = (ChessMove) yylex();
8401 if (appData.debugMode)
8402 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8404 if (*p == '{' || *p == '[' || *p == '(') {
8405 p[strlen(p) - 1] = NULLCHAR;
8409 /* append the comment but don't display it */
8410 while (*p == '\n') p++;
8411 AppendComment(currentMove, p);
8414 case WhiteCapturesEnPassant:
8415 case BlackCapturesEnPassant:
8416 case WhitePromotionChancellor:
8417 case BlackPromotionChancellor:
8418 case WhitePromotionArchbishop:
8419 case BlackPromotionArchbishop:
8420 case WhitePromotionCentaur:
8421 case BlackPromotionCentaur:
8422 case WhitePromotionQueen:
8423 case BlackPromotionQueen:
8424 case WhitePromotionRook:
8425 case BlackPromotionRook:
8426 case WhitePromotionBishop:
8427 case BlackPromotionBishop:
8428 case WhitePromotionKnight:
8429 case BlackPromotionKnight:
8430 case WhitePromotionKing:
8431 case BlackPromotionKing:
8433 case WhiteKingSideCastle:
8434 case WhiteQueenSideCastle:
8435 case BlackKingSideCastle:
8436 case BlackQueenSideCastle:
8437 case WhiteKingSideCastleWild:
8438 case WhiteQueenSideCastleWild:
8439 case BlackKingSideCastleWild:
8440 case BlackQueenSideCastleWild:
8442 case WhiteHSideCastleFR:
8443 case WhiteASideCastleFR:
8444 case BlackHSideCastleFR:
8445 case BlackASideCastleFR:
8447 if (appData.debugMode)
8448 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8449 fromX = currentMoveString[0] - AAA;
8450 fromY = currentMoveString[1] - ONE;
8451 toX = currentMoveString[2] - AAA;
8452 toY = currentMoveString[3] - ONE;
8453 promoChar = currentMoveString[4];
8458 if (appData.debugMode)
8459 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8460 fromX = moveType == WhiteDrop ?
8461 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8462 (int) CharToPiece(ToLower(currentMoveString[0]));
8464 toX = currentMoveString[2] - AAA;
8465 toY = currentMoveString[3] - ONE;
8471 case GameUnfinished:
8472 if (appData.debugMode)
8473 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8474 p = strchr(yy_text, '{');
8475 if (p == NULL) p = strchr(yy_text, '(');
8478 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8480 q = strchr(p, *p == '{' ? '}' : ')');
8481 if (q != NULL) *q = NULLCHAR;
8484 GameEnds(moveType, p, GE_FILE);
8486 if (cmailMsgLoaded) {
8488 flipView = WhiteOnMove(currentMove);
8489 if (moveType == GameUnfinished) flipView = !flipView;
8490 if (appData.debugMode)
8491 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8495 case (ChessMove) 0: /* end of file */
8496 if (appData.debugMode)
8497 fprintf(debugFP, "Parser hit end of file\n");
8498 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8499 EP_UNKNOWN, castlingRights[currentMove]) ) {
8505 if (WhiteOnMove(currentMove)) {
8506 GameEnds(BlackWins, "Black mates", GE_FILE);
8508 GameEnds(WhiteWins, "White mates", GE_FILE);
8512 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8519 if (lastLoadGameStart == GNUChessGame) {
8520 /* GNUChessGames have numbers, but they aren't move numbers */
8521 if (appData.debugMode)
8522 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8523 yy_text, (int) moveType);
8524 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8526 /* else fall thru */
8531 /* Reached start of next game in file */
8532 if (appData.debugMode)
8533 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8534 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8535 EP_UNKNOWN, castlingRights[currentMove]) ) {
8541 if (WhiteOnMove(currentMove)) {
8542 GameEnds(BlackWins, "Black mates", GE_FILE);
8544 GameEnds(WhiteWins, "White mates", GE_FILE);
8548 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8554 case PositionDiagram: /* should not happen; ignore */
8555 case ElapsedTime: /* ignore */
8556 case NAG: /* ignore */
8557 if (appData.debugMode)
8558 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8559 yy_text, (int) moveType);
8560 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8563 if (appData.testLegality) {
8564 if (appData.debugMode)
8565 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8566 sprintf(move, _("Illegal move: %d.%s%s"),
8567 (forwardMostMove / 2) + 1,
8568 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8569 DisplayError(move, 0);
8572 if (appData.debugMode)
8573 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8574 yy_text, currentMoveString);
8575 fromX = currentMoveString[0] - AAA;
8576 fromY = currentMoveString[1] - ONE;
8577 toX = currentMoveString[2] - AAA;
8578 toY = currentMoveString[3] - ONE;
8579 promoChar = currentMoveString[4];
8584 if (appData.debugMode)
8585 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8586 sprintf(move, _("Ambiguous move: %d.%s%s"),
8587 (forwardMostMove / 2) + 1,
8588 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8589 DisplayError(move, 0);
8594 case ImpossibleMove:
8595 if (appData.debugMode)
8596 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8597 sprintf(move, _("Illegal move: %d.%s%s"),
8598 (forwardMostMove / 2) + 1,
8599 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8600 DisplayError(move, 0);
8606 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8607 DrawPosition(FALSE, boards[currentMove]);
8608 DisplayBothClocks();
8609 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8610 DisplayComment(currentMove - 1, commentList[currentMove]);
8612 (void) StopLoadGameTimer();
8614 cmailOldMove = forwardMostMove;
8617 /* currentMoveString is set as a side-effect of yylex */
8618 strcat(currentMoveString, "\n");
8619 strcpy(moveList[forwardMostMove], currentMoveString);
8621 thinkOutput[0] = NULLCHAR;
8622 MakeMove(fromX, fromY, toX, toY, promoChar);
8623 currentMove = forwardMostMove;
8628 /* Load the nth game from the given file */
8630 LoadGameFromFile(filename, n, title, useList)
8634 /*Boolean*/ int useList;
8639 if (strcmp(filename, "-") == 0) {
8643 f = fopen(filename, "rb");
8645 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8646 DisplayError(buf, errno);
8650 if (fseek(f, 0, 0) == -1) {
8651 /* f is not seekable; probably a pipe */
8654 if (useList && n == 0) {
8655 int error = GameListBuild(f);
8657 DisplayError(_("Cannot build game list"), error);
8658 } else if (!ListEmpty(&gameList) &&
8659 ((ListGame *) gameList.tailPred)->number > 1) {
8660 GameListPopUp(f, title);
8667 return LoadGame(f, n, title, FALSE);
8672 MakeRegisteredMove()
8674 int fromX, fromY, toX, toY;
8676 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8677 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8680 if (appData.debugMode)
8681 fprintf(debugFP, "Restoring %s for game %d\n",
8682 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8684 thinkOutput[0] = NULLCHAR;
8685 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8686 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8687 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8688 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8689 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8690 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8691 MakeMove(fromX, fromY, toX, toY, promoChar);
8692 ShowMove(fromX, fromY, toX, toY);
8694 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8695 EP_UNKNOWN, castlingRights[currentMove]) ) {
8702 if (WhiteOnMove(currentMove)) {
8703 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8705 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8710 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8717 if (WhiteOnMove(currentMove)) {
8718 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8720 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8725 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8736 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8738 CmailLoadGame(f, gameNumber, title, useList)
8746 if (gameNumber > nCmailGames) {
8747 DisplayError(_("No more games in this message"), 0);
8750 if (f == lastLoadGameFP) {
8751 int offset = gameNumber - lastLoadGameNumber;
8753 cmailMsg[0] = NULLCHAR;
8754 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8755 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8756 nCmailMovesRegistered--;
8758 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8759 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8760 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8763 if (! RegisterMove()) return FALSE;
8767 retVal = LoadGame(f, gameNumber, title, useList);
8769 /* Make move registered during previous look at this game, if any */
8770 MakeRegisteredMove();
8772 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8773 commentList[currentMove]
8774 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8775 DisplayComment(currentMove - 1, commentList[currentMove]);
8781 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8786 int gameNumber = lastLoadGameNumber + offset;
8787 if (lastLoadGameFP == NULL) {
8788 DisplayError(_("No game has been loaded yet"), 0);
8791 if (gameNumber <= 0) {
8792 DisplayError(_("Can't back up any further"), 0);
8795 if (cmailMsgLoaded) {
8796 return CmailLoadGame(lastLoadGameFP, gameNumber,
8797 lastLoadGameTitle, lastLoadGameUseList);
8799 return LoadGame(lastLoadGameFP, gameNumber,
8800 lastLoadGameTitle, lastLoadGameUseList);
8806 /* Load the nth game from open file f */
8808 LoadGame(f, gameNumber, title, useList)
8816 int gn = gameNumber;
8817 ListGame *lg = NULL;
8820 GameMode oldGameMode;
8821 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8823 if (appData.debugMode)
8824 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8826 if (gameMode == Training )
8827 SetTrainingModeOff();
8829 oldGameMode = gameMode;
8830 if (gameMode != BeginningOfGame) {
8835 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8836 fclose(lastLoadGameFP);
8840 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8843 fseek(f, lg->offset, 0);
8844 GameListHighlight(gameNumber);
8848 DisplayError(_("Game number out of range"), 0);
8853 if (fseek(f, 0, 0) == -1) {
8854 if (f == lastLoadGameFP ?
8855 gameNumber == lastLoadGameNumber + 1 :
8859 DisplayError(_("Can't seek on game file"), 0);
8865 lastLoadGameNumber = gameNumber;
8866 strcpy(lastLoadGameTitle, title);
8867 lastLoadGameUseList = useList;
8871 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8872 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8873 lg->gameInfo.black);
8875 } else if (*title != NULLCHAR) {
8876 if (gameNumber > 1) {
8877 sprintf(buf, "%s %d", title, gameNumber);
8880 DisplayTitle(title);
8884 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8885 gameMode = PlayFromGameFile;
8889 currentMove = forwardMostMove = backwardMostMove = 0;
8890 CopyBoard(boards[0], initialPosition);
8894 * Skip the first gn-1 games in the file.
8895 * Also skip over anything that precedes an identifiable
8896 * start of game marker, to avoid being confused by
8897 * garbage at the start of the file. Currently
8898 * recognized start of game markers are the move number "1",
8899 * the pattern "gnuchess .* game", the pattern
8900 * "^[#;%] [^ ]* game file", and a PGN tag block.
8901 * A game that starts with one of the latter two patterns
8902 * will also have a move number 1, possibly
8903 * following a position diagram.
8904 * 5-4-02: Let's try being more lenient and allowing a game to
8905 * start with an unnumbered move. Does that break anything?
8907 cm = lastLoadGameStart = (ChessMove) 0;
8909 yyboardindex = forwardMostMove;
8910 cm = (ChessMove) yylex();
8913 if (cmailMsgLoaded) {
8914 nCmailGames = CMAIL_MAX_GAMES - gn;
8917 DisplayError(_("Game not found in file"), 0);
8924 lastLoadGameStart = cm;
8928 switch (lastLoadGameStart) {
8935 gn--; /* count this game */
8936 lastLoadGameStart = cm;
8945 switch (lastLoadGameStart) {
8950 gn--; /* count this game */
8951 lastLoadGameStart = cm;
8954 lastLoadGameStart = cm; /* game counted already */
8962 yyboardindex = forwardMostMove;
8963 cm = (ChessMove) yylex();
8964 } while (cm == PGNTag || cm == Comment);
8971 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8972 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8973 != CMAIL_OLD_RESULT) {
8975 cmailResult[ CMAIL_MAX_GAMES
8976 - gn - 1] = CMAIL_OLD_RESULT;
8982 /* Only a NormalMove can be at the start of a game
8983 * without a position diagram. */
8984 if (lastLoadGameStart == (ChessMove) 0) {
8986 lastLoadGameStart = MoveNumberOne;
8995 if (appData.debugMode)
8996 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8998 if (cm == XBoardGame) {
8999 /* Skip any header junk before position diagram and/or move 1 */
9001 yyboardindex = forwardMostMove;
9002 cm = (ChessMove) yylex();
9004 if (cm == (ChessMove) 0 ||
9005 cm == GNUChessGame || cm == XBoardGame) {
9006 /* Empty game; pretend end-of-file and handle later */
9011 if (cm == MoveNumberOne || cm == PositionDiagram ||
9012 cm == PGNTag || cm == Comment)
9015 } else if (cm == GNUChessGame) {
9016 if (gameInfo.event != NULL) {
9017 free(gameInfo.event);
9019 gameInfo.event = StrSave(yy_text);
9022 startedFromSetupPosition = FALSE;
9023 while (cm == PGNTag) {
9024 if (appData.debugMode)
9025 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9026 err = ParsePGNTag(yy_text, &gameInfo);
9027 if (!err) numPGNTags++;
9029 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9030 if(gameInfo.variant != oldVariant) {
9031 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9033 oldVariant = gameInfo.variant;
9034 if (appData.debugMode)
9035 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9039 if (gameInfo.fen != NULL) {
9040 Board initial_position;
9041 startedFromSetupPosition = TRUE;
9042 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9044 DisplayError(_("Bad FEN position in file"), 0);
9047 CopyBoard(boards[0], initial_position);
9048 if (blackPlaysFirst) {
9049 currentMove = forwardMostMove = backwardMostMove = 1;
9050 CopyBoard(boards[1], initial_position);
9051 strcpy(moveList[0], "");
9052 strcpy(parseList[0], "");
9053 timeRemaining[0][1] = whiteTimeRemaining;
9054 timeRemaining[1][1] = blackTimeRemaining;
9055 if (commentList[0] != NULL) {
9056 commentList[1] = commentList[0];
9057 commentList[0] = NULL;
9060 currentMove = forwardMostMove = backwardMostMove = 0;
9062 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9064 initialRulePlies = FENrulePlies;
9065 epStatus[forwardMostMove] = FENepStatus;
9066 for( i=0; i< nrCastlingRights; i++ )
9067 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9069 yyboardindex = forwardMostMove;
9071 gameInfo.fen = NULL;
9074 yyboardindex = forwardMostMove;
9075 cm = (ChessMove) yylex();
9077 /* Handle comments interspersed among the tags */
9078 while (cm == Comment) {
9080 if (appData.debugMode)
9081 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9083 if (*p == '{' || *p == '[' || *p == '(') {
9084 p[strlen(p) - 1] = NULLCHAR;
9087 while (*p == '\n') p++;
9088 AppendComment(currentMove, p);
9089 yyboardindex = forwardMostMove;
9090 cm = (ChessMove) yylex();
9094 /* don't rely on existence of Event tag since if game was
9095 * pasted from clipboard the Event tag may not exist
9097 if (numPGNTags > 0){
9099 if (gameInfo.variant == VariantNormal) {
9100 gameInfo.variant = StringToVariant(gameInfo.event);
9103 if( appData.autoDisplayTags ) {
9104 tags = PGNTags(&gameInfo);
9105 TagsPopUp(tags, CmailMsg());
9110 /* Make something up, but don't display it now */
9115 if (cm == PositionDiagram) {
9118 Board initial_position;
9120 if (appData.debugMode)
9121 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9123 if (!startedFromSetupPosition) {
9125 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9126 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9136 initial_position[i][j++] = CharToPiece(*p);
9139 while (*p == ' ' || *p == '\t' ||
9140 *p == '\n' || *p == '\r') p++;
9142 if (strncmp(p, "black", strlen("black"))==0)
9143 blackPlaysFirst = TRUE;
9145 blackPlaysFirst = FALSE;
9146 startedFromSetupPosition = TRUE;
9148 CopyBoard(boards[0], initial_position);
9149 if (blackPlaysFirst) {
9150 currentMove = forwardMostMove = backwardMostMove = 1;
9151 CopyBoard(boards[1], initial_position);
9152 strcpy(moveList[0], "");
9153 strcpy(parseList[0], "");
9154 timeRemaining[0][1] = whiteTimeRemaining;
9155 timeRemaining[1][1] = blackTimeRemaining;
9156 if (commentList[0] != NULL) {
9157 commentList[1] = commentList[0];
9158 commentList[0] = NULL;
9161 currentMove = forwardMostMove = backwardMostMove = 0;
9164 yyboardindex = forwardMostMove;
9165 cm = (ChessMove) yylex();
9168 if (first.pr == NoProc) {
9169 StartChessProgram(&first);
9171 InitChessProgram(&first, FALSE);
9172 SendToProgram("force\n", &first);
9173 if (startedFromSetupPosition) {
9174 SendBoard(&first, forwardMostMove);
9175 if (appData.debugMode) {
9176 fprintf(debugFP, "Load Game\n");
9178 DisplayBothClocks();
9181 /* [HGM] server: flag to write setup moves in broadcast file as one */
9182 loadFlag = appData.suppressLoadMoves;
9184 while (cm == Comment) {
9186 if (appData.debugMode)
9187 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9189 if (*p == '{' || *p == '[' || *p == '(') {
9190 p[strlen(p) - 1] = NULLCHAR;
9193 while (*p == '\n') p++;
9194 AppendComment(currentMove, p);
9195 yyboardindex = forwardMostMove;
9196 cm = (ChessMove) yylex();
9199 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9200 cm == WhiteWins || cm == BlackWins ||
9201 cm == GameIsDrawn || cm == GameUnfinished) {
9202 DisplayMessage("", _("No moves in game"));
9203 if (cmailMsgLoaded) {
9204 if (appData.debugMode)
9205 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9209 DrawPosition(FALSE, boards[currentMove]);
9210 DisplayBothClocks();
9211 gameMode = EditGame;
9218 // [HGM] PV info: routine tests if comment empty
9219 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9220 DisplayComment(currentMove - 1, commentList[currentMove]);
9222 if (!matchMode && appData.timeDelay != 0)
9223 DrawPosition(FALSE, boards[currentMove]);
9225 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9226 programStats.ok_to_send = 1;
9229 /* if the first token after the PGN tags is a move
9230 * and not move number 1, retrieve it from the parser
9232 if (cm != MoveNumberOne)
9233 LoadGameOneMove(cm);
9235 /* load the remaining moves from the file */
9236 while (LoadGameOneMove((ChessMove)0)) {
9237 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9238 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9241 /* rewind to the start of the game */
9242 currentMove = backwardMostMove;
9244 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9246 if (oldGameMode == AnalyzeFile ||
9247 oldGameMode == AnalyzeMode) {
9251 if (matchMode || appData.timeDelay == 0) {
9253 gameMode = EditGame;
9255 } else if (appData.timeDelay > 0) {
9259 if (appData.debugMode)
9260 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9262 loadFlag = 0; /* [HGM] true game starts */
9266 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9268 ReloadPosition(offset)
9271 int positionNumber = lastLoadPositionNumber + offset;
9272 if (lastLoadPositionFP == NULL) {
9273 DisplayError(_("No position has been loaded yet"), 0);
9276 if (positionNumber <= 0) {
9277 DisplayError(_("Can't back up any further"), 0);
9280 return LoadPosition(lastLoadPositionFP, positionNumber,
9281 lastLoadPositionTitle);
9284 /* Load the nth position from the given file */
9286 LoadPositionFromFile(filename, n, title)
9294 if (strcmp(filename, "-") == 0) {
9295 return LoadPosition(stdin, n, "stdin");
9297 f = fopen(filename, "rb");
9299 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9300 DisplayError(buf, errno);
9303 return LoadPosition(f, n, title);
9308 /* Load the nth position from the given open file, and close it */
9310 LoadPosition(f, positionNumber, title)
9315 char *p, line[MSG_SIZ];
9316 Board initial_position;
9317 int i, j, fenMode, pn;
9319 if (gameMode == Training )
9320 SetTrainingModeOff();
9322 if (gameMode != BeginningOfGame) {
9325 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9326 fclose(lastLoadPositionFP);
9328 if (positionNumber == 0) positionNumber = 1;
9329 lastLoadPositionFP = f;
9330 lastLoadPositionNumber = positionNumber;
9331 strcpy(lastLoadPositionTitle, title);
9332 if (first.pr == NoProc) {
9333 StartChessProgram(&first);
9334 InitChessProgram(&first, FALSE);
9336 pn = positionNumber;
9337 if (positionNumber < 0) {
9338 /* Negative position number means to seek to that byte offset */
9339 if (fseek(f, -positionNumber, 0) == -1) {
9340 DisplayError(_("Can't seek on position file"), 0);
9345 if (fseek(f, 0, 0) == -1) {
9346 if (f == lastLoadPositionFP ?
9347 positionNumber == lastLoadPositionNumber + 1 :
9348 positionNumber == 1) {
9351 DisplayError(_("Can't seek on position file"), 0);
9356 /* See if this file is FEN or old-style xboard */
9357 if (fgets(line, MSG_SIZ, f) == NULL) {
9358 DisplayError(_("Position not found in file"), 0);
9367 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
9368 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
9369 case '1': case '2': case '3': case '4': case '5': case '6':
9370 case '7': case '8': case '9':
9371 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
9372 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
9373 case 'C': case 'W': case 'c': case 'w':
9378 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9379 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9383 if (fenMode || line[0] == '#') pn--;
9385 /* skip positions before number pn */
9386 if (fgets(line, MSG_SIZ, f) == NULL) {
9388 DisplayError(_("Position not found in file"), 0);
9391 if (fenMode || line[0] == '#') pn--;
9396 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9397 DisplayError(_("Bad FEN position in file"), 0);
9401 (void) fgets(line, MSG_SIZ, f);
9402 (void) fgets(line, MSG_SIZ, f);
9404 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9405 (void) fgets(line, MSG_SIZ, f);
9406 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9409 initial_position[i][j++] = CharToPiece(*p);
9413 blackPlaysFirst = FALSE;
9415 (void) fgets(line, MSG_SIZ, f);
9416 if (strncmp(line, "black", strlen("black"))==0)
9417 blackPlaysFirst = TRUE;
9420 startedFromSetupPosition = TRUE;
9422 SendToProgram("force\n", &first);
9423 CopyBoard(boards[0], initial_position);
9424 if (blackPlaysFirst) {
9425 currentMove = forwardMostMove = backwardMostMove = 1;
9426 strcpy(moveList[0], "");
9427 strcpy(parseList[0], "");
9428 CopyBoard(boards[1], initial_position);
9429 DisplayMessage("", _("Black to play"));
9431 currentMove = forwardMostMove = backwardMostMove = 0;
9432 DisplayMessage("", _("White to play"));
9434 /* [HGM] copy FEN attributes as well */
9436 initialRulePlies = FENrulePlies;
9437 epStatus[forwardMostMove] = FENepStatus;
9438 for( i=0; i< nrCastlingRights; i++ )
9439 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9441 SendBoard(&first, forwardMostMove);
9442 if (appData.debugMode) {
9444 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9445 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9446 fprintf(debugFP, "Load Position\n");
9449 if (positionNumber > 1) {
9450 sprintf(line, "%s %d", title, positionNumber);
9453 DisplayTitle(title);
9455 gameMode = EditGame;
9458 timeRemaining[0][1] = whiteTimeRemaining;
9459 timeRemaining[1][1] = blackTimeRemaining;
9460 DrawPosition(FALSE, boards[currentMove]);
9467 CopyPlayerNameIntoFileName(dest, src)
9470 while (*src != NULLCHAR && *src != ',') {
9475 *(*dest)++ = *src++;
9480 char *DefaultFileName(ext)
9483 static char def[MSG_SIZ];
9486 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9488 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9490 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9499 /* Save the current game to the given file */
9501 SaveGameToFile(filename, append)
9508 if (strcmp(filename, "-") == 0) {
9509 return SaveGame(stdout, 0, NULL);
9511 f = fopen(filename, append ? "a" : "w");
9513 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9514 DisplayError(buf, errno);
9517 return SaveGame(f, 0, NULL);
9526 static char buf[MSG_SIZ];
9529 p = strchr(str, ' ');
9530 if (p == NULL) return str;
9531 strncpy(buf, str, p - str);
9532 buf[p - str] = NULLCHAR;
9536 #define PGN_MAX_LINE 75
9538 #define PGN_SIDE_WHITE 0
9539 #define PGN_SIDE_BLACK 1
9542 static int FindFirstMoveOutOfBook( int side )
9546 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9547 int index = backwardMostMove;
9548 int has_book_hit = 0;
9550 if( (index % 2) != side ) {
9554 while( index < forwardMostMove ) {
9555 /* Check to see if engine is in book */
9556 int depth = pvInfoList[index].depth;
9557 int score = pvInfoList[index].score;
9563 else if( score == 0 && depth == 63 ) {
9564 in_book = 1; /* Zappa */
9566 else if( score == 2 && depth == 99 ) {
9567 in_book = 1; /* Abrok */
9570 has_book_hit += in_book;
9586 void GetOutOfBookInfo( char * buf )
9590 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9592 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9593 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9597 if( oob[0] >= 0 || oob[1] >= 0 ) {
9598 for( i=0; i<2; i++ ) {
9602 if( i > 0 && oob[0] >= 0 ) {
9606 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9607 sprintf( buf+strlen(buf), "%s%.2f",
9608 pvInfoList[idx].score >= 0 ? "+" : "",
9609 pvInfoList[idx].score / 100.0 );
9615 /* Save game in PGN style and close the file */
9620 int i, offset, linelen, newblock;
9624 int movelen, numlen, blank;
9625 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9627 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9629 tm = time((time_t *) NULL);
9631 PrintPGNTags(f, &gameInfo);
9633 if (backwardMostMove > 0 || startedFromSetupPosition) {
9634 char *fen = PositionToFEN(backwardMostMove, NULL);
9635 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9636 fprintf(f, "\n{--------------\n");
9637 PrintPosition(f, backwardMostMove);
9638 fprintf(f, "--------------}\n");
9642 /* [AS] Out of book annotation */
9643 if( appData.saveOutOfBookInfo ) {
9646 GetOutOfBookInfo( buf );
9648 if( buf[0] != '\0' ) {
9649 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9656 i = backwardMostMove;
9660 while (i < forwardMostMove) {
9661 /* Print comments preceding this move */
9662 if (commentList[i] != NULL) {
9663 if (linelen > 0) fprintf(f, "\n");
9664 fprintf(f, "{\n%s}\n", commentList[i]);
9669 /* Format move number */
9671 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9674 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9676 numtext[0] = NULLCHAR;
9679 numlen = strlen(numtext);
9682 /* Print move number */
9683 blank = linelen > 0 && numlen > 0;
9684 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9693 fprintf(f, numtext);
9697 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9698 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9700 // SavePart already does this!
9701 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9702 int p = movelen - 1;
9703 if(move_buffer[p] == ' ') p--;
9704 if(move_buffer[p] == ')') { // [HGM] pgn: strip off ICS time if we have extended info
9705 while(p && move_buffer[--p] != '(');
9706 if(p && move_buffer[p-1] == ' ') move_buffer[movelen=p-1] = 0;
9711 blank = linelen > 0 && movelen > 0;
9712 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9721 fprintf(f, move_buffer);
9724 /* [AS] Add PV info if present */
9725 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9726 /* [HGM] add time */
9727 char buf[MSG_SIZ]; int seconds = 0;
9730 if(i >= backwardMostMove) {
9732 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9733 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9735 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9736 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9738 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9740 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
9743 if( seconds <= 0) buf[0] = 0; else
9744 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9745 seconds = (seconds + 4)/10; // round to full seconds
9746 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9747 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9750 sprintf( move_buffer, "{%s%.2f/%d%s}",
9751 pvInfoList[i].score >= 0 ? "+" : "",
9752 pvInfoList[i].score / 100.0,
9753 pvInfoList[i].depth,
9756 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9758 /* Print score/depth */
9759 blank = linelen > 0 && movelen > 0;
9760 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9769 fprintf(f, move_buffer);
9776 /* Start a new line */
9777 if (linelen > 0) fprintf(f, "\n");
9779 /* Print comments after last move */
9780 if (commentList[i] != NULL) {
9781 fprintf(f, "{\n%s}\n", commentList[i]);
9785 if (gameInfo.resultDetails != NULL &&
9786 gameInfo.resultDetails[0] != NULLCHAR) {
9787 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9788 PGNResult(gameInfo.result));
9790 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9794 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9798 /* Save game in old style and close the file */
9806 tm = time((time_t *) NULL);
9808 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9811 if (backwardMostMove > 0 || startedFromSetupPosition) {
9812 fprintf(f, "\n[--------------\n");
9813 PrintPosition(f, backwardMostMove);
9814 fprintf(f, "--------------]\n");
9819 i = backwardMostMove;
9820 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9822 while (i < forwardMostMove) {
9823 if (commentList[i] != NULL) {
9824 fprintf(f, "[%s]\n", commentList[i]);
9828 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9831 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9833 if (commentList[i] != NULL) {
9837 if (i >= forwardMostMove) {
9841 fprintf(f, "%s\n", parseList[i]);
9846 if (commentList[i] != NULL) {
9847 fprintf(f, "[%s]\n", commentList[i]);
9850 /* This isn't really the old style, but it's close enough */
9851 if (gameInfo.resultDetails != NULL &&
9852 gameInfo.resultDetails[0] != NULLCHAR) {
9853 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9854 gameInfo.resultDetails);
9856 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9863 /* Save the current game to open file f and close the file */
9865 SaveGame(f, dummy, dummy2)
9870 if (gameMode == EditPosition) EditPositionDone();
9871 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9872 if (appData.oldSaveStyle)
9873 return SaveGameOldStyle(f);
9875 return SaveGamePGN(f);
9878 /* Save the current position to the given file */
9880 SavePositionToFile(filename)
9886 if (strcmp(filename, "-") == 0) {
9887 return SavePosition(stdout, 0, NULL);
9889 f = fopen(filename, "a");
9891 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9892 DisplayError(buf, errno);
9895 SavePosition(f, 0, NULL);
9901 /* Save the current position to the given open file and close the file */
9903 SavePosition(f, dummy, dummy2)
9911 if (appData.oldSaveStyle) {
9912 tm = time((time_t *) NULL);
9914 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9916 fprintf(f, "[--------------\n");
9917 PrintPosition(f, currentMove);
9918 fprintf(f, "--------------]\n");
9920 fen = PositionToFEN(currentMove, NULL);
9921 fprintf(f, "%s\n", fen);
9929 ReloadCmailMsgEvent(unregister)
9933 static char *inFilename = NULL;
9934 static char *outFilename;
9936 struct stat inbuf, outbuf;
9939 /* Any registered moves are unregistered if unregister is set, */
9940 /* i.e. invoked by the signal handler */
9942 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9943 cmailMoveRegistered[i] = FALSE;
9944 if (cmailCommentList[i] != NULL) {
9945 free(cmailCommentList[i]);
9946 cmailCommentList[i] = NULL;
9949 nCmailMovesRegistered = 0;
9952 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9953 cmailResult[i] = CMAIL_NOT_RESULT;
9957 if (inFilename == NULL) {
9958 /* Because the filenames are static they only get malloced once */
9959 /* and they never get freed */
9960 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9961 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9963 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9964 sprintf(outFilename, "%s.out", appData.cmailGameName);
9967 status = stat(outFilename, &outbuf);
9969 cmailMailedMove = FALSE;
9971 status = stat(inFilename, &inbuf);
9972 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9975 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9976 counts the games, notes how each one terminated, etc.
9978 It would be nice to remove this kludge and instead gather all
9979 the information while building the game list. (And to keep it
9980 in the game list nodes instead of having a bunch of fixed-size
9981 parallel arrays.) Note this will require getting each game's
9982 termination from the PGN tags, as the game list builder does
9983 not process the game moves. --mann
9985 cmailMsgLoaded = TRUE;
9986 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9988 /* Load first game in the file or popup game menu */
9989 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9999 char string[MSG_SIZ];
10001 if ( cmailMailedMove
10002 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10003 return TRUE; /* Allow free viewing */
10006 /* Unregister move to ensure that we don't leave RegisterMove */
10007 /* with the move registered when the conditions for registering no */
10009 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10010 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10011 nCmailMovesRegistered --;
10013 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10015 free(cmailCommentList[lastLoadGameNumber - 1]);
10016 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10020 if (cmailOldMove == -1) {
10021 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10025 if (currentMove > cmailOldMove + 1) {
10026 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10030 if (currentMove < cmailOldMove) {
10031 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10035 if (forwardMostMove > currentMove) {
10036 /* Silently truncate extra moves */
10040 if ( (currentMove == cmailOldMove + 1)
10041 || ( (currentMove == cmailOldMove)
10042 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10043 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10044 if (gameInfo.result != GameUnfinished) {
10045 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10048 if (commentList[currentMove] != NULL) {
10049 cmailCommentList[lastLoadGameNumber - 1]
10050 = StrSave(commentList[currentMove]);
10052 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10054 if (appData.debugMode)
10055 fprintf(debugFP, "Saving %s for game %d\n",
10056 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10059 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10061 f = fopen(string, "w");
10062 if (appData.oldSaveStyle) {
10063 SaveGameOldStyle(f); /* also closes the file */
10065 sprintf(string, "%s.pos.out", appData.cmailGameName);
10066 f = fopen(string, "w");
10067 SavePosition(f, 0, NULL); /* also closes the file */
10069 fprintf(f, "{--------------\n");
10070 PrintPosition(f, currentMove);
10071 fprintf(f, "--------------}\n\n");
10073 SaveGame(f, 0, NULL); /* also closes the file*/
10076 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10077 nCmailMovesRegistered ++;
10078 } else if (nCmailGames == 1) {
10079 DisplayError(_("You have not made a move yet"), 0);
10090 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10091 FILE *commandOutput;
10092 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10093 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10099 if (! cmailMsgLoaded) {
10100 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10104 if (nCmailGames == nCmailResults) {
10105 DisplayError(_("No unfinished games"), 0);
10109 #if CMAIL_PROHIBIT_REMAIL
10110 if (cmailMailedMove) {
10111 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);
10112 DisplayError(msg, 0);
10117 if (! (cmailMailedMove || RegisterMove())) return;
10119 if ( cmailMailedMove
10120 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10121 sprintf(string, partCommandString,
10122 appData.debugMode ? " -v" : "", appData.cmailGameName);
10123 commandOutput = popen(string, "r");
10125 if (commandOutput == NULL) {
10126 DisplayError(_("Failed to invoke cmail"), 0);
10128 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10129 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10131 if (nBuffers > 1) {
10132 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10133 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10134 nBytes = MSG_SIZ - 1;
10136 (void) memcpy(msg, buffer, nBytes);
10138 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10140 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10141 cmailMailedMove = TRUE; /* Prevent >1 moves */
10144 for (i = 0; i < nCmailGames; i ++) {
10145 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10150 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10152 sprintf(buffer, "%s/%s.%s.archive",
10154 appData.cmailGameName,
10156 LoadGameFromFile(buffer, 1, buffer, FALSE);
10157 cmailMsgLoaded = FALSE;
10161 DisplayInformation(msg);
10162 pclose(commandOutput);
10165 if ((*cmailMsg) != '\0') {
10166 DisplayInformation(cmailMsg);
10171 #endif /* !WIN32 */
10180 int prependComma = 0;
10182 char string[MSG_SIZ]; /* Space for game-list */
10185 if (!cmailMsgLoaded) return "";
10187 if (cmailMailedMove) {
10188 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10190 /* Create a list of games left */
10191 sprintf(string, "[");
10192 for (i = 0; i < nCmailGames; i ++) {
10193 if (! ( cmailMoveRegistered[i]
10194 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10195 if (prependComma) {
10196 sprintf(number, ",%d", i + 1);
10198 sprintf(number, "%d", i + 1);
10202 strcat(string, number);
10205 strcat(string, "]");
10207 if (nCmailMovesRegistered + nCmailResults == 0) {
10208 switch (nCmailGames) {
10211 _("Still need to make move for game\n"));
10216 _("Still need to make moves for both games\n"));
10221 _("Still need to make moves for all %d games\n"),
10226 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10229 _("Still need to make a move for game %s\n"),
10234 if (nCmailResults == nCmailGames) {
10235 sprintf(cmailMsg, _("No unfinished games\n"));
10237 sprintf(cmailMsg, _("Ready to send mail\n"));
10243 _("Still need to make moves for games %s\n"),
10255 if (gameMode == Training)
10256 SetTrainingModeOff();
10259 cmailMsgLoaded = FALSE;
10260 if (appData.icsActive) {
10261 SendToICS(ics_prefix);
10262 SendToICS("refresh\n");
10272 /* Give up on clean exit */
10276 /* Keep trying for clean exit */
10280 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10282 if (telnetISR != NULL) {
10283 RemoveInputSource(telnetISR);
10285 if (icsPR != NoProc) {
10286 DestroyChildProcess(icsPR, TRUE);
10289 /* Save game if resource set and not already saved by GameEnds() */
10290 if ((gameInfo.resultDetails == NULL || errorExitFlag )
10291 && forwardMostMove > 0) {
10292 if (*appData.saveGameFile != NULLCHAR) {
10293 SaveGameToFile(appData.saveGameFile, TRUE);
10294 } else if (appData.autoSaveGames) {
10297 if (*appData.savePositionFile != NULLCHAR) {
10298 SavePositionToFile(appData.savePositionFile);
10301 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10303 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10304 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10306 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10307 /* make sure this other one finishes before killing it! */
10308 if(endingGame) { int count = 0;
10309 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10310 while(endingGame && count++ < 10) DoSleep(1);
10311 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10314 /* Kill off chess programs */
10315 if (first.pr != NoProc) {
10318 DoSleep( appData.delayBeforeQuit );
10319 SendToProgram("quit\n", &first);
10320 DoSleep( appData.delayAfterQuit );
10321 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10323 if (second.pr != NoProc) {
10324 DoSleep( appData.delayBeforeQuit );
10325 SendToProgram("quit\n", &second);
10326 DoSleep( appData.delayAfterQuit );
10327 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10329 if (first.isr != NULL) {
10330 RemoveInputSource(first.isr);
10332 if (second.isr != NULL) {
10333 RemoveInputSource(second.isr);
10336 ShutDownFrontEnd();
10343 if (appData.debugMode)
10344 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10348 if (gameMode == MachinePlaysWhite ||
10349 gameMode == MachinePlaysBlack) {
10352 DisplayBothClocks();
10354 if (gameMode == PlayFromGameFile) {
10355 if (appData.timeDelay >= 0)
10356 AutoPlayGameLoop();
10357 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10358 Reset(FALSE, TRUE);
10359 SendToICS(ics_prefix);
10360 SendToICS("refresh\n");
10361 } else if (currentMove < forwardMostMove) {
10362 ForwardInner(forwardMostMove);
10364 pauseExamInvalid = FALSE;
10366 switch (gameMode) {
10370 pauseExamForwardMostMove = forwardMostMove;
10371 pauseExamInvalid = FALSE;
10374 case IcsPlayingWhite:
10375 case IcsPlayingBlack:
10379 case PlayFromGameFile:
10380 (void) StopLoadGameTimer();
10384 case BeginningOfGame:
10385 if (appData.icsActive) return;
10386 /* else fall through */
10387 case MachinePlaysWhite:
10388 case MachinePlaysBlack:
10389 case TwoMachinesPlay:
10390 if (forwardMostMove == 0)
10391 return; /* don't pause if no one has moved */
10392 if ((gameMode == MachinePlaysWhite &&
10393 !WhiteOnMove(forwardMostMove)) ||
10394 (gameMode == MachinePlaysBlack &&
10395 WhiteOnMove(forwardMostMove))) {
10408 char title[MSG_SIZ];
10410 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10411 strcpy(title, _("Edit comment"));
10413 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10414 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10415 parseList[currentMove - 1]);
10418 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10425 char *tags = PGNTags(&gameInfo);
10426 EditTagsPopUp(tags);
10433 if (appData.noChessProgram || gameMode == AnalyzeMode)
10436 if (gameMode != AnalyzeFile) {
10437 if (!appData.icsEngineAnalyze) {
10439 if (gameMode != EditGame) return;
10441 ResurrectChessProgram();
10442 SendToProgram("analyze\n", &first);
10443 first.analyzing = TRUE;
10444 /*first.maybeThinking = TRUE;*/
10445 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10446 AnalysisPopUp(_("Analysis"),
10447 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10449 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10454 StartAnalysisClock();
10455 GetTimeMark(&lastNodeCountTime);
10462 if (appData.noChessProgram || gameMode == AnalyzeFile)
10465 if (gameMode != AnalyzeMode) {
10467 if (gameMode != EditGame) return;
10468 ResurrectChessProgram();
10469 SendToProgram("analyze\n", &first);
10470 first.analyzing = TRUE;
10471 /*first.maybeThinking = TRUE;*/
10472 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10473 AnalysisPopUp(_("Analysis"),
10474 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10476 gameMode = AnalyzeFile;
10481 StartAnalysisClock();
10482 GetTimeMark(&lastNodeCountTime);
10487 MachineWhiteEvent()
10490 char *bookHit = NULL;
10492 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10496 if (gameMode == PlayFromGameFile ||
10497 gameMode == TwoMachinesPlay ||
10498 gameMode == Training ||
10499 gameMode == AnalyzeMode ||
10500 gameMode == EndOfGame)
10503 if (gameMode == EditPosition)
10504 EditPositionDone();
10506 if (!WhiteOnMove(currentMove)) {
10507 DisplayError(_("It is not White's turn"), 0);
10511 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10514 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10515 gameMode == AnalyzeFile)
10518 ResurrectChessProgram(); /* in case it isn't running */
10519 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10520 gameMode = MachinePlaysWhite;
10523 gameMode = MachinePlaysWhite;
10527 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10529 if (first.sendName) {
10530 sprintf(buf, "name %s\n", gameInfo.black);
10531 SendToProgram(buf, &first);
10533 if (first.sendTime) {
10534 if (first.useColors) {
10535 SendToProgram("black\n", &first); /*gnu kludge*/
10537 SendTimeRemaining(&first, TRUE);
10539 if (first.useColors) {
10540 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10542 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10543 SetMachineThinkingEnables();
10544 first.maybeThinking = TRUE;
10548 if (appData.autoFlipView && !flipView) {
10549 flipView = !flipView;
10550 DrawPosition(FALSE, NULL);
10551 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10554 if(bookHit) { // [HGM] book: simulate book reply
10555 static char bookMove[MSG_SIZ]; // a bit generous?
10557 programStats.nodes = programStats.depth = programStats.time =
10558 programStats.score = programStats.got_only_move = 0;
10559 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10561 strcpy(bookMove, "move ");
10562 strcat(bookMove, bookHit);
10563 HandleMachineMove(bookMove, &first);
10568 MachineBlackEvent()
10571 char *bookHit = NULL;
10573 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10577 if (gameMode == PlayFromGameFile ||
10578 gameMode == TwoMachinesPlay ||
10579 gameMode == Training ||
10580 gameMode == AnalyzeMode ||
10581 gameMode == EndOfGame)
10584 if (gameMode == EditPosition)
10585 EditPositionDone();
10587 if (WhiteOnMove(currentMove)) {
10588 DisplayError(_("It is not Black's turn"), 0);
10592 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10595 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10596 gameMode == AnalyzeFile)
10599 ResurrectChessProgram(); /* in case it isn't running */
10600 gameMode = MachinePlaysBlack;
10604 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10606 if (first.sendName) {
10607 sprintf(buf, "name %s\n", gameInfo.white);
10608 SendToProgram(buf, &first);
10610 if (first.sendTime) {
10611 if (first.useColors) {
10612 SendToProgram("white\n", &first); /*gnu kludge*/
10614 SendTimeRemaining(&first, FALSE);
10616 if (first.useColors) {
10617 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10619 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10620 SetMachineThinkingEnables();
10621 first.maybeThinking = TRUE;
10624 if (appData.autoFlipView && flipView) {
10625 flipView = !flipView;
10626 DrawPosition(FALSE, NULL);
10627 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10629 if(bookHit) { // [HGM] book: simulate book reply
10630 static char bookMove[MSG_SIZ]; // a bit generous?
10632 programStats.nodes = programStats.depth = programStats.time =
10633 programStats.score = programStats.got_only_move = 0;
10634 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10636 strcpy(bookMove, "move ");
10637 strcat(bookMove, bookHit);
10638 HandleMachineMove(bookMove, &first);
10644 DisplayTwoMachinesTitle()
10647 if (appData.matchGames > 0) {
10648 if (first.twoMachinesColor[0] == 'w') {
10649 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10650 gameInfo.white, gameInfo.black,
10651 first.matchWins, second.matchWins,
10652 matchGame - 1 - (first.matchWins + second.matchWins));
10654 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10655 gameInfo.white, gameInfo.black,
10656 second.matchWins, first.matchWins,
10657 matchGame - 1 - (first.matchWins + second.matchWins));
10660 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10666 TwoMachinesEvent P((void))
10670 ChessProgramState *onmove;
10671 char *bookHit = NULL;
10673 if (appData.noChessProgram) return;
10675 switch (gameMode) {
10676 case TwoMachinesPlay:
10678 case MachinePlaysWhite:
10679 case MachinePlaysBlack:
10680 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10681 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10685 case BeginningOfGame:
10686 case PlayFromGameFile:
10689 if (gameMode != EditGame) return;
10692 EditPositionDone();
10703 forwardMostMove = currentMove;
10704 ResurrectChessProgram(); /* in case first program isn't running */
10706 if (second.pr == NULL) {
10707 StartChessProgram(&second);
10708 if (second.protocolVersion == 1) {
10709 TwoMachinesEventIfReady();
10711 /* kludge: allow timeout for initial "feature" command */
10713 DisplayMessage("", _("Starting second chess program"));
10714 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10718 DisplayMessage("", "");
10719 InitChessProgram(&second, FALSE);
10720 SendToProgram("force\n", &second);
10721 if (startedFromSetupPosition) {
10722 SendBoard(&second, backwardMostMove);
10723 if (appData.debugMode) {
10724 fprintf(debugFP, "Two Machines\n");
10727 for (i = backwardMostMove; i < forwardMostMove; i++) {
10728 SendMoveToProgram(i, &second);
10731 gameMode = TwoMachinesPlay;
10735 DisplayTwoMachinesTitle();
10737 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10743 SendToProgram(first.computerString, &first);
10744 if (first.sendName) {
10745 sprintf(buf, "name %s\n", second.tidy);
10746 SendToProgram(buf, &first);
10748 SendToProgram(second.computerString, &second);
10749 if (second.sendName) {
10750 sprintf(buf, "name %s\n", first.tidy);
10751 SendToProgram(buf, &second);
10755 if (!first.sendTime || !second.sendTime) {
10756 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10757 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10759 if (onmove->sendTime) {
10760 if (onmove->useColors) {
10761 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10763 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10765 if (onmove->useColors) {
10766 SendToProgram(onmove->twoMachinesColor, onmove);
10768 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10769 // SendToProgram("go\n", onmove);
10770 onmove->maybeThinking = TRUE;
10771 SetMachineThinkingEnables();
10775 if(bookHit) { // [HGM] book: simulate book reply
10776 static char bookMove[MSG_SIZ]; // a bit generous?
10778 programStats.nodes = programStats.depth = programStats.time =
10779 programStats.score = programStats.got_only_move = 0;
10780 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10782 strcpy(bookMove, "move ");
10783 strcat(bookMove, bookHit);
10784 HandleMachineMove(bookMove, &first);
10791 if (gameMode == Training) {
10792 SetTrainingModeOff();
10793 gameMode = PlayFromGameFile;
10794 DisplayMessage("", _("Training mode off"));
10796 gameMode = Training;
10797 animateTraining = appData.animate;
10799 /* make sure we are not already at the end of the game */
10800 if (currentMove < forwardMostMove) {
10801 SetTrainingModeOn();
10802 DisplayMessage("", _("Training mode on"));
10804 gameMode = PlayFromGameFile;
10805 DisplayError(_("Already at end of game"), 0);
10814 if (!appData.icsActive) return;
10815 switch (gameMode) {
10816 case IcsPlayingWhite:
10817 case IcsPlayingBlack:
10820 case BeginningOfGame:
10828 EditPositionDone();
10841 gameMode = IcsIdle;
10852 switch (gameMode) {
10854 SetTrainingModeOff();
10856 case MachinePlaysWhite:
10857 case MachinePlaysBlack:
10858 case BeginningOfGame:
10859 SendToProgram("force\n", &first);
10860 SetUserThinkingEnables();
10862 case PlayFromGameFile:
10863 (void) StopLoadGameTimer();
10864 if (gameFileFP != NULL) {
10869 EditPositionDone();
10874 SendToProgram("force\n", &first);
10876 case TwoMachinesPlay:
10877 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10878 ResurrectChessProgram();
10879 SetUserThinkingEnables();
10882 ResurrectChessProgram();
10884 case IcsPlayingBlack:
10885 case IcsPlayingWhite:
10886 DisplayError(_("Warning: You are still playing a game"), 0);
10889 DisplayError(_("Warning: You are still observing a game"), 0);
10892 DisplayError(_("Warning: You are still examining a game"), 0);
10903 first.offeredDraw = second.offeredDraw = 0;
10905 if (gameMode == PlayFromGameFile) {
10906 whiteTimeRemaining = timeRemaining[0][currentMove];
10907 blackTimeRemaining = timeRemaining[1][currentMove];
10911 if (gameMode == MachinePlaysWhite ||
10912 gameMode == MachinePlaysBlack ||
10913 gameMode == TwoMachinesPlay ||
10914 gameMode == EndOfGame) {
10915 i = forwardMostMove;
10916 while (i > currentMove) {
10917 SendToProgram("undo\n", &first);
10920 whiteTimeRemaining = timeRemaining[0][currentMove];
10921 blackTimeRemaining = timeRemaining[1][currentMove];
10922 DisplayBothClocks();
10923 if (whiteFlag || blackFlag) {
10924 whiteFlag = blackFlag = 0;
10929 gameMode = EditGame;
10936 EditPositionEvent()
10938 if (gameMode == EditPosition) {
10944 if (gameMode != EditGame) return;
10946 gameMode = EditPosition;
10949 if (currentMove > 0)
10950 CopyBoard(boards[0], boards[currentMove]);
10952 blackPlaysFirst = !WhiteOnMove(currentMove);
10954 currentMove = forwardMostMove = backwardMostMove = 0;
10955 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10962 /* [DM] icsEngineAnalyze - possible call from other functions */
10963 if (appData.icsEngineAnalyze) {
10964 appData.icsEngineAnalyze = FALSE;
10966 DisplayMessage("",_("Close ICS engine analyze..."));
10968 if (first.analysisSupport && first.analyzing) {
10969 SendToProgram("exit\n", &first);
10970 first.analyzing = FALSE;
10973 thinkOutput[0] = NULLCHAR;
10979 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10981 startedFromSetupPosition = TRUE;
10982 InitChessProgram(&first, FALSE);
10983 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10984 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10985 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10986 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10987 } else castlingRights[0][2] = -1;
10988 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10989 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10990 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10991 } else castlingRights[0][5] = -1;
10992 SendToProgram("force\n", &first);
10993 if (blackPlaysFirst) {
10994 strcpy(moveList[0], "");
10995 strcpy(parseList[0], "");
10996 currentMove = forwardMostMove = backwardMostMove = 1;
10997 CopyBoard(boards[1], boards[0]);
10998 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11000 epStatus[1] = epStatus[0];
11001 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11004 currentMove = forwardMostMove = backwardMostMove = 0;
11006 SendBoard(&first, forwardMostMove);
11007 if (appData.debugMode) {
11008 fprintf(debugFP, "EditPosDone\n");
11011 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11012 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11013 gameMode = EditGame;
11015 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11016 ClearHighlights(); /* [AS] */
11019 /* Pause for `ms' milliseconds */
11020 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11030 } while (SubtractTimeMarks(&m2, &m1) < ms);
11033 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11035 SendMultiLineToICS(buf)
11038 char temp[MSG_SIZ+1], *p;
11045 strncpy(temp, buf, len);
11050 if (*p == '\n' || *p == '\r')
11055 strcat(temp, "\n");
11057 SendToPlayer(temp, strlen(temp));
11061 SetWhiteToPlayEvent()
11063 if (gameMode == EditPosition) {
11064 blackPlaysFirst = FALSE;
11065 DisplayBothClocks(); /* works because currentMove is 0 */
11066 } else if (gameMode == IcsExamining) {
11067 SendToICS(ics_prefix);
11068 SendToICS("tomove white\n");
11073 SetBlackToPlayEvent()
11075 if (gameMode == EditPosition) {
11076 blackPlaysFirst = TRUE;
11077 currentMove = 1; /* kludge */
11078 DisplayBothClocks();
11080 } else if (gameMode == IcsExamining) {
11081 SendToICS(ics_prefix);
11082 SendToICS("tomove black\n");
11087 EditPositionMenuEvent(selection, x, y)
11088 ChessSquare selection;
11092 ChessSquare piece = boards[0][y][x];
11094 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11096 switch (selection) {
11098 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11099 SendToICS(ics_prefix);
11100 SendToICS("bsetup clear\n");
11101 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11102 SendToICS(ics_prefix);
11103 SendToICS("clearboard\n");
11105 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11106 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11107 for (y = 0; y < BOARD_HEIGHT; y++) {
11108 if (gameMode == IcsExamining) {
11109 if (boards[currentMove][y][x] != EmptySquare) {
11110 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11115 boards[0][y][x] = p;
11120 if (gameMode == EditPosition) {
11121 DrawPosition(FALSE, boards[0]);
11126 SetWhiteToPlayEvent();
11130 SetBlackToPlayEvent();
11134 if (gameMode == IcsExamining) {
11135 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11138 boards[0][y][x] = EmptySquare;
11139 DrawPosition(FALSE, boards[0]);
11144 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11145 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11146 selection = (ChessSquare) (PROMOTED piece);
11147 } else if(piece == EmptySquare) selection = WhiteSilver;
11148 else selection = (ChessSquare)((int)piece - 1);
11152 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11153 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11154 selection = (ChessSquare) (DEMOTED piece);
11155 } else if(piece == EmptySquare) selection = BlackSilver;
11156 else selection = (ChessSquare)((int)piece + 1);
11161 if(gameInfo.variant == VariantShatranj ||
11162 gameInfo.variant == VariantXiangqi ||
11163 gameInfo.variant == VariantCourier )
11164 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11169 if(gameInfo.variant == VariantXiangqi)
11170 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11171 if(gameInfo.variant == VariantKnightmate)
11172 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11175 if (gameMode == IcsExamining) {
11176 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11177 PieceToChar(selection), AAA + x, ONE + y);
11180 boards[0][y][x] = selection;
11181 DrawPosition(FALSE, boards[0]);
11189 DropMenuEvent(selection, x, y)
11190 ChessSquare selection;
11193 ChessMove moveType;
11195 switch (gameMode) {
11196 case IcsPlayingWhite:
11197 case MachinePlaysBlack:
11198 if (!WhiteOnMove(currentMove)) {
11199 DisplayMoveError(_("It is Black's turn"));
11202 moveType = WhiteDrop;
11204 case IcsPlayingBlack:
11205 case MachinePlaysWhite:
11206 if (WhiteOnMove(currentMove)) {
11207 DisplayMoveError(_("It is White's turn"));
11210 moveType = BlackDrop;
11213 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11219 if (moveType == BlackDrop && selection < BlackPawn) {
11220 selection = (ChessSquare) ((int) selection
11221 + (int) BlackPawn - (int) WhitePawn);
11223 if (boards[currentMove][y][x] != EmptySquare) {
11224 DisplayMoveError(_("That square is occupied"));
11228 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11234 /* Accept a pending offer of any kind from opponent */
11236 if (appData.icsActive) {
11237 SendToICS(ics_prefix);
11238 SendToICS("accept\n");
11239 } else if (cmailMsgLoaded) {
11240 if (currentMove == cmailOldMove &&
11241 commentList[cmailOldMove] != NULL &&
11242 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11243 "Black offers a draw" : "White offers a draw")) {
11245 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11246 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11248 DisplayError(_("There is no pending offer on this move"), 0);
11249 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11252 /* Not used for offers from chess program */
11259 /* Decline a pending offer of any kind from opponent */
11261 if (appData.icsActive) {
11262 SendToICS(ics_prefix);
11263 SendToICS("decline\n");
11264 } else if (cmailMsgLoaded) {
11265 if (currentMove == cmailOldMove &&
11266 commentList[cmailOldMove] != NULL &&
11267 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11268 "Black offers a draw" : "White offers a draw")) {
11270 AppendComment(cmailOldMove, "Draw declined");
11271 DisplayComment(cmailOldMove - 1, "Draw declined");
11274 DisplayError(_("There is no pending offer on this move"), 0);
11277 /* Not used for offers from chess program */
11284 /* Issue ICS rematch command */
11285 if (appData.icsActive) {
11286 SendToICS(ics_prefix);
11287 SendToICS("rematch\n");
11294 /* Call your opponent's flag (claim a win on time) */
11295 if (appData.icsActive) {
11296 SendToICS(ics_prefix);
11297 SendToICS("flag\n");
11299 switch (gameMode) {
11302 case MachinePlaysWhite:
11305 GameEnds(GameIsDrawn, "Both players ran out of time",
11308 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11310 DisplayError(_("Your opponent is not out of time"), 0);
11313 case MachinePlaysBlack:
11316 GameEnds(GameIsDrawn, "Both players ran out of time",
11319 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11321 DisplayError(_("Your opponent is not out of time"), 0);
11331 /* Offer draw or accept pending draw offer from opponent */
11333 if (appData.icsActive) {
11334 /* Note: tournament rules require draw offers to be
11335 made after you make your move but before you punch
11336 your clock. Currently ICS doesn't let you do that;
11337 instead, you immediately punch your clock after making
11338 a move, but you can offer a draw at any time. */
11340 SendToICS(ics_prefix);
11341 SendToICS("draw\n");
11342 } else if (cmailMsgLoaded) {
11343 if (currentMove == cmailOldMove &&
11344 commentList[cmailOldMove] != NULL &&
11345 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11346 "Black offers a draw" : "White offers a draw")) {
11347 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11348 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11349 } else if (currentMove == cmailOldMove + 1) {
11350 char *offer = WhiteOnMove(cmailOldMove) ?
11351 "White offers a draw" : "Black offers a draw";
11352 AppendComment(currentMove, offer);
11353 DisplayComment(currentMove - 1, offer);
11354 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11356 DisplayError(_("You must make your move before offering a draw"), 0);
11357 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11359 } else if (first.offeredDraw) {
11360 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11362 if (first.sendDrawOffers) {
11363 SendToProgram("draw\n", &first);
11364 userOfferedDraw = TRUE;
11372 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11374 if (appData.icsActive) {
11375 SendToICS(ics_prefix);
11376 SendToICS("adjourn\n");
11378 /* Currently GNU Chess doesn't offer or accept Adjourns */
11386 /* Offer Abort or accept pending Abort offer from opponent */
11388 if (appData.icsActive) {
11389 SendToICS(ics_prefix);
11390 SendToICS("abort\n");
11392 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11399 /* Resign. You can do this even if it's not your turn. */
11401 if (appData.icsActive) {
11402 SendToICS(ics_prefix);
11403 SendToICS("resign\n");
11405 switch (gameMode) {
11406 case MachinePlaysWhite:
11407 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11409 case MachinePlaysBlack:
11410 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11413 if (cmailMsgLoaded) {
11415 if (WhiteOnMove(cmailOldMove)) {
11416 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11418 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11420 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11431 StopObservingEvent()
11433 /* Stop observing current games */
11434 SendToICS(ics_prefix);
11435 SendToICS("unobserve\n");
11439 StopExaminingEvent()
11441 /* Stop observing current game */
11442 SendToICS(ics_prefix);
11443 SendToICS("unexamine\n");
11447 ForwardInner(target)
11452 if (appData.debugMode)
11453 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11454 target, currentMove, forwardMostMove);
11456 if (gameMode == EditPosition)
11459 if (gameMode == PlayFromGameFile && !pausing)
11462 if (gameMode == IcsExamining && pausing)
11463 limit = pauseExamForwardMostMove;
11465 limit = forwardMostMove;
11467 if (target > limit) target = limit;
11469 if (target > 0 && moveList[target - 1][0]) {
11470 int fromX, fromY, toX, toY;
11471 toX = moveList[target - 1][2] - AAA;
11472 toY = moveList[target - 1][3] - ONE;
11473 if (moveList[target - 1][1] == '@') {
11474 if (appData.highlightLastMove) {
11475 SetHighlights(-1, -1, toX, toY);
11478 fromX = moveList[target - 1][0] - AAA;
11479 fromY = moveList[target - 1][1] - ONE;
11480 if (target == currentMove + 1) {
11481 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11483 if (appData.highlightLastMove) {
11484 SetHighlights(fromX, fromY, toX, toY);
11488 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11489 gameMode == Training || gameMode == PlayFromGameFile ||
11490 gameMode == AnalyzeFile) {
11491 while (currentMove < target) {
11492 SendMoveToProgram(currentMove++, &first);
11495 currentMove = target;
11498 if (gameMode == EditGame || gameMode == EndOfGame) {
11499 whiteTimeRemaining = timeRemaining[0][currentMove];
11500 blackTimeRemaining = timeRemaining[1][currentMove];
11502 DisplayBothClocks();
11503 DisplayMove(currentMove - 1);
11504 DrawPosition(FALSE, boards[currentMove]);
11505 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11506 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11507 DisplayComment(currentMove - 1, commentList[currentMove]);
11515 if (gameMode == IcsExamining && !pausing) {
11516 SendToICS(ics_prefix);
11517 SendToICS("forward\n");
11519 ForwardInner(currentMove + 1);
11526 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11527 /* to optimze, we temporarily turn off analysis mode while we feed
11528 * the remaining moves to the engine. Otherwise we get analysis output
11531 if (first.analysisSupport) {
11532 SendToProgram("exit\nforce\n", &first);
11533 first.analyzing = FALSE;
11537 if (gameMode == IcsExamining && !pausing) {
11538 SendToICS(ics_prefix);
11539 SendToICS("forward 999999\n");
11541 ForwardInner(forwardMostMove);
11544 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11545 /* we have fed all the moves, so reactivate analysis mode */
11546 SendToProgram("analyze\n", &first);
11547 first.analyzing = TRUE;
11548 /*first.maybeThinking = TRUE;*/
11549 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11554 BackwardInner(target)
11557 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11559 if (appData.debugMode)
11560 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11561 target, currentMove, forwardMostMove);
11563 if (gameMode == EditPosition) return;
11564 if (currentMove <= backwardMostMove) {
11566 DrawPosition(full_redraw, boards[currentMove]);
11569 if (gameMode == PlayFromGameFile && !pausing)
11572 if (moveList[target][0]) {
11573 int fromX, fromY, toX, toY;
11574 toX = moveList[target][2] - AAA;
11575 toY = moveList[target][3] - ONE;
11576 if (moveList[target][1] == '@') {
11577 if (appData.highlightLastMove) {
11578 SetHighlights(-1, -1, toX, toY);
11581 fromX = moveList[target][0] - AAA;
11582 fromY = moveList[target][1] - ONE;
11583 if (target == currentMove - 1) {
11584 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11586 if (appData.highlightLastMove) {
11587 SetHighlights(fromX, fromY, toX, toY);
11591 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11592 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11593 while (currentMove > target) {
11594 SendToProgram("undo\n", &first);
11598 currentMove = target;
11601 if (gameMode == EditGame || gameMode == EndOfGame) {
11602 whiteTimeRemaining = timeRemaining[0][currentMove];
11603 blackTimeRemaining = timeRemaining[1][currentMove];
11605 DisplayBothClocks();
11606 DisplayMove(currentMove - 1);
11607 DrawPosition(full_redraw, boards[currentMove]);
11608 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11609 // [HGM] PV info: routine tests if comment empty
11610 DisplayComment(currentMove - 1, commentList[currentMove]);
11616 if (gameMode == IcsExamining && !pausing) {
11617 SendToICS(ics_prefix);
11618 SendToICS("backward\n");
11620 BackwardInner(currentMove - 1);
11627 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11628 /* to optimze, we temporarily turn off analysis mode while we undo
11629 * all the moves. Otherwise we get analysis output after each undo.
11631 if (first.analysisSupport) {
11632 SendToProgram("exit\nforce\n", &first);
11633 first.analyzing = FALSE;
11637 if (gameMode == IcsExamining && !pausing) {
11638 SendToICS(ics_prefix);
11639 SendToICS("backward 999999\n");
11641 BackwardInner(backwardMostMove);
11644 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11645 /* we have fed all the moves, so reactivate analysis mode */
11646 SendToProgram("analyze\n", &first);
11647 first.analyzing = TRUE;
11648 /*first.maybeThinking = TRUE;*/
11649 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11656 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11657 if (to >= forwardMostMove) to = forwardMostMove;
11658 if (to <= backwardMostMove) to = backwardMostMove;
11659 if (to < currentMove) {
11669 if (gameMode != IcsExamining) {
11670 DisplayError(_("You are not examining a game"), 0);
11674 DisplayError(_("You can't revert while pausing"), 0);
11677 SendToICS(ics_prefix);
11678 SendToICS("revert\n");
11684 switch (gameMode) {
11685 case MachinePlaysWhite:
11686 case MachinePlaysBlack:
11687 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11688 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11691 if (forwardMostMove < 2) return;
11692 currentMove = forwardMostMove = forwardMostMove - 2;
11693 whiteTimeRemaining = timeRemaining[0][currentMove];
11694 blackTimeRemaining = timeRemaining[1][currentMove];
11695 DisplayBothClocks();
11696 DisplayMove(currentMove - 1);
11697 ClearHighlights();/*!! could figure this out*/
11698 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11699 SendToProgram("remove\n", &first);
11700 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11703 case BeginningOfGame:
11707 case IcsPlayingWhite:
11708 case IcsPlayingBlack:
11709 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11710 SendToICS(ics_prefix);
11711 SendToICS("takeback 2\n");
11713 SendToICS(ics_prefix);
11714 SendToICS("takeback 1\n");
11723 ChessProgramState *cps;
11725 switch (gameMode) {
11726 case MachinePlaysWhite:
11727 if (!WhiteOnMove(forwardMostMove)) {
11728 DisplayError(_("It is your turn"), 0);
11733 case MachinePlaysBlack:
11734 if (WhiteOnMove(forwardMostMove)) {
11735 DisplayError(_("It is your turn"), 0);
11740 case TwoMachinesPlay:
11741 if (WhiteOnMove(forwardMostMove) ==
11742 (first.twoMachinesColor[0] == 'w')) {
11748 case BeginningOfGame:
11752 SendToProgram("?\n", cps);
11756 TruncateGameEvent()
11759 if (gameMode != EditGame) return;
11766 if (forwardMostMove > currentMove) {
11767 if (gameInfo.resultDetails != NULL) {
11768 free(gameInfo.resultDetails);
11769 gameInfo.resultDetails = NULL;
11770 gameInfo.result = GameUnfinished;
11772 forwardMostMove = currentMove;
11773 HistorySet(parseList, backwardMostMove, forwardMostMove,
11781 if (appData.noChessProgram) return;
11782 switch (gameMode) {
11783 case MachinePlaysWhite:
11784 if (WhiteOnMove(forwardMostMove)) {
11785 DisplayError(_("Wait until your turn"), 0);
11789 case BeginningOfGame:
11790 case MachinePlaysBlack:
11791 if (!WhiteOnMove(forwardMostMove)) {
11792 DisplayError(_("Wait until your turn"), 0);
11797 DisplayError(_("No hint available"), 0);
11800 SendToProgram("hint\n", &first);
11801 hintRequested = TRUE;
11807 if (appData.noChessProgram) return;
11808 switch (gameMode) {
11809 case MachinePlaysWhite:
11810 if (WhiteOnMove(forwardMostMove)) {
11811 DisplayError(_("Wait until your turn"), 0);
11815 case BeginningOfGame:
11816 case MachinePlaysBlack:
11817 if (!WhiteOnMove(forwardMostMove)) {
11818 DisplayError(_("Wait until your turn"), 0);
11823 EditPositionDone();
11825 case TwoMachinesPlay:
11830 SendToProgram("bk\n", &first);
11831 bookOutput[0] = NULLCHAR;
11832 bookRequested = TRUE;
11838 char *tags = PGNTags(&gameInfo);
11839 TagsPopUp(tags, CmailMsg());
11843 /* end button procedures */
11846 PrintPosition(fp, move)
11852 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11853 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11854 char c = PieceToChar(boards[move][i][j]);
11855 fputc(c == 'x' ? '.' : c, fp);
11856 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11859 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11860 fprintf(fp, "white to play\n");
11862 fprintf(fp, "black to play\n");
11869 if (gameInfo.white != NULL) {
11870 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11876 /* Find last component of program's own name, using some heuristics */
11878 TidyProgramName(prog, host, buf)
11879 char *prog, *host, buf[MSG_SIZ];
11882 int local = (strcmp(host, "localhost") == 0);
11883 while (!local && (p = strchr(prog, ';')) != NULL) {
11885 while (*p == ' ') p++;
11888 if (*prog == '"' || *prog == '\'') {
11889 q = strchr(prog + 1, *prog);
11891 q = strchr(prog, ' ');
11893 if (q == NULL) q = prog + strlen(prog);
11895 while (p >= prog && *p != '/' && *p != '\\') p--;
11897 if(p == prog && *p == '"') p++;
11898 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11899 memcpy(buf, p, q - p);
11900 buf[q - p] = NULLCHAR;
11908 TimeControlTagValue()
11911 if (!appData.clockMode) {
11913 } else if (movesPerSession > 0) {
11914 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11915 } else if (timeIncrement == 0) {
11916 sprintf(buf, "%ld", timeControl/1000);
11918 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11920 return StrSave(buf);
11926 /* This routine is used only for certain modes */
11927 VariantClass v = gameInfo.variant;
11928 ClearGameInfo(&gameInfo);
11929 gameInfo.variant = v;
11931 switch (gameMode) {
11932 case MachinePlaysWhite:
11933 gameInfo.event = StrSave( appData.pgnEventHeader );
11934 gameInfo.site = StrSave(HostName());
11935 gameInfo.date = PGNDate();
11936 gameInfo.round = StrSave("-");
11937 gameInfo.white = StrSave(first.tidy);
11938 gameInfo.black = StrSave(UserName());
11939 gameInfo.timeControl = TimeControlTagValue();
11942 case MachinePlaysBlack:
11943 gameInfo.event = StrSave( appData.pgnEventHeader );
11944 gameInfo.site = StrSave(HostName());
11945 gameInfo.date = PGNDate();
11946 gameInfo.round = StrSave("-");
11947 gameInfo.white = StrSave(UserName());
11948 gameInfo.black = StrSave(first.tidy);
11949 gameInfo.timeControl = TimeControlTagValue();
11952 case TwoMachinesPlay:
11953 gameInfo.event = StrSave( appData.pgnEventHeader );
11954 gameInfo.site = StrSave(HostName());
11955 gameInfo.date = PGNDate();
11956 if (matchGame > 0) {
11958 sprintf(buf, "%d", matchGame);
11959 gameInfo.round = StrSave(buf);
11961 gameInfo.round = StrSave("-");
11963 if (first.twoMachinesColor[0] == 'w') {
11964 gameInfo.white = StrSave(first.tidy);
11965 gameInfo.black = StrSave(second.tidy);
11967 gameInfo.white = StrSave(second.tidy);
11968 gameInfo.black = StrSave(first.tidy);
11970 gameInfo.timeControl = TimeControlTagValue();
11974 gameInfo.event = StrSave("Edited game");
11975 gameInfo.site = StrSave(HostName());
11976 gameInfo.date = PGNDate();
11977 gameInfo.round = StrSave("-");
11978 gameInfo.white = StrSave("-");
11979 gameInfo.black = StrSave("-");
11983 gameInfo.event = StrSave("Edited position");
11984 gameInfo.site = StrSave(HostName());
11985 gameInfo.date = PGNDate();
11986 gameInfo.round = StrSave("-");
11987 gameInfo.white = StrSave("-");
11988 gameInfo.black = StrSave("-");
11991 case IcsPlayingWhite:
11992 case IcsPlayingBlack:
11997 case PlayFromGameFile:
11998 gameInfo.event = StrSave("Game from non-PGN file");
11999 gameInfo.site = StrSave(HostName());
12000 gameInfo.date = PGNDate();
12001 gameInfo.round = StrSave("-");
12002 gameInfo.white = StrSave("?");
12003 gameInfo.black = StrSave("?");
12012 ReplaceComment(index, text)
12018 while (*text == '\n') text++;
12019 len = strlen(text);
12020 while (len > 0 && text[len - 1] == '\n') len--;
12022 if (commentList[index] != NULL)
12023 free(commentList[index]);
12026 commentList[index] = NULL;
12029 commentList[index] = (char *) malloc(len + 2);
12030 strncpy(commentList[index], text, len);
12031 commentList[index][len] = '\n';
12032 commentList[index][len + 1] = NULLCHAR;
12045 if (ch == '\r') continue;
12047 } while (ch != '\0');
12051 AppendComment(index, text)
12058 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12061 while (*text == '\n') text++;
12062 len = strlen(text);
12063 while (len > 0 && text[len - 1] == '\n') len--;
12065 if (len == 0) return;
12067 if (commentList[index] != NULL) {
12068 old = commentList[index];
12069 oldlen = strlen(old);
12070 commentList[index] = (char *) malloc(oldlen + len + 2);
12071 strcpy(commentList[index], old);
12073 strncpy(&commentList[index][oldlen], text, len);
12074 commentList[index][oldlen + len] = '\n';
12075 commentList[index][oldlen + len + 1] = NULLCHAR;
12077 commentList[index] = (char *) malloc(len + 2);
12078 strncpy(commentList[index], text, len);
12079 commentList[index][len] = '\n';
12080 commentList[index][len + 1] = NULLCHAR;
12084 static char * FindStr( char * text, char * sub_text )
12086 char * result = strstr( text, sub_text );
12088 if( result != NULL ) {
12089 result += strlen( sub_text );
12095 /* [AS] Try to extract PV info from PGN comment */
12096 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12097 char *GetInfoFromComment( int index, char * text )
12101 if( text != NULL && index > 0 ) {
12104 int time = -1, sec = 0, deci;
12105 char * s_eval = FindStr( text, "[%eval " );
12106 char * s_emt = FindStr( text, "[%emt " );
12108 if( s_eval != NULL || s_emt != NULL ) {
12112 if( s_eval != NULL ) {
12113 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12117 if( delim != ']' ) {
12122 if( s_emt != NULL ) {
12126 /* We expect something like: [+|-]nnn.nn/dd */
12129 sep = strchr( text, '/' );
12130 if( sep == NULL || sep < (text+4) ) {
12134 time = -1; sec = -1; deci = -1;
12135 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12136 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12137 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12138 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12142 if( score_lo < 0 || score_lo >= 100 ) {
12146 if(sec >= 0) time = 600*time + 10*sec; else
12147 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12149 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12151 /* [HGM] PV time: now locate end of PV info */
12152 while( *++sep >= '0' && *sep <= '9'); // strip depth
12154 while( *++sep >= '0' && *sep <= '9'); // strip time
12156 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12158 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12159 while(*sep == ' ') sep++;
12170 pvInfoList[index-1].depth = depth;
12171 pvInfoList[index-1].score = score;
12172 pvInfoList[index-1].time = 10*time; // centi-sec
12178 SendToProgram(message, cps)
12180 ChessProgramState *cps;
12182 int count, outCount, error;
12185 if (cps->pr == NULL) return;
12188 if (appData.debugMode) {
12191 fprintf(debugFP, "%ld >%-6s: %s",
12192 SubtractTimeMarks(&now, &programStartTime),
12193 cps->which, message);
12196 count = strlen(message);
12197 outCount = OutputToProcess(cps->pr, message, count, &error);
12198 if (outCount < count && !exiting
12199 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12200 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12201 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12202 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12203 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12204 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12206 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12208 gameInfo.resultDetails = buf;
12210 DisplayFatalError(buf, error, 1);
12215 ReceiveFromProgram(isr, closure, message, count, error)
12216 InputSourceRef isr;
12224 ChessProgramState *cps = (ChessProgramState *)closure;
12226 if (isr != cps->isr) return; /* Killed intentionally */
12230 _("Error: %s chess program (%s) exited unexpectedly"),
12231 cps->which, cps->program);
12232 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12233 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12234 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12235 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12237 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12239 gameInfo.resultDetails = buf;
12241 RemoveInputSource(cps->isr);
12242 DisplayFatalError(buf, 0, 1);
12245 _("Error reading from %s chess program (%s)"),
12246 cps->which, cps->program);
12247 RemoveInputSource(cps->isr);
12249 /* [AS] Program is misbehaving badly... kill it */
12250 if( count == -2 ) {
12251 DestroyChildProcess( cps->pr, 9 );
12255 DisplayFatalError(buf, error, 1);
12260 if ((end_str = strchr(message, '\r')) != NULL)
12261 *end_str = NULLCHAR;
12262 if ((end_str = strchr(message, '\n')) != NULL)
12263 *end_str = NULLCHAR;
12265 if (appData.debugMode) {
12266 TimeMark now; int print = 1;
12267 char *quote = ""; char c; int i;
12269 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12270 char start = message[0];
12271 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12272 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12273 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12274 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12275 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12276 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12277 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12278 sscanf(message, "pong %c", &c)!=1 && start != '#')
12279 { quote = "# "; print = (appData.engineComments == 2); }
12280 message[0] = start; // restore original message
12284 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12285 SubtractTimeMarks(&now, &programStartTime), cps->which,
12291 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12292 if (appData.icsEngineAnalyze) {
12293 if (strstr(message, "whisper") != NULL ||
12294 strstr(message, "kibitz") != NULL ||
12295 strstr(message, "tellics") != NULL) return;
12298 HandleMachineMove(message, cps);
12303 SendTimeControl(cps, mps, tc, inc, sd, st)
12304 ChessProgramState *cps;
12305 int mps, inc, sd, st;
12311 if( timeControl_2 > 0 ) {
12312 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12313 tc = timeControl_2;
12316 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12317 inc /= cps->timeOdds;
12318 st /= cps->timeOdds;
12320 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12323 /* Set exact time per move, normally using st command */
12324 if (cps->stKludge) {
12325 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12327 if (seconds == 0) {
12328 sprintf(buf, "level 1 %d\n", st/60);
12330 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12333 sprintf(buf, "st %d\n", st);
12336 /* Set conventional or incremental time control, using level command */
12337 if (seconds == 0) {
12338 /* Note old gnuchess bug -- minutes:seconds used to not work.
12339 Fixed in later versions, but still avoid :seconds
12340 when seconds is 0. */
12341 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12343 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12344 seconds, inc/1000);
12347 SendToProgram(buf, cps);
12349 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12350 /* Orthogonally, limit search to given depth */
12352 if (cps->sdKludge) {
12353 sprintf(buf, "depth\n%d\n", sd);
12355 sprintf(buf, "sd %d\n", sd);
12357 SendToProgram(buf, cps);
12360 if(cps->nps > 0) { /* [HGM] nps */
12361 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12363 sprintf(buf, "nps %d\n", cps->nps);
12364 SendToProgram(buf, cps);
12369 ChessProgramState *WhitePlayer()
12370 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12372 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12373 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12379 SendTimeRemaining(cps, machineWhite)
12380 ChessProgramState *cps;
12381 int /*boolean*/ machineWhite;
12383 char message[MSG_SIZ];
12386 /* Note: this routine must be called when the clocks are stopped
12387 or when they have *just* been set or switched; otherwise
12388 it will be off by the time since the current tick started.
12390 if (machineWhite) {
12391 time = whiteTimeRemaining / 10;
12392 otime = blackTimeRemaining / 10;
12394 time = blackTimeRemaining / 10;
12395 otime = whiteTimeRemaining / 10;
12397 /* [HGM] translate opponent's time by time-odds factor */
12398 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12399 if (appData.debugMode) {
12400 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12403 if (time <= 0) time = 1;
12404 if (otime <= 0) otime = 1;
12406 sprintf(message, "time %ld\n", time);
12407 SendToProgram(message, cps);
12409 sprintf(message, "otim %ld\n", otime);
12410 SendToProgram(message, cps);
12414 BoolFeature(p, name, loc, cps)
12418 ChessProgramState *cps;
12421 int len = strlen(name);
12423 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12425 sscanf(*p, "%d", &val);
12427 while (**p && **p != ' ') (*p)++;
12428 sprintf(buf, "accepted %s\n", name);
12429 SendToProgram(buf, cps);
12436 IntFeature(p, name, loc, cps)
12440 ChessProgramState *cps;
12443 int len = strlen(name);
12444 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12446 sscanf(*p, "%d", loc);
12447 while (**p && **p != ' ') (*p)++;
12448 sprintf(buf, "accepted %s\n", name);
12449 SendToProgram(buf, cps);
12456 StringFeature(p, name, loc, cps)
12460 ChessProgramState *cps;
12463 int len = strlen(name);
12464 if (strncmp((*p), name, len) == 0
12465 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12467 sscanf(*p, "%[^\"]", loc);
12468 while (**p && **p != '\"') (*p)++;
12469 if (**p == '\"') (*p)++;
12470 sprintf(buf, "accepted %s\n", name);
12471 SendToProgram(buf, cps);
12478 ParseOption(Option *opt, ChessProgramState *cps)
12479 // [HGM] options: process the string that defines an engine option, and determine
12480 // name, type, default value, and allowed value range
12482 char *p, *q, buf[MSG_SIZ];
12483 int n, min = (-1)<<31, max = 1<<31, def;
12485 if(p = strstr(opt->name, " -spin ")) {
12486 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12487 if(max < min) max = min; // enforce consistency
12488 if(def < min) def = min;
12489 if(def > max) def = max;
12494 } else if((p = strstr(opt->name, " -slider "))) {
12495 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12496 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12497 if(max < min) max = min; // enforce consistency
12498 if(def < min) def = min;
12499 if(def > max) def = max;
12503 opt->type = Spin; // Slider;
12504 } else if((p = strstr(opt->name, " -string "))) {
12505 opt->textValue = p+9;
12506 opt->type = TextBox;
12507 } else if((p = strstr(opt->name, " -file "))) {
12508 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12509 opt->textValue = p+7;
12510 opt->type = TextBox; // FileName;
12511 } else if((p = strstr(opt->name, " -path "))) {
12512 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12513 opt->textValue = p+7;
12514 opt->type = TextBox; // PathName;
12515 } else if(p = strstr(opt->name, " -check ")) {
12516 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12517 opt->value = (def != 0);
12518 opt->type = CheckBox;
12519 } else if(p = strstr(opt->name, " -combo ")) {
12520 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12521 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12522 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12523 opt->value = n = 0;
12524 while(q = StrStr(q, " /// ")) {
12525 n++; *q = 0; // count choices, and null-terminate each of them
12527 if(*q == '*') { // remember default, which is marked with * prefix
12531 cps->comboList[cps->comboCnt++] = q;
12533 cps->comboList[cps->comboCnt++] = NULL;
12535 opt->type = ComboBox;
12536 } else if(p = strstr(opt->name, " -button")) {
12537 opt->type = Button;
12538 } else if(p = strstr(opt->name, " -save")) {
12539 opt->type = SaveButton;
12540 } else return FALSE;
12541 *p = 0; // terminate option name
12542 // now look if the command-line options define a setting for this engine option.
12543 if(cps->optionSettings && cps->optionSettings[0])
12544 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12545 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12546 sprintf(buf, "option %s", p);
12547 if(p = strstr(buf, ",")) *p = 0;
12549 SendToProgram(buf, cps);
12555 FeatureDone(cps, val)
12556 ChessProgramState* cps;
12559 DelayedEventCallback cb = GetDelayedEvent();
12560 if ((cb == InitBackEnd3 && cps == &first) ||
12561 (cb == TwoMachinesEventIfReady && cps == &second)) {
12562 CancelDelayedEvent();
12563 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12565 cps->initDone = val;
12568 /* Parse feature command from engine */
12570 ParseFeatures(args, cps)
12572 ChessProgramState *cps;
12580 while (*p == ' ') p++;
12581 if (*p == NULLCHAR) return;
12583 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12584 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12585 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12586 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12587 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12588 if (BoolFeature(&p, "reuse", &val, cps)) {
12589 /* Engine can disable reuse, but can't enable it if user said no */
12590 if (!val) cps->reuse = FALSE;
12593 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12594 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12595 if (gameMode == TwoMachinesPlay) {
12596 DisplayTwoMachinesTitle();
12602 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12603 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12604 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12605 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12606 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12607 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12608 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12609 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12610 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12611 if (IntFeature(&p, "done", &val, cps)) {
12612 FeatureDone(cps, val);
12615 /* Added by Tord: */
12616 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12617 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12618 /* End of additions by Tord */
12620 /* [HGM] added features: */
12621 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12622 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12623 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12624 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12625 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12626 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12627 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12628 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12629 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12630 SendToProgram(buf, cps);
12633 if(cps->nrOptions >= MAX_OPTIONS) {
12635 sprintf(buf, "%s engine has too many options\n", cps->which);
12636 DisplayError(buf, 0);
12640 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12641 /* End of additions by HGM */
12643 /* unknown feature: complain and skip */
12645 while (*q && *q != '=') q++;
12646 sprintf(buf, "rejected %.*s\n", q-p, p);
12647 SendToProgram(buf, cps);
12653 while (*p && *p != '\"') p++;
12654 if (*p == '\"') p++;
12656 while (*p && *p != ' ') p++;
12664 PeriodicUpdatesEvent(newState)
12667 if (newState == appData.periodicUpdates)
12670 appData.periodicUpdates=newState;
12672 /* Display type changes, so update it now */
12675 /* Get the ball rolling again... */
12677 AnalysisPeriodicEvent(1);
12678 StartAnalysisClock();
12683 PonderNextMoveEvent(newState)
12686 if (newState == appData.ponderNextMove) return;
12687 if (gameMode == EditPosition) EditPositionDone();
12689 SendToProgram("hard\n", &first);
12690 if (gameMode == TwoMachinesPlay) {
12691 SendToProgram("hard\n", &second);
12694 SendToProgram("easy\n", &first);
12695 thinkOutput[0] = NULLCHAR;
12696 if (gameMode == TwoMachinesPlay) {
12697 SendToProgram("easy\n", &second);
12700 appData.ponderNextMove = newState;
12704 NewSettingEvent(option, command, value)
12710 if (gameMode == EditPosition) EditPositionDone();
12711 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12712 SendToProgram(buf, &first);
12713 if (gameMode == TwoMachinesPlay) {
12714 SendToProgram(buf, &second);
12719 ShowThinkingEvent()
12720 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12722 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12723 int newState = appData.showThinking
12724 // [HGM] thinking: other features now need thinking output as well
12725 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12727 if (oldState == newState) return;
12728 oldState = newState;
12729 if (gameMode == EditPosition) EditPositionDone();
12731 SendToProgram("post\n", &first);
12732 if (gameMode == TwoMachinesPlay) {
12733 SendToProgram("post\n", &second);
12736 SendToProgram("nopost\n", &first);
12737 thinkOutput[0] = NULLCHAR;
12738 if (gameMode == TwoMachinesPlay) {
12739 SendToProgram("nopost\n", &second);
12742 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12746 AskQuestionEvent(title, question, replyPrefix, which)
12747 char *title; char *question; char *replyPrefix; char *which;
12749 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12750 if (pr == NoProc) return;
12751 AskQuestion(title, question, replyPrefix, pr);
12755 DisplayMove(moveNumber)
12758 char message[MSG_SIZ];
12760 char cpThinkOutput[MSG_SIZ];
12762 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12764 if (moveNumber == forwardMostMove - 1 ||
12765 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12767 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12769 if (strchr(cpThinkOutput, '\n')) {
12770 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12773 *cpThinkOutput = NULLCHAR;
12776 /* [AS] Hide thinking from human user */
12777 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12778 *cpThinkOutput = NULLCHAR;
12779 if( thinkOutput[0] != NULLCHAR ) {
12782 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12783 cpThinkOutput[i] = '.';
12785 cpThinkOutput[i] = NULLCHAR;
12786 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12790 if (moveNumber == forwardMostMove - 1 &&
12791 gameInfo.resultDetails != NULL) {
12792 if (gameInfo.resultDetails[0] == NULLCHAR) {
12793 sprintf(res, " %s", PGNResult(gameInfo.result));
12795 sprintf(res, " {%s} %s",
12796 gameInfo.resultDetails, PGNResult(gameInfo.result));
12802 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12803 DisplayMessage(res, cpThinkOutput);
12805 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12806 WhiteOnMove(moveNumber) ? " " : ".. ",
12807 parseList[moveNumber], res);
12808 DisplayMessage(message, cpThinkOutput);
12813 DisplayAnalysisText(text)
12818 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12819 || appData.icsEngineAnalyze) {
12820 sprintf(buf, "Analysis (%s)", first.tidy);
12821 AnalysisPopUp(buf, text);
12829 while (*str && isspace(*str)) ++str;
12830 while (*str && !isspace(*str)) ++str;
12831 if (!*str) return 1;
12832 while (*str && isspace(*str)) ++str;
12833 if (!*str) return 1;
12841 char lst[MSG_SIZ / 2];
12843 static char *xtra[] = { "", " (--)", " (++)" };
12846 if (programStats.time == 0) {
12847 programStats.time = 1;
12850 if (programStats.got_only_move) {
12851 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12853 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12855 nps = (u64ToDouble(programStats.nodes) /
12856 ((double)programStats.time /100.0));
12858 cs = programStats.time % 100;
12859 s = programStats.time / 100;
12865 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12866 if (programStats.move_name[0] != NULLCHAR) {
12867 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12868 programStats.depth,
12869 programStats.nr_moves-programStats.moves_left,
12870 programStats.nr_moves, programStats.move_name,
12871 ((float)programStats.score)/100.0, lst,
12872 only_one_move(lst)?
12873 xtra[programStats.got_fail] : "",
12874 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12876 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12877 programStats.depth,
12878 programStats.nr_moves-programStats.moves_left,
12879 programStats.nr_moves, ((float)programStats.score)/100.0,
12881 only_one_move(lst)?
12882 xtra[programStats.got_fail] : "",
12883 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12886 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12887 programStats.depth,
12888 ((float)programStats.score)/100.0,
12890 only_one_move(lst)?
12891 xtra[programStats.got_fail] : "",
12892 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12895 DisplayAnalysisText(buf);
12899 DisplayComment(moveNumber, text)
12903 char title[MSG_SIZ];
12904 char buf[8000]; // comment can be long!
12907 if( appData.autoDisplayComment ) {
12908 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12909 strcpy(title, "Comment");
12911 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12912 WhiteOnMove(moveNumber) ? " " : ".. ",
12913 parseList[moveNumber]);
12915 // [HGM] PV info: display PV info together with (or as) comment
12916 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12917 if(text == NULL) text = "";
12918 score = pvInfoList[moveNumber].score;
12919 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12920 depth, (pvInfoList[moveNumber].time+50)/100, text);
12923 } else title[0] = 0;
12926 CommentPopUp(title, text);
12929 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12930 * might be busy thinking or pondering. It can be omitted if your
12931 * gnuchess is configured to stop thinking immediately on any user
12932 * input. However, that gnuchess feature depends on the FIONREAD
12933 * ioctl, which does not work properly on some flavors of Unix.
12937 ChessProgramState *cps;
12940 if (!cps->useSigint) return;
12941 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12942 switch (gameMode) {
12943 case MachinePlaysWhite:
12944 case MachinePlaysBlack:
12945 case TwoMachinesPlay:
12946 case IcsPlayingWhite:
12947 case IcsPlayingBlack:
12950 /* Skip if we know it isn't thinking */
12951 if (!cps->maybeThinking) return;
12952 if (appData.debugMode)
12953 fprintf(debugFP, "Interrupting %s\n", cps->which);
12954 InterruptChildProcess(cps->pr);
12955 cps->maybeThinking = FALSE;
12960 #endif /*ATTENTION*/
12966 if (whiteTimeRemaining <= 0) {
12969 if (appData.icsActive) {
12970 if (appData.autoCallFlag &&
12971 gameMode == IcsPlayingBlack && !blackFlag) {
12972 SendToICS(ics_prefix);
12973 SendToICS("flag\n");
12977 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12979 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12980 if (appData.autoCallFlag) {
12981 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12988 if (blackTimeRemaining <= 0) {
12991 if (appData.icsActive) {
12992 if (appData.autoCallFlag &&
12993 gameMode == IcsPlayingWhite && !whiteFlag) {
12994 SendToICS(ics_prefix);
12995 SendToICS("flag\n");
12999 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13001 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13002 if (appData.autoCallFlag) {
13003 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13016 if (!appData.clockMode || appData.icsActive ||
13017 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13020 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13022 if ( !WhiteOnMove(forwardMostMove) )
13023 /* White made time control */
13024 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13025 /* [HGM] time odds: correct new time quota for time odds! */
13026 / WhitePlayer()->timeOdds;
13028 /* Black made time control */
13029 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13030 / WhitePlayer()->other->timeOdds;
13034 DisplayBothClocks()
13036 int wom = gameMode == EditPosition ?
13037 !blackPlaysFirst : WhiteOnMove(currentMove);
13038 DisplayWhiteClock(whiteTimeRemaining, wom);
13039 DisplayBlackClock(blackTimeRemaining, !wom);
13043 /* Timekeeping seems to be a portability nightmare. I think everyone
13044 has ftime(), but I'm really not sure, so I'm including some ifdefs
13045 to use other calls if you don't. Clocks will be less accurate if
13046 you have neither ftime nor gettimeofday.
13049 /* VS 2008 requires the #include outside of the function */
13050 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13051 #include <sys/timeb.h>
13054 /* Get the current time as a TimeMark */
13059 #if HAVE_GETTIMEOFDAY
13061 struct timeval timeVal;
13062 struct timezone timeZone;
13064 gettimeofday(&timeVal, &timeZone);
13065 tm->sec = (long) timeVal.tv_sec;
13066 tm->ms = (int) (timeVal.tv_usec / 1000L);
13068 #else /*!HAVE_GETTIMEOFDAY*/
13071 // include <sys/timeb.h> / moved to just above start of function
13072 struct timeb timeB;
13075 tm->sec = (long) timeB.time;
13076 tm->ms = (int) timeB.millitm;
13078 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13079 tm->sec = (long) time(NULL);
13085 /* Return the difference in milliseconds between two
13086 time marks. We assume the difference will fit in a long!
13089 SubtractTimeMarks(tm2, tm1)
13090 TimeMark *tm2, *tm1;
13092 return 1000L*(tm2->sec - tm1->sec) +
13093 (long) (tm2->ms - tm1->ms);
13098 * Code to manage the game clocks.
13100 * In tournament play, black starts the clock and then white makes a move.
13101 * We give the human user a slight advantage if he is playing white---the
13102 * clocks don't run until he makes his first move, so it takes zero time.
13103 * Also, we don't account for network lag, so we could get out of sync
13104 * with GNU Chess's clock -- but then, referees are always right.
13107 static TimeMark tickStartTM;
13108 static long intendedTickLength;
13111 NextTickLength(timeRemaining)
13112 long timeRemaining;
13114 long nominalTickLength, nextTickLength;
13116 if (timeRemaining > 0L && timeRemaining <= 10000L)
13117 nominalTickLength = 100L;
13119 nominalTickLength = 1000L;
13120 nextTickLength = timeRemaining % nominalTickLength;
13121 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13123 return nextTickLength;
13126 /* Adjust clock one minute up or down */
13128 AdjustClock(Boolean which, int dir)
13130 if(which) blackTimeRemaining += 60000*dir;
13131 else whiteTimeRemaining += 60000*dir;
13132 DisplayBothClocks();
13135 /* Stop clocks and reset to a fresh time control */
13139 (void) StopClockTimer();
13140 if (appData.icsActive) {
13141 whiteTimeRemaining = blackTimeRemaining = 0;
13142 } else { /* [HGM] correct new time quote for time odds */
13143 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13144 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13146 if (whiteFlag || blackFlag) {
13148 whiteFlag = blackFlag = FALSE;
13150 DisplayBothClocks();
13153 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13155 /* Decrement running clock by amount of time that has passed */
13159 long timeRemaining;
13160 long lastTickLength, fudge;
13163 if (!appData.clockMode) return;
13164 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13168 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13170 /* Fudge if we woke up a little too soon */
13171 fudge = intendedTickLength - lastTickLength;
13172 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13174 if (WhiteOnMove(forwardMostMove)) {
13175 if(whiteNPS >= 0) lastTickLength = 0;
13176 timeRemaining = whiteTimeRemaining -= lastTickLength;
13177 DisplayWhiteClock(whiteTimeRemaining - fudge,
13178 WhiteOnMove(currentMove));
13180 if(blackNPS >= 0) lastTickLength = 0;
13181 timeRemaining = blackTimeRemaining -= lastTickLength;
13182 DisplayBlackClock(blackTimeRemaining - fudge,
13183 !WhiteOnMove(currentMove));
13186 if (CheckFlags()) return;
13189 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13190 StartClockTimer(intendedTickLength);
13192 /* if the time remaining has fallen below the alarm threshold, sound the
13193 * alarm. if the alarm has sounded and (due to a takeback or time control
13194 * with increment) the time remaining has increased to a level above the
13195 * threshold, reset the alarm so it can sound again.
13198 if (appData.icsActive && appData.icsAlarm) {
13200 /* make sure we are dealing with the user's clock */
13201 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13202 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13205 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13206 alarmSounded = FALSE;
13207 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13209 alarmSounded = TRUE;
13215 /* A player has just moved, so stop the previously running
13216 clock and (if in clock mode) start the other one.
13217 We redisplay both clocks in case we're in ICS mode, because
13218 ICS gives us an update to both clocks after every move.
13219 Note that this routine is called *after* forwardMostMove
13220 is updated, so the last fractional tick must be subtracted
13221 from the color that is *not* on move now.
13226 long lastTickLength;
13228 int flagged = FALSE;
13232 if (StopClockTimer() && appData.clockMode) {
13233 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13234 if (WhiteOnMove(forwardMostMove)) {
13235 if(blackNPS >= 0) lastTickLength = 0;
13236 blackTimeRemaining -= lastTickLength;
13237 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13238 // if(pvInfoList[forwardMostMove-1].time == -1)
13239 pvInfoList[forwardMostMove-1].time = // use GUI time
13240 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13242 if(whiteNPS >= 0) lastTickLength = 0;
13243 whiteTimeRemaining -= lastTickLength;
13244 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13245 // if(pvInfoList[forwardMostMove-1].time == -1)
13246 pvInfoList[forwardMostMove-1].time =
13247 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13249 flagged = CheckFlags();
13251 CheckTimeControl();
13253 if (flagged || !appData.clockMode) return;
13255 switch (gameMode) {
13256 case MachinePlaysBlack:
13257 case MachinePlaysWhite:
13258 case BeginningOfGame:
13259 if (pausing) return;
13263 case PlayFromGameFile:
13272 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13273 whiteTimeRemaining : blackTimeRemaining);
13274 StartClockTimer(intendedTickLength);
13278 /* Stop both clocks */
13282 long lastTickLength;
13285 if (!StopClockTimer()) return;
13286 if (!appData.clockMode) return;
13290 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13291 if (WhiteOnMove(forwardMostMove)) {
13292 if(whiteNPS >= 0) lastTickLength = 0;
13293 whiteTimeRemaining -= lastTickLength;
13294 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13296 if(blackNPS >= 0) lastTickLength = 0;
13297 blackTimeRemaining -= lastTickLength;
13298 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13303 /* Start clock of player on move. Time may have been reset, so
13304 if clock is already running, stop and restart it. */
13308 (void) StopClockTimer(); /* in case it was running already */
13309 DisplayBothClocks();
13310 if (CheckFlags()) return;
13312 if (!appData.clockMode) return;
13313 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13315 GetTimeMark(&tickStartTM);
13316 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13317 whiteTimeRemaining : blackTimeRemaining);
13319 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13320 whiteNPS = blackNPS = -1;
13321 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13322 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13323 whiteNPS = first.nps;
13324 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13325 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13326 blackNPS = first.nps;
13327 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13328 whiteNPS = second.nps;
13329 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13330 blackNPS = second.nps;
13331 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13333 StartClockTimer(intendedTickLength);
13340 long second, minute, hour, day;
13342 static char buf[32];
13344 if (ms > 0 && ms <= 9900) {
13345 /* convert milliseconds to tenths, rounding up */
13346 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13348 sprintf(buf, " %03.1f ", tenths/10.0);
13352 /* convert milliseconds to seconds, rounding up */
13353 /* use floating point to avoid strangeness of integer division
13354 with negative dividends on many machines */
13355 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13362 day = second / (60 * 60 * 24);
13363 second = second % (60 * 60 * 24);
13364 hour = second / (60 * 60);
13365 second = second % (60 * 60);
13366 minute = second / 60;
13367 second = second % 60;
13370 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13371 sign, day, hour, minute, second);
13373 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13375 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13382 * This is necessary because some C libraries aren't ANSI C compliant yet.
13385 StrStr(string, match)
13386 char *string, *match;
13390 length = strlen(match);
13392 for (i = strlen(string) - length; i >= 0; i--, string++)
13393 if (!strncmp(match, string, length))
13400 StrCaseStr(string, match)
13401 char *string, *match;
13405 length = strlen(match);
13407 for (i = strlen(string) - length; i >= 0; i--, string++) {
13408 for (j = 0; j < length; j++) {
13409 if (ToLower(match[j]) != ToLower(string[j]))
13412 if (j == length) return string;
13426 c1 = ToLower(*s1++);
13427 c2 = ToLower(*s2++);
13428 if (c1 > c2) return 1;
13429 if (c1 < c2) return -1;
13430 if (c1 == NULLCHAR) return 0;
13439 return isupper(c) ? tolower(c) : c;
13447 return islower(c) ? toupper(c) : c;
13449 #endif /* !_amigados */
13457 if ((ret = (char *) malloc(strlen(s) + 1))) {
13464 StrSavePtr(s, savePtr)
13465 char *s, **savePtr;
13470 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13471 strcpy(*savePtr, s);
13483 clock = time((time_t *)NULL);
13484 tm = localtime(&clock);
13485 sprintf(buf, "%04d.%02d.%02d",
13486 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13487 return StrSave(buf);
13492 PositionToFEN(move, overrideCastling)
13494 char *overrideCastling;
13496 int i, j, fromX, fromY, toX, toY;
13503 whiteToPlay = (gameMode == EditPosition) ?
13504 !blackPlaysFirst : (move % 2 == 0);
13507 /* Piece placement data */
13508 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13510 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13511 if (boards[move][i][j] == EmptySquare) {
13513 } else { ChessSquare piece = boards[move][i][j];
13514 if (emptycount > 0) {
13515 if(emptycount<10) /* [HGM] can be >= 10 */
13516 *p++ = '0' + emptycount;
13517 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13520 if(PieceToChar(piece) == '+') {
13521 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13523 piece = (ChessSquare)(DEMOTED piece);
13525 *p++ = PieceToChar(piece);
13527 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13528 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13533 if (emptycount > 0) {
13534 if(emptycount<10) /* [HGM] can be >= 10 */
13535 *p++ = '0' + emptycount;
13536 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13543 /* [HGM] print Crazyhouse or Shogi holdings */
13544 if( gameInfo.holdingsWidth ) {
13545 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13547 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13548 piece = boards[move][i][BOARD_WIDTH-1];
13549 if( piece != EmptySquare )
13550 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13551 *p++ = PieceToChar(piece);
13553 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13554 piece = boards[move][BOARD_HEIGHT-i-1][0];
13555 if( piece != EmptySquare )
13556 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13557 *p++ = PieceToChar(piece);
13560 if( q == p ) *p++ = '-';
13566 *p++ = whiteToPlay ? 'w' : 'b';
13569 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13570 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13572 if(nrCastlingRights) {
13574 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13575 /* [HGM] write directly from rights */
13576 if(castlingRights[move][2] >= 0 &&
13577 castlingRights[move][0] >= 0 )
13578 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13579 if(castlingRights[move][2] >= 0 &&
13580 castlingRights[move][1] >= 0 )
13581 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13582 if(castlingRights[move][5] >= 0 &&
13583 castlingRights[move][3] >= 0 )
13584 *p++ = castlingRights[move][3] + AAA;
13585 if(castlingRights[move][5] >= 0 &&
13586 castlingRights[move][4] >= 0 )
13587 *p++ = castlingRights[move][4] + AAA;
13590 /* [HGM] write true castling rights */
13591 if( nrCastlingRights == 6 ) {
13592 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13593 castlingRights[move][2] >= 0 ) *p++ = 'K';
13594 if(castlingRights[move][1] == BOARD_LEFT &&
13595 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13596 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13597 castlingRights[move][5] >= 0 ) *p++ = 'k';
13598 if(castlingRights[move][4] == BOARD_LEFT &&
13599 castlingRights[move][5] >= 0 ) *p++ = 'q';
13602 if (q == p) *p++ = '-'; /* No castling rights */
13606 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13607 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13608 /* En passant target square */
13609 if (move > backwardMostMove) {
13610 fromX = moveList[move - 1][0] - AAA;
13611 fromY = moveList[move - 1][1] - ONE;
13612 toX = moveList[move - 1][2] - AAA;
13613 toY = moveList[move - 1][3] - ONE;
13614 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13615 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13616 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13618 /* 2-square pawn move just happened */
13620 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13631 /* [HGM] find reversible plies */
13632 { int i = 0, j=move;
13634 if (appData.debugMode) { int k;
13635 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13636 for(k=backwardMostMove; k<=forwardMostMove; k++)
13637 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13641 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13642 if( j == backwardMostMove ) i += initialRulePlies;
13643 sprintf(p, "%d ", i);
13644 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13646 /* Fullmove number */
13647 sprintf(p, "%d", (move / 2) + 1);
13649 return StrSave(buf);
13653 ParseFEN(board, blackPlaysFirst, fen)
13655 int *blackPlaysFirst;
13665 /* [HGM] by default clear Crazyhouse holdings, if present */
13666 if(gameInfo.holdingsWidth) {
13667 for(i=0; i<BOARD_HEIGHT; i++) {
13668 board[i][0] = EmptySquare; /* black holdings */
13669 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13670 board[i][1] = (ChessSquare) 0; /* black counts */
13671 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13675 /* Piece placement data */
13676 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13679 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13680 if (*p == '/') p++;
13681 emptycount = gameInfo.boardWidth - j;
13682 while (emptycount--)
13683 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13685 #if(BOARD_SIZE >= 10)
13686 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13687 p++; emptycount=10;
13688 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13689 while (emptycount--)
13690 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13692 } else if (isdigit(*p)) {
13693 emptycount = *p++ - '0';
13694 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13695 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13696 while (emptycount--)
13697 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13698 } else if (*p == '+' || isalpha(*p)) {
13699 if (j >= gameInfo.boardWidth) return FALSE;
13701 piece = CharToPiece(*++p);
13702 if(piece == EmptySquare) return FALSE; /* unknown piece */
13703 piece = (ChessSquare) (PROMOTED piece ); p++;
13704 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13705 } else piece = CharToPiece(*p++);
13707 if(piece==EmptySquare) return FALSE; /* unknown piece */
13708 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13709 piece = (ChessSquare) (PROMOTED piece);
13710 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13713 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13719 while (*p == '/' || *p == ' ') p++;
13721 /* [HGM] look for Crazyhouse holdings here */
13722 while(*p==' ') p++;
13723 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13725 if(*p == '-' ) *p++; /* empty holdings */ else {
13726 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13727 /* if we would allow FEN reading to set board size, we would */
13728 /* have to add holdings and shift the board read so far here */
13729 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13731 if((int) piece >= (int) BlackPawn ) {
13732 i = (int)piece - (int)BlackPawn;
13733 i = PieceToNumber((ChessSquare)i);
13734 if( i >= gameInfo.holdingsSize ) return FALSE;
13735 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13736 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13738 i = (int)piece - (int)WhitePawn;
13739 i = PieceToNumber((ChessSquare)i);
13740 if( i >= gameInfo.holdingsSize ) return FALSE;
13741 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13742 board[i][BOARD_WIDTH-2]++; /* black holdings */
13746 if(*p == ']') *p++;
13749 while(*p == ' ') p++;
13754 *blackPlaysFirst = FALSE;
13757 *blackPlaysFirst = TRUE;
13763 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13764 /* return the extra info in global variiables */
13766 /* set defaults in case FEN is incomplete */
13767 FENepStatus = EP_UNKNOWN;
13768 for(i=0; i<nrCastlingRights; i++ ) {
13769 FENcastlingRights[i] =
13770 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13771 } /* assume possible unless obviously impossible */
13772 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13773 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13774 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13775 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13776 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13777 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13780 while(*p==' ') p++;
13781 if(nrCastlingRights) {
13782 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13783 /* castling indicator present, so default becomes no castlings */
13784 for(i=0; i<nrCastlingRights; i++ ) {
13785 FENcastlingRights[i] = -1;
13788 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13789 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13790 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13791 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13792 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13794 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13795 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13796 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13800 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13801 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13802 FENcastlingRights[2] = whiteKingFile;
13805 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13806 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13807 FENcastlingRights[2] = whiteKingFile;
13810 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13811 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13812 FENcastlingRights[5] = blackKingFile;
13815 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13816 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13817 FENcastlingRights[5] = blackKingFile;
13820 default: /* FRC castlings */
13821 if(c >= 'a') { /* black rights */
13822 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13823 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13824 if(i == BOARD_RGHT) break;
13825 FENcastlingRights[5] = i;
13827 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13828 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13830 FENcastlingRights[3] = c;
13832 FENcastlingRights[4] = c;
13833 } else { /* white rights */
13834 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13835 if(board[0][i] == WhiteKing) break;
13836 if(i == BOARD_RGHT) break;
13837 FENcastlingRights[2] = i;
13838 c -= AAA - 'a' + 'A';
13839 if(board[0][c] >= WhiteKing) break;
13841 FENcastlingRights[0] = c;
13843 FENcastlingRights[1] = c;
13847 if (appData.debugMode) {
13848 fprintf(debugFP, "FEN castling rights:");
13849 for(i=0; i<nrCastlingRights; i++)
13850 fprintf(debugFP, " %d", FENcastlingRights[i]);
13851 fprintf(debugFP, "\n");
13854 while(*p==' ') p++;
13857 /* read e.p. field in games that know e.p. capture */
13858 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13859 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13861 p++; FENepStatus = EP_NONE;
13863 char c = *p++ - AAA;
13865 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13866 if(*p >= '0' && *p <='9') *p++;
13872 if(sscanf(p, "%d", &i) == 1) {
13873 FENrulePlies = i; /* 50-move ply counter */
13874 /* (The move number is still ignored) */
13881 EditPositionPasteFEN(char *fen)
13884 Board initial_position;
13886 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13887 DisplayError(_("Bad FEN position in clipboard"), 0);
13890 int savedBlackPlaysFirst = blackPlaysFirst;
13891 EditPositionEvent();
13892 blackPlaysFirst = savedBlackPlaysFirst;
13893 CopyBoard(boards[0], initial_position);
13894 /* [HGM] copy FEN attributes as well */
13896 initialRulePlies = FENrulePlies;
13897 epStatus[0] = FENepStatus;
13898 for( i=0; i<nrCastlingRights; i++ )
13899 castlingRights[0][i] = FENcastlingRights[i];
13901 EditPositionDone();
13902 DisplayBothClocks();
13903 DrawPosition(FALSE, boards[currentMove]);